diff --git a/.CFVERSION b/.CFVERSION new file mode 100644 index 0000000000..954e228821 --- /dev/null +++ b/.CFVERSION @@ -0,0 +1 @@ +3.24.0 diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..cd379ed8da --- /dev/null +++ b/.clang-format @@ -0,0 +1,40 @@ +--- +BasedOnStyle: Google +--- +Language: Cpp +ColumnLimit: 79 +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +MaxEmptyLinesToKeep: 3 +AlignTrailingComments: true +SpacesBeforeTrailingComments: 1 +SortIncludes: false +DerivePointerAlignment: false +PointerAlignment: Right +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +SpaceAfterCStyleCast: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +BreakStringLiterals: false +BinPackArguments: false +BinPackParameters: false +AlignAfterOpenBracket: AlwaysBreak +AllowAllParametersOfDeclarationOnNextLine: true +IndentCaseLabels: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterStruct: true + AfterUnion: true + BeforeElse: true + SplitEmptyFunction: true +BreakBeforeBinaryOperators: NonAssignment +--- diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000000..b082519997 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,5 @@ +name: "CFEngine cpp CodeQL config" + +queries: + - uses: cfengine/core/.github/codeql/cpp-queries/bool-type-mismatch-return.ql@master + - uses: cfengine/core/.github/codeql/cpp-queries/missing-argument-null-check.ql@master diff --git a/.github/codeql/cpp-queries/bool-type-mismatch-return.c b/.github/codeql/cpp-queries/bool-type-mismatch-return.c new file mode 100644 index 0000000000..bb506d157c --- /dev/null +++ b/.github/codeql/cpp-queries/bool-type-mismatch-return.c @@ -0,0 +1,62 @@ +#include + +int good_int() +{ + return 0; +} + +int bad_int() +{ + return false; +} + +bool good_with_variable() +{ + bool r = true; + return r; +} + +bool bad_with_variable() +{ + int r = true; + return r; +} + +bool good_with_constant() +{ + return false; +} + +bool bad_with_constant() +{ + return 0; +} + +bool good_with_function() +{ + return good_with_constant(); +} + +bool bad_with_function() +{ + return good_int(); +} + +bool good_with_comparison() +{ + return good_int() != 0; +} + +int main(void) +{ + good_int(); + bad_int(); + good_with_variable(); + bad_with_variable(); + good_with_constant(); + bad_with_constant(); + good_with_function(); + bad_with_function(); + good_with_comparison(); + return 0; +} diff --git a/.github/codeql/cpp-queries/bool-type-mismatch-return.qhelp b/.github/codeql/cpp-queries/bool-type-mismatch-return.qhelp new file mode 100644 index 0000000000..c9c53ee4f0 --- /dev/null +++ b/.github/codeql/cpp-queries/bool-type-mismatch-return.qhelp @@ -0,0 +1,66 @@ + + + + + +

+Boolean type mismatch between function type and return value. +This can be the result of copy-pasted code, which should have been modified. +

+ +

+In C, most things we think of as boolean are actually int (0 or 1). +We prefer to use bool return type for functions which have 2 return values (true or false). +We consider some different things boolean: +

+ +
    +
  • +A variable with type bool +
  • +
  • +A function call with return type bool +
  • +
  • +A boolean macro - true or false +
  • +
  • +A comparison - == or != (or less than / greater than variants). +
  • +
  • +A logical operation - AND or OR. +
  • +
+ +

+The reason why this query was added was to catch cases like a boolean function returning -1. +int functions typically return 0 for success and -1 for failure. +This means that if you copied the error handling of an int function to a bool function, it would return true (success) in case of error. +Error handling is the main reason to have this strict type checking for bool. +

+ +
+ + +

+Change the returned value to something boolean, or change the function return type to bool. +Sometimes this means adding an an explicit comparison in the return statement. +(Typecasting is almost never the right answer). +

+ +
+ + + + + + + +
  • +CFEngine Contribution guidelines: CONTRIBUTING.md +
  • + +
    +
    diff --git a/.github/codeql/cpp-queries/bool-type-mismatch-return.ql b/.github/codeql/cpp-queries/bool-type-mismatch-return.ql new file mode 100644 index 0000000000..e8ce038bfd --- /dev/null +++ b/.github/codeql/cpp-queries/bool-type-mismatch-return.ql @@ -0,0 +1,76 @@ +/** + * @name Boolean type mismatch between function type and return value + * @description Strict boolean type-checking for return statements. + * Boolean functions must return something boolean. + * Non-boolean functions must not return something boolean. + * Comparisons, logical AND/OR, and true/false macros are bool. + * @kind problem + * @problem.severity warning + * @id cpp/bool-type-mismatch-return + * @tags readability + * correctness + * @precision very-high + */ + +import cpp + +predicate isBoolExpr(Expr expression){ + // Not bool if there is an explicit cast to something else: + not exists(CStyleCast cast | + cast.getType().getName() != "bool" + and + cast.getExplicitlyConverted() = expression.getExplicitlyConverted() + ) + and + // One of these imply boolean: + ( + // variable of type bool: + expression.getType().getName() = "bool" + // true or false macro: + or exists(MacroInvocation m | + m.getExpr() = expression + and (m.getMacroName() = "true" or m.getMacroName() = "false")) + // && or || operator + or exists(BinaryLogicalOperation b | + b = expression) + // == or != or > or < or >= or <= operator: + or exists(ComparisonOperation cmp | + cmp = expression) + // ! operator: + or exists(NotExpr n | + n = expression + and isBoolExpr(n.getOperand())) + // Recursively check both branches of ternary operator: + or exists(ConditionalExpr c | + c = expression + and isBoolExpr(c.getThen()) + and isBoolExpr(c.getElse())) + ) +} + +string showMacroExpr(Expr e){ + not e.isInMacroExpansion() + and result = e.toString() +or + e.isInMacroExpansion() // Show true=1/false=0 in alert + and result = e.findRootCause() + .toString() + .replaceAll("#define ", "") + .replaceAll(" ", "=") +} + +from Function f, ReturnStmt r, string rt +where r.getEnclosingFunction() = f + and ( + f.getType().getName() = "bool" + and not isBoolExpr(r.getExpr()) + and rt = "non-bool" + or + f.getType().getName() != "bool" + and isBoolExpr(r.getExpr()) + and rt = "bool" + ) +select r, "Function " + f.getName() + + " has return type " + f.getType().getName() + + " and returns " + rt + + "(" + showMacroExpr(r.getExpr()) + ")" diff --git a/.github/codeql/cpp-queries/missing-argument-null-check.c b/.github/codeql/cpp-queries/missing-argument-null-check.c new file mode 100644 index 0000000000..7a95060e0f --- /dev/null +++ b/.github/codeql/cpp-queries/missing-argument-null-check.c @@ -0,0 +1,39 @@ +#include + +typedef struct { + char *string; +} StructWithString; + +void good_with_assert(StructWithString *data) +{ + assert(data != NULL); + char *string = data->string; +} + +void good_with_no_deref(StructWithString *data) +{ + good_with_assert(data); +} + +void good_with_if(StructWithString *data) +{ + if (data != NULL) + { + char *string = data->string; + } +} + +void bad_deref(StructWithString *data) +{ + char *string = data->string; +} + +int main(void) +{ + StructWithString *data = NULL; + good_with_no_deref(data); // Doesn't dereference, so no problem + good_with_assert(data); // Assert will detect our error + good_with_if(data); // Works with NULL pointers + bad_deref(data); // Blows up - will be detected in alert + return 0; +} diff --git a/.github/codeql/cpp-queries/missing-argument-null-check.qhelp b/.github/codeql/cpp-queries/missing-argument-null-check.qhelp new file mode 100644 index 0000000000..c283a4220d --- /dev/null +++ b/.github/codeql/cpp-queries/missing-argument-null-check.qhelp @@ -0,0 +1,49 @@ + + + + + +

    +Functions which dereference a pointer should test for NULL. +This should be done as an explicit comparison in an assert or if statement. +If the function assumes that an argument is non-null, add an assert at the beginning of the function body. +

    + +

    +There are some limitations in the current implementation. +It only looks for explicit comparisons, if (ptr == NULL), but this is the correct way to write it, according to our style guidelines. +Only arrow syntax is detected, ptr->field, so it should be expanded to also detect asterisk syntax, *ptr. +Also, it doesn't check that the comparison is before the dereference, it should be improved to also alert in cases where the check is after the dereference. +There shouldn't be false positives, but it should be expanded to find more problematic cases. +

    + +
    + + +

    +Add an assert to the beginning of the function if it assumes the argument is not null: assert(ptr != NULL). +Add an an explicit comparison somewhere in the function if it is okay for the argument to be NULL. +(Usually this should be an if around the dereference). +Note that in both cases, the comparison must be explicit (using == NULL or != NULL). +

    + +
    + + +

    +This example has 2 correct (good) functions, and one incorrect (bad) function: +

    + + + +
    + + +
  • +CFEngine Contribution guidelines: CONTRIBUTING.md +
  • + +
    +
    diff --git a/.github/codeql/cpp-queries/missing-argument-null-check.ql b/.github/codeql/cpp-queries/missing-argument-null-check.ql new file mode 100644 index 0000000000..3ecddfcce4 --- /dev/null +++ b/.github/codeql/cpp-queries/missing-argument-null-check.ql @@ -0,0 +1,37 @@ +/** + * @name Pointer argument is dereferenced without checking for NULL + * @description Functions which dereference a pointer should test for NULL. + * This should be done as an explicit comparison in an assert or if statement. + * @kind problem + * @problem.severity recommendation + * @id cpp/missing-argument-null-check + * @tags readability + * correctness + * safety + * @precision very-high + */ + +import cpp + +predicate hasNullCheck(Function func, Parameter p){ + exists(MacroInvocation m | + m.getMacroName() = "assert" + and m.getEnclosingFunction() = func + and m.getUnexpandedArgument(0) = p.getName() + " != NULL") + or + exists(EqualityOperation comparison, MacroInvocation m| + comparison.getEnclosingFunction() = func + and comparison.getLeftOperand().toString() = p.getName() + and comparison.getRightOperand() = m.getExpr() + and m.getMacroName() = "NULL") +} + +from Function func, PointerFieldAccess acc, Parameter p, PointerType pt +where acc.getEnclosingFunction() = func + and p.getFunction() = func + and p.getType() = pt + and acc.getQualifier().toString() = p.getName() + and not hasNullCheck(func, p) +select acc, "Parameter " + p.getName() + + " in " + func.getName() + + "() is dereferenced without an explicit null-check" diff --git a/.github/codeql/cpp-queries/qlpack.yml b/.github/codeql/cpp-queries/qlpack.yml new file mode 100644 index 0000000000..3be7e4e80c --- /dev/null +++ b/.github/codeql/cpp-queries/qlpack.yml @@ -0,0 +1,4 @@ +name: cfengine/codeql-cpp-queries +version: 1.0.0 +dependencies: + codeql/cpp-all: ~0.5.4 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..c9c8900175 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: gitsubmodule + directory: / + schedule: + interval: "daily" diff --git a/.github/workflows/acceptance_tests.yml b/.github/workflows/acceptance_tests.yml new file mode 100644 index 0000000000..9eac099968 --- /dev/null +++ b/.github/workflows/acceptance_tests.yml @@ -0,0 +1,23 @@ +name: Acceptance Tests + +on: + workflow_call + +jobs: + acceptance_tests: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install dependencies + run: sudo apt-get update -y && sudo apt-get install -y libssl-dev libpam0g-dev liblmdb-dev byacc curl libyaml-dev + - name: Run autotools / configure + run: ./autogen.sh --enable-debug + - name: Compile and link (make) + run: make -j8 CFLAGS="-Werror -Wall" + - name: Run acceptance tests + run: | + cd tests/acceptance + chmod -R go-w . + ./testall --printlog --tests=common,errorlog diff --git a/.github/workflows/asan_unit_tests.yml b/.github/workflows/asan_unit_tests.yml new file mode 100644 index 0000000000..9f7ba2076e --- /dev/null +++ b/.github/workflows/asan_unit_tests.yml @@ -0,0 +1,20 @@ +name: ASAN Unit Tests + +on: + workflow_call + +jobs: + asan_unit_tests: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install dependencies + run: sudo apt-get update -y && sudo apt-get install -y libssl-dev libpam0g-dev liblmdb-dev byacc curl + - name: Run autotools / configure + run: ./autogen.sh --enable-debug + - name: Compile and link (make) + run: make -j8 CFLAGS="-Werror -Wall -fsanitize=address" LDFLAGS="-fsanitize=address" + - name: Run unit tests + run: make -C tests/unit CFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address" check diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..a483d07448 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: Continuous Integration + +on: + # run this workflow on pull_request activity + # this includes opening and pushing more commits + pull_request: + branches: [ master, 3.21.x, 3.18.x ] + +jobs: +# unit_tests: +# uses: ./.github/workflows/unit_tests.yml +# shellcheck_tests: +# uses: ./.github/workflows/shellcheck.yml +# asan_unit_tests: +# needs: unit_tests +# uses: ./.github/workflows/asan_unit_tests.yml +# acceptance_tests: +# needs: unit_tests +# uses: ./.github/workflows/acceptance_tests.yml + windows_acceptance_tests: +# needs: unit_tests + uses: ./.github/workflows/windows_acceptance_tests.yml +# macos_unit_tests: +# needs: unit_tests +# uses: ./.github/workflows/macos_unit_tests.yml +# valgrind_tests: +# needs: unit_tests +# uses: ./.github/workflows/valgrind.yml +# static_check: +# needs: unit_tests +# uses: ./.github/workflows/job-static-check.yml +# valgrind_check: +# needs: unit_tests +# uses: ./.github/workflows/job-valgrind-check.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..4777259e8e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,53 @@ +name: "CodeQL" + +on: + push: + branches: [ "master", "3.15.x", "3.18.x", "3.21.x"] + pull_request: + branches: [ "master", "3.15.x", "3.18.x", "3.21.x"] + schedule: + - cron: "40 18 * * 6" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python, cpp ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + config-file: .github/codeql/codeql-config.yml + + - name: Autobuild (Python) + if: ${{ matrix.language == 'python' }} + uses: github/codeql-action/autobuild@v2 + + - name: Install dependencies (C) + if: ${{ matrix.language == 'cpp' }} + run: sudo apt-get update -y && sudo apt-get install -y libssl-dev libpam0g-dev liblmdb-dev byacc curl + + - name: Build (C) + if: ${{ matrix.language == 'cpp' }} + run: ./autogen.sh && make + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/job-static-check.yml b/.github/workflows/job-static-check.yml new file mode 100644 index 0000000000..a86a5836f8 --- /dev/null +++ b/.github/workflows/job-static-check.yml @@ -0,0 +1,51 @@ +name: Static Check + +on: + workflow_call + +jobs: + static_check: + runs-on: ubuntu-latest + steps: + - name: Checkout Core + uses: actions/checkout@v3 + with: + submodules: recursive + path: core + + - name: Get Togethers + uses: cfengine/together-javascript-action@main + id: together + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout Buildscripts + uses: actions/checkout@v3 + with: + repository: cfengine/buildscripts + submodules: recursive + path: buildscripts + ref: ${{steps.together.outputs.buildscripts || github.base_ref || github.ref}} + + - name: Checkout Masterfiles + uses: actions/checkout@v3 + with: + repository: cfengine/masterfiles + submodules: recursive + path: masterfiles + ref: ${{steps.together.outputs.masterfiles || github.base_ref || github.ref}} + + - name: Prepare Environment + run: | + sudo apt-get update && \ + sudo apt-get install -y dpkg-dev debhelper g++ libncurses5 pkg-config \ + build-essential libpam0g-dev fakeroot gcc make autoconf buildah \ + liblmdb-dev libacl1-dev libcurl4-openssl-dev libyaml-dev libxml2-dev \ + libssl-dev libpcre2-dev + + - name: Run Autogen + run: NO_CONFIGURE=1 PROJECT=community ./buildscripts/build-scripts/autogen + + - name: Run The Test + working-directory: ./core + run: ./tests/static-check/run.sh diff --git a/.github/workflows/job-valgrind-check.yml b/.github/workflows/job-valgrind-check.yml new file mode 100644 index 0000000000..4517f60133 --- /dev/null +++ b/.github/workflows/job-valgrind-check.yml @@ -0,0 +1,32 @@ +name: Valgrind Check + +on: + workflow_call + +jobs: + valgrind_check: + runs-on: ubuntu-latest + steps: + - name: Checkout Core + uses: actions/checkout@v3 + with: + submodules: recursive + path: core + + - name: Get Togethers + uses: cfengine/together-javascript-action@main + id: together + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout Masterfiles + uses: actions/checkout@v3 + with: + repository: cfengine/masterfiles + submodules: recursive + path: masterfiles + ref: ${{steps.together.outputs.masterfiles || github.base_ref || github.ref}} + + - name: Run The Test + working-directory: ./core/tests/valgrind-check + run: sudo bash run.sh diff --git a/.github/workflows/macos_unit_tests.yml b/.github/workflows/macos_unit_tests.yml new file mode 100644 index 0000000000..c29a1ce2e0 --- /dev/null +++ b/.github/workflows/macos_unit_tests.yml @@ -0,0 +1,28 @@ +name: MacOS Unit Tests + +on: + workflow_call + +jobs: + macos_unit_tests: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install dependencies + run: brew install lmdb automake openssl pcre2 autoconf libtool + - name: Check tools + run: command -v libtool && command -v automake && command -v autoconf + - name: Check tools versions + run: libtool -V && automake --version && autoconf --version + - name: Run autotools / configure + run: > + LDFLAGS="-L`brew --prefix lmdb`/lib -L`brew --prefix openssl`/lib -L`brew --prefix pcre2`/lib" + CPPFLAGS="-I`brew --prefix lmdb`/include -I`brew --prefix openssl`/include -I`brew --prefix pcre2`/include" + PATH="/opt/homebrew/opt/libtool/libexec/gnubin:$PATH" + ./autogen.sh --enable-debug + - name: Compile and link + run: make -j8 CFLAGS="-Werror -Wall" + - name: Run unit tests + run: make -C tests/unit check diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000000..adc97226e7 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,20 @@ +name: Shellcheck tests + +on: + workflow_call + +jobs: + unit_tests: + name: Run shellcheck on shell scripts + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install dependencies + run: sudo apt-get update -y && sudo apt-get install -y libssl-dev libpam0g-dev liblmdb-dev byacc curl shellcheck + - name: Run autotools / configure + run: ./autogen.sh --enable-debug + - name: Run shellcheck +# todo: add more places to run shellcheck besides misc/cf-support + run: make -C misc check diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000000..932560b1ea --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,21 @@ +name: Unit tests + +on: + workflow_call + +jobs: + unit_tests: + name: Run Unit Tests + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install dependencies + run: sudo apt-get update -y && sudo apt-get install -y libssl-dev libpam0g-dev liblmdb-dev byacc curl + - name: Run autotools / configure + run: ./autogen.sh --enable-debug + - name: Compile and link (make) + run: make -j8 CFLAGS="-Werror -Wall" + - name: Run unit tests + run: make -C tests/unit check diff --git a/.github/workflows/valgrind.sh b/.github/workflows/valgrind.sh new file mode 100644 index 0000000000..73e7a48dd0 --- /dev/null +++ b/.github/workflows/valgrind.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash +set -e + +function print_ps { + set +e + echo "CFEngine processes:" + ps aux | grep [c]f- + + echo "Valgrind processes:" + ps aux | grep [v]algrind + set -e +} + +function no_errors { + set +e + grep -i "error" $1 + grep -i "error" $1 && exit 1 + set -e +} + +function check_daemon_output { + echo "Examining $1:" + no_errors $1 +} + +function check_output { + set -e + if [ ! -f "$1" ]; then + echo "$1 does not exists!" + exit 1 + fi + echo "Looking for problems in $1:" + grep -i "ERROR SUMMARY: 0 errors" "$1" + cat $1 | sed -e "/ 0 errors/d" -e "/and suppressed error/d" -e "/a memory error detector/d" -e "/This database contains unknown binary data/d" > filtered.txt + no_errors filtered.txt + set +e + grep -i "at 0x" filtered.txt + grep -i "at 0x" filtered.txt && exit 1 + grep -i "by 0x" filtered.txt + grep -i "by 0x" filtered.txt && exit 1 + grep -i "Failed to connect" filtered.txt + grep -i "Failed to connect" filtered.txt && exit 1 + set -e +} + +function check_serverd_valgrind_output { + if [ ! -f "$1" ]; then + echo "$1 does not exists!" + exit 1 + fi + set -e + echo "Serverd has 1 expected valgrind error because of old glibc" + echo "Because of this we use special assertions on output" + echo "Looking for problems in $1:" + grep -i "definitely lost" $1 + grep -i "indirectly lost" $1 + grep -i "ERROR SUMMARY" $1 + grep -i "definitely lost: 0 bytes in 0 blocks" $1 + grep -i "indirectly lost: 0 bytes in 0 blocks" $1 + grep -i "ERROR SUMMARY: 0 errors" "$1" + + cat $1 | sed -e "/ERROR SUMMARY/d" -e "/and suppressed error/d" -e "/a memory error detector/d" > filtered.txt + + no_errors filtered.txt + set +e + grep -i "at 0x" filtered.txt + grep -i "at 0x" filtered.txt && exit 1 + grep -i "by 0x" filtered.txt + grep -i "by 0x" filtered.txt && exit 1 + set -e +} + +function check_masterfiles_and_inputs { + set -e + echo "Comparing promises.cf from inputs and masterfiles:" + diff /var/cfengine/inputs/promises.cf /var/cfengine/masterfiles/promises.cf +} + +VG_OPTS="--leak-check=full --track-origins=yes --error-exitcode=1" +BOOTSTRAP_IP="$(ifconfig | grep -C1 Ethernet | sed 's/.*inet \([0-9.]*\).*/\1/;t;d')" + +valgrind $VG_OPTS /var/cfengine/bin/cf-key 2>&1 | tee cf-key.txt +check_output cf-key.txt +valgrind $VG_OPTS /var/cfengine/bin/cf-agent -B $BOOTSTRAP_IP 2>&1 | tee bootstrap.txt +check_output bootstrap.txt + +# Validate all databases here, because later, we cannot validate +# cf_lastseen.lmdb: +echo "Running cf-check diagnose --validate on all databases:" +valgrind $VG_OPTS /var/cfengine/bin/cf-check diagnose --validate 2>&1 | tee cf_check_validate_all.txt +check_output cf_check_validate_all.txt + +check_masterfiles_and_inputs + +print_ps + +echo "Stopping service to relaunch under valgrind" +systemctl stop cfengine3 +sleep 10 +print_ps + +# The IP we bootstrapped to cannot actually be used for communication. +# This ensures that cf-serverd binds to loopback interface, and cf-net +# connects to it: +echo "127.0.0.1" > /var/cfengine/policy_server.dat + +echo "Starting cf-serverd with valgrind in background:" +valgrind $VG_OPTS --log-file=serverd.txt /var/cfengine/bin/cf-serverd --no-fork 2>&1 > serverd_output.txt & +server_pid="$!" +sleep 20 + +echo "Starting cf-execd with valgrind in background:" +valgrind $VG_OPTS --log-file=execd.txt /var/cfengine/bin/cf-execd --no-fork 2>&1 > execd_output.txt & +exec_pid="$!" +sleep 10 + +print_ps + +echo "Running cf-net:" +valgrind $VG_OPTS /var/cfengine/bin/cf-net GET /var/cfengine/masterfiles/promises.cf 2>&1 | tee get.txt +check_output get.txt + +echo "Checking promises.cf diff (from cf-net GET):" +diff ./promises.cf /var/cfengine/masterfiles/promises.cf + +echo "Running update.cf:" +valgrind $VG_OPTS /var/cfengine/bin/cf-agent -K -f update.cf 2>&1 | tee update.txt +check_output update.txt +check_masterfiles_and_inputs +echo "Running update.cf without local copy:" +valgrind $VG_OPTS /var/cfengine/bin/cf-agent -K -f update.cf -D mpf_skip_local_copy_optimization 2>&1 | tee update2.txt +check_output update2.txt +check_masterfiles_and_inputs +echo "Running promises.cf:" +valgrind $VG_OPTS /var/cfengine/bin/cf-agent -K -f promises.cf 2>&1 | tee promises.txt +check_output promises.txt + +# Dump all databases, use grep to filter the JSON lines +# (optional whitespace then double quote or curly brackets). +# Some of the databases have strings containing "error" +# which check_output greps for. +echo "Running cf-check dump:" +valgrind $VG_OPTS /var/cfengine/bin/cf-check dump 2>&1 | grep -E '\s*[{}"]' --invert-match | tee cf_check_dump.txt +check_output cf_check_dump.txt + +echo "Running cf-check diagnose on all databases" +valgrind $VG_OPTS /var/cfengine/bin/cf-check diagnose 2>&1 | tee cf_check_diagnose.txt +check_output cf_check_diagnose.txt + +# Because of the hack with bootstrap IP / policy_server.dat above +# lastseen would not pass validation: +echo "Running cf-check diagnose --validate on all databases except cf_lastseen.lmdb:" +find /var/cfengine/state -name '*.lmdb' ! -name 'cf_lastseen.lmdb' -exec \ + valgrind $VG_OPTS /var/cfengine/bin/cf-check diagnose --validate {} + 2>&1 \ + | tee cf_check_validate_no_lastseen.txt +check_output cf_check_validate_no_lastseen.txt + +echo "Checking that bootstrap ID doesn't change" +/var/cfengine/bin/cf-agent --show-evaluated-vars | grep bootstrap_id > id_a +/var/cfengine/bin/cf-agent -K --show-evaluated-vars | grep bootstrap_id > id_b +cat id_a +diff id_a id_b + +echo "Checking that bootstrap ID has expected length" +[ `cat id_a | awk '{print $2}' | wc -c` -eq 41 ] + +print_ps + +echo "Checking that serverd and execd PIDs are still correct/alive:" +ps -p $exec_pid +ps -p $server_pid + +echo "Killing valgrind cf-execd" +kill $exec_pid +echo "Killing valgrind cf-serverd" +kill $server_pid +sleep 30 + +echo "Output from cf-execd in valgrind:" +cat execd.txt +check_output execd.txt +check_daemon_output execd_output.txt + +echo "Output from cf-serverd in valgrind:" +cat serverd.txt +check_serverd_valgrind_output serverd.txt +check_daemon_output serverd_output.txt + +echo "Stopping cfengine3 service" +systemctl stop cfengine3 + +echo "Done killing" +sleep 10 +print_ps + +echo "Check that bootstrap was successful" +grep "This host assumes the role of policy server" bootstrap.txt +grep "completed successfully!" bootstrap.txt + +echo "valgrind_health_check successful! (valgrind.sh)" diff --git a/.github/workflows/valgrind.yml b/.github/workflows/valgrind.yml new file mode 100644 index 0000000000..db600e6556 --- /dev/null +++ b/.github/workflows/valgrind.yml @@ -0,0 +1,51 @@ +name: Valgrind Tests + +on: + workflow_call + +jobs: + valgrind_tests: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: core + steps: + - uses: actions/checkout@v3 + with: + path: core + submodules: recursive + - name: Get Togethers + uses: cfengine/together-javascript-action@main + id: together + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + - name: Clone masterfiles (master) + uses: actions/checkout@v3 + with: + repository: cfengine/masterfiles + ref: ${{steps.together.outputs.masterfiles || github.base_ref || github.ref}} + path: masterfiles + submodules: recursive + - name: Install dependencies + run: sudo apt-get update -y && sudo apt-get install -y libssl-dev libpam0g-dev liblmdb-dev byacc curl libyaml-dev valgrind + - name: Run autotools / configure + run: ./autogen.sh --enable-debug --with-systemd-service + - name: Compile and link (make) + run: make -j8 CFLAGS="-Werror -Wall" + - name: Install CFEngine + run: sudo make install + - name: Generate masterfiles + run: ./autogen.sh + working-directory: masterfiles + - name: Install masterfiles + run: sudo make install + working-directory: masterfiles + - name: Reload systemd + run: sudo systemctl daemon-reload + + - name: See if cf-agent runs at all + run: /var/cfengine/bin/cf-agent --version + + - name: Run valgrind.sh + run: sudo bash .github/workflows/valgrind.sh + diff --git a/.github/workflows/windows_acceptance_tests.yml b/.github/workflows/windows_acceptance_tests.yml new file mode 100644 index 0000000000..b382975018 --- /dev/null +++ b/.github/workflows/windows_acceptance_tests.yml @@ -0,0 +1,85 @@ +name: Windows Acceptance Tests + +on: + workflow_call + +defaults: + run: + shell: msys2 {0} + +jobs: + windows_acceptance_tests: + runs-on: windows-latest + steps: + - name: Install msys2 packages + run: pacman -S rsync dos2unix diffutils util-linux python-pip + + - name: Checkout Core + uses: actions/checkout@v3 + + - name: install cf-remote + run: pip install cf-remote + + # Note that msiexec can't install packages when running under msys; + # But cf-remote currently can't run under powershell + # (because it requires `pwd` module which is Unix-only). + # Hence, we _download_ msi package using cf-remote under msys, + # and install it by msiexec running under powershell. + + - name: get CFEngine package + run: cf-remote --version master download x86_64 msi + + - name: move CFEngine package to current workdir + run: "mv $HOME/.cfengine/cf-remote/packages/*.msi cfengine.msi" + + - name: install CFEngine + run: | + Get-Location # pwd + New-Item -Path "c:\" -Name "artifacts" -ItemType Directory + Start-Process msiexec.exe -Wait -ArgumentList '/quiet /qn /i cfengine.msi /L*V c:\tmp.log' + Get-Content c:\tmp.log | Set-Content -Encoding utf8 c:\artifacts\CFEngine-Install.log + file c:\artifacts\CFEngine-Install.log + shell: pwsh + + - name: run cf-agent + run: "'/c/Program Files/Cfengine/bin/cf-agent.exe' --version" + + # tests run easier if the core repo and CFEngine install are on the same partition: C: + - name: copy core checkout to main partition + run: 'rsync -avz /d/a/core/core /c/ || true' + + - name: prune platform independent tests to make the job more efficient + run: 'Remove-Item -Recurse -Force 00_basics, 01_vars, 02_classes, 10_files, 14_reports, 15_control, 16_cf-serverd, 21_methods, 22_cf-runagent, 26_cf-net, 27_cf-secret, 28_inform_testing' + working-directory: C:/core/tests/acceptance + shell: pwsh + + - name: run the tests + run: './testall --bindir="/c/Program Files/Cfengine/bin" --extraclasses=EXTRA' + working-directory: C:/core/tests/acceptance + env: + # env vars for testall script to properly detect environment + USER: runneradmin + OSTYPE: msys + + - name: print test.log + run: 'cat ./tests/acceptance/test.log || true' + working-directory: C:/core + if: ${{ always() }} + + - name: save test.log in artifacts + run: 'cp ./tests/acceptance/test.log /c/artifacts/test.log || true' + working-directory: C:/core + if: ${{ always() }} + + # make a tarball because otherwise there will be too many files and github won't save the artifact + - name: save workdir to artifacts + run: 'tar cfz /c/artifacts/workdir.tar.gz ./tests/acceptance/workdir || true' + working-directory: C:/core + if: ${{ always() }} + + - name: save artifacts + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: artifacts + path: c:\artifacts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..18892a31fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,193 @@ +# git mergetool leftovers + +*.[ch].orig + +# intermediate / autogen + +*.la +*.lo +*.o +.deps +.libs +Makefile.in +stamp-h1 +.dirstamp + +# Created by autogen.sh +/CFVERSION +/aclocal.m4 +/autom4te.cache +/compile +/config.cache +/config.guess +/config.h +/config.h.in +/config.log +/config.post.h +/config.status +/config.sub +/configure +/depcomp + +# Generated Makefiles (Some deps use plain make): +/Makefile +/cf-agent/Makefile +/cf-check/Makefile +/cf-execd/Makefile +/cf-key/Makefile +/cf-secret/Makefile +/cf-monitord/Makefile +/cf-net/Makefile +/cf-promises/Makefile +/cf-runagent/Makefile +/cf-serverd/Makefile +/cf-testd/Makefile +/cf-upgrade/Makefile +/contrib/vagrant-ci/centos-9s-x64/Makefile +/examples/Makefile +/ext/Makefile +/libcfecompat/Makefile +/libcfnet/Makefile +/libenv/Makefile +/libpromises/Makefile +/misc/Makefile +/misc/selinux/Makefile +/python/Makefile +/tests/Makefile +/tests/acceptance/25_cf-execd/Makefile +/tests/acceptance/Makefile +/tests/load/Makefile +/tests/unit/Makefile +/tests/static-check/Makefile +/tests/valgrind-check/Makefile + +/install-sh +/libtool +/ltmain.sh +/m4/libtool.m4 +/m4/lt*.m4 +/missing +/revision +/ylwrap +/test-driver +/tests/unit/*.log +/tests/unit/init_script_test_helper +/tests/unit/redirection_test_stub +class_test.xml +tests/acceptance/30_custom_promise_types/cfengine.py +tests/acceptance/30_custom_promise_types/cfengine.sh +xml_tmp_case +xml_tmp_suite +/tests/load/*.log + +/configure_flags.env + +# tarballs +/*.tar.gz + +# binaries +/cf-agent/cf-agent +/cf-agent/cf-agent.exe +/cf-agent/manifest_file +/cf-check/cf-check +/cf-check/cf-check.exe +/cf-execd/cf-execd +/cf-execd/cf-execd.exe +/cf-key/cf-key +/cf-key/cf-key.exe +/cf-monitord/cf-monitord +/cf-monitord/cf-monitord.exe +/cf-monitord/get_socket_info +/cf-promises/cf-promises +/cf-promises/cf-promises.exe +/cf-runagent/cf-runagent +/cf-runagent/cf-runagent.exe +/cf-net/cf-net +/cf-net/cf-net.exe +/cf-secret/cf-secret +/cf-secret/cf-secret.exe +/cf-serverd/cf-serverd +/cf-serverd/cf-serverd.exe +/cf-testd/cf-testd +/cf-testd/cf-testd.exe +/cf-upgrade/cf-upgrade +/cf-upgrade/cf-upgrade.exe +/ext/rpmvercmp +/ext/rpmvercmp.exe + +# autogen +/libpromises/cf3lex.c +/libpromises/cf3parse.c +/libpromises/cf3parse.h +/libutils/config.h +/libutils/config.h.in +/libutils/config.post.h + +# examples +/examples/cfengine_stdlib.cf + +# misc - systemd services (from AC templates) +misc/systemd/*.service + +# gcov/lcov +*.gcno +*.gcda +*.trace +coverage-html/ +coverage/ + +# links +/nova + +# tags +TAGS +tags +cscope.* + +# vim +*~ +.*.swp +.*.swo + +# emacs +*~ +\#*# +.dir-locals.el +/contrib/*.elc +/contrib/vagrant-ci/centos-9s-x64/.vagrant + +## Flycheck +flycheck_* + +# eclipse +.cproject +.project +.settings + +# qt-creator +/core.config +/core.creator +/core.creator.user +/core.files +/core.includes +*.autosave + +# vscode +/.vscode + +# clang +.clang_complete +*.pch + +# Mac +.DS_Store +*.dSYM + +# python +__pycache__ + +# SELinux policy build artifacts +misc/selinux/cfengine-enterprise.pp +misc/selinux/cfengine-enterprise.if +misc/selinux/cfengine-enterprise.te +misc/selinux/tmp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..3b5d026868 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "libntech"] + path = libntech + url = https://github.com/NorthernTechHQ/libntech +[submodule "contrib/emacs-code-style"] + path = contrib/emacs-code-style + url = https://github.com/cfengine/cfengine-code-style diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..bb3788518e --- /dev/null +++ b/.mailmap @@ -0,0 +1,50 @@ + + +Aleksey Tsalolikhin Aleksey Tsalolikhin +Aleksey Tsalolikhin Aleksey Tsalolikhin +Aleksey Tsalolikhin Aleksey Tsalolikhin +Aleksey Tsalolikhin Aleksey Tsalolikhin +Alexis Mousset Alexis Mousset +Bas van der Vlies +Bishwa Shrestha +Carlos Manuel Duclos Vergara +Carlos Manuel Duclos Vergara +Carlos Manuel Duclos Vergara +Chris Dituri chris dituri +Chris Dituri chris.dituri +Craig Comstock +Craig Comstock Craig Comstock +Craig Comstock craigcomstock +Craig Comstock craigcomstock +Diego Zamboni +Geir Nygård +John D. "Trix" Farrar +Karl Hole Totland Karl Hole Totland +Karl Hole Totland Karl Hole Totland +Kristian Amlie Kristian Amlie +Kristian Amlie Kristian Amlie +Kristian Amlie kacfengine +Maciej Mrowiec Maciej Mrowiec +Maciej Patucha +Marcin Pasinski Marcin Pasinski +Marcin Pasinski pasinskim +Marcin Pasinski pasinskim +Mark Burgess +Michael Clelland +Michael V. Pelletier +Mike Weilgart Mike Weilgart +Mike Weilgart mikeweilgart +Mike Weilgart mikeweilgart +Mikhail Gusarov +Ole Herman Schumacher Elgesem +Ole Herman Schumacher Elgesem Ole Herman S. Elgesem +Ole Herman Schumacher Elgesem Ole Herman Schumacher Elgesem +Ole Herman Schumacher Elgesem Ole Herman Schumacher Elgesem +Ole Herman Schumacher Elgesem Ole Herman Schumacher Elgesem +Sigurd Teigen +Vratislav Podzimek +Vratislav Podzimek Vratislav Podzimek +Vratislav Podzimek Vratislav Podzimek +Hichame Jeffali hicham +Hichame Jeffali hicham +Hichame Jeffali hicham diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 0000000000..13adb3bbd6 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,5 @@ +[style] +based_on_style = pep8 +split_before_first_argument = true +column_limit = 98 +split_all_comma_separated_values = true diff --git a/3rdparty/peg-0.1.15/ChangeLog b/3rdparty/peg-0.1.15/ChangeLog new file mode 100644 index 0000000000..dff4142c5e --- /dev/null +++ b/3rdparty/peg-0.1.15/ChangeLog @@ -0,0 +1,93 @@ +2013-12-18 piumarta + + * src/version.h: 0.1.15 + + * src/compile.c: YY_FREE takes context and pointer as arguments. + + * YYRELEASE: Pass yyctx and pointer to YY_FREE. + +2013-12-01 Ian Piumarta + + * src/version.h: 0.1.14 + + * src/peg.1: Fix several typos and escape backslashes (thanks to + Giulio Paci). + + * LICENSE.txt: Replace "the the" with "the". + +2013-08-16 Ian Piumarta + + * src/compile.c: Predicate actions can refer to yytext (thanks to + Gregory Pakosz). + + * src/leg.leg: Hexadecimal character escapes are supported by leg + (thanks to Hugo Etchegoyen). + +2013-07-20 Ian Piumarta + + * src/getopt.c: Use BSD-licensed getopt() in Windows + build. + + * src/compile.c: Verbose mode handles Variable nodes. + +2013-06-03 Ian Piumarta + + * src/leg.leg, src/compile.c: Add error actions via "~" operator. + + * src/compile.c: Support declaration of local variables at the top + level of semantic actions. Dynamically grow data structures to + remove artificial limits on rule recursion (thanks to Alex + Klinkhamer). Many small changes to better support C++. + + * src/peg.1: Update manual page to describe new features. + + Add build files for Win32 and MacOS thanks to Fyodor Sheremetyev). + +2012-04-29 Ian Piumarta + + * compile.c: Move global state into a structure to facilitate + reentrant and thread-safe parsers (thanks to Dmitry Lipovoi). + +2012-03-29 Ian Piumarta + + * leg.leg: Allow nested, matched braces within actions. + +2011-11-25 Ian Piumarta + + * compile.c: Fix matching of 8-bit chars to allow utf-8 sequences + in matching expressions (thanks to Gregory Pakosz). + +2011-11-24 Ian Piumarta + + * compile.c: Allow octal escapes in character classes. + +2011-11-24 Ian Piumarta + + * Makefile: Remove dwarf sym dirs when cleaning. + + * compile.c: Fix size calculation when resizing text + buffers. + + * leg.leg, peg.peg: Backslash can be escaped. + +2009-08-26 Ian Piumarta + + * leg.leg: Fix match of a single single quote character. + + * examples/basic.leg: Rename getline -> nextline to avoid C + namespace conflict. + +2007-09-13 Ian Piumarta + + * leg.leg: Allow matched braces inside leg actions. Handle empty + rules. Handle empty grammars. + +2007-08-31 Ian Piumarta + + * compile.c: Grow buffers while (not if) they are too + small. Remove dependencies on grammar files. Add more basic + examples. + +2007-05-15 Ian Piumarta + + First public release. diff --git a/3rdparty/peg-0.1.15/LICENSE.txt b/3rdparty/peg-0.1.15/LICENSE.txt new file mode 100644 index 0000000000..7eee1eaf3c --- /dev/null +++ b/3rdparty/peg-0.1.15/LICENSE.txt @@ -0,0 +1,14 @@ +Copyright (c) 2007-2013, Ian Piumarta +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the 'Software'), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +provided that the above copyright notice(s) and this permission notice appear +in all copies or substantial portions of the Software. Inclusion of the +above copyright notice(s) and this permission notice in supporting +documentation would be appreciated but is not required. + +THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. diff --git a/3rdparty/peg-0.1.15/Makefile b/3rdparty/peg-0.1.15/Makefile new file mode 100644 index 0000000000..6a46e57f63 --- /dev/null +++ b/3rdparty/peg-0.1.15/Makefile @@ -0,0 +1,87 @@ +CFLAGS = -g -Wall $(OFLAGS) $(XFLAGS) -Isrc +OFLAGS = -O3 -DNDEBUG +#OFLAGS = -pg + +OBJS = tree.o compile.o + +all : peg leg + +peg : peg.o $(OBJS) + $(CC) $(CFLAGS) -o $@-new peg.o $(OBJS) + mv $@-new $@ + +leg : leg.o $(OBJS) + $(CC) $(CFLAGS) -o $@-new leg.o $(OBJS) + mv $@-new $@ + +ROOT = +PREFIX = /usr/local +BINDIR = $(ROOT)$(PREFIX)/bin +MANDIR = $(ROOT)$(PREFIX)/man/man1 + +install : $(BINDIR) $(BINDIR)/peg $(BINDIR)/leg $(MANDIR) $(MANDIR)/peg.1 + +$(BINDIR) : + mkdir -p $(BINDIR) + +$(BINDIR)/% : % + cp -p $< $@ + strip $@ + +$(MANDIR) : + mkdir -p $(MANDIR) + +$(MANDIR)/% : src/% + cp -p $< $@ + +uninstall : .FORCE + rm -f $(BINDIR)/peg + rm -f $(BINDIR)/leg + rm -f $(MANDIR)/peg.1 + +%.o : src/%.c + $(CC) $(CFLAGS) -c -o $@ $< + +peg.o : src/peg.c src/peg.peg-c + +leg.o : src/leg.c + +check : check-peg check-leg + +check-peg : peg.peg-c .FORCE + diff src/peg.peg-c peg.peg-c + +check-leg : leg.c .FORCE + diff src/leg.c leg.c + +peg.peg-c : src/peg.peg peg + ./peg -o $@ $< + +leg.c : src/leg.leg leg + ./leg -o $@ $< + +new : newpeg newleg + +newpeg : peg.peg-c + mv src/peg.peg-c src/peg.peg-c- + mv peg.peg-c src/. + +newleg : leg.c + mv src/leg.c src/leg.c- + mv leg.c src/. + +test examples : peg leg .FORCE + $(SHELL) -ec '(cd examples; $(MAKE))' + +clean : .FORCE + rm -f src/*~ *~ *.o *.peg.[cd] *.leg.[cd] peg.peg-c leg.c + $(SHELL) -ec '(cd examples; $(MAKE) $@)' + +spotless : clean .FORCE + rm -f src/*- + rm -rf build + rm -f peg + rm -f leg + $(SHELL) -ec '(cd examples; $(MAKE) $@)' + +.FORCE : diff --git a/3rdparty/peg-0.1.15/README.txt b/3rdparty/peg-0.1.15/README.txt new file mode 100644 index 0000000000..1b011d00b0 --- /dev/null +++ b/3rdparty/peg-0.1.15/README.txt @@ -0,0 +1,42 @@ +Building on a Unix-like system +------------------------------ + +Type 'make' or 'make test'. + +The latter builds all the examples and runs them, comparing their +output with the expected output. + +Type 'make install' to install the binaries and manual page under +/usr/local. (Type 'make uninstall' to remove them.) You may have to +do this using 'sudo' or while logged in as root. + +Edit 'Makefile' to change the way things are built and/or the places +where things are installed. + + +Building on MacOS X +------------------- + +Run the 'build-mac.sh' script from a terminal or by double-clicking on +it in the Finder. + +You will need Xcode. The provided project is known to work with Xcode +versions 3.2.6 and 4.3.2. + +Modify build-mac.sh and/or peg.xcodeproj to change the way things are +built. + + +Building on Windows +------------------- + +Run the 'build-win.cmd' script. + +You will need Visual Studio 2010 Express. + +Modify build-win.cmd, leg.vcxproj, leg.vcxproj.filters, peg.gyp, +peg.sln, peg.vcxproj and/or peg.vcxproj.filters to change the way +things are built. + +Local implementations of getopt() and basename() are provided in the +'win' directory. diff --git a/3rdparty/peg-0.1.15/build-mac.sh b/3rdparty/peg-0.1.15/build-mac.sh new file mode 100755 index 0000000000..b8180a4fae --- /dev/null +++ b/3rdparty/peg-0.1.15/build-mac.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +xcodebuild -project peg.xcodeproj -configuration Release + +cp build/Release/peg ./ +cp build/Release/leg ./ diff --git a/3rdparty/peg-0.1.15/build-win.cmd b/3rdparty/peg-0.1.15/build-win.cmd new file mode 100644 index 0000000000..5c3893dd9b --- /dev/null +++ b/3rdparty/peg-0.1.15/build-win.cmd @@ -0,0 +1,5 @@ +@echo off +call "%VS100COMNTOOLS%vsvars32.bat" +msbuild peg.sln /p:Configuration=Release + +xcopy /Y /D Release\*.exe .\ diff --git a/3rdparty/peg-0.1.15/examples/accept.c b/3rdparty/peg-0.1.15/examples/accept.c new file mode 100644 index 0000000000..781e3b11d5 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/accept.c @@ -0,0 +1,11 @@ +#include +#include + +#include "accept.peg.c" + +int main() +{ + while (yyparse()); + + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/accept.peg b/3rdparty/peg-0.1.15/examples/accept.peg new file mode 100644 index 0000000000..9b28e4040d --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/accept.peg @@ -0,0 +1,8 @@ +start <- abcd+ + +abcd <- 'a' { printf("A %d\n", yypos); } bc { printf("ABC %d\n", yypos); } &{YYACCEPT} + / 'b' { printf("B %d\n", yypos); } cd { printf("BCD %d\n", yypos); } &{YYACCEPT} + +bc <- 'b' { printf("B %d\n", yypos); } 'c' { printf("C %d\n", yypos); } + +cd <- 'c' { printf("C %d\n", yypos); } 'd' { printf("D %d\n", yypos); } diff --git a/3rdparty/peg-0.1.15/examples/accept.ref b/3rdparty/peg-0.1.15/examples/accept.ref new file mode 100644 index 0000000000..789f528306 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/accept.ref @@ -0,0 +1,32 @@ +A 3 +B 3 +C 3 +ABC 3 +B 3 +C 3 +D 3 +BCD 3 +A 3 +B 3 +C 3 +ABC 3 +B 3 +C 3 +D 3 +BCD 3 +A 3 +B 3 +C 3 +ABC 3 +B 3 +C 3 +D 3 +BCD 3 +A 3 +B 3 +C 3 +ABC 3 +B 3 +C 3 +D 3 +BCD 3 diff --git a/3rdparty/peg-0.1.15/examples/basic.leg b/3rdparty/peg-0.1.15/examples/basic.leg new file mode 100644 index 0000000000..ed38a8d711 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/basic.leg @@ -0,0 +1,361 @@ +# A 'syntax-directed interpreter' (all execution is a side-effect of parsing). +# Inspired by Dennis Allison's original Tiny BASIC grammar, circa 1975. +# +# Copyright (c) 2007 by Ian Piumarta +# All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the 'Software'), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, provided that the above copyright notice(s) and this +# permission notice appear in all copies of the Software. Acknowledgement +# of the use of this Software in supporting documentation would be +# appreciated but is not required. +# +# THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. +# +# Last edited: 2012-04-29 15:14:06 by piumarta on emilia + +%{ +# include + + typedef struct line line; + + struct line + { + int number; + int length; + char *text; + }; + + line *lines= 0; + int numLines= 0; + int pc= -1, epc= -1; + int batch= 0; + + int nextline(char *buf, int max); + +# define min(x, y) ((x) < (y) ? (x) : (y)) + +# define YY_INPUT(buf, result, max_size) \ + { \ + if ((pc >= 0) && (pc < numLines)) \ + { \ + line *linep= lines+pc++; \ + result= min(max_size, linep->length); \ + memcpy(buf, linep->text, result); \ + } \ + else \ + result= nextline(buf, max_size); \ + } + + union value { + int number; + char *string; + int (*binop)(int lhs, int rhs); + }; + +# define YYSTYPE union value + + int variables[26]; + + void accept(int number, char *line); + + void save(char *name); + void load(char *name); + void type(char *name); + + int lessThan(int lhs, int rhs) { return lhs < rhs; } + int lessEqual(int lhs, int rhs) { return lhs <= rhs; } + int notEqual(int lhs, int rhs) { return lhs != rhs; } + int equalTo(int lhs, int rhs) { return lhs == rhs; } + int greaterEqual(int lhs, int rhs) { return lhs >= rhs; } + int greaterThan(int lhs, int rhs) { return lhs > rhs; } + + int input(void); + + int stack[1024], sp= 0; + + char *help; + + void error(char *fmt, ...); + int findLine(int n, int create); +%} + +line = - s:statement CR +| - n:number < ( !CR . )* CR > { accept(n.number, yytext); } +| - CR +| - < ( !CR . )* CR > { epc= pc; error("syntax error"); } +| - !. { exit(0); } + +statement = 'print'- expr-list +| 'if'- e1:expression r:relop e2:expression { if (!r.binop(e1.number, e2.number)) yythunkpos= 0; } + 'then'- statement +| 'goto'- e:expression { epc= pc; if ((pc= findLine(e.number, 0)) < 0) error("no such line"); } +| 'input'- var-list +| 'let'- v:var EQUAL e:expression { variables[v.number]= e.number; } +| 'gosub'- e:expression { epc= pc; if (sp < 1024) stack[sp++]= pc, pc= findLine(e.number, 0); else error("too many gosubs"); + if (pc < 0) error("no such line"); } +| 'return'- { epc= pc; if ((pc= sp ? stack[--sp] : -1) < 0) error("no gosub"); } +| 'clear'- { while (numLines) accept(lines->number, "\n"); } +| 'list'- { int i; for (i= 0; i < numLines; ++i) printf("%5d %s", lines[i].number, lines[i].text); } +| 'run'- s:string { load(s.string); pc= 0; } +| 'run'- { pc= 0; } +| 'end'- { pc= -1; if (batch) exit(0); } +| 'rem'- ( !CR . )* +| ('bye'|'quit'|'exit')- { exit(0); } +| 'save'- s:string { save(s.string); } +| 'load'- s:string { load(s.string); } +| 'type'- s:string { type(s.string); } +| 'dir'- { system("ls *.bas"); } +| 'help'- { fprintf(stderr, "%s", help); } + +expr-list = ( e:string { printf("%s", e.string); } + | e:expression { printf("%d", e.number); } + )? ( COMMA ( e:string { printf("%s", e.string); } + | e:expression { printf("%d", e.number); } + ) + )* ( COMMA + | !COMMA { printf("\n"); } + ) + +var-list = v:var { variables[v.number]= input(); } + ( COMMA v:var { variables[v.number]= input(); } + )* + +expression = ( PLUS? l:term + | MINUS l:term { l.number = -l.number } + ) ( PLUS r:term { l.number += r.number } + | MINUS r:term { l.number -= r.number } + )* { $$.number = l.number } + +term = l:factor ( STAR r:factor { l.number *= r.number } + | SLASH r:factor { l.number /= r.number } + )* { $$.number = l.number } + +factor = v:var { $$.number = variables[v.number] } +| n:number +| OPEN expression CLOSE + +var = < [a-z] > - { $$.number = yytext[0] - 'a' } + +number = < digit+ > - { $$.number = atoi(yytext); } + +digit = [0-9] + +string = '"' < [^\"]* > '"' - { $$.string = yytext; } + +relop = '<=' - { $$.binop= lessEqual; } +| '<>' - { $$.binop= notEqual; } +| '<' - { $$.binop= lessThan; } +| '>=' - { $$.binop= greaterEqual; } +| '>' - { $$.binop= greaterThan; } +| '=' - { $$.binop= equalTo; } + +EQUAL = '=' - CLOSE = ')' - OPEN = '(' - +SLASH = '/' - STAR = '*' - MINUS = '-' - +PLUS = '+' - COMMA = ',' - + +- = [ \t]* + +CR = '\n' | '\r' | '\r\n' + +%% + +#include +#include + +char *help= + "print | [, | ...] [,]\n" + "if <|<=|<>|=|>=|> then \n" + "input [, ...] let = \n" + "goto gosub \n" + "end return\n" + "list clear\n" + "run [\"filename\"] rem \n" + "dir type \"filename\"\n" + "save \"filename\" load \"filename\"\n" + "bye|quit|exit help\n" + ; + +void error(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (epc > 0) + fprintf(stderr, "\nline %d: %s", lines[epc-1].number, lines[epc-1].text); + else + fprintf(stderr, "\n"); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + epc= pc= -1; +} + +#ifdef USE_READLINE +# include +# include +#endif + +int nextline(char *buf, int max) +{ + pc= -1; + if (batch) exit(0); + if (isatty(fileno(stdin))) + { +# ifdef USE_READLINE + char *line= readline(">"); + if (line) + { + int len= strlen(line); + if (len >= max) len= max - 1; + strncpy(buf, line, len); + (buf)[len]= '\n'; + add_history(line); + free(line); + return len + 1; + } + else + { + printf("\n"); + return 0; + } +# endif + putchar('>'); + fflush(stdout); + } + return fgets(buf, max, stdin) ? strlen(buf) : 0; +} + +int maxLines= 0; + +int findLine(int n, int create) +{ + int lo= 0, hi= numLines - 1; + while (lo <= hi) + { + int mid= (lo + hi) / 2, lno= lines[mid].number; + if (lno > n) + hi= mid - 1; + else if (lno < n) + lo= mid + 1; + else + return mid; + } + if (create) + { + if (numLines == maxLines) + { + maxLines *= 2; + lines= realloc(lines, sizeof(line) * maxLines); + } + if (lo < numLines) + memmove(lines + lo + 1, lines + lo, sizeof(line) * (numLines - lo)); + ++numLines; + lines[lo].number= n; + lines[lo].text= 0; + return lo; + } + return -1; +} + +void accept(int n, char *s) +{ + if (s[0] < 32) /* delete */ + { + int lno= findLine(n, 0); + if (lno >= 0) + { + if (lno < numLines - 1) + memmove(lines + lno, lines + lno + 1, sizeof(line) * (numLines - lno - 1)); + --numLines; + } + } + else /* insert */ + { + int lno= findLine(n, 1); + if (lines[lno].text) free(lines[lno].text); + lines[lno].length= strlen(s); + lines[lno].text= strdup(s); + } +} + +char *extend(char *name) +{ + static char path[1024]; + int len= strlen(name); + sprintf(path, "%s%s", name, (((len > 4) && !strcasecmp(".bas", name + len - 4)) ? "" : ".bas")); + return path; +} + +void save(char *name) +{ + FILE *f= fopen(name= extend(name), "w"); + if (!f) + perror(name); + else + { + int i; + for (i= 0; i < numLines; ++i) + fprintf(f, "%d %s", lines[i].number, lines[i].text); + fclose(f); + } +} + +void load(char *name) +{ + FILE *f= fopen(name= extend(name), "r"); + if (!f) + perror(name); + else + { + int lineNumber; + char lineText[1024]; + while ((1 == fscanf(f, " %d ", &lineNumber)) && fgets(lineText, sizeof(lineText), f)) + accept(lineNumber, lineText); + fclose(f); + } +} + +void type(char *name) +{ + FILE *f= fopen(name= extend(name), "r"); + if (!f) + perror(name); + else + { + int c, d; + while ((c= getc(f)) >= 0) + putchar(d= c); + fclose(f); + if ('\n' != d && '\r' != d) putchar('\n'); + } +} + +int input(void) +{ + char line[32]; + fgets(line, sizeof(line), stdin); + return atoi(line); +} + +int main(int argc, char **argv) +{ + lines= malloc(sizeof(line) * (maxLines= 32)); + numLines= 0; + + if (argc > 1) + { + batch= 1; + while (argc-- > 1) + load(*++argv); + pc= 0; + } + + while (!feof(stdin)) + yyparse(); + + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/basic.ref b/3rdparty/peg-0.1.15/examples/basic.ref new file mode 100644 index 0000000000..90d916c89b --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/basic.ref @@ -0,0 +1,10 @@ + 1 + 2 4 + 3 6 9 + 4 8 12 16 + 5 10 15 20 25 + 6 12 18 24 30 36 + 7 14 21 28 35 42 49 + 8 16 24 32 40 48 56 64 + 9 18 27 36 45 54 63 72 81 + 10 20 30 40 50 60 70 80 90 100 diff --git a/3rdparty/peg-0.1.15/examples/bench.bas b/3rdparty/peg-0.1.15/examples/bench.bas new file mode 100644 index 0000000000..ffdbd44ffe --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/bench.bas @@ -0,0 +1,8 @@ +100 let n=100000 +120 let m=0 +110 let s=0 +130 let m=m+1 +140 let s=s+m +150 if m +int vars[26]; +%} + +Stmt = - e:Expr EOL { printf("%d\n", e); } + | ( !EOL . )* EOL { printf("error\n"); } + +Expr = i:ID ASSIGN s:Sum { $$= vars[i]= s; } + | s:Sum { $$= s; } + +Sum = l:Product + ( PLUS r:Product { l += r; } + | MINUS r:Product { l -= r; } + )* { $$= l; } + +Product = l:Value + ( TIMES r:Value { l *= r; } + | DIVIDE r:Value { l /= r; } + )* { $$= l; } + +Value = i:NUMBER { $$= atoi(yytext); } + | i:ID !ASSIGN { $$= vars[i]; } + | OPEN i:Expr CLOSE { $$= i; } + +NUMBER = < [0-9]+ > - { $$= atoi(yytext); } +ID = < [a-z] > - { $$= yytext[0] - 'a'; } +ASSIGN = '=' - +PLUS = '+' - +MINUS = '-' - +TIMES = '*' - +DIVIDE = '/' - +OPEN = '(' - +CLOSE = ')' - + +- = [ \t]* +EOL = '\n' | '\r\n' | '\r' | ';' + +%% + +int main() +{ + while (yyparse()); + + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/calc.ref b/3rdparty/peg-0.1.15/examples/calc.ref new file mode 100644 index 0000000000..dbd7d59ec2 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/calc.ref @@ -0,0 +1,3 @@ +6 +7 +42 diff --git a/3rdparty/peg-0.1.15/examples/dc.c b/3rdparty/peg-0.1.15/examples/dc.c new file mode 100644 index 0000000000..32bf1a54a8 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/dc.c @@ -0,0 +1,17 @@ +#include +#include + +int stack[1024]; +int stackp= -1; + +int push(int n) { return stack[++stackp]= n; } +int pop(void) { return stack[stackp--]; } + +#include "dc.peg.c" + +int main() +{ + while (yyparse()); + + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/dc.peg b/3rdparty/peg-0.1.15/examples/dc.peg new file mode 100644 index 0000000000..75dcb6719e --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/dc.peg @@ -0,0 +1,27 @@ +# Grammar + +Expr <- SPACE Sum EOL { printf("%d\n", pop()); } + / (!EOL .)* EOL { printf("error\n"); } + +Sum <- Product ( PLUS Product { int r= pop(), l= pop(); push(l + r); } + / MINUS Product { int r= pop(), l= pop(); push(l - r); } + )* + +Product <- Value ( TIMES Value { int r= pop(), l= pop(); push(l * r); } + / DIVIDE Value { int r= pop(), l= pop(); push(l / r); } + )* + +Value <- NUMBER { push(atoi(yytext)); } + / OPEN Sum CLOSE + +# Lexemes + +NUMBER <- < [0-9]+ > SPACE +PLUS <- '+' SPACE +MINUS <- '-' SPACE +TIMES <- '*' SPACE +DIVIDE <- '/' SPACE +OPEN <- '(' SPACE +CLOSE <- ')' SPACE +SPACE <- [ \t]* +EOL <- '\n' / '\r\n' / '\r' diff --git a/3rdparty/peg-0.1.15/examples/dc.ref b/3rdparty/peg-0.1.15/examples/dc.ref new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/dc.ref @@ -0,0 +1 @@ +42 diff --git a/3rdparty/peg-0.1.15/examples/dcv.c b/3rdparty/peg-0.1.15/examples/dcv.c new file mode 100644 index 0000000000..0c5c46d857 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/dcv.c @@ -0,0 +1,20 @@ +#include +#include + +int stack[1024]; +int stackp= -1; +int var= 0; +int vars[26]; + +int push(int n) { return stack[++stackp]= n; } +int pop(void) { return stack[stackp--]; } +int top(void) { return stack[stackp]; } + +#include "dcv.peg.c" + +int main() +{ + while (yyparse()); + + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/dcv.peg b/3rdparty/peg-0.1.15/examples/dcv.peg new file mode 100644 index 0000000000..2ae3a8cb0e --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/dcv.peg @@ -0,0 +1,34 @@ +# Grammar + +Stmt <- SPACE Expr EOL { printf("%d\n", pop()); } + / (!EOL .)* EOL { printf("error\n"); } + +Expr <- ID { var= yytext[0] } ASSIGN Sum { vars[var - 'a']= top(); } + / Sum + +Sum <- Product ( PLUS Product { int r= pop(), l= pop(); push(l + r); } + / MINUS Product { int r= pop(), l= pop(); push(l - r); } + )* + +Product <- Value ( TIMES Value { int r= pop(), l= pop(); push(l * r); } + / DIVIDE Value { int r= pop(), l= pop(); push(l / r); } + )* + +Value <- NUMBER { push(atoi(yytext)); } + / < ID > !ASSIGN { push(vars[yytext[0] - 'a']); } + / OPEN Expr CLOSE + +# Lexemes + +NUMBER <- < [0-9]+ > SPACE +ID <- < [a-z] > SPACE +ASSIGN <- '=' SPACE +PLUS <- '+' SPACE +MINUS <- '-' SPACE +TIMES <- '*' SPACE +DIVIDE <- '/' SPACE +OPEN <- '(' SPACE +CLOSE <- ')' SPACE + +SPACE <- [ \t]* +EOL <- '\n' / '\r\n' / '\r' / ';' diff --git a/3rdparty/peg-0.1.15/examples/dcv.ref b/3rdparty/peg-0.1.15/examples/dcv.ref new file mode 100644 index 0000000000..dbd7d59ec2 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/dcv.ref @@ -0,0 +1,3 @@ +6 +7 +42 diff --git a/3rdparty/peg-0.1.15/examples/erract.leg b/3rdparty/peg-0.1.15/examples/erract.leg new file mode 100644 index 0000000000..55757302dc --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/erract.leg @@ -0,0 +1,27 @@ +%{ +#include +%} + +Expr = a:NUMBER PLUS ~{ printf("fail at PLUS\n") } b:NUMBER { printf("got addition\n"); } + | ( a:NUMBER MINUS b:NUMBER { printf("got subtraction\n"); } ) ~{ printf("fail at subtraction\n") } + | a:NUMBER TIMES b:NUMBER { printf("got multiplication\n"); } + | a:NUMBER DIVIDE b:NUMBER { printf("got division\n"); } + +NUMBER = < [0-9]+ > - { $$= atoi(yytext); } +PLUS = '+' - +MINUS = '-' - +TIMES = '*' - +DIVIDE = '/' - + +- = (SPACE | EOL)* +SPACE = [ \t] +EOL = '\n' | '\r\n' | '\r' | ';' + +%% + +int main() +{ + while (yyparse()); + + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/erract.ref b/3rdparty/peg-0.1.15/examples/erract.ref new file mode 100644 index 0000000000..338ffcfa96 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/erract.ref @@ -0,0 +1,4 @@ +fail at PLUS +fail at subtraction +got multiplication +fail at subtraction diff --git a/3rdparty/peg-0.1.15/examples/fibonacci.bas b/3rdparty/peg-0.1.15/examples/fibonacci.bas new file mode 100644 index 0000000000..1872bd3b2a --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/fibonacci.bas @@ -0,0 +1,17 @@ +100 let n=32 +110 gosub 200 +120 print "fibonacci(",n,") = ", m +130 end + +200 let c=n +210 let b=1 +220 if c<2 then goto 400 +230 let c=c-1 +240 let a=1 +300 let c=c-1 +310 let d=a+b +320 let a=b +330 let b=d+1 +340 if c<>0 then goto 300 +400 let m=b +410 return diff --git a/3rdparty/peg-0.1.15/examples/left.c b/3rdparty/peg-0.1.15/examples/left.c new file mode 100644 index 0000000000..ac8cd0bd86 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/left.c @@ -0,0 +1,17 @@ +#include + +#define YY_INPUT(buf, result, max) \ +{ \ + int c= getchar(); \ + result= (EOF == c) ? 0 : (*(buf)= c, 1); \ + if (EOF != c) printf("<%c>\n", c); \ +} + +#include "left.peg.c" + +int main() +{ + printf(yyparse() ? "success\n" : "failure\n"); + + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/left.peg b/3rdparty/peg-0.1.15/examples/left.peg new file mode 100644 index 0000000000..f282227d59 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/left.peg @@ -0,0 +1,3 @@ +# Grammar + +S <- (S 'a' / 'a') !'a' diff --git a/3rdparty/peg-0.1.15/examples/localleg.leg b/3rdparty/peg-0.1.15/examples/localleg.leg new file mode 100644 index 0000000000..4d52d5693d --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/localleg.leg @@ -0,0 +1,24 @@ +%{ +#define YY_CTX_LOCAL 1 +#define YY_CTX_MEMBERS \ + int count; +%} + +Char = ('\n' | '\r\n' | '\r') { yy->count++ } + | . + +%% + +#include +#include + +int main() +{ + yycontext yy; + memset(&yy, 0, sizeof(yy)); + while (yyparse(&yy)) + ; + printf("%d newlines\n", yy.count); + yyrelease(&yy); + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/localleg.ref b/3rdparty/peg-0.1.15/examples/localleg.ref new file mode 100644 index 0000000000..f4a0919851 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/localleg.ref @@ -0,0 +1 @@ +24 newlines diff --git a/3rdparty/peg-0.1.15/examples/localpeg.c b/3rdparty/peg-0.1.15/examples/localpeg.c new file mode 100644 index 0000000000..837ebc8842 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/localpeg.c @@ -0,0 +1,13 @@ +#include + +#define YY_CTX_LOCAL + +#include "test.peg.c" + +int main() +{ + yycontext ctx; + memset(&ctx, 0, sizeof(yycontext)); + while (yyparse(&ctx)); + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/localpeg.ref b/3rdparty/peg-0.1.15/examples/localpeg.ref new file mode 100644 index 0000000000..2d181091a0 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/localpeg.ref @@ -0,0 +1,10 @@ +a1 ab1 . +a2 ac2 . +a3 ad3 . +a3 ae3 . +a4 af4 afg4 . +a4 af5 afh5 . +a4 af4 afg4 . +a4 af5 afh5 . +af6 afi6 a6 . +af6 af7 afj7 a6 . diff --git a/3rdparty/peg-0.1.15/examples/rule.c b/3rdparty/peg-0.1.15/examples/rule.c new file mode 100644 index 0000000000..15eb0c64ba --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/rule.c @@ -0,0 +1,11 @@ +#include +#include + +#include "rule.peg.c" + +int main() +{ + while (yyparse()); + + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/rule.peg b/3rdparty/peg-0.1.15/examples/rule.peg new file mode 100644 index 0000000000..60a32faa13 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/rule.peg @@ -0,0 +1,8 @@ +start <- abcd+ + +abcd <- 'a' { printf("A %d\n", yypos); } bc { printf("ABC %d\n", yypos); } + / 'b' { printf("B %d\n", yypos); } cd { printf("BCD %d\n", yypos); } + +bc <- 'b' { printf("B %d\n", yypos); } 'c' { printf("C %d\n", yypos); } + +cd <- 'c' { printf("C %d\n", yypos); } 'd' { printf("D %d\n", yypos); } diff --git a/3rdparty/peg-0.1.15/examples/rule.ref b/3rdparty/peg-0.1.15/examples/rule.ref new file mode 100644 index 0000000000..4249ebec53 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/rule.ref @@ -0,0 +1,32 @@ +A 24 +B 24 +C 24 +ABC 24 +B 24 +C 24 +D 24 +BCD 24 +A 24 +B 24 +C 24 +ABC 24 +B 24 +C 24 +D 24 +BCD 24 +A 24 +B 24 +C 24 +ABC 24 +B 24 +C 24 +D 24 +BCD 24 +A 24 +B 24 +C 24 +ABC 24 +B 24 +C 24 +D 24 +BCD 24 diff --git a/3rdparty/peg-0.1.15/examples/test.bas b/3rdparty/peg-0.1.15/examples/test.bas new file mode 100644 index 0000000000..8a96e10707 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/test.bas @@ -0,0 +1,12 @@ +10 let i=1 +20 gosub 100 +30 let i=i+1 +40 if i<=10 then goto 20 +50 end + +100 let j=1 +110 print " ", i*j, +120 let j=j+1 +130 if j<=i then goto 110 +140 print +150 return diff --git a/3rdparty/peg-0.1.15/examples/test.c b/3rdparty/peg-0.1.15/examples/test.c new file mode 100644 index 0000000000..0403422c33 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/test.c @@ -0,0 +1,8 @@ +#include +#include "test.peg.c" + +int main() +{ + while (yyparse()); + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/test.peg b/3rdparty/peg-0.1.15/examples/test.peg new file mode 100644 index 0000000000..716d52372f --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/test.peg @@ -0,0 +1,13 @@ +start <- body '.' { printf(".\n"); } + +body <- 'a' { printf("a1 "); } 'b' { printf("ab1 "); } + + / 'a' { printf("a2 "); } 'c' { printf("ac2 "); } + + / 'a' { printf("a3 "); } ( 'd' { printf("ad3 "); } / 'e' { printf("ae3 "); } ) + + / 'a' { printf("a4 "); } ( 'f' { printf("af4 "); } 'g' { printf("afg4 "); } + / 'f' { printf("af5 "); } 'h' { printf("afh5 "); } ) + + / 'a' { printf("a6 "); } ( 'f' &{ printf("af6 ") } 'i' &{ printf("afi6 ") } + / 'f' &{ printf("af7 ") } 'j' &{ printf("afj7 ") } ) diff --git a/3rdparty/peg-0.1.15/examples/test.ref b/3rdparty/peg-0.1.15/examples/test.ref new file mode 100644 index 0000000000..2d181091a0 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/test.ref @@ -0,0 +1,10 @@ +a1 ab1 . +a2 ac2 . +a3 ad3 . +a3 ae3 . +a4 af4 afg4 . +a4 af5 afh5 . +a4 af4 afg4 . +a4 af5 afh5 . +af6 afi6 a6 . +af6 af7 afj7 a6 . diff --git a/3rdparty/peg-0.1.15/examples/username.leg b/3rdparty/peg-0.1.15/examples/username.leg new file mode 100644 index 0000000000..2170052aad --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/username.leg @@ -0,0 +1,14 @@ +%{ +#include +%} + +start = "username" { printf("%s", getlogin()); } +| < . > { putchar(yytext[0]); } + +%% + +int main() +{ + while (yyparse()); + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/wc.leg b/3rdparty/peg-0.1.15/examples/wc.leg new file mode 100644 index 0000000000..59199c89fb --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/wc.leg @@ -0,0 +1,22 @@ +%{ +#include +int lines= 0, words= 0, chars= 0; +%} + +start = (line | word | char) + +line = < (( '\n' '\r'* ) | ( '\r' '\n'* )) > { lines++; chars += yyleng; } +word = < [a-zA-Z]+ > { words++; chars += yyleng; printf("<%s>\n", yytext); } +char = . { chars++; } + +%% + +int main() +{ + while (yyparse()) + ; + printf("%d lines\n", lines); + printf("%d chars\n", chars); + printf("%d words\n", words); + return 0; +} diff --git a/3rdparty/peg-0.1.15/examples/wc.ref b/3rdparty/peg-0.1.15/examples/wc.ref new file mode 100644 index 0000000000..083a46e649 --- /dev/null +++ b/3rdparty/peg-0.1.15/examples/wc.ref @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +22 lines +425 chars +52 words diff --git a/3rdparty/peg-0.1.15/leg b/3rdparty/peg-0.1.15/leg new file mode 100755 index 0000000000..f104fd5093 Binary files /dev/null and b/3rdparty/peg-0.1.15/leg differ diff --git a/3rdparty/peg-0.1.15/leg.vcxproj b/3rdparty/peg-0.1.15/leg.vcxproj new file mode 100644 index 0000000000..6c6a058958 --- /dev/null +++ b/3rdparty/peg-0.1.15/leg.vcxproj @@ -0,0 +1,75 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE66} + Win32Proj + leg + $(ProjectName) + + + + Application + + + + + + + + + $(ExecutablePath);$(MSBuildProjectDirectory)\.\bin\;$(MSBuildProjectDirectory)\.\bin\ + $(Configuration)\obj\$(ProjectName)\ + $(SolutionDir)$(Configuration)\ + + + + win;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;DEBUG;%(PreprocessorDefinitions) + + + true + $(OutDir)$(ProjectName).exe + Console + + + win;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;DEBUG;%(PreprocessorDefinitions);%(PreprocessorDefinitions) + + + + + win;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + + + true + $(OutDir)$(ProjectName).exe + Console + + + win;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions);%(PreprocessorDefinitions) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/3rdparty/peg-0.1.15/leg.vcxproj.filters b/3rdparty/peg-0.1.15/leg.vcxproj.filters new file mode 100644 index 0000000000..8ac047ae9a --- /dev/null +++ b/3rdparty/peg-0.1.15/leg.vcxproj.filters @@ -0,0 +1,19 @@ + + + + + {47FC5EC4-15EB-E92F-89D7-AFE51CF838A9} + + + + + + win + + + + + + + + \ No newline at end of file diff --git a/3rdparty/peg-0.1.15/peg b/3rdparty/peg-0.1.15/peg new file mode 100755 index 0000000000..a86667622b Binary files /dev/null and b/3rdparty/peg-0.1.15/peg differ diff --git a/3rdparty/peg-0.1.15/peg.gyp b/3rdparty/peg-0.1.15/peg.gyp new file mode 100644 index 0000000000..17c5bc0239 --- /dev/null +++ b/3rdparty/peg-0.1.15/peg.gyp @@ -0,0 +1,81 @@ +{ + 'targets': [ + { + 'target_name': 'peg', + 'type': 'executable', + 'msvs_guid': '5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65', + 'sources': [ + 'peg.c', + 'tree.c', + 'compile.c', + ], + 'conditions': [ + ['OS=="win"', { + 'include_dirs': [ + 'win', + ], + 'sources': [ + 'win/getopt.c', + ], + }], + ], + }, + { + 'target_name': 'leg', + 'type': 'executable', + 'msvs_guid': '5ECEC9E5-8F23-47B6-93E0-C3B328B3BE66', + 'sources': [ + 'leg.c', + 'tree.c', + 'compile.c', + ], + 'conditions': [ + ['OS=="win"', { + 'include_dirs': [ + 'win', + ], + 'sources': [ + 'win/getopt.c', + ], + }], + ], + }, + ], + + 'target_defaults': { + 'configurations': { + 'Debug': { + 'defines': [ + 'DEBUG', + ], + }, + 'Release': { + 'defines': [ + 'NDEBUG', + ], + }, + }, + }, + + # define default project settings + 'conditions': [ + ['OS=="win"', { + 'target_defaults': { + 'defines': [ + 'WIN32', + '_WINDOWS', + ], + 'msvs_settings': { + 'VCLinkerTool': { + 'GenerateDebugInformation': 'true', + # SubSystem values: + # 0 == not set + # 1 == /SUBSYSTEM:CONSOLE + # 2 == /SUBSYSTEM:WINDOWS + 'SubSystem': '1', + }, + }, + }, + }], + ], +} diff --git a/3rdparty/peg-0.1.15/peg.sln b/3rdparty/peg-0.1.15/peg.sln new file mode 100644 index 0000000000..4956e5f484 --- /dev/null +++ b/3rdparty/peg-0.1.15/peg.sln @@ -0,0 +1,27 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "leg", "leg.vcxproj", "{5ECEC9E5-8F23-47B6-93E0-C3B328B3BE66}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "peg", "peg.vcxproj", "{5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65}.Debug|Win32.ActiveCfg = Debug|Win32 + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65}.Debug|Win32.Build.0 = Debug|Win32 + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65}.Release|Win32.ActiveCfg = Release|Win32 + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65}.Release|Win32.Build.0 = Release|Win32 + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE66}.Debug|Win32.ActiveCfg = Debug|Win32 + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE66}.Debug|Win32.Build.0 = Debug|Win32 + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE66}.Release|Win32.ActiveCfg = Release|Win32 + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE66}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection +EndGlobal diff --git a/3rdparty/peg-0.1.15/peg.vcxproj b/3rdparty/peg-0.1.15/peg.vcxproj new file mode 100644 index 0000000000..a8e2d5a0cc --- /dev/null +++ b/3rdparty/peg-0.1.15/peg.vcxproj @@ -0,0 +1,75 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65} + Win32Proj + peg + $(ProjectName) + + + + Application + + + + + + + + + $(ExecutablePath);$(MSBuildProjectDirectory)\.\bin\;$(MSBuildProjectDirectory)\.\bin\ + $(Configuration)\obj\$(ProjectName)\ + $(SolutionDir)$(Configuration)\ + + + + win;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;DEBUG;%(PreprocessorDefinitions) + + + true + $(OutDir)$(ProjectName).exe + Console + + + win;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;DEBUG;%(PreprocessorDefinitions);%(PreprocessorDefinitions) + + + + + win;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + + + true + $(OutDir)$(ProjectName).exe + Console + + + win;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions);%(PreprocessorDefinitions) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/3rdparty/peg-0.1.15/peg.vcxproj.filters b/3rdparty/peg-0.1.15/peg.vcxproj.filters new file mode 100644 index 0000000000..8075efc7d9 --- /dev/null +++ b/3rdparty/peg-0.1.15/peg.vcxproj.filters @@ -0,0 +1,19 @@ + + + + + {47FC5EC4-15EB-E92F-89D7-AFE51CF838A9} + + + + + + win + + + + + + + + \ No newline at end of file diff --git a/3rdparty/peg-0.1.15/peg.xcodeproj/project.pbxproj b/3rdparty/peg-0.1.15/peg.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..788498fff2 --- /dev/null +++ b/3rdparty/peg-0.1.15/peg.xcodeproj/project.pbxproj @@ -0,0 +1,317 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXAggregateTarget section */ + EFBC7368F96EACB75989C21D /* All */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 007E1122105A105C63F8A59F /* Build configuration list for PBXAggregateTarget "All" */; + buildPhases = ( + ); + dependencies = ( + B85F90055126E00C1334834C /* PBXTargetDependency */, + 3DF530CEA77A591E4DFBFF2F /* PBXTargetDependency */, + ); + name = All; + productName = All; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 2D4E663DE432A398FC78635B /* compile.c in Sources */ = {isa = PBXBuildFile; fileRef = D4BAF07C3AF28E51DD58E853 /* compile.c */; }; + 3FE25C706AB45972C102CBB4 /* tree.c in Sources */ = {isa = PBXBuildFile; fileRef = E503317C684EFEB3E7E03861 /* tree.c */; }; + 4D30CEABCD51397A50F65058 /* compile.c in Sources */ = {isa = PBXBuildFile; fileRef = D4BAF07C3AF28E51DD58E853 /* compile.c */; }; + 7921C7C2AD25A4A4C02470F5 /* peg.c in Sources */ = {isa = PBXBuildFile; fileRef = 27FA3C119507A9A914A66936 /* peg.c */; }; + 8B61C6AE75A1750C17350E64 /* tree.c in Sources */ = {isa = PBXBuildFile; fileRef = E503317C684EFEB3E7E03861 /* tree.c */; }; + D8C3FFD80B6642D8BB341B90 /* leg.c in Sources */ = {isa = PBXBuildFile; fileRef = 454F9F599E7B65F90C62CF9C /* leg.c */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 12CD2CA862C5C1693300D7EF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D23EA86C97C1C940E2591A06 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 84A1E5C1231D1E337ED0FC84; + remoteInfo = leg; + }; + 22C9A5E8EF76D4B801BB24E5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D23EA86C97C1C940E2591A06 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 050EA9DBA8F5C296C3E39B8A; + remoteInfo = peg; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 27FA3C119507A9A914A66936 /* peg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = src/peg.c; sourceTree = ""; }; + 4165F882B6F541E12DBD6A0D /* peg */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = peg; sourceTree = BUILT_PRODUCTS_DIR; }; + 454F9F599E7B65F90C62CF9C /* leg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = src/leg.c; sourceTree = ""; }; + 5C991B5472A7498A982B7350 /* peg.gyp */ = {isa = PBXFileReference; lastKnownFileType = text; path = peg.gyp; sourceTree = ""; }; + C9B3F1D07DD720C50DE87DC5 /* leg */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = leg; sourceTree = BUILT_PRODUCTS_DIR; }; + D4BAF07C3AF28E51DD58E853 /* compile.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = src/compile.c; sourceTree = ""; }; + E503317C684EFEB3E7E03861 /* tree.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = src/tree.c; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3DF86BD64E76AD4F8D892CF6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97D5812F2529A3E39CE17CDE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3854A05A8AE7E9B329F09174 /* Build */ = { + isa = PBXGroup; + children = ( + 5C991B5472A7498A982B7350 /* peg.gyp */, + ); + name = Build; + sourceTree = ""; + }; + AB7FD4EAF4FFE1A1CE63E31B = { + isa = PBXGroup; + children = ( + E2D71C5771542F758C302162 /* Source */, + AF64E60ED33C9E5DC5DB4C21 /* Products */, + 3854A05A8AE7E9B329F09174 /* Build */, + ); + sourceTree = ""; + }; + AF64E60ED33C9E5DC5DB4C21 /* Products */ = { + isa = PBXGroup; + children = ( + 4165F882B6F541E12DBD6A0D /* peg */, + C9B3F1D07DD720C50DE87DC5 /* leg */, + ); + name = Products; + sourceTree = ""; + }; + E2D71C5771542F758C302162 /* Source */ = { + isa = PBXGroup; + children = ( + D4BAF07C3AF28E51DD58E853 /* compile.c */, + 454F9F599E7B65F90C62CF9C /* leg.c */, + 27FA3C119507A9A914A66936 /* peg.c */, + E503317C684EFEB3E7E03861 /* tree.c */, + ); + name = Source; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 050EA9DBA8F5C296C3E39B8A /* peg */ = { + isa = PBXNativeTarget; + buildConfigurationList = EF4B00311D83FC2C01B276A4 /* Build configuration list for PBXNativeTarget "peg" */; + buildPhases = ( + 74AA795D8990365CCE282118 /* Sources */, + 97D5812F2529A3E39CE17CDE /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = peg; + productName = peg; + productReference = 4165F882B6F541E12DBD6A0D /* peg */; + productType = "com.apple.product-type.tool"; + }; + 84A1E5C1231D1E337ED0FC84 /* leg */ = { + isa = PBXNativeTarget; + buildConfigurationList = F7568C4D321FF46C2F4B43FB /* Build configuration list for PBXNativeTarget "leg" */; + buildPhases = ( + 915DAD0C515729956FE2BC69 /* Sources */, + 3DF86BD64E76AD4F8D892CF6 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = leg; + productName = leg; + productReference = C9B3F1D07DD720C50DE87DC5 /* leg */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D23EA86C97C1C940E2591A06 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + }; + buildConfigurationList = 283B121430353A60CB56914F /* Build configuration list for PBXProject "peg" */; + compatibilityVersion = "Xcode 3.2"; + hasScannedForEncodings = 1; + mainGroup = AB7FD4EAF4FFE1A1CE63E31B; + projectDirPath = ""; + projectRoot = ""; + targets = ( + EFBC7368F96EACB75989C21D /* All */, + 050EA9DBA8F5C296C3E39B8A /* peg */, + 84A1E5C1231D1E337ED0FC84 /* leg */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 74AA795D8990365CCE282118 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7921C7C2AD25A4A4C02470F5 /* peg.c in Sources */, + 8B61C6AE75A1750C17350E64 /* tree.c in Sources */, + 4D30CEABCD51397A50F65058 /* compile.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 915DAD0C515729956FE2BC69 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D8C3FFD80B6642D8BB341B90 /* leg.c in Sources */, + 3FE25C706AB45972C102CBB4 /* tree.c in Sources */, + 2D4E663DE432A398FC78635B /* compile.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3DF530CEA77A591E4DFBFF2F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 84A1E5C1231D1E337ED0FC84 /* leg */; + targetProxy = 12CD2CA862C5C1693300D7EF /* PBXContainerItemProxy */; + }; + B85F90055126E00C1334834C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 050EA9DBA8F5C296C3E39B8A /* peg */; + targetProxy = 22C9A5E8EF76D4B801BB24E5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 05AA053A004215362908ED84 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = ""; + GCC_PREPROCESSOR_DEFINITIONS = "\"NDEBUG\""; + PRODUCT_NAME = peg; + }; + name = Release; + }; + 2CB45BB7949774F10834EB3B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INTERMEDIATE_DIR = "$(PROJECT_DERIVED_FILE_DIR)/$(CONFIGURATION)"; + SHARED_INTERMEDIATE_DIR = "$(SYMROOT)/DerivedSources/$(CONFIGURATION)"; + }; + name = Release; + }; + 7C93F597151F1782DBAF2E6E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INTERMEDIATE_DIR = "$(PROJECT_DERIVED_FILE_DIR)/$(CONFIGURATION)"; + SHARED_INTERMEDIATE_DIR = "$(SYMROOT)/DerivedSources/$(CONFIGURATION)"; + }; + name = Debug; + }; + 879858F43394AA4CEFDC7263 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = ""; + GCC_PREPROCESSOR_DEFINITIONS = "\"NDEBUG\""; + PRODUCT_NAME = leg; + }; + name = Release; + }; + 8CF09EF6E825DDB8D49A6EA9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = All; + }; + name = Release; + }; + DEFEA64AA5600BECDDA9A939 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = All; + }; + name = Debug; + }; + E2B1202070FAC019FA0BF2ED /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = ""; + GCC_PREPROCESSOR_DEFINITIONS = "\"DEBUG\""; + PRODUCT_NAME = leg; + }; + name = Debug; + }; + F41144D27A6369001B96C713 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = ""; + GCC_PREPROCESSOR_DEFINITIONS = "\"DEBUG\""; + PRODUCT_NAME = peg; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 007E1122105A105C63F8A59F /* Build configuration list for PBXAggregateTarget "All" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DEFEA64AA5600BECDDA9A939 /* Debug */, + 8CF09EF6E825DDB8D49A6EA9 /* Release */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Debug; + }; + 283B121430353A60CB56914F /* Build configuration list for PBXProject "peg" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7C93F597151F1782DBAF2E6E /* Debug */, + 2CB45BB7949774F10834EB3B /* Release */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Debug; + }; + EF4B00311D83FC2C01B276A4 /* Build configuration list for PBXNativeTarget "peg" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F41144D27A6369001B96C713 /* Debug */, + 05AA053A004215362908ED84 /* Release */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Debug; + }; + F7568C4D321FF46C2F4B43FB /* Build configuration list for PBXNativeTarget "leg" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E2B1202070FAC019FA0BF2ED /* Debug */, + 879858F43394AA4CEFDC7263 /* Release */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = D23EA86C97C1C940E2591A06 /* Project object */; +} diff --git a/3rdparty/peg-0.1.15/src/compile.c b/3rdparty/peg-0.1.15/src/compile.c new file mode 100644 index 0000000000..8884e38b84 --- /dev/null +++ b/3rdparty/peg-0.1.15/src/compile.c @@ -0,0 +1,817 @@ +/* Copyright (c) 2007--2013 by Ian Piumarta + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the 'Software'), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, provided that the above copyright notice(s) and this + * permission notice appear in all copies of the Software. Acknowledgement + * of the use of this Software in supporting documentation would be + * appreciated but is not required. + * + * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. + * + * Last edited: 2013-12-18 10:09:42 by piumarta on linux32 + */ + +#include +#include +#include +#include + +#ifdef WIN32 +# undef inline +# define inline __inline +#endif + +#include "version.h" +#include "tree.h" + +static int yyl(void) +{ + static int prev= 0; + return ++prev; +} + +static void charClassSet (unsigned char bits[], int c) { bits[c >> 3] |= (1 << (c & 7)); } +static void charClassClear(unsigned char bits[], int c) { bits[c >> 3] &= ~(1 << (c & 7)); } + +typedef void (*setter)(unsigned char bits[], int c); + +static inline int oigit(int c) { return ('0' <= c && c <= '7'); } +static inline int higit(int c) { return ('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'); } + +static inline int hexval(int c) +{ + if ('0' <= c && c <= '9') return c - '0'; + if ('A' <= c && c <= 'F') return 10 - 'A' + c; + if ('a' <= c && c <= 'f') return 10 - 'a' + c; + return 0; +} + +static int cnext(unsigned char **ccp) +{ + unsigned char *cclass= *ccp; + int c= *cclass++; + if (c) + { + if ('\\' == c && *cclass) + { + switch (c= *cclass++) + { + case 'a': c= '\a'; break; /* bel */ + case 'b': c= '\b'; break; /* bs */ + case 'e': c= '\033'; break; /* esc */ + case 'f': c= '\f'; break; /* ff */ + case 'n': c= '\n'; break; /* nl */ + case 'r': c= '\r'; break; /* cr */ + case 't': c= '\t'; break; /* ht */ + case 'v': c= '\v'; break; /* vt */ + case 'x': + c= 0; + if (higit(*cclass)) c= (c << 4) + hexval(*cclass++); + if (higit(*cclass)) c= (c << 4) + hexval(*cclass++); + break; + default: + if (oigit(c)) + { + c -= '0'; + if (oigit(*cclass)) c= (c << 3) + *cclass++ - '0'; + if (oigit(*cclass)) c= (c << 3) + *cclass++ - '0'; + } + break; + } + } + *ccp= cclass; + } + return c; +} + +static char *makeCharClass(unsigned char *cclass) +{ + unsigned char bits[32]; + setter set; + int c, prev= -1; + static char string[256]; + char *ptr; + + if ('^' == *cclass) + { + memset(bits, 255, 32); + set= charClassClear; + ++cclass; + } + else + { + memset(bits, 0, 32); + set= charClassSet; + } + + while (*cclass) + { + if ('-' == *cclass && cclass[1] && prev >= 0) + { + ++cclass; + for (c= cnext(&cclass); prev <= c; ++prev) + set(bits, prev); + prev= -1; + } + else + { + c= cnext(&cclass); + set(bits, prev= c); + } + } + + ptr= string; + for (c= 0; c < 32; ++c) + ptr += sprintf(ptr, "\\%03o", bits[c]); + + return string; +} + +static void begin(void) { fprintf(output, "\n {"); } +static void end(void) { fprintf(output, "\n }"); } +static void label(int n) { fprintf(output, "\n l%d:;\t", n); } +static void jump(int n) { fprintf(output, " goto l%d;", n); } +static void save(int n) { fprintf(output, " int yypos%d= yy->__pos, yythunkpos%d= yy->__thunkpos;", n, n); } +static void restore(int n) { fprintf(output, " yy->__pos= yypos%d; yy->__thunkpos= yythunkpos%d;", n, n); } + +static void Node_compile_c_ko(Node *node, int ko) +{ + assert(node); + switch (node->type) + { + case Rule: + fprintf(stderr, "\ninternal error #1 (%s)\n", node->rule.name); + exit(1); + break; + + case Dot: + fprintf(output, " if (!yymatchDot(yy)) goto l%d;", ko); + break; + + case Name: + fprintf(output, " if (!yy_%s(yy)) goto l%d;", node->name.rule->rule.name, ko); + if (node->name.variable) + fprintf(output, " yyDo(yy, yySet, %d, 0);", node->name.variable->variable.offset); + break; + + case Character: + case String: + { + int len= strlen(node->string.value); + if (1 == len) + { + if ('\'' == node->string.value[0]) + fprintf(output, " if (!yymatchChar(yy, '\\'')) goto l%d;", ko); + else + fprintf(output, " if (!yymatchChar(yy, '%s')) goto l%d;", node->string.value, ko); + } + else + if (2 == len && '\\' == node->string.value[0]) + fprintf(output, " if (!yymatchChar(yy, '%s')) goto l%d;", node->string.value, ko); + else + fprintf(output, " if (!yymatchString(yy, \"%s\")) goto l%d;", node->string.value, ko); + } + break; + + case Class: + fprintf(output, " if (!yymatchClass(yy, (unsigned char *)\"%s\")) goto l%d;", makeCharClass(node->cclass.value), ko); + break; + + case Action: + fprintf(output, " yyDo(yy, yy%s, yy->__begin, yy->__end);", node->action.name); + break; + + case Predicate: + fprintf(output, " yyText(yy, yy->__begin, yy->__end); {\n"); + fprintf(output, "#define yytext yy->__text\n"); + fprintf(output, "#define yyleng yy->__textlen\n"); + fprintf(output, "if (!(%s)) goto l%d;\n", node->action.text, ko); + fprintf(output, "#undef yytext\n"); + fprintf(output, "#undef yyleng\n"); + fprintf(output, " }"); + break; + + case Error: + { + int eok= yyl(), eko= yyl(); + Node_compile_c_ko(node->error.element, eko); + jump(eok); + label(eko); + fprintf(output, " yyText(yy, yy->__begin, yy->__end); {\n"); + fprintf(output, "#define yytext yy->__text\n"); + fprintf(output, "#define yyleng yy->__textlen\n"); + fprintf(output, " %s;\n", node->error.text); + fprintf(output, "#undef yytext\n"); + fprintf(output, "#undef yyleng\n"); + fprintf(output, " }"); + jump(ko); + label(eok); + } + break; + + case Alternate: + { + int ok= yyl(); + begin(); + save(ok); + for (node= node->alternate.first; node; node= node->alternate.next) + if (node->alternate.next) + { + int next= yyl(); + Node_compile_c_ko(node, next); + jump(ok); + label(next); + restore(ok); + } + else + Node_compile_c_ko(node, ko); + end(); + label(ok); + } + break; + + case Sequence: + for (node= node->sequence.first; node; node= node->sequence.next) + Node_compile_c_ko(node, ko); + break; + + case PeekFor: + { + int ok= yyl(); + begin(); + save(ok); + Node_compile_c_ko(node->peekFor.element, ko); + restore(ok); + end(); + } + break; + + case PeekNot: + { + int ok= yyl(); + begin(); + save(ok); + Node_compile_c_ko(node->peekFor.element, ok); + jump(ko); + label(ok); + restore(ok); + end(); + } + break; + + case Query: + { + int qko= yyl(), qok= yyl(); + begin(); + save(qko); + Node_compile_c_ko(node->query.element, qko); + jump(qok); + label(qko); + restore(qko); + end(); + label(qok); + } + break; + + case Star: + { + int again= yyl(), out= yyl(); + label(again); + begin(); + save(out); + Node_compile_c_ko(node->star.element, out); + jump(again); + label(out); + restore(out); + end(); + } + break; + + case Plus: + { + int again= yyl(), out= yyl(); + Node_compile_c_ko(node->plus.element, ko); + label(again); + begin(); + save(out); + Node_compile_c_ko(node->plus.element, out); + jump(again); + label(out); + restore(out); + end(); + } + break; + + default: + fprintf(stderr, "\nNode_compile_c_ko: illegal node type %d\n", node->type); + exit(1); + } +} + + +static int countVariables(Node *node) +{ + int count= 0; + while (node) + { + ++count; + node= node->variable.next; + } + return count; +} + +static void defineVariables(Node *node) +{ + int count= 0; + while (node) + { + fprintf(output, "#define %s yy->__val[%d]\n", node->variable.name, --count); + node->variable.offset= count; + node= node->variable.next; + } + fprintf(output, "#define __ yy->__\n"); + fprintf(output, "#define yypos yy->__pos\n"); + fprintf(output, "#define yythunkpos yy->__thunkpos\n"); +} + +static void undefineVariables(Node *node) +{ + fprintf(output, "#undef yythunkpos\n"); + fprintf(output, "#undef yypos\n"); + fprintf(output, "#undef yy\n"); + while (node) + { + fprintf(output, "#undef %s\n", node->variable.name); + node= node->variable.next; + } +} + + +static void Rule_compile_c2(Node *node) +{ + assert(node); + assert(Rule == node->type); + + if (!node->rule.expression) + fprintf(stderr, "rule '%s' used but not defined\n", node->rule.name); + else + { + int ko= yyl(), safe; + + if ((!(RuleUsed & node->rule.flags)) && (node != start)) + fprintf(stderr, "rule '%s' defined but not used\n", node->rule.name); + + safe= ((Query == node->rule.expression->type) || (Star == node->rule.expression->type)); + + fprintf(output, "\nYY_RULE(int) yy_%s(yycontext *yy)\n{", node->rule.name); + if (!safe) save(0); + if (node->rule.variables) + fprintf(output, " yyDo(yy, yyPush, %d, 0);", countVariables(node->rule.variables)); + fprintf(output, "\n yyprintf((stderr, \"%%s\\n\", \"%s\"));", node->rule.name); + Node_compile_c_ko(node->rule.expression, ko); + fprintf(output, "\n yyprintf((stderr, \" ok %%s @ %%s\\n\", \"%s\", yy->__buf+yy->__pos));", node->rule.name); + if (node->rule.variables) + fprintf(output, " yyDo(yy, yyPop, %d, 0);", countVariables(node->rule.variables)); + fprintf(output, "\n return 1;"); + if (!safe) + { + label(ko); + restore(0); + fprintf(output, "\n yyprintf((stderr, \" fail %%s @ %%s\\n\", \"%s\", yy->__buf+yy->__pos));", node->rule.name); + fprintf(output, "\n return 0;"); + } + fprintf(output, "\n}"); + } + + if (node->rule.next) + Rule_compile_c2(node->rule.next); +} + +static char *header= "\ +#include \n\ +#include \n\ +#include \n\ +"; + +static char *preamble= "\ +#ifndef YY_MALLOC\n\ +#define YY_MALLOC(C, N) malloc(N)\n\ +#endif\n\ +#ifndef YY_REALLOC\n\ +#define YY_REALLOC(C, P, N) realloc(P, N)\n\ +#endif\n\ +#ifndef YY_FREE\n\ +#define YY_FREE(C, P) free(P)\n\ +#endif\n\ +#ifndef YY_LOCAL\n\ +#define YY_LOCAL(T) static T\n\ +#endif\n\ +#ifndef YY_ACTION\n\ +#define YY_ACTION(T) static T\n\ +#endif\n\ +#ifndef YY_RULE\n\ +#define YY_RULE(T) static T\n\ +#endif\n\ +#ifndef YY_PARSE\n\ +#define YY_PARSE(T) T\n\ +#endif\n\ +#ifndef YYPARSE\n\ +#define YYPARSE yyparse\n\ +#endif\n\ +#ifndef YYPARSEFROM\n\ +#define YYPARSEFROM yyparsefrom\n\ +#endif\n\ +#ifndef YYRELEASE\n\ +#define YYRELEASE yyrelease\n\ +#endif\n\ +#ifndef YY_BEGIN\n\ +#define YY_BEGIN ( yy->__begin= yy->__pos, 1)\n\ +#endif\n\ +#ifndef YY_END\n\ +#define YY_END ( yy->__end= yy->__pos, 1)\n\ +#endif\n\ +#ifdef YY_DEBUG\n\ +# define yyprintf(args) fprintf args\n\ +#else\n\ +# define yyprintf(args)\n\ +#endif\n\ +#ifndef YYSTYPE\n\ +#define YYSTYPE int\n\ +#endif\n\ +#ifndef YY_STACK_SIZE\n\ +#define YY_STACK_SIZE 128\n\ +#endif\n\ +\n\ +#ifndef YY_BUFFER_SIZE\n\ +#define YY_BUFFER_SIZE 1024\n\ +#endif\n\ +\n\ +#ifndef YY_PART\n\ +\n\ +typedef struct _yycontext yycontext;\n\ +typedef void (*yyaction)(yycontext *yy, char *yytext, int yyleng);\n\ +typedef struct _yythunk { int begin, end; yyaction action; struct _yythunk *next; } yythunk;\n\ +\n\ +struct _yycontext {\n\ + char *__buf;\n\ + int __buflen;\n\ + int __pos;\n\ + int __limit;\n\ + char *__text;\n\ + int __textlen;\n\ + int __begin;\n\ + int __end;\n\ + int __textmax;\n\ + yythunk *__thunks;\n\ + int __thunkslen;\n\ + int __thunkpos;\n\ + YYSTYPE __;\n\ + YYSTYPE *__val;\n\ + YYSTYPE *__vals;\n\ + int __valslen;\n\ +#ifdef YY_CTX_MEMBERS\n\ + YY_CTX_MEMBERS\n\ +#endif\n\ +};\n\ +\n\ +#ifdef YY_CTX_LOCAL\n\ +#define YY_CTX_PARAM_ yycontext *yyctx,\n\ +#define YY_CTX_PARAM yycontext *yyctx\n\ +#define YY_CTX_ARG_ yyctx,\n\ +#define YY_CTX_ARG yyctx\n\ +#ifndef YY_INPUT\n\ +#define YY_INPUT(yy, buf, result, max_size) \\\n\ + { \\\n\ + int yyc= getchar(); \\\n\ + result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \\\n\ + yyprintf((stderr, \"<%c>\", yyc)); \\\n\ + }\n\ +#endif\n\ +#else\n\ +#define YY_CTX_PARAM_\n\ +#define YY_CTX_PARAM\n\ +#define YY_CTX_ARG_\n\ +#define YY_CTX_ARG\n\ +yycontext _yyctx= { 0, 0 };\n\ +yycontext *yyctx= &_yyctx;\n\ +#ifndef YY_INPUT\n\ +#define YY_INPUT(buf, result, max_size) \\\n\ + { \\\n\ + int yyc= getchar(); \\\n\ + result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \\\n\ + yyprintf((stderr, \"<%c>\", yyc)); \\\n\ + }\n\ +#endif\n\ +#endif\n\ +\n\ +YY_LOCAL(int) yyrefill(yycontext *yy)\n\ +{\n\ + int yyn;\n\ + while (yy->__buflen - yy->__pos < 512)\n\ + {\n\ + yy->__buflen *= 2;\n\ + yy->__buf= (char *)YY_REALLOC(yy, yy->__buf, yy->__buflen);\n\ + }\n\ +#ifdef YY_CTX_LOCAL\n\ + YY_INPUT(yy, (yy->__buf + yy->__pos), yyn, (yy->__buflen - yy->__pos));\n\ +#else\n\ + YY_INPUT((yy->__buf + yy->__pos), yyn, (yy->__buflen - yy->__pos));\n\ +#endif\n\ + if (!yyn) return 0;\n\ + yy->__limit += yyn;\n\ + return 1;\n\ +}\n\ +\n\ +YY_LOCAL(int) yymatchDot(yycontext *yy)\n\ +{\n\ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0;\n\ + ++yy->__pos;\n\ + return 1;\n\ +}\n\ +\n\ +YY_LOCAL(int) yymatchChar(yycontext *yy, int c)\n\ +{\n\ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0;\n\ + if ((unsigned char)yy->__buf[yy->__pos] == c)\n\ + {\n\ + ++yy->__pos;\n\ + yyprintf((stderr, \" ok yymatchChar(yy, %c) @ %s\\n\", c, yy->__buf+yy->__pos));\n\ + return 1;\n\ + }\n\ + yyprintf((stderr, \" fail yymatchChar(yy, %c) @ %s\\n\", c, yy->__buf+yy->__pos));\n\ + return 0;\n\ +}\n\ +\n\ +YY_LOCAL(int) yymatchString(yycontext *yy, const char *s)\n\ +{\n\ + int yysav= yy->__pos;\n\ + while (*s)\n\ + {\n\ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0;\n\ + if (yy->__buf[yy->__pos] != *s)\n\ + {\n\ + yy->__pos= yysav;\n\ + return 0;\n\ + }\n\ + ++s;\n\ + ++yy->__pos;\n\ + }\n\ + return 1;\n\ +}\n\ +\n\ +YY_LOCAL(int) yymatchClass(yycontext *yy, unsigned char *bits)\n\ +{\n\ + int c;\n\ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0;\n\ + c= (unsigned char)yy->__buf[yy->__pos];\n\ + if (bits[c >> 3] & (1 << (c & 7)))\n\ + {\n\ + ++yy->__pos;\n\ + yyprintf((stderr, \" ok yymatchClass @ %s\\n\", yy->__buf+yy->__pos));\n\ + return 1;\n\ + }\n\ + yyprintf((stderr, \" fail yymatchClass @ %s\\n\", yy->__buf+yy->__pos));\n\ + return 0;\n\ +}\n\ +\n\ +YY_LOCAL(void) yyDo(yycontext *yy, yyaction action, int begin, int end)\n\ +{\n\ + while (yy->__thunkpos >= yy->__thunkslen)\n\ + {\n\ + yy->__thunkslen *= 2;\n\ + yy->__thunks= (yythunk *)YY_REALLOC(yy, yy->__thunks, sizeof(yythunk) * yy->__thunkslen);\n\ + }\n\ + yy->__thunks[yy->__thunkpos].begin= begin;\n\ + yy->__thunks[yy->__thunkpos].end= end;\n\ + yy->__thunks[yy->__thunkpos].action= action;\n\ + ++yy->__thunkpos;\n\ +}\n\ +\n\ +YY_LOCAL(int) yyText(yycontext *yy, int begin, int end)\n\ +{\n\ + int yyleng= end - begin;\n\ + if (yyleng <= 0)\n\ + yyleng= 0;\n\ + else\n\ + {\n\ + while (yy->__textlen < (yyleng + 1))\n\ + {\n\ + yy->__textlen *= 2;\n\ + yy->__text= (char *)YY_REALLOC(yy, yy->__text, yy->__textlen);\n\ + }\n\ + memcpy(yy->__text, yy->__buf + begin, yyleng);\n\ + }\n\ + yy->__text[yyleng]= '\\0';\n\ + return yyleng;\n\ +}\n\ +\n\ +YY_LOCAL(void) yyDone(yycontext *yy)\n\ +{\n\ + int pos;\n\ + for (pos= 0; pos < yy->__thunkpos; ++pos)\n\ + {\n\ + yythunk *thunk= &yy->__thunks[pos];\n\ + int yyleng= thunk->end ? yyText(yy, thunk->begin, thunk->end) : thunk->begin;\n\ + yyprintf((stderr, \"DO [%d] %p %s\\n\", pos, thunk->action, yy->__text));\n\ + thunk->action(yy, yy->__text, yyleng);\n\ + }\n\ + yy->__thunkpos= 0;\n\ +}\n\ +\n\ +YY_LOCAL(void) yyCommit(yycontext *yy)\n\ +{\n\ + if ((yy->__limit -= yy->__pos))\n\ + {\n\ + memmove(yy->__buf, yy->__buf + yy->__pos, yy->__limit);\n\ + }\n\ + yy->__begin -= yy->__pos;\n\ + yy->__end -= yy->__pos;\n\ + yy->__pos= yy->__thunkpos= 0;\n\ +}\n\ +\n\ +YY_LOCAL(int) yyAccept(yycontext *yy, int tp0)\n\ +{\n\ + if (tp0)\n\ + {\n\ + fprintf(stderr, \"accept denied at %d\\n\", tp0);\n\ + return 0;\n\ + }\n\ + else\n\ + {\n\ + yyDone(yy);\n\ + yyCommit(yy);\n\ + }\n\ + return 1;\n\ +}\n\ +\n\ +YY_LOCAL(void) yyPush(yycontext *yy, char *text, int count)\n\ +{\n\ + yy->__val += count;\n\ + while (yy->__valslen <= yy->__val - yy->__vals)\n\ + {\n\ + long offset= yy->__val - yy->__vals;\n\ + yy->__valslen *= 2;\n\ + yy->__vals= (YYSTYPE *)YY_REALLOC(yy, yy->__vals, sizeof(YYSTYPE) * yy->__valslen);\n\ + yy->__val= yy->__vals + offset;\n\ + }\n\ +}\n\ +YY_LOCAL(void) yyPop(yycontext *yy, char *text, int count) { yy->__val -= count; }\n\ +YY_LOCAL(void) yySet(yycontext *yy, char *text, int count) { yy->__val[count]= yy->__; }\n\ +\n\ +#endif /* YY_PART */\n\ +\n\ +#define YYACCEPT yyAccept(yy, yythunkpos0)\n\ +\n\ +"; + +static char *footer= "\n\ +\n\ +#ifndef YY_PART\n\ +\n\ +typedef int (*yyrule)(yycontext *yy);\n\ +\n\ +YY_PARSE(int) YYPARSEFROM(YY_CTX_PARAM_ yyrule yystart)\n\ +{\n\ + int yyok;\n\ + if (!yyctx->__buflen)\n\ + {\n\ + yyctx->__buflen= YY_BUFFER_SIZE;\n\ + yyctx->__buf= (char *)YY_MALLOC(yyctx, yyctx->__buflen);\n\ + yyctx->__textlen= YY_BUFFER_SIZE;\n\ + yyctx->__text= (char *)YY_MALLOC(yyctx, yyctx->__textlen);\n\ + yyctx->__thunkslen= YY_STACK_SIZE;\n\ + yyctx->__thunks= (yythunk *)YY_MALLOC(yyctx, sizeof(yythunk) * yyctx->__thunkslen);\n\ + yyctx->__valslen= YY_STACK_SIZE;\n\ + yyctx->__vals= (YYSTYPE *)YY_MALLOC(yyctx, sizeof(YYSTYPE) * yyctx->__valslen);\n\ + yyctx->__begin= yyctx->__end= yyctx->__pos= yyctx->__limit= yyctx->__thunkpos= 0;\n\ + }\n\ + yyctx->__begin= yyctx->__end= yyctx->__pos;\n\ + yyctx->__thunkpos= 0;\n\ + yyctx->__val= yyctx->__vals;\n\ + yyok= yystart(yyctx);\n\ + if (yyok) yyDone(yyctx);\n\ + yyCommit(yyctx);\n\ + return yyok;\n\ +}\n\ +\n\ +YY_PARSE(int) YYPARSE(YY_CTX_PARAM)\n\ +{\n\ + return YYPARSEFROM(YY_CTX_ARG_ yy_%s);\n\ +}\n\ +\n\ +YY_PARSE(yycontext *) YYRELEASE(yycontext *yyctx)\n\ +{\n\ + if (yyctx->__buflen)\n\ + {\n\ + yyctx->__buflen= 0;\n\ + YY_FREE(yyctx, yyctx->__buf);\n\ + YY_FREE(yyctx, yyctx->__text);\n\ + YY_FREE(yyctx, yyctx->__thunks);\n\ + YY_FREE(yyctx, yyctx->__vals);\n\ + }\n\ + return yyctx;\n\ +}\n\ +\n\ +#endif\n\ +"; + +void Rule_compile_c_header(void) +{ + fprintf(output, "/* A recursive-descent parser generated by peg %d.%d.%d */\n", PEG_MAJOR, PEG_MINOR, PEG_LEVEL); + fprintf(output, "\n"); + fprintf(output, "%s", header); + fprintf(output, "#define YYRULECOUNT %d\n", ruleCount); +} + +int consumesInput(Node *node) +{ + if (!node) return 0; + + switch (node->type) + { + case Rule: + { + int result= 0; + if (RuleReached & node->rule.flags) + fprintf(stderr, "possible infinite left recursion in rule '%s'\n", node->rule.name); + else + { + node->rule.flags |= RuleReached; + result= consumesInput(node->rule.expression); + node->rule.flags &= ~RuleReached; + } + return result; + } + break; + + case Dot: return 1; + case Name: return consumesInput(node->name.rule); + case Character: + case String: return strlen(node->string.value) > 0; + case Class: return 1; + case Action: return 0; + case Predicate: return 0; + case Error: return consumesInput(node->error.element); + + case Alternate: + { + Node *n; + for (n= node->alternate.first; n; n= n->alternate.next) + if (!consumesInput(n)) + return 0; + } + return 1; + + case Sequence: + { + Node *n; + for (n= node->alternate.first; n; n= n->alternate.next) + if (consumesInput(n)) + return 1; + } + return 0; + + case PeekFor: return 0; + case PeekNot: return 0; + case Query: return 0; + case Star: return 0; + case Plus: return consumesInput(node->plus.element); + + default: + fprintf(stderr, "\nconsumesInput: illegal node type %d\n", node->type); + exit(1); + } + return 0; +} + + +void Rule_compile_c(Node *node) +{ + Node *n; + + for (n= rules; n; n= n->rule.next) + consumesInput(n); + + fprintf(output, "%s", preamble); + for (n= node; n; n= n->rule.next) + fprintf(output, "YY_RULE(int) yy_%s(yycontext *yy); /* %d */\n", n->rule.name, n->rule.id); + fprintf(output, "\n"); + for (n= actions; n; n= n->action.list) + { + fprintf(output, "YY_ACTION(void) yy%s(yycontext *yy, char *yytext, int yyleng)\n{\n", n->action.name); + defineVariables(n->action.rule->rule.variables); + fprintf(output, " yyprintf((stderr, \"do yy%s\\n\"));\n", n->action.name); + fprintf(output, " {\n"); + fprintf(output, " %s;\n", n->action.text); + fprintf(output, " }\n"); + undefineVariables(n->action.rule->rule.variables); + fprintf(output, "}\n"); + } + Rule_compile_c2(node); + fprintf(output, footer, start->rule.name); +} diff --git a/3rdparty/peg-0.1.15/src/leg.c b/3rdparty/peg-0.1.15/src/leg.c new file mode 100644 index 0000000000..2c1383ed1f --- /dev/null +++ b/3rdparty/peg-0.1.15/src/leg.c @@ -0,0 +1,1428 @@ +/* A recursive-descent parser generated by peg 0.1.15 */ + +#include +#include +#include +#define YYRULECOUNT 38 + +# include "tree.h" +# include "version.h" + +# include +# include +# include +# include +# include +# include + + typedef struct Header Header; + + struct Header { + char *text; + Header *next; + }; + + FILE *input= 0; + + int verboseFlag= 0; + + static int lineNumber= 0; + static char *fileName= 0; + static char *trailer= 0; + static Header *headers= 0; + + void makeHeader(char *text); + void makeTrailer(char *text); + + void yyerror(char *message); + +# define YY_INPUT(buf, result, max) \ + { \ + int c= getc(input); \ + if ('\n' == c || '\r' == c) ++lineNumber; \ + result= (EOF == c) ? 0 : (*(buf)= c, 1); \ + } + +# define YY_LOCAL(T) static T +# define YY_RULE(T) static T + +#ifndef YY_MALLOC +#define YY_MALLOC(C, N) malloc(N) +#endif +#ifndef YY_REALLOC +#define YY_REALLOC(C, P, N) realloc(P, N) +#endif +#ifndef YY_FREE +#define YY_FREE(C, P) free(P) +#endif +#ifndef YY_LOCAL +#define YY_LOCAL(T) static T +#endif +#ifndef YY_ACTION +#define YY_ACTION(T) static T +#endif +#ifndef YY_RULE +#define YY_RULE(T) static T +#endif +#ifndef YY_PARSE +#define YY_PARSE(T) T +#endif +#ifndef YYPARSE +#define YYPARSE yyparse +#endif +#ifndef YYPARSEFROM +#define YYPARSEFROM yyparsefrom +#endif +#ifndef YYRELEASE +#define YYRELEASE yyrelease +#endif +#ifndef YY_BEGIN +#define YY_BEGIN ( yy->__begin= yy->__pos, 1) +#endif +#ifndef YY_END +#define YY_END ( yy->__end= yy->__pos, 1) +#endif +#ifdef YY_DEBUG +# define yyprintf(args) fprintf args +#else +# define yyprintf(args) +#endif +#ifndef YYSTYPE +#define YYSTYPE int +#endif +#ifndef YY_STACK_SIZE +#define YY_STACK_SIZE 128 +#endif + +#ifndef YY_BUFFER_SIZE +#define YY_BUFFER_SIZE 1024 +#endif + +#ifndef YY_PART + +typedef struct _yycontext yycontext; +typedef void (*yyaction)(yycontext *yy, char *yytext, int yyleng); +typedef struct _yythunk { int begin, end; yyaction action; struct _yythunk *next; } yythunk; + +struct _yycontext { + char *__buf; + int __buflen; + int __pos; + int __limit; + char *__text; + int __textlen; + int __begin; + int __end; + int __textmax; + yythunk *__thunks; + int __thunkslen; + int __thunkpos; + YYSTYPE __; + YYSTYPE *__val; + YYSTYPE *__vals; + int __valslen; +#ifdef YY_CTX_MEMBERS + YY_CTX_MEMBERS +#endif +}; + +#ifdef YY_CTX_LOCAL +#define YY_CTX_PARAM_ yycontext *yyctx, +#define YY_CTX_PARAM yycontext *yyctx +#define YY_CTX_ARG_ yyctx, +#define YY_CTX_ARG yyctx +#ifndef YY_INPUT +#define YY_INPUT(yy, buf, result, max_size) \ + { \ + int yyc= getchar(); \ + result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \ + yyprintf((stderr, "<%c>", yyc)); \ + } +#endif +#else +#define YY_CTX_PARAM_ +#define YY_CTX_PARAM +#define YY_CTX_ARG_ +#define YY_CTX_ARG +yycontext _yyctx= { 0, 0 }; +yycontext *yyctx= &_yyctx; +#ifndef YY_INPUT +#define YY_INPUT(buf, result, max_size) \ + { \ + int yyc= getchar(); \ + result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \ + yyprintf((stderr, "<%c>", yyc)); \ + } +#endif +#endif + +YY_LOCAL(int) yyrefill(yycontext *yy) +{ + int yyn; + while (yy->__buflen - yy->__pos < 512) + { + yy->__buflen *= 2; + yy->__buf= (char *)YY_REALLOC(yy, yy->__buf, yy->__buflen); + } +#ifdef YY_CTX_LOCAL + YY_INPUT(yy, (yy->__buf + yy->__pos), yyn, (yy->__buflen - yy->__pos)); +#else + YY_INPUT((yy->__buf + yy->__pos), yyn, (yy->__buflen - yy->__pos)); +#endif + if (!yyn) return 0; + yy->__limit += yyn; + return 1; +} + +YY_LOCAL(int) yymatchDot(yycontext *yy) +{ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + ++yy->__pos; + return 1; +} + +YY_LOCAL(int) yymatchChar(yycontext *yy, int c) +{ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + if ((unsigned char)yy->__buf[yy->__pos] == c) + { + ++yy->__pos; + yyprintf((stderr, " ok yymatchChar(yy, %c) @ %s\n", c, yy->__buf+yy->__pos)); + return 1; + } + yyprintf((stderr, " fail yymatchChar(yy, %c) @ %s\n", c, yy->__buf+yy->__pos)); + return 0; +} + +YY_LOCAL(int) yymatchString(yycontext *yy, const char *s) +{ + int yysav= yy->__pos; + while (*s) + { + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + if (yy->__buf[yy->__pos] != *s) + { + yy->__pos= yysav; + return 0; + } + ++s; + ++yy->__pos; + } + return 1; +} + +YY_LOCAL(int) yymatchClass(yycontext *yy, unsigned char *bits) +{ + int c; + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + c= (unsigned char)yy->__buf[yy->__pos]; + if (bits[c >> 3] & (1 << (c & 7))) + { + ++yy->__pos; + yyprintf((stderr, " ok yymatchClass @ %s\n", yy->__buf+yy->__pos)); + return 1; + } + yyprintf((stderr, " fail yymatchClass @ %s\n", yy->__buf+yy->__pos)); + return 0; +} + +YY_LOCAL(void) yyDo(yycontext *yy, yyaction action, int begin, int end) +{ + while (yy->__thunkpos >= yy->__thunkslen) + { + yy->__thunkslen *= 2; + yy->__thunks= (yythunk *)YY_REALLOC(yy, yy->__thunks, sizeof(yythunk) * yy->__thunkslen); + } + yy->__thunks[yy->__thunkpos].begin= begin; + yy->__thunks[yy->__thunkpos].end= end; + yy->__thunks[yy->__thunkpos].action= action; + ++yy->__thunkpos; +} + +YY_LOCAL(int) yyText(yycontext *yy, int begin, int end) +{ + int yyleng= end - begin; + if (yyleng <= 0) + yyleng= 0; + else + { + while (yy->__textlen < (yyleng + 1)) + { + yy->__textlen *= 2; + yy->__text= (char *)YY_REALLOC(yy, yy->__text, yy->__textlen); + } + memcpy(yy->__text, yy->__buf + begin, yyleng); + } + yy->__text[yyleng]= '\0'; + return yyleng; +} + +YY_LOCAL(void) yyDone(yycontext *yy) +{ + int pos; + for (pos= 0; pos < yy->__thunkpos; ++pos) + { + yythunk *thunk= &yy->__thunks[pos]; + int yyleng= thunk->end ? yyText(yy, thunk->begin, thunk->end) : thunk->begin; + yyprintf((stderr, "DO [%d] %p %s\n", pos, thunk->action, yy->__text)); + thunk->action(yy, yy->__text, yyleng); + } + yy->__thunkpos= 0; +} + +YY_LOCAL(void) yyCommit(yycontext *yy) +{ + if ((yy->__limit -= yy->__pos)) + { + memmove(yy->__buf, yy->__buf + yy->__pos, yy->__limit); + } + yy->__begin -= yy->__pos; + yy->__end -= yy->__pos; + yy->__pos= yy->__thunkpos= 0; +} + +YY_LOCAL(int) yyAccept(yycontext *yy, int tp0) +{ + if (tp0) + { + fprintf(stderr, "accept denied at %d\n", tp0); + return 0; + } + else + { + yyDone(yy); + yyCommit(yy); + } + return 1; +} + +YY_LOCAL(void) yyPush(yycontext *yy, char *text, int count) +{ + yy->__val += count; + while (yy->__valslen <= yy->__val - yy->__vals) + { + long offset= yy->__val - yy->__vals; + yy->__valslen *= 2; + yy->__vals= (YYSTYPE *)YY_REALLOC(yy, yy->__vals, sizeof(YYSTYPE) * yy->__valslen); + yy->__val= yy->__vals + offset; + } +} +YY_LOCAL(void) yyPop(yycontext *yy, char *text, int count) { yy->__val -= count; } +YY_LOCAL(void) yySet(yycontext *yy, char *text, int count) { yy->__val[count]= yy->__; } + +#endif /* YY_PART */ + +#define YYACCEPT yyAccept(yy, yythunkpos0) + +YY_RULE(int) yy_end_of_line(yycontext *yy); /* 38 */ +YY_RULE(int) yy_comment(yycontext *yy); /* 37 */ +YY_RULE(int) yy_space(yycontext *yy); /* 36 */ +YY_RULE(int) yy_braces(yycontext *yy); /* 35 */ +YY_RULE(int) yy_range(yycontext *yy); /* 34 */ +YY_RULE(int) yy_char(yycontext *yy); /* 33 */ +YY_RULE(int) yy_END(yycontext *yy); /* 32 */ +YY_RULE(int) yy_BEGIN(yycontext *yy); /* 31 */ +YY_RULE(int) yy_DOT(yycontext *yy); /* 30 */ +YY_RULE(int) yy_class(yycontext *yy); /* 29 */ +YY_RULE(int) yy_literal(yycontext *yy); /* 28 */ +YY_RULE(int) yy_CLOSE(yycontext *yy); /* 27 */ +YY_RULE(int) yy_OPEN(yycontext *yy); /* 26 */ +YY_RULE(int) yy_COLON(yycontext *yy); /* 25 */ +YY_RULE(int) yy_PLUS(yycontext *yy); /* 24 */ +YY_RULE(int) yy_STAR(yycontext *yy); /* 23 */ +YY_RULE(int) yy_QUESTION(yycontext *yy); /* 22 */ +YY_RULE(int) yy_primary(yycontext *yy); /* 21 */ +YY_RULE(int) yy_NOT(yycontext *yy); /* 20 */ +YY_RULE(int) yy_suffix(yycontext *yy); /* 19 */ +YY_RULE(int) yy_AND(yycontext *yy); /* 18 */ +YY_RULE(int) yy_action(yycontext *yy); /* 17 */ +YY_RULE(int) yy_TILDE(yycontext *yy); /* 16 */ +YY_RULE(int) yy_prefix(yycontext *yy); /* 15 */ +YY_RULE(int) yy_error(yycontext *yy); /* 14 */ +YY_RULE(int) yy_BAR(yycontext *yy); /* 13 */ +YY_RULE(int) yy_sequence(yycontext *yy); /* 12 */ +YY_RULE(int) yy_SEMICOLON(yycontext *yy); /* 11 */ +YY_RULE(int) yy_expression(yycontext *yy); /* 10 */ +YY_RULE(int) yy_EQUAL(yycontext *yy); /* 9 */ +YY_RULE(int) yy_identifier(yycontext *yy); /* 8 */ +YY_RULE(int) yy_RPERCENT(yycontext *yy); /* 7 */ +YY_RULE(int) yy_end_of_file(yycontext *yy); /* 6 */ +YY_RULE(int) yy_trailer(yycontext *yy); /* 5 */ +YY_RULE(int) yy_definition(yycontext *yy); /* 4 */ +YY_RULE(int) yy_declaration(yycontext *yy); /* 3 */ +YY_RULE(int) yy__(yycontext *yy); /* 2 */ +YY_RULE(int) yy_grammar(yycontext *yy); /* 1 */ + +YY_ACTION(void) yy_9_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_9_primary\n")); + { + push(makePredicate("YY_END")); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_8_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_8_primary\n")); + { + push(makePredicate("YY_BEGIN")); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_7_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_7_primary\n")); + { + push(makeAction(yytext)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_6_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_6_primary\n")); + { + push(makeDot()); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_5_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_5_primary\n")); + { + push(makeClass(yytext)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_primary\n")); + { + push(makeString(yytext)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_primary\n")); + { + push(makeName(findRule(yytext))); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_primary\n")); + { + Node *name= makeName(findRule(yytext)); name->name.variable= pop(); push(name); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_primary(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_primary\n")); + { + push(makeVariable(yytext)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_suffix(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_suffix\n")); + { + push(makePlus (pop())); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_suffix(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_suffix\n")); + { + push(makeStar (pop())); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_suffix(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_suffix\n")); + { + push(makeQuery(pop())); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_prefix(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_prefix\n")); + { + push(makePeekNot(pop())); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_prefix(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_prefix\n")); + { + push(makePeekFor(pop())); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_prefix(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_prefix\n")); + { + push(makePredicate(yytext)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_error(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_error\n")); + { + push(makeError(pop(), yytext)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_sequence(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_sequence\n")); + { + Node *f= pop(); push(Sequence_append(pop(), f)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_expression(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_expression\n")); + { + Node *f= pop(); push(Alternate_append(pop(), f)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_definition(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_definition\n")); + { + Node *e= pop(); Rule_setExpression(pop(), e); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_definition(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_definition\n")); + { + if (push(beginRule(findRule(yytext)))->rule.expression) + fprintf(stderr, "rule '%s' redefined\n", yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_trailer(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_trailer\n")); + { + makeTrailer(yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_declaration(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_declaration\n")); + { + makeHeader(yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} + +YY_RULE(int) yy_end_of_line(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "end_of_line")); + { int yypos2= yy->__pos, yythunkpos2= yy->__thunkpos; if (!yymatchString(yy, "\r\n")) goto l3; goto l2; + l3:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchChar(yy, '\n')) goto l4; goto l2; + l4:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchChar(yy, '\r')) goto l1; + } + l2:; + yyprintf((stderr, " ok %s @ %s\n", "end_of_line", yy->__buf+yy->__pos)); + return 1; + l1:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "end_of_line", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_comment(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "comment")); if (!yymatchChar(yy, '#')) goto l5; + l6:; + { int yypos7= yy->__pos, yythunkpos7= yy->__thunkpos; + { int yypos8= yy->__pos, yythunkpos8= yy->__thunkpos; if (!yy_end_of_line(yy)) goto l8; goto l7; + l8:; yy->__pos= yypos8; yy->__thunkpos= yythunkpos8; + } if (!yymatchDot(yy)) goto l7; goto l6; + l7:; yy->__pos= yypos7; yy->__thunkpos= yythunkpos7; + } if (!yy_end_of_line(yy)) goto l5; + yyprintf((stderr, " ok %s @ %s\n", "comment", yy->__buf+yy->__pos)); + return 1; + l5:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "comment", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_space(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "space")); + { int yypos10= yy->__pos, yythunkpos10= yy->__thunkpos; if (!yymatchChar(yy, ' ')) goto l11; goto l10; + l11:; yy->__pos= yypos10; yy->__thunkpos= yythunkpos10; if (!yymatchChar(yy, '\t')) goto l12; goto l10; + l12:; yy->__pos= yypos10; yy->__thunkpos= yythunkpos10; if (!yy_end_of_line(yy)) goto l9; + } + l10:; + yyprintf((stderr, " ok %s @ %s\n", "space", yy->__buf+yy->__pos)); + return 1; + l9:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "space", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_braces(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "braces")); + { int yypos14= yy->__pos, yythunkpos14= yy->__thunkpos; if (!yymatchChar(yy, '{')) goto l15; + l16:; + { int yypos17= yy->__pos, yythunkpos17= yy->__thunkpos; if (!yy_braces(yy)) goto l17; goto l16; + l17:; yy->__pos= yypos17; yy->__thunkpos= yythunkpos17; + } if (!yymatchChar(yy, '}')) goto l15; goto l14; + l15:; yy->__pos= yypos14; yy->__thunkpos= yythunkpos14; + { int yypos18= yy->__pos, yythunkpos18= yy->__thunkpos; if (!yymatchChar(yy, '}')) goto l18; goto l13; + l18:; yy->__pos= yypos18; yy->__thunkpos= yythunkpos18; + } if (!yymatchDot(yy)) goto l13; + } + l14:; + yyprintf((stderr, " ok %s @ %s\n", "braces", yy->__buf+yy->__pos)); + return 1; + l13:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "braces", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_range(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "range")); + { int yypos20= yy->__pos, yythunkpos20= yy->__thunkpos; if (!yy_char(yy)) goto l21; if (!yymatchChar(yy, '-')) goto l21; if (!yy_char(yy)) goto l21; goto l20; + l21:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yy_char(yy)) goto l19; + } + l20:; + yyprintf((stderr, " ok %s @ %s\n", "range", yy->__buf+yy->__pos)); + return 1; + l19:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "range", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_char(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "char")); + { int yypos23= yy->__pos, yythunkpos23= yy->__thunkpos; if (!yymatchChar(yy, '\\')) goto l24; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\204\040\000\000\000\000\000\070\146\100\124\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l24; goto l23; + l24:; yy->__pos= yypos23; yy->__thunkpos= yythunkpos23; if (!yymatchChar(yy, '\\')) goto l25; if (!yymatchChar(yy, 'x')) goto l25; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\176\000\000\000\176\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l25; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\176\000\000\000\176\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l25; goto l23; + l25:; yy->__pos= yypos23; yy->__thunkpos= yythunkpos23; if (!yymatchChar(yy, '\\')) goto l26; if (!yymatchChar(yy, 'x')) goto l26; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\176\000\000\000\176\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l26; goto l23; + l26:; yy->__pos= yypos23; yy->__thunkpos= yythunkpos23; if (!yymatchChar(yy, '\\')) goto l27; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l27; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l27; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l27; goto l23; + l27:; yy->__pos= yypos23; yy->__thunkpos= yythunkpos23; if (!yymatchChar(yy, '\\')) goto l28; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l28; + { int yypos29= yy->__pos, yythunkpos29= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l29; goto l30; + l29:; yy->__pos= yypos29; yy->__thunkpos= yythunkpos29; + } + l30:; goto l23; + l28:; yy->__pos= yypos23; yy->__thunkpos= yythunkpos23; + { int yypos31= yy->__pos, yythunkpos31= yy->__thunkpos; if (!yymatchChar(yy, '\\')) goto l31; goto l22; + l31:; yy->__pos= yypos31; yy->__thunkpos= yythunkpos31; + } if (!yymatchDot(yy)) goto l22; + } + l23:; + yyprintf((stderr, " ok %s @ %s\n", "char", yy->__buf+yy->__pos)); + return 1; + l22:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "char", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_END(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "END")); if (!yymatchChar(yy, '>')) goto l32; if (!yy__(yy)) goto l32; + yyprintf((stderr, " ok %s @ %s\n", "END", yy->__buf+yy->__pos)); + return 1; + l32:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "END", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_BEGIN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "BEGIN")); if (!yymatchChar(yy, '<')) goto l33; if (!yy__(yy)) goto l33; + yyprintf((stderr, " ok %s @ %s\n", "BEGIN", yy->__buf+yy->__pos)); + return 1; + l33:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "BEGIN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_DOT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "DOT")); if (!yymatchChar(yy, '.')) goto l34; if (!yy__(yy)) goto l34; + yyprintf((stderr, " ok %s @ %s\n", "DOT", yy->__buf+yy->__pos)); + return 1; + l34:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "DOT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_class(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "class")); if (!yymatchChar(yy, '[')) goto l35; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l35; +#undef yytext +#undef yyleng + } + l36:; + { int yypos37= yy->__pos, yythunkpos37= yy->__thunkpos; + { int yypos38= yy->__pos, yythunkpos38= yy->__thunkpos; if (!yymatchChar(yy, ']')) goto l38; goto l37; + l38:; yy->__pos= yypos38; yy->__thunkpos= yythunkpos38; + } if (!yy_range(yy)) goto l37; goto l36; + l37:; yy->__pos= yypos37; yy->__thunkpos= yythunkpos37; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l35; +#undef yytext +#undef yyleng + } if (!yymatchChar(yy, ']')) goto l35; if (!yy__(yy)) goto l35; + yyprintf((stderr, " ok %s @ %s\n", "class", yy->__buf+yy->__pos)); + return 1; + l35:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "class", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_literal(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "literal")); + { int yypos40= yy->__pos, yythunkpos40= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l41; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l41; +#undef yytext +#undef yyleng + } + l42:; + { int yypos43= yy->__pos, yythunkpos43= yy->__thunkpos; + { int yypos44= yy->__pos, yythunkpos44= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l44; goto l43; + l44:; yy->__pos= yypos44; yy->__thunkpos= yythunkpos44; + } if (!yy_char(yy)) goto l43; goto l42; + l43:; yy->__pos= yypos43; yy->__thunkpos= yythunkpos43; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l41; +#undef yytext +#undef yyleng + } if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l41; if (!yy__(yy)) goto l41; goto l40; + l41:; yy->__pos= yypos40; yy->__thunkpos= yythunkpos40; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l39; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l39; +#undef yytext +#undef yyleng + } + l45:; + { int yypos46= yy->__pos, yythunkpos46= yy->__thunkpos; + { int yypos47= yy->__pos, yythunkpos47= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l47; goto l46; + l47:; yy->__pos= yypos47; yy->__thunkpos= yythunkpos47; + } if (!yy_char(yy)) goto l46; goto l45; + l46:; yy->__pos= yypos46; yy->__thunkpos= yythunkpos46; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l39; +#undef yytext +#undef yyleng + } if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l39; if (!yy__(yy)) goto l39; + } + l40:; + yyprintf((stderr, " ok %s @ %s\n", "literal", yy->__buf+yy->__pos)); + return 1; + l39:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "literal", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_CLOSE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "CLOSE")); if (!yymatchChar(yy, ')')) goto l48; if (!yy__(yy)) goto l48; + yyprintf((stderr, " ok %s @ %s\n", "CLOSE", yy->__buf+yy->__pos)); + return 1; + l48:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "CLOSE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_OPEN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "OPEN")); if (!yymatchChar(yy, '(')) goto l49; if (!yy__(yy)) goto l49; + yyprintf((stderr, " ok %s @ %s\n", "OPEN", yy->__buf+yy->__pos)); + return 1; + l49:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "OPEN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_COLON(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "COLON")); if (!yymatchChar(yy, ':')) goto l50; if (!yy__(yy)) goto l50; + yyprintf((stderr, " ok %s @ %s\n", "COLON", yy->__buf+yy->__pos)); + return 1; + l50:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "COLON", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PLUS(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PLUS")); if (!yymatchChar(yy, '+')) goto l51; if (!yy__(yy)) goto l51; + yyprintf((stderr, " ok %s @ %s\n", "PLUS", yy->__buf+yy->__pos)); + return 1; + l51:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PLUS", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_STAR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "STAR")); if (!yymatchChar(yy, '*')) goto l52; if (!yy__(yy)) goto l52; + yyprintf((stderr, " ok %s @ %s\n", "STAR", yy->__buf+yy->__pos)); + return 1; + l52:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "STAR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_QUESTION(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "QUESTION")); if (!yymatchChar(yy, '?')) goto l53; if (!yy__(yy)) goto l53; + yyprintf((stderr, " ok %s @ %s\n", "QUESTION", yy->__buf+yy->__pos)); + return 1; + l53:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "QUESTION", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_primary(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "primary")); + { int yypos55= yy->__pos, yythunkpos55= yy->__thunkpos; if (!yy_identifier(yy)) goto l56; yyDo(yy, yy_1_primary, yy->__begin, yy->__end); if (!yy_COLON(yy)) goto l56; if (!yy_identifier(yy)) goto l56; + { int yypos57= yy->__pos, yythunkpos57= yy->__thunkpos; if (!yy_EQUAL(yy)) goto l57; goto l56; + l57:; yy->__pos= yypos57; yy->__thunkpos= yythunkpos57; + } yyDo(yy, yy_2_primary, yy->__begin, yy->__end); goto l55; + l56:; yy->__pos= yypos55; yy->__thunkpos= yythunkpos55; if (!yy_identifier(yy)) goto l58; + { int yypos59= yy->__pos, yythunkpos59= yy->__thunkpos; if (!yy_EQUAL(yy)) goto l59; goto l58; + l59:; yy->__pos= yypos59; yy->__thunkpos= yythunkpos59; + } yyDo(yy, yy_3_primary, yy->__begin, yy->__end); goto l55; + l58:; yy->__pos= yypos55; yy->__thunkpos= yythunkpos55; if (!yy_OPEN(yy)) goto l60; if (!yy_expression(yy)) goto l60; if (!yy_CLOSE(yy)) goto l60; goto l55; + l60:; yy->__pos= yypos55; yy->__thunkpos= yythunkpos55; if (!yy_literal(yy)) goto l61; yyDo(yy, yy_4_primary, yy->__begin, yy->__end); goto l55; + l61:; yy->__pos= yypos55; yy->__thunkpos= yythunkpos55; if (!yy_class(yy)) goto l62; yyDo(yy, yy_5_primary, yy->__begin, yy->__end); goto l55; + l62:; yy->__pos= yypos55; yy->__thunkpos= yythunkpos55; if (!yy_DOT(yy)) goto l63; yyDo(yy, yy_6_primary, yy->__begin, yy->__end); goto l55; + l63:; yy->__pos= yypos55; yy->__thunkpos= yythunkpos55; if (!yy_action(yy)) goto l64; yyDo(yy, yy_7_primary, yy->__begin, yy->__end); goto l55; + l64:; yy->__pos= yypos55; yy->__thunkpos= yythunkpos55; if (!yy_BEGIN(yy)) goto l65; yyDo(yy, yy_8_primary, yy->__begin, yy->__end); goto l55; + l65:; yy->__pos= yypos55; yy->__thunkpos= yythunkpos55; if (!yy_END(yy)) goto l54; yyDo(yy, yy_9_primary, yy->__begin, yy->__end); + } + l55:; + yyprintf((stderr, " ok %s @ %s\n", "primary", yy->__buf+yy->__pos)); + return 1; + l54:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "primary", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_NOT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "NOT")); if (!yymatchChar(yy, '!')) goto l66; if (!yy__(yy)) goto l66; + yyprintf((stderr, " ok %s @ %s\n", "NOT", yy->__buf+yy->__pos)); + return 1; + l66:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "NOT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_suffix(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "suffix")); if (!yy_primary(yy)) goto l67; + { int yypos68= yy->__pos, yythunkpos68= yy->__thunkpos; + { int yypos70= yy->__pos, yythunkpos70= yy->__thunkpos; if (!yy_QUESTION(yy)) goto l71; yyDo(yy, yy_1_suffix, yy->__begin, yy->__end); goto l70; + l71:; yy->__pos= yypos70; yy->__thunkpos= yythunkpos70; if (!yy_STAR(yy)) goto l72; yyDo(yy, yy_2_suffix, yy->__begin, yy->__end); goto l70; + l72:; yy->__pos= yypos70; yy->__thunkpos= yythunkpos70; if (!yy_PLUS(yy)) goto l68; yyDo(yy, yy_3_suffix, yy->__begin, yy->__end); + } + l70:; goto l69; + l68:; yy->__pos= yypos68; yy->__thunkpos= yythunkpos68; + } + l69:; + yyprintf((stderr, " ok %s @ %s\n", "suffix", yy->__buf+yy->__pos)); + return 1; + l67:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "suffix", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_AND(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "AND")); if (!yymatchChar(yy, '&')) goto l73; if (!yy__(yy)) goto l73; + yyprintf((stderr, " ok %s @ %s\n", "AND", yy->__buf+yy->__pos)); + return 1; + l73:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "AND", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_action(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "action")); if (!yymatchChar(yy, '{')) goto l74; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l74; +#undef yytext +#undef yyleng + } + l75:; + { int yypos76= yy->__pos, yythunkpos76= yy->__thunkpos; if (!yy_braces(yy)) goto l76; goto l75; + l76:; yy->__pos= yypos76; yy->__thunkpos= yythunkpos76; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l74; +#undef yytext +#undef yyleng + } if (!yymatchChar(yy, '}')) goto l74; if (!yy__(yy)) goto l74; + yyprintf((stderr, " ok %s @ %s\n", "action", yy->__buf+yy->__pos)); + return 1; + l74:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "action", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_TILDE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "TILDE")); if (!yymatchChar(yy, '~')) goto l77; if (!yy__(yy)) goto l77; + yyprintf((stderr, " ok %s @ %s\n", "TILDE", yy->__buf+yy->__pos)); + return 1; + l77:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "TILDE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_prefix(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "prefix")); + { int yypos79= yy->__pos, yythunkpos79= yy->__thunkpos; if (!yy_AND(yy)) goto l80; if (!yy_action(yy)) goto l80; yyDo(yy, yy_1_prefix, yy->__begin, yy->__end); goto l79; + l80:; yy->__pos= yypos79; yy->__thunkpos= yythunkpos79; if (!yy_AND(yy)) goto l81; if (!yy_suffix(yy)) goto l81; yyDo(yy, yy_2_prefix, yy->__begin, yy->__end); goto l79; + l81:; yy->__pos= yypos79; yy->__thunkpos= yythunkpos79; if (!yy_NOT(yy)) goto l82; if (!yy_suffix(yy)) goto l82; yyDo(yy, yy_3_prefix, yy->__begin, yy->__end); goto l79; + l82:; yy->__pos= yypos79; yy->__thunkpos= yythunkpos79; if (!yy_suffix(yy)) goto l78; + } + l79:; + yyprintf((stderr, " ok %s @ %s\n", "prefix", yy->__buf+yy->__pos)); + return 1; + l78:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "prefix", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_error(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "error")); if (!yy_prefix(yy)) goto l83; + { int yypos84= yy->__pos, yythunkpos84= yy->__thunkpos; if (!yy_TILDE(yy)) goto l84; if (!yy_action(yy)) goto l84; yyDo(yy, yy_1_error, yy->__begin, yy->__end); goto l85; + l84:; yy->__pos= yypos84; yy->__thunkpos= yythunkpos84; + } + l85:; + yyprintf((stderr, " ok %s @ %s\n", "error", yy->__buf+yy->__pos)); + return 1; + l83:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "error", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_BAR(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "BAR")); if (!yymatchChar(yy, '|')) goto l86; if (!yy__(yy)) goto l86; + yyprintf((stderr, " ok %s @ %s\n", "BAR", yy->__buf+yy->__pos)); + return 1; + l86:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "BAR", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_sequence(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "sequence")); if (!yy_error(yy)) goto l87; + l88:; + { int yypos89= yy->__pos, yythunkpos89= yy->__thunkpos; if (!yy_error(yy)) goto l89; yyDo(yy, yy_1_sequence, yy->__begin, yy->__end); goto l88; + l89:; yy->__pos= yypos89; yy->__thunkpos= yythunkpos89; + } + yyprintf((stderr, " ok %s @ %s\n", "sequence", yy->__buf+yy->__pos)); + return 1; + l87:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "sequence", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_SEMICOLON(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "SEMICOLON")); if (!yymatchChar(yy, ';')) goto l90; if (!yy__(yy)) goto l90; + yyprintf((stderr, " ok %s @ %s\n", "SEMICOLON", yy->__buf+yy->__pos)); + return 1; + l90:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "SEMICOLON", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_expression(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "expression")); if (!yy_sequence(yy)) goto l91; + l92:; + { int yypos93= yy->__pos, yythunkpos93= yy->__thunkpos; if (!yy_BAR(yy)) goto l93; if (!yy_sequence(yy)) goto l93; yyDo(yy, yy_1_expression, yy->__begin, yy->__end); goto l92; + l93:; yy->__pos= yypos93; yy->__thunkpos= yythunkpos93; + } + yyprintf((stderr, " ok %s @ %s\n", "expression", yy->__buf+yy->__pos)); + return 1; + l91:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "expression", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_EQUAL(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "EQUAL")); if (!yymatchChar(yy, '=')) goto l94; if (!yy__(yy)) goto l94; + yyprintf((stderr, " ok %s @ %s\n", "EQUAL", yy->__buf+yy->__pos)); + return 1; + l94:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "EQUAL", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_identifier(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "identifier")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l95; +#undef yytext +#undef yyleng + } if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\040\000\000\376\377\377\207\376\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l95; + l96:; + { int yypos97= yy->__pos, yythunkpos97= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\040\377\003\376\377\377\207\376\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l97; goto l96; + l97:; yy->__pos= yypos97; yy->__thunkpos= yythunkpos97; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l95; +#undef yytext +#undef yyleng + } if (!yy__(yy)) goto l95; + yyprintf((stderr, " ok %s @ %s\n", "identifier", yy->__buf+yy->__pos)); + return 1; + l95:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "identifier", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_RPERCENT(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "RPERCENT")); if (!yymatchString(yy, "%}")) goto l98; if (!yy__(yy)) goto l98; + yyprintf((stderr, " ok %s @ %s\n", "RPERCENT", yy->__buf+yy->__pos)); + return 1; + l98:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "RPERCENT", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_end_of_file(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "end_of_file")); + { int yypos100= yy->__pos, yythunkpos100= yy->__thunkpos; if (!yymatchDot(yy)) goto l100; goto l99; + l100:; yy->__pos= yypos100; yy->__thunkpos= yythunkpos100; + } + yyprintf((stderr, " ok %s @ %s\n", "end_of_file", yy->__buf+yy->__pos)); + return 1; + l99:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "end_of_file", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_trailer(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "trailer")); if (!yymatchString(yy, "%%")) goto l101; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l101; +#undef yytext +#undef yyleng + } + l102:; + { int yypos103= yy->__pos, yythunkpos103= yy->__thunkpos; if (!yymatchDot(yy)) goto l103; goto l102; + l103:; yy->__pos= yypos103; yy->__thunkpos= yythunkpos103; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l101; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_trailer, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "trailer", yy->__buf+yy->__pos)); + return 1; + l101:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "trailer", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_definition(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "definition")); if (!yy_identifier(yy)) goto l104; yyDo(yy, yy_1_definition, yy->__begin, yy->__end); if (!yy_EQUAL(yy)) goto l104; if (!yy_expression(yy)) goto l104; yyDo(yy, yy_2_definition, yy->__begin, yy->__end); + { int yypos105= yy->__pos, yythunkpos105= yy->__thunkpos; if (!yy_SEMICOLON(yy)) goto l105; goto l106; + l105:; yy->__pos= yypos105; yy->__thunkpos= yythunkpos105; + } + l106:; + yyprintf((stderr, " ok %s @ %s\n", "definition", yy->__buf+yy->__pos)); + return 1; + l104:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "definition", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_declaration(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "declaration")); if (!yymatchString(yy, "%{")) goto l107; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l107; +#undef yytext +#undef yyleng + } + l108:; + { int yypos109= yy->__pos, yythunkpos109= yy->__thunkpos; + { int yypos110= yy->__pos, yythunkpos110= yy->__thunkpos; if (!yymatchString(yy, "%}")) goto l110; goto l109; + l110:; yy->__pos= yypos110; yy->__thunkpos= yythunkpos110; + } if (!yymatchDot(yy)) goto l109; goto l108; + l109:; yy->__pos= yypos109; yy->__thunkpos= yythunkpos109; + } yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l107; +#undef yytext +#undef yyleng + } if (!yy_RPERCENT(yy)) goto l107; yyDo(yy, yy_1_declaration, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "declaration", yy->__buf+yy->__pos)); + return 1; + l107:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "declaration", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy__(yycontext *yy) +{ + yyprintf((stderr, "%s\n", "_")); + l112:; + { int yypos113= yy->__pos, yythunkpos113= yy->__thunkpos; + { int yypos114= yy->__pos, yythunkpos114= yy->__thunkpos; if (!yy_space(yy)) goto l115; goto l114; + l115:; yy->__pos= yypos114; yy->__thunkpos= yythunkpos114; if (!yy_comment(yy)) goto l113; + } + l114:; goto l112; + l113:; yy->__pos= yypos113; yy->__thunkpos= yythunkpos113; + } + yyprintf((stderr, " ok %s @ %s\n", "_", yy->__buf+yy->__pos)); + return 1; +} +YY_RULE(int) yy_grammar(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "grammar")); if (!yy__(yy)) goto l116; + { int yypos119= yy->__pos, yythunkpos119= yy->__thunkpos; if (!yy_declaration(yy)) goto l120; goto l119; + l120:; yy->__pos= yypos119; yy->__thunkpos= yythunkpos119; if (!yy_definition(yy)) goto l116; + } + l119:; + l117:; + { int yypos118= yy->__pos, yythunkpos118= yy->__thunkpos; + { int yypos121= yy->__pos, yythunkpos121= yy->__thunkpos; if (!yy_declaration(yy)) goto l122; goto l121; + l122:; yy->__pos= yypos121; yy->__thunkpos= yythunkpos121; if (!yy_definition(yy)) goto l118; + } + l121:; goto l117; + l118:; yy->__pos= yypos118; yy->__thunkpos= yythunkpos118; + } + { int yypos123= yy->__pos, yythunkpos123= yy->__thunkpos; if (!yy_trailer(yy)) goto l123; goto l124; + l123:; yy->__pos= yypos123; yy->__thunkpos= yythunkpos123; + } + l124:; if (!yy_end_of_file(yy)) goto l116; + yyprintf((stderr, " ok %s @ %s\n", "grammar", yy->__buf+yy->__pos)); + return 1; + l116:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "grammar", yy->__buf+yy->__pos)); + return 0; +} + +#ifndef YY_PART + +typedef int (*yyrule)(yycontext *yy); + +YY_PARSE(int) YYPARSEFROM(YY_CTX_PARAM_ yyrule yystart) +{ + int yyok; + if (!yyctx->__buflen) + { + yyctx->__buflen= YY_BUFFER_SIZE; + yyctx->__buf= (char *)YY_MALLOC(yyctx, yyctx->__buflen); + yyctx->__textlen= YY_BUFFER_SIZE; + yyctx->__text= (char *)YY_MALLOC(yyctx, yyctx->__textlen); + yyctx->__thunkslen= YY_STACK_SIZE; + yyctx->__thunks= (yythunk *)YY_MALLOC(yyctx, sizeof(yythunk) * yyctx->__thunkslen); + yyctx->__valslen= YY_STACK_SIZE; + yyctx->__vals= (YYSTYPE *)YY_MALLOC(yyctx, sizeof(YYSTYPE) * yyctx->__valslen); + yyctx->__begin= yyctx->__end= yyctx->__pos= yyctx->__limit= yyctx->__thunkpos= 0; + } + yyctx->__begin= yyctx->__end= yyctx->__pos; + yyctx->__thunkpos= 0; + yyctx->__val= yyctx->__vals; + yyok= yystart(yyctx); + if (yyok) yyDone(yyctx); + yyCommit(yyctx); + return yyok; +} + +YY_PARSE(int) YYPARSE(YY_CTX_PARAM) +{ + return YYPARSEFROM(YY_CTX_ARG_ yy_grammar); +} + +YY_PARSE(yycontext *) YYRELEASE(yycontext *yyctx) +{ + if (yyctx->__buflen) + { + yyctx->__buflen= 0; + YY_FREE(yyctx, yyctx->__buf); + YY_FREE(yyctx, yyctx->__text); + YY_FREE(yyctx, yyctx->__thunks); + YY_FREE(yyctx, yyctx->__vals); + } + return yyctx; +} + +#endif + + +void yyerror(char *message) +{ + fprintf(stderr, "%s:%d: %s", fileName, lineNumber, message); + if (yyctx->__text[0]) fprintf(stderr, " near token '%s'", yyctx->__text); + if (yyctx->__pos < yyctx->__limit || !feof(input)) + { + yyctx->__buf[yyctx->__limit]= '\0'; + fprintf(stderr, " before text \""); + while (yyctx->__pos < yyctx->__limit) + { + if ('\n' == yyctx->__buf[yyctx->__pos] || '\r' == yyctx->__buf[yyctx->__pos]) break; + fputc(yyctx->__buf[yyctx->__pos++], stderr); + } + if (yyctx->__pos == yyctx->__limit) + { + int c; + while (EOF != (c= fgetc(input)) && '\n' != c && '\r' != c) + fputc(c, stderr); + } + fputc('\"', stderr); + } + fprintf(stderr, "\n"); + exit(1); +} + +void makeHeader(char *text) +{ + Header *header= (Header *)malloc(sizeof(Header)); + header->text= strdup(text); + header->next= headers; + headers= header; +} + +void makeTrailer(char *text) +{ + trailer= strdup(text); +} + +static void version(char *name) +{ + printf("%s version %d.%d.%d\n", name, PEG_MAJOR, PEG_MINOR, PEG_LEVEL); +} + +static void usage(char *name) +{ + version(name); + fprintf(stderr, "usage: %s [ +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [0,1..20]) +# that doxygen will group on one line in the generated HTML documentation. +# Note that a value of 0 will completely suppress the enum values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will write a font called Helvetica to the output +# directory and reference it in all dot files that doxygen generates. +# When you want a differently looking font you can specify the font name +# using DOT_FONTNAME. You need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, svg, gif or svg. +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/docs/Makefile.am b/docs/Makefile.am new file mode 100644 index 0000000000..f636feadf3 --- /dev/null +++ b/docs/Makefile.am @@ -0,0 +1,24 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +SUBDIRS = manpages diff --git a/docs/benchmark.sh b/docs/benchmark.sh new file mode 100644 index 0000000000..edd5a4a2b1 --- /dev/null +++ b/docs/benchmark.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# +# This is a policy benchmark script +# +# ARG_POSITIONAL_SINGLE([agent],[path to agent],[]) +# ARG_POSITIONAL_SINGLE([policy],[path to policy file],[]) +# ARG_POSITIONAL_SINGLE([n],[number of times to run policy],[]) +# ARG_HELP([Policy Benchmark Script]) +# ARGBASH_GO() +# needed because of Argbash --> m4_ignore([ +### START OF CODE GENERATED BY Argbash v2.9.0 one line above ### +# Argbash is a bash code generator used to get arguments parsing right. +# Argbash is FREE SOFTWARE, see https://argbash.io for more info +# Generated online by https://argbash.io/generate + + +die() +{ + local _ret="${2:-1}" + test "${_PRINT_HELP:-no}" = yes && print_help >&2 + echo "$1" >&2 + exit "${_ret}" +} + + +begins_with_short_option() +{ + local first_option all_short_options='h' + first_option="${1:0:1}" + test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0 +} + +# THE DEFAULTS INITIALIZATION - POSITIONALS +_positionals=() +# THE DEFAULTS INITIALIZATION - OPTIONALS + + +print_help() +{ + printf '%s\n' "Policy Benchmark Script" + printf 'Usage: %s [-h|--help] \n' "$0" + printf '\t%s\n' ": absolute path to agent" + printf '\t%s\n' ": absolute path to policy file" + printf '\t%s\n' ": number of times to run policy" + printf '\t%s\n' "-h, --help: Prints help" +} + + +parse_commandline() +{ + _positionals_count=0 + while test $# -gt 0 + do + _key="$1" + case "$_key" in + -h|--help) + print_help + exit 0 + ;; + -h*) + print_help + exit 0 + ;; + *) + _last_positional="$1" + _positionals+=("$_last_positional") + _positionals_count=$((_positionals_count + 1)) + ;; + esac + shift + done +} + + +handle_passed_args_count() +{ + local _required_args_string="'agent', 'policy' and 'n'" + test "${_positionals_count}" -ge 3 || _PRINT_HELP=yes die "FATAL ERROR: Not enough positional arguments - we require exactly 3 (namely: $_required_args_string), but got only ${_positionals_count}." 1 + test "${_positionals_count}" -le 3 || _PRINT_HELP=yes die "FATAL ERROR: There were spurious positional arguments --- we expect exactly 3 (namely: $_required_args_string), but got ${_positionals_count} (the last one was: '${_last_positional}')." 1 +} + + +assign_positional_args() +{ + local _positional_name _shift_for=$1 + _positional_names="_arg_agent _arg_policy _arg_n " + + shift "$_shift_for" + for _positional_name in ${_positional_names} + do + test $# -gt 0 || break + eval "$_positional_name=\${1}" || die "Error during argument parsing, possibly an Argbash bug." 1 + shift + done +} + +parse_commandline "$@" +handle_passed_args_count +assign_positional_args 1 "${_positionals[@]}" + +# OTHER STUFF GENERATED BY Argbash + +### END OF CODE GENERATED BY Argbash (sortof) ### ]) +# [ <-- needed because of Argbash + +# Collect only real result from `time` command +TIMEFORMAT=%R + +sum=0 +array=() +for (( i=0; i<$_arg_n; i++)) +do + time=$(time ($_arg_agent -KIf $_arg_policy &>/dev/null) 2>&1) + sum=$(echo "$sum + $time" | bc -l) + array+=($time) + printf "bench %d: %.03f s\n" $i $time +done + +# Sort array of results +IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS + +echo +echo "*** RESULTS ***" + +# Max time +max=$(($NUM_RUNS-1)) +printf "max : %.03f s\n" "${sorted[$max]}" + +# Min time +printf "min : %.03f s\n" "${sorted[0]}" + +# Mean time +mean=$(echo "$sum / $_arg_n" | bc -l) +printf "mean : %4.03f s\n" $mean + +# Median time +half=$(echo "$_arg_n / 2" | bc -l) +half=$(printf "%.0f" "$half") +printf "median : %.03f s\n" "${sorted[$half]}" + +# ] <-- needed because of Argbash diff --git a/docs/cf_remote_demo.sh b/docs/cf_remote_demo.sh new file mode 100644 index 0000000000..8302a91e89 --- /dev/null +++ b/docs/cf_remote_demo.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e +set -x + +SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +cf-remote spawn --platform ubuntu-22-04-x64 --count 1 --name hub --role hub +cf-remote spawn --platform ubuntu-20-04-x64 --count 1 --name ubuntu-20 --role client +cf-remote spawn --platform ubuntu-18-04-x64 --count 1 --name ubuntu-18 --role client +cf-remote spawn --platform centos-7-x64 --count 1 --name centos-7 --role client +cf-remote spawn --platform debian-11-x64 --count 1 --name debian-11 --role client +cf-remote spawn --platform rhel-7-x64 --count 1 --name rhel-7 --role client + +cf-remote --version master install --demo --bootstrap hub --hub hub + +# cf-remote deploy --hub hub "$SCRIPTPATH/../../masterfiles" + +cf-remote --version master install --demo --bootstrap hub --clients ubuntu-20 +cf-remote --version master install --demo --bootstrap hub --clients ubuntu-18 +cf-remote --version master install --demo --bootstrap hub --clients centos-7 +cf-remote --version master install --demo --bootstrap hub --clients debian-11 +cf-remote --version master install --demo --bootstrap hub --clients rhel-7 + +cf-remote info -H hub diff --git a/docs/manpages/Makefile.am b/docs/manpages/Makefile.am new file mode 100644 index 0000000000..f4ba024daf --- /dev/null +++ b/docs/manpages/Makefile.am @@ -0,0 +1,45 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +if !CROSS_COMPILING + +man_MANS = \ + cf-agent.8 \ + cf-execd.8 \ + cf-key.8 \ + cf-monitord.8 \ + cf-promises.8 \ + cf-runagent.8 \ + cf-serverd.8 + +endif + +.PRECIOUS: ../../%/% + +../../%/.doc-stamp: + echo $@ + $(MAKE) -C $(dir $@) $(AM_MAKEFLAGS) + touch $@ + +%.8 : ../../%/.doc-stamp + $(AM_V_GEN)$(dir $<)$(notdir $(patsubst %/,%,$(dir $<))) -M > $@ diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000000..662351a12e --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,38 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +examplesdir = $(docdir)/examples +dist_examples_DATA = $(srcdir)/*.cf +dist_examples_SCRIPTS = remake_outputs.pl +FAKE_WORKDIR=/tmp/fake-cfengine-workdir +MAINTAINERCLEANFILES = Makefile.in mdate-sh + +# only re-run the outputs for examples that already have an example_output block +remake: + perl ./remake_outputs.pl $(shell grep -l example_output $(dist_examples_DATA)) + +recheck: + perl ./remake_outputs.pl -c $(shell grep -l example_output $(dist_examples_DATA)) + +recheck_verbose: + perl ./remake_outputs.pl -v -c $(shell grep -l example_output $(dist_examples_DATA)) diff --git a/examples/README.org b/examples/README.org new file mode 100644 index 0000000000..bb331ef799 --- /dev/null +++ b/examples/README.org @@ -0,0 +1,139 @@ +#+Title: Examples + +This directory contains simple stand alone examples that illustrate the language +and how cfengine works. + +These examples are shipped in our packages as part embedded package +documentation and are commonly included from the [[https://github.com/cfengine/documentation][documentation repository]] for +inclusion in the [[https://docs.cfengine.com][reference manual]]. + +Examples that contain an =example_output= =src= block are run as part of our +=common= [[./../tests/acceptance/04_examples/.][automated tests]] during ci builds. + +Before running an example be sure to execute the commands inside the =prep= +=src= block if one exists. + +#+BEGIN_SRC shell :results output :exports both + awk '/.*begin_src prep/ {p=1}; p; /.*end_src/ {p=0}' ./file_hash.cf +#+END_SRC + +#+RESULTS: +: #+begin_src prep +: #@ ``` +: #@ echo 1 > /tmp/1 +: #@ echo 2 > /tmp/2 +: #@ echo 3 > /tmp/3 +: #@ ``` +: #+end_src + +They can be run directly: + +#+BEGIN_SRC shell :exports both + chmod 600 ./countclassesmatching.cf + cf-agent -If ./countclassesmatching.cf +#+END_SRC + +#+RESULTS: +: R: Found 1 classes matching + +And their output can be compared with the =example_output= section. + +#+BEGIN_SRC shell :results output :exports both + awk '/.*begin_src example_output/ {p=1}; p; /.*end_src/ {p=0}' ./countclassesmatching.cf +#+END_SRC + +#+RESULTS: +: #+begin_src example_output +: #@ ``` +: #@ R: Found 1 classes matching +: #@ ``` +: #+end_src + +**Note:** Output inside of the =example_output= must be stable across runs of + the agent in order to work for testing. If including an example that has + unstable output from inside of the documentation repository consider adding a + static section to show in the documentation and a section showing an example + of the expected output. For example see the [[./randomint.cf][=randomint.cf= example]], and the + [[https://docs.cfengine.com/docs/master/reference-functions-randomint.html][=randomint()= in the reference manual]]. + +Running the examples: + +#+BEGIN_SRC shell :results output :exports both + cd .. + find . -name "*.cf*" | xargs chmod 600 + tests/acceptance/testall --jobs=4\ + --printlog\ + --baseclasses=AUTO\ + --extraclasses=DEBUG\ + --bindir=/var/cfengine/bin\ + tests/acceptance/04_examples/outputs/check_outputs.cf +#+END_SRC + +#+RESULTS: +#+begin_example +====================================================================== +Testsuite started at 2018-04-14 00:12:10 +---------------------------------------------------------------------- +Total tests: 1 + + COMMON_TESTS: enabled + TIMED_TESTS: enabled + SLOW_TESTS: enabled + ERROREXIT_TESTS: enabled + SERIAL_TESTS: enabled + NETWORK_TESTS: enabled + LIBXML2_TESTS: enabled + LIBCURL_TESTS: enabled + UNSAFE_TESTS: disabled + STAGING_TESTS: disabled + +Test run is PARALLEL with MAKEFLAGS= --jobs=4 + +./tests/acceptance/04_examples/outputs/check_outputs.cf Pass + +====================================================================== +Testsuite finished at 2018-04-14 00:12:11 (1 seconds) + +Passed tests: 1 +Failed tests: 0 +Skipped tests: 0 +Soft failures: 0 +Total tests: 1 +====================================================================== +Testsuite started at 2018-04-14 00:12:10 +---------------------------------------------------------------------- +Total tests: 1 + + COMMON_TESTS: enabled + TIMED_TESTS: enabled + SLOW_TESTS: enabled + ERROREXIT_TESTS: enabled + SERIAL_TESTS: enabled + NETWORK_TESTS: enabled + LIBXML2_TESTS: enabled + LIBCURL_TESTS: enabled + UNSAFE_TESTS: disabled + STAGING_TESTS: disabled + +Test run is PARALLEL with MAKEFLAGS= --jobs=4 + +---------------------------------------------------------------------- +./tests/acceptance/04_examples/outputs/check_outputs.cf +---------------------------------------------------------------------- +R: /home/nickanderson/Northern.Tech/CFEngine/core/./tests/acceptance/04_examples/outputs/check_outputs.cf Pass + +Return code is 0. + + ==> Pass + + +====================================================================== +Testsuite finished at 2018-04-14 00:12:11 (1 seconds) + +Passed tests: 1 +Failed tests: 0 +Skipped tests: 0 +Soft failures: 0 +Total tests: 1 +#+end_example + diff --git a/examples/abort.cf b/examples/abort.cf new file mode 100644 index 0000000000..2c5063af32 --- /dev/null +++ b/examples/abort.cf @@ -0,0 +1,59 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#[%+%] +body common control + +{ + bundlesequence => { "example" }; +} + +body agent control + +{ + abortbundleclasses => { "invalid" }; +} +#[%-%] + +########################################### +#[%+%] + +bundle agent example + +{ + vars: + + #"userlist" slist => { "mark", "john" }; # contains all valid entries + "userlist" slist => { "mark", "john", "thomas" }; # contains one invalid entry + + classes: + + "invalid" not => regcmp("[a-z][a-z][a-z][a-z]","$(userlist)"); # The class 'invalid' is set if the user name does not + # contain exactly four un-capitalized letters (bundle + # execution will be aborted if set) + + reports: + + !invalid:: + + "User name $(userlist) is valid at 4 letters"; +} diff --git a/examples/accessed_before.cf b/examples/accessed_before.cf new file mode 100644 index 0000000000..87487282f4 --- /dev/null +++ b/examples/accessed_before.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Show the definition of classes by comparing timestamps +# + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + classes: + + "do_it" and => { + accessedbefore("/tmp/earlier","/tmp/later") + }; + + reports: + + do_it:: + + "The earlier file has been accessed before the later"; + +} + diff --git a/examples/accessedbefore.cf b/examples/accessedbefore.cf new file mode 100644 index 0000000000..c2569581e7 --- /dev/null +++ b/examples/accessedbefore.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ touch -a -t '200102031234.56' /tmp/earlier +#@ touch -a -t '200202031234.56' /tmp/later +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + "do_it" expression => accessedbefore("/tmp/earlier","/tmp/later"); + + reports: + do_it:: + "The secret changes have been accessed after the reference time"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The secret changes have been accessed after the reference time +#@ ``` +#+end_src diff --git a/examples/accumulated_time.cf b/examples/accumulated_time.cf new file mode 100644 index 0000000000..49bc874475 --- /dev/null +++ b/examples/accumulated_time.cf @@ -0,0 +1,71 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test processes +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + processes: + + ".*" + + process_count => anyprocs, + process_select => proc_finder; + + reports: + + any_procs:: + + "Found processes in range"; +} + +######################################################## + +body process_select proc_finder + +{ + ttime_range => irange(accumulated(0,0,0,0,2,0),accumulated(0,0,0,0,20,0)); + process_result => "ttime"; +} + +######################################################## + +body process_count anyprocs + +{ + match_range => "0,0"; + out_of_range_define => { "any_procs" }; +} + diff --git a/examples/acl.cf b/examples/acl.cf new file mode 100644 index 0000000000..271b73d25f --- /dev/null +++ b/examples/acl.cf @@ -0,0 +1,70 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "acls" }; +} + +######################################### + +bundle agent acls + +{ + files: + + "/media/flash/acl/test_dir" + + depth_search => include_base, + acl => template; +} + +######################################### + +body acl template + +{ + acl_method => "overwrite"; + acl_type => "posix"; + acl_default => "access"; + aces => { "user:*:r(wwx),-r:allow", "group:*:+rw:allow", "mask:x:allow", "all:r"}; +} + +######################################### + +body acl win + +{ + acl_method => "overwrite"; + acl_type => "ntfs"; + acl_default => "nochange"; + aces => { "user:Administrator:rw", "group:Bad:rwx(Dpo):deny" }; +} + +######################################### + +body depth_search include_base + +{ + include_basedir => "true"; +} diff --git a/examples/acl_generic.cf b/examples/acl_generic.cf new file mode 100644 index 0000000000..68b030d7a8 --- /dev/null +++ b/examples/acl_generic.cf @@ -0,0 +1,57 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "acls" }; +} + +######################################### + +bundle agent acls + +{ + files: + + "/media/flash/acl/test_dir" + + depth_search => include_base, + acl => test; +} + +######################################### + +body acl test + +{ + acl_type => "generic"; + aces => {"user:bob:rwx", "group:staff:rx", "all:r"}; +} + +######################################### + +body depth_search include_base + +{ + include_basedir => "true"; +} diff --git a/examples/acl_ntfs.cf b/examples/acl_ntfs.cf new file mode 100644 index 0000000000..a9649a9622 --- /dev/null +++ b/examples/acl_ntfs.cf @@ -0,0 +1,26 @@ +body common control +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; + bundlesequence => { "example" }; +} + +### + +bundle agent example +{ + vars: + "acl_secret_dir" slist => { "user:Administrator:rwx:allow", "group:Administrators:rx:allow" }; + "acl_secret_file" slist => { "user:Administrator:rw:allow" }; + + files: + + windows:: + "C:\Secret" + acl => ntfs( "@(acl_secret_dir)" ), + depth_search => include_base, + perms => owner( "Administrator" ); + + "C:\Secret\file.txt" + acl => ntfs( "@(acl_secret_file)" ), + perms => owner( "Administrator" ); +} diff --git a/examples/acl_secret.cf b/examples/acl_secret.cf new file mode 100644 index 0000000000..0bab7c4c97 --- /dev/null +++ b/examples/acl_secret.cf @@ -0,0 +1,56 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "acls" }; +} + +######################################### + +bundle agent acls + +{ + files: + + windows:: + + "c:\Secret" + acl => win, + depth_search => include_base, + comment => "Secure the secret directory from unauthorized access"; +} + +######################################### + +body acl win + +{ + acl_method => "overwrite"; + aces => { "user:Administrator:rwx" }; +} + +######################################### + +body depth_search include_base + +{ + include_basedir => "true"; +} diff --git a/examples/action_policy.cf b/examples/action_policy.cf new file mode 100755 index 0000000000..422063b190 --- /dev/null +++ b/examples/action_policy.cf @@ -0,0 +1,127 @@ +#!/var/cfengine/bin/cf-agent -f- +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body file control +{ + # Include the stdlib for local_dcp, policy, delete_lines + inputs => { "$(sys.libdir)/stdlib.cf" }; +} +bundle agent example_action_policy +# @brief Example illustrating how action_policy in action bodies control promise actuation and outcomes +{ + files: + + # We make sure there is some files to operate on, so we simply make a copy + # of ourselves + + "$(this.promise_filename).nop" + copy_from => local_dcp( $(this.promise_filename) ); + + "$(this.promise_filename).warn" + copy_from => local_dcp( $(this.promise_filename) ); + + "$(this.promise_filename).fix" + copy_from => local_dcp( $(this.promise_filename) ); + + # We exercise each valid value of action_policy (nop, fix, warn) defining + # classes named for the action_policy + + "$(this.promise_filename).nop" + handle => "delete_lines_action_nop", + edit_line => delete_lines_matching ( ".*" ), + action => policy( "nop" ), + classes => results( "namespace", "MY_files_promise_nop" ); + + "$(this.promise_filename).warn" + handle => "delete_lines_action_warn", + edit_line => delete_lines_matching ( ".*" ), + action => policy( "warn" ), + classes => results( "namespace", "MY_files_promise_warn" ); + + "$(this.promise_filename).fix" + handle => "delete_lines_action_fix", + edit_line => delete_lines_matching ( ".*" ), + action => policy( "fix" ), + classes => results( "namespace", "MY_files_promise_fix" ); + + commands: + + "/bin/echo Running Command nop" + handle => "command_nop", + action => policy( "nop" ), + classes => results( "namespace", "MY_commands_promise_nop" ); + + "/bin/echo Running Command warn" + handle => "command_warn", + action => policy( "warn" ), + classes => results( "namespace", "MY_commands_promise_warn" ); + + "/bin/echo Running Command fix" + handle => "command_fix", + action => policy( "fix" ), + classes => results( "namespace", "MY_commands_promise_fix" ); + + reports: + + "MY classes:$(const.n)$(const.t)$(with)" + with => join( "$(const.n)$(const.t)", sort( classesmatching( "MY_.*" ), "lex" )); + +} +bundle agent __main__ +{ + methods: + "example_action_policy"; +} +#+end_src +############################################################################### +#+begin_src mock_example_output +#@ ``` +#@ warning: Should edit file '/tmp/action_policy.cf.nop' but only a warning promised +#@ warning: Should edit file '/tmp/action_policy.cf.warn' but only a warning promised +#@ warning: Command '/bin/echo Running Command nop' needs to be executed, but only warning was promised +#@ warning: Command '/bin/echo Running Command warn' needs to be executed, but only warning was promised +#@ R: MY classes: +#@ MY_commands_promise_fix_reached +#@ MY_commands_promise_fix_repaired +#@ MY_commands_promise_nop_error +#@ MY_commands_promise_nop_failed +#@ MY_commands_promise_nop_not_kept +#@ MY_commands_promise_nop_reached +#@ MY_commands_promise_warn_error +#@ MY_commands_promise_warn_failed +#@ MY_commands_promise_warn_not_kept +#@ MY_commands_promise_warn_reached +#@ MY_files_promise_fix_reached +#@ MY_files_promise_fix_repaired +#@ MY_files_promise_nop_error +#@ MY_files_promise_nop_failed +#@ MY_files_promise_nop_not_kept +#@ MY_files_promise_nop_reached +#@ MY_files_promise_warn_error +#@ MY_files_promise_warn_failed +#@ MY_files_promise_warn_not_kept +#@ MY_files_promise_warn_reached +#@ warning: Method 'example_action_policy' invoked repairs, but only warnings promised +#@ ``` +#+end_src diff --git a/examples/active_directory.cf b/examples/active_directory.cf new file mode 100644 index 0000000000..f0d1491bdb --- /dev/null +++ b/examples/active_directory.cf @@ -0,0 +1,73 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################################### +# active_directory.cf - Extract Data From Windows Domain Controllers +# +# NOTE: Since we don't supply any credentials in this policy file, +# the Domain Controller must allow anonymous bind. Also, +# the user "NT AUTHORITY\ANONYMOUS LOGON" must be granted access +# to the resources we want to read. +# +######################################################################### + +bundle agent active_directory +{ + vars: + # NOTE: Edit this to your domain, e.g. "corp", may also need more DC's after it + "domain_name" string => "cftesting"; + "user_name" string => "Guest"; + + + # NOTE: We can also extract data from remote Domain Controllers + + dummy.DomainController:: + "domain_controller" string => "localhost"; + + "userlist" slist => ldaplist( + "ldap://$(domain_controller)", + "CN=Users,DC=$(domain_name),DC=com", + "(objectClass=user)", + "sAMAccountName", + "subtree", + "none"); + + classes: + + dummy.DomainController:: + + "gotuser" expression => ldaparray( + "userinfo", + "ldap://$(domain_controller)", + "CN=$(user_name),CN=Users,DC=$(domain_name),DC=com", + "(name=*)", + "subtree", + "none"); + + + reports: + dummy.DomainController:: + "Username is \"$(userlist)\""; + + dummy.gotuser:: + "Got user data; $(userinfo[name]) has logged on $(userinfo[logonCount]) times"; + +} + diff --git a/examples/activedirectory_listusers.cf b/examples/activedirectory_listusers.cf new file mode 100644 index 0000000000..dc86612f3e --- /dev/null +++ b/examples/activedirectory_listusers.cf @@ -0,0 +1,38 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# List users from Active Directory through LDAP +# Note: Anonymous LDAP binding must be allowed, and the Anonymous user +# must have read access to CN=Users + +bundle agent ldap +{ + vars: + "userlist" slist => ldaplist( + "ldap://cf-win2003", + "CN=Users,DC=domain,DC=cf-win2003", + "(objectClass=user)", + "sAMAccountName", + "subtree", + "none"); + reports: + "Username: \"$(userlist)\""; +} + diff --git a/examples/activedirectory_showuser.cf b/examples/activedirectory_showuser.cf new file mode 100644 index 0000000000..77959ddcd6 --- /dev/null +++ b/examples/activedirectory_showuser.cf @@ -0,0 +1,41 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# List users from Active Directory through LDAP +# Note: Anonymous LDAP binding must be allowed, and the Anonymous user +# must have read access to CN=Users and CN=theusername +# Run the agent in verbose mode to see the data + +bundle agent ldap +{ + classes: + "gotdata" expression => ldaparray( + "myarray", + "ldap://cf-win2003", + "CN=Test Pilot,CN=Users,DC=domain,DC=cf-win2003", + "(name=*)", + "subtree", + "none"); + reports: + gotdata:: + "Got user data"; + !gotdata:: + "Did not get user data"; +} diff --git a/examples/ago.cf b/examples/ago.cf new file mode 100644 index 0000000000..07710f9a9f --- /dev/null +++ b/examples/ago.cf @@ -0,0 +1,66 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "testbundle" }; +} + +bundle agent testbundle +{ + processes: + + ".*" + + process_count => anyprocs, + process_select => proc_finder; + + reports: + + any_procs:: + + "Found processes out of range"; +} + + +body process_select proc_finder + +{ + # Processes started between 100 years + 5.5 hours and 1 minute ago + stime_range => irange(ago(100,0,0,5,30,0),ago(0,0,0,0,1,0)); + process_result => "stime"; +} + +body process_count anyprocs + +{ + match_range => "0,0"; + out_of_range_define => { "any_procs" }; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found processes out of range +#@ ``` +#+end_src diff --git a/examples/app_baseline.cf b/examples/app_baseline.cf new file mode 100644 index 0000000000..0afcda60d7 --- /dev/null +++ b/examples/app_baseline.cf @@ -0,0 +1,66 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################################### +# +# app_baseline.cf - Verify Existence of Applications +# +# NOTE: Sometimes applications are not correctly installed even +# though the native package manager reports them to be. +# Cfengine can check for application-specific configuration +# and act upon or report any anomalies. +# +######################################################################### + + +bundle agent app_baseline +{ + + methods: + windows:: + "any" usebundle => detect_adobereader; + +} + +### + +bundle agent detect_adobereader +{ + vars: + + windows:: + "value1" string => registryvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Adobe\Acrobat Reader\9.0\Installer", "ENU_GUID"); + "value2" string => registryvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Adobe\Acrobat Reader\9.0\Installer", "VersionMax"); + "value3" string => registryvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Adobe\Acrobat Reader\9.0\Installer", "VersionMin"); + + classes: + + windows:: + "is_correct" and => { + strcmp("$(value1)", "{AC76BA86-7AD7-1033-7B44-A93000000001}"), + strcmp("$(value2)", "90003"), + islessthan("$(value3)", "10001" ) + }; + + reports: + + windows.!is_correct:: + "Adobe Reader is not correctly deployed - got \"$(value1)\", \"$(value2)\", \"$(value3)\""; +} diff --git a/examples/appgroups.cf b/examples/appgroups.cf new file mode 100644 index 0000000000..d2b38f9bf2 --- /dev/null +++ b/examples/appgroups.cf @@ -0,0 +1,157 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + # you can also readjson() from a file + "groups" data => parsejson('{ +"ByGroup": +{ + "App1": + [ + "GrpA", + "GrpB", + "GrpC" + ], + "App2": + [ + "GrpX", + "GrpY", + "GrpZ" + ] +}, + +"ByApp": +{ + "App1": + [ + "Host1", + "Host2", + "Host3" + ], + "App2": + [ + "Host1", + "Host3" + ] +} +}'); + + methods: + # use the first one on the client + #"go" usebundle => appgroups($(sys.uqhost), @(groups)); + "go" usebundle => appgroups("Host1", @(groups)); + "go" usebundle => appgroups("Host2", @(groups)); +} + +bundle agent appgroups(name, g) +{ + classes: + # stage (3) now for each APP, define have_app_APP if the host is in APP's host list + "have_app_$(apps)" expression => strcmp($(name), "$(hosts_$(apps))"); + + # stage (4) define the class have_group_GROUP for every GROUP belonging to APP + "have_group_$(groups_$(apps))" expression => "have_app_$(apps)"; + + # stage (5) define the class have_group if we found any groups + "have_group" not => strcmp("0", length("$(cname)_list")); + + vars: + # stage (1) start here: we get the apps from the list of "by app" keys + "apps" slist => getindices("g[ByApp]"); + "apps_str" string => format("%S", "apps"); + # stage (2) now for each app, we collect the hosts assigned to it + "hosts_$(apps)" slist => getvalues("g[ByApp][$(apps)]"); + "hosts_$(apps)_str" string => format("%S", "hosts_$(apps)"); + + # stage (2) now for each app, we collect the groups assigned to it + "groups_$(apps)" slist => getvalues("g[ByGroup][$(apps)]"); + "groups_$(apps)_str" string => format("%S", "groups_$(apps)"); + + # stage (5) collect the space-separated group names from an intermediate array + "cname" string => canonify($(name)); + + "$(cname)_grouplist[$(groups_$(apps))]" string => "1", + if => "have_app_$(apps)"; + + "$(cname)_grouplist_slist" slist => getvalues("$(cname)_grouplist"); + + # get the keys of the array and sort them, then join with spaces + "$(cname)_list" slist => getindices("$(cname)_grouplist"); + "$(cname)_list_sorted" slist => sort("$(cname)_list", "lex"); + "$(cname)_list_spaces" string => join(" ", "$(cname)_list_sorted"); + + reports: + # stage (1) + "$(this.bundle): looking for $(name)"; + + "$(this.bundle): apps: $(apps_str)"; + # stage (2) + "$(this.bundle): hosts for $(apps): $(hosts_$(apps)_str)"; + "$(this.bundle): groups for $(apps): $(groups_$(apps)_str)"; + + # stage (3) + "$(this.bundle): $(name) is assigned $(apps)" + if => "have_app_$(apps)"; + + # stage (4) + "$(this.bundle): all groups for $(name) = $(groups_$(apps))" + if => "have_group_$(groups_$(apps))"; + + # stage (5) + have_group:: + "$(this.bundle): space-separated groups for $(name) = $($(cname)_list_spaces)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: appgroups: looking for Host1 +#@ R: appgroups: apps: { "App1", "App2" } +#@ R: appgroups: hosts for App1: { "Host1", "Host2", "Host3" } +#@ R: appgroups: hosts for App2: { "Host1", "Host3" } +#@ R: appgroups: groups for App1: { "GrpA", "GrpB", "GrpC" } +#@ R: appgroups: groups for App2: { "GrpX", "GrpY", "GrpZ" } +#@ R: appgroups: Host1 is assigned App1 +#@ R: appgroups: Host1 is assigned App2 +#@ R: appgroups: all groups for Host1 = GrpA +#@ R: appgroups: all groups for Host1 = GrpB +#@ R: appgroups: all groups for Host1 = GrpC +#@ R: appgroups: all groups for Host1 = GrpX +#@ R: appgroups: all groups for Host1 = GrpY +#@ R: appgroups: all groups for Host1 = GrpZ +#@ R: appgroups: space-separated groups for Host1 = GrpA GrpB GrpC GrpX GrpY GrpZ +#@ R: appgroups: looking for Host2 +#@ R: appgroups: Host2 is assigned App1 +#@ R: appgroups: all groups for Host2 = GrpA +#@ R: appgroups: all groups for Host2 = GrpB +#@ R: appgroups: all groups for Host2 = GrpC +#@ R: appgroups: space-separated groups for Host2 = GrpA GrpB GrpC +#@ ``` +#+end_src diff --git a/examples/arrays.cf b/examples/arrays.cf new file mode 100644 index 0000000000..09ce51d9c1 --- /dev/null +++ b/examples/arrays.cf @@ -0,0 +1,40 @@ +#+begin_src cfengine3 +bundle common g +{ + vars: + + "array[key1]" string => "one"; + "array[key2]" string => "two"; +} + +bundle agent __main__ +{ + vars: + "thing[1][color]" string => "red"; + "thing[1][name]" string => "one"; + "thing[2][color]" string => "blue"; + "thing[2][name]" string => "two"; + + "_thing_idx" + slist => sort( getindices( thing ), lex ); + + reports: + + "Keys in default:g.array = $(with)" + with => join( ", ", sort( getindices( "default:g.array" ), lex)); + + "Keys of default:main.thing[1] = $(with)" + with => join( ", ", sort( getindices( "default:main.thing[1]" ), lex)); + + "Thing $(thing[$(_thing_idx)][name]) is $(thing[$(_thing_idx)][color])"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Keys in default:g.array = key1, key2 +#@ R: Keys of default:main.thing[1] = color, name +#@ R: Thing one is red +#@ R: Thing two is blue +#@ ``` +#+end_src diff --git a/examples/augment.cf b/examples/augment.cf new file mode 100644 index 0000000000..87a476e956 --- /dev/null +++ b/examples/augment.cf @@ -0,0 +1,38 @@ +bundle common def +{ + vars: + # Only define this variable if it is not yet defined. + "example_augment_string_override" + unless => isvariable("example_augment_string_override"); + + # Define this variable regardless if it has been set in def.json + "example_augment_list_override" + slist => { "defined", "in", "bundle", "common", "def" }; +} +bundle agent main +{ + vars: + "def_vars" + slist => variablesmatching("default\:def\..*"); + + reports: + "Def var: '$(def_vars)'"; + + "def.example_augment_string_override = '$(def.example_augment_string_override)'"; + "def.example_augment_list_override = '$(def.example_augment_list_override)'"; + "def.example_augment_structured_override[key1] = '$(def.example_augment_structured_override[key1])'"; +} +#+begin_src example_output +#@ ``` +#@ R: Def var: 'default:def.example_augment_structured_override' +#@ R: Def var: 'default:def.example_augment_string_override' +#@ R: Def var: 'default:def.example_augment_list_override' +#@ R: def.example_augment_string_override = 'defined in def.json' +#@ R: def.example_augment_list_override = 'defined' +#@ R: def.example_augment_list_override = 'in' +#@ R: def.example_augment_list_override = 'bundle' +#@ R: def.example_augment_list_override = 'common' +#@ R: def.example_augment_list_override = 'def' +#@ R: def.example_augment_structured_override[key1] = 'defined in def.json' +#@ ``` +#+end_src diff --git a/examples/backreferences_files.cf b/examples/backreferences_files.cf new file mode 100644 index 0000000000..f73b12f020 --- /dev/null +++ b/examples/backreferences_files.cf @@ -0,0 +1,74 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# File editing - back reference +# +###################################################################### + + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + # The back reference in a path only applies to the last link + # of the pathname, so the (tmp) gets ignored + + "/tmp/(cf3)_(.*)" + + edit_line => myedit("second $(match.2)"); + + + # but ... + + # "/tmp/cf3_test" + # create => "true", + # edit_line => myedit("second $(match.1)"); + + +} + +######################################################## + +bundle edit_line myedit(parameter) +{ + vars: + + "edit_variable" string => "private edit variable is $(parameter)"; + + insert_lines: + + "$(edit_variable)"; + +} + diff --git a/examples/basename.cf b/examples/basename.cf new file mode 100644 index 0000000000..23ab6504f9 --- /dev/null +++ b/examples/basename.cf @@ -0,0 +1,24 @@ +############################################################################### +#+begin_src cfengine3 +bundle agent main +# @brief Example illustrating the behavior of basename() +{ + vars: + "basename" -> { "CFE-3196" } + string => basename( $(this.promise_filename) ); + + "basename_wo_extension" -> { "CFE-3196" } + string => basename( $(this.promise_filename), ".cf" ); + reports: + + "basename = '$(basename)'"; + "basename without '.cf' extension = '$(basename_wo_extension)'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: basename = 'basename.cf' +#@ R: basename without '.cf' extension = 'basename' +#@ ``` +#+end_example diff --git a/examples/bsdflags.cf b/examples/bsdflags.cf new file mode 100644 index 0000000000..1119407e17 --- /dev/null +++ b/examples/bsdflags.cf @@ -0,0 +1,47 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + files: + + freebsd:: + + "/tmp/newfile" + + create => "true", + perms => setbsd; + +} + + +body perms setbsd +{ + bsdflags => { "+uappnd","+uchg", "+uunlnk", "-nodump" }; +} + diff --git a/examples/buggy/namespaces.cf b/examples/buggy/namespaces.cf new file mode 100644 index 0000000000..692b2b0170 --- /dev/null +++ b/examples/buggy/namespaces.cf @@ -0,0 +1,60 @@ + +body common control +{ + bundlesequence => { "main" }; + inputs => { "namespaces/1.cf", "namespaces/2.cf", "namespaces/3.cf" }; +} + + +bundle agent main +{ + vars: + + "alien_list" slist => { getindices("name1:mymethod.array")}; + + "local_list" slist => { getindices("example.array") }; + + files: + + "/tmp/bla" + + create => "true", + perms => zub("700"); + # or perms => default:zub("700"); + + methods: + + "namespace demo" usebundle => name1:mymethod("arg1"); + + "namespace demo" usebundle => name2:mymethod("arg1","arg2"); + + "namespace demo" usebundle => example; + # Or "namespace demo" usebundle => default:example; + + "namespace demo" usebundle => test_in_last_file; + + reports: + + "Foreign namespace index list: $(alien_list)"; + "Local index list: $(local_list)"; +} + + +body perms zub(x) +{ + mode => "$(x)"; +} + + +bundle agent example +{ + vars: + + "array[one]" string => "text one"; + "array[two]" string => "text two"; + "array[three]" string => "text three"; + + reports: + + "TEST OK in no namespace"; +} diff --git a/examples/buggy/namespaces/1.cf b/examples/buggy/namespaces/1.cf new file mode 100644 index 0000000000..33c7b534e4 --- /dev/null +++ b/examples/buggy/namespaces/1.cf @@ -0,0 +1,42 @@ +body file control +{ + namespace => "name1"; +} + + +bundle agent mymethod(one) +{ + vars: + + "array[one]" string => "text one"; + "array[two]" string => "text two"; + "array[three]" string => "text three"; + + files: + + "/tmp/$(one)" + + create => "true", + edit_line => makefile, + perms => name2:settings; + + reports: + + "test $(one) in $(this.namespace)_$(this.bundle)"; +} + + +bundle edit_line makefile +{ + insert_lines: + + "THIS IS NAME1"; + + reports: + "makefile in name 1"; +} + +body perms settings +{ + mode => "600"; +} diff --git a/examples/buggy/namespaces/2.cf b/examples/buggy/namespaces/2.cf new file mode 100644 index 0000000000..facaf8aa63 --- /dev/null +++ b/examples/buggy/namespaces/2.cf @@ -0,0 +1,38 @@ + +body file control +{ + namespace => "name2"; +} + + +bundle agent mymethod(one,two) +{ + files: + + "/tmp/$(one)$(two)" + + create => "true", + edit_line => makefile, + perms => name1:settings; + + reports: + "test $(one) $(two) in $(this.namespace)_$(this.bundle)"; + +} + +bundle edit_line makefile +{ + insert_lines: + + "THIS IS NAME2"; + + reports: + + "makefile in name 2"; +} + + +body perms settings +{ + mode => "666"; +} diff --git a/examples/buggy/namespaces/3.cf b/examples/buggy/namespaces/3.cf new file mode 100644 index 0000000000..077a92237a --- /dev/null +++ b/examples/buggy/namespaces/3.cf @@ -0,0 +1,9 @@ + + +bundle agent test_in_last_file +{ + reports: + + "Final namespace is the default"; + +} diff --git a/examples/bundle_return_values.cf b/examples/bundle_return_values.cf new file mode 100644 index 0000000000..4c53048301 --- /dev/null +++ b/examples/bundle_return_values.cf @@ -0,0 +1,34 @@ + +body common control +{ + bundlesequence => { "example" }; +} + + +bundle agent example +{ + methods: + + "any" usebundle => child, + useresult => "my_return_var"; + + + reports: + + "My return was: \"$(my_return_var[1])\" and \"$(my_return_var[2])\""; + +} + +bundle agent child +{ + reports: + + # Map these indices into the useresult namespace + + "this is a return value" + bundle_return_value_index => "1"; + + "this is another return value" + bundle_return_value_index => "2"; + +} diff --git a/examples/bundlesequence.cf b/examples/bundlesequence.cf new file mode 100644 index 0000000000..ca28033932 --- /dev/null +++ b/examples/bundlesequence.cf @@ -0,0 +1,20 @@ +# Example showing how to override the default bundlesequence +body common control +{ + bundlesequence => { "hello" }; +} + +bundle agent hello +# @brief say hello and report my bundle name +{ + reports: + "Hello, $(this.bundle) bundle."; +} + +#@ The policy promises to report the name of the current bundle, and produces this output: + +#+begin_src example_output +#@ ``` +#@ R: Hello, hello bundle. +#@ ``` +#+end_src diff --git a/examples/bundlesmatching.cf b/examples/bundlesmatching.cf new file mode 100644 index 0000000000..910f13af5e --- /dev/null +++ b/examples/bundlesmatching.cf @@ -0,0 +1,84 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { mefirst }; +} + +bundle common g +{ + vars: + + # Here we find all bundles in the default namespace whos name starts with + # run. + + "todo" slist => bundlesmatching("default:run.*"); +} + +bundle agent mefirst +{ + methods: + + # Here, we actuate each of the bundles that were found using + # bundlesmatching in bundle common g. + + "" usebundle => $(g.todo); +} + +bundle agent run_deprecated +{ + meta: + # This bundle is tagged with deprecated + "tags" slist => { "deprecated" }; +} + +bundle agent run_123_456 +{ + vars: + # Here we find all bundles in our policy. + "bundles" slist => bundlesmatching(".*"); + + # Here we find all the bundles that are tagged as deprecated. + "deprecated_bundles" slist => bundlesmatching(".*", "deprecated"); + + # Here we find all bundles that match 891 (none will). + "no_bundles" slist => bundlesmatching("891"); + + reports: + # Here we report on our findings: + "bundles = $(bundles)"; + "deprecated bundles = $(deprecated_bundles)"; + "no bundles = $(no_bundles)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: bundles = default:run_123_456 +#@ R: bundles = default:run_deprecated +#@ R: bundles = default:mefirst +#@ R: bundles = default:g +#@ R: deprecated bundles = default:run_deprecated +#@ ``` +#+end_src diff --git a/examples/bundlestate.cf b/examples/bundlestate.cf new file mode 100644 index 0000000000..9efde97e23 --- /dev/null +++ b/examples/bundlestate.cf @@ -0,0 +1,57 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { holder, test }; +} + +bundle common holder +{ + classes: + "holderclass" expression => "any"; # will be global + + vars: + "s" string => "Hello!"; + "d" data => parsejson('[4,5,6]'); + "list" slist => { "element1", "element2" }; +} + +bundle agent test +{ + vars: + "bundle_state" data => bundlestate("holder"); + + # all the variables in bundle "holder" defined as of the execution of bundlestate() will be here + "holderstate" string => format("%S", "bundle_state"); + + reports: + "holder vars = $(holderstate)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: holder vars = {"d":[4,5,6],"list":["element1","element2"],"s":"Hello!"} +#@ ``` +#+end_src diff --git a/examples/canonify.cf b/examples/canonify.cf new file mode 100644 index 0000000000..22166e35d7 --- /dev/null +++ b/examples/canonify.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "component" string => "/var/cfengine/bin/cf-serverd"; + "canon" string => canonify("$(component)"); + + reports: + "canonified component == $(canon)"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: canonified component == _var_cfengine_bin_cf_serverd +#@ ``` +#+end_src diff --git a/examples/cf-secret.cf b/examples/cf-secret.cf new file mode 100644 index 0000000000..0f610c57ce --- /dev/null +++ b/examples/cf-secret.cf @@ -0,0 +1,66 @@ +# Copyright (C) Cfengine AS + +# This file is part of CFEngine 3 - written and maintained by Cfengine AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +############################################################################### +#+begin_src cfengine3 +bundle agent main +{ + vars: + + "private_key" + comment => "The decryption key", + string => "$(this.promise_filename).priv"; + + "encrypted_file" string => "$(this.promise_filename).cfcrypt"; + + "secret" + comment => "We decrypt the encrypted file directly into a variable.", + string => execresult("$(sys.cf_secret) -d $(private_key) -i $(encrypted_file) -o -", noshell); + + reports: + "Encrypted file content:" + printfile => cat( $(encrypted_file) ); + + "Decrypted content:$(const.n)$(secret)"; +} + +body printfile cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} +#+end_src + +############################################################################### +#+begin_src static_example_output +#@ ``` +#@ R: Encrypted file content: +#@ R: Version: 1.0 +#@ R: +#@ R: ���V�cv�#�P��, ��-O�8旼[i����p򢢦�Q� +#@ R: Φ&l�x'�#j���qQ����[�F�1����v�Q��ˮ�J'�թ�|^HG%)�`&�����~k�$wd]"�%4X\(Q�~�O����s�A~���/��:�" gi�Rn&ٍ�E^���߬3��M�ə�%2s�SB��b3���K4wm����o�B�:P��O�#��1�t8��`�@��j/��+����j��g஡����Z�D�iJ��͞j��8ĉ�ag�9vz?+�暢��So��.Org]�"+�S����_HѢ=_O% +#@ R: Decrypted content: +#@ Super secret message is here +#@ ``` +#+end_src diff --git a/examples/cf-secret.cf.cfcrypt b/examples/cf-secret.cf.cfcrypt new file mode 100644 index 0000000000..273c496dd6 --- /dev/null +++ b/examples/cf-secret.cf.cfcrypt @@ -0,0 +1,4 @@ +Version: 1.0 + +Vcv#P, -O8旼[ip򢢦Q +Φ&lx'#jqQ[F1vQˮJ'թ|^HG%)`&~k$wd]"%4X\(Q~OsA~/:" giRn&ٍE^߬3Mə%2sSBb3K4wmoB:PO#1t8`@j/+jg஡ZDiJP͞j8ĉag9vz?+暢So.Org]"+S_HѢ=_O% \ No newline at end of file diff --git a/examples/cf-secret.cf.priv b/examples/cf-secret.cf.priv new file mode 100644 index 0000000000..9057519827 --- /dev/null +++ b/examples/cf-secret.cf.priv @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,C213C4387710A6A4 + +L1J7NLoZ0DogIHAQXb0MR62kSGyIqRkZX5mLDKAf76mP/ejKGo27s8gwV7wVioFC +uTAY5j9066/XsUdRN2vjBmX+/VM9SPciRrXwsKFUxqd23swxEEG9FjTWmp8z5mKK +VNUh3C1CcSMVYjigSKInQP3M4/iMZ5EvzXgCsPxgfLgIf0j2+lF3Qxsdy0uzBCxz +t19IjgvG4LCn26Vd1tm8QomPBK5y1e6OEuxh7Ke8TcOh/93nACIpuo/hGw1FtJ6Z +zQXNdrDklP96FwrzsUdaDd6tuHTTTiqHkviaF9jkEUbfkIZJkWzqAFGhcW0So+28 +OiCzpfESTDTaj3aVagc9UovJ2/S+AnlbHxrf24VCo5tD0dQzk9h5Pjg8Ze5N5el8 +tmGkPMcvSDwr1RkPm1Xqipa1FbPGnfnebRNxc95vRw1JIf0eF0PCvVS0f4XjncBG +QhUVeM6XKsqxoq3GC6ab/6cC3ZiVZNQpcptzt6Qy9+cDz0Et21Y8RJPdOT8wR+Sr +SQST/wKW+H/xNh4sld+gLF3v6MACq5Wk2trAZYS+MdmelVps7LSQBnHPD1oDOkkq +cL21cdevScDMjvbRnL2oj2054320rGBXS2VkT4XI7Qw/JeqNfT0Ue/9n+TABx60u +pbPKpCfJzxQIErE5XEQU27+2IVYu5fK+BVi6s7UYUU40NePRlWFTfLRCVskrm63u +UnZ4o8nKz+5DLtCdtTlRGOZDCwYQYOFawCfwLN9wToHqQZxC1wzqRTsqXoTEVmby +hp+Qji55e93dFgLpIKB54flj2v3Wdbk8FPx3mjzf9MCxRaaGhsuX0WHQyHzIDb9K +raFWYcZJ1S8DUz5o7Q8otJaxj+nPGLCKTpBw/UyfXi1tXkGSRSIDo1dUtMaE2MpL +J44u4BuokKSOZbOi/piJy5wR0GSN6FHiDJo3yNbz/ZFXCq8l4ZQsVFNtB+9BzKX1 +ZkTSe8JX1guGLo3+MKsLOpriBzmjDJ2JvziXa1S4P9l3SziwCfreDqMM3dw5+N2z +rmtruWDMCURiVQXyuUToKRvzxiroOWIUofHrBoCG23Ztz3R+qlpXtYNy93z9tcg1 +R8CeFXymXq8q2TpUorhLrZGf7Q6fZxLV66tWfkYje4oicH4qo+SFqN8T1/T6kJd4 +kVj28Vb1RB4Phz7dkOyjKNo7qYXcedUbygUiAepLfnFgk7L9NEldn5CR6e1TxJoZ +fD0ib/naOuUKdesc2YF2EAoS0Un9O4/YgZMCBNBUI412MGteiqOWwn0K6MNgiIKr +0ToBtNRsEXM/7p5/q32NDBuWOfL1/82ptMShRIlCMemZAlswbibuJnA6KI6smUIi +LD4fti2143FqLQg3pkvjc6PEuAHUFVxfUSx3Kn/8mhKNbZxJuU8V/OfTEnfPKo+3 +DyesHX7fTvnea5K+XJZY4fZRMxuzXtCcbIfq2wc5D4XPUy32wGqfsV9fmiEGqG7/ +ul1uquFUg4qfBhKqbFdIt7x7XSDiCSzdQsC0kQ9XI5OiIQBrPIctXEsQxfsvGzx5 +tI9xJ40eiNbFZfOLNyPOZ9RS5E7SHe0YeOQGkcbb1/+yY8GjaxTTYg== +-----END RSA PRIVATE KEY----- diff --git a/examples/cf-secret.cf.pub b/examples/cf-secret.cf.pub new file mode 100644 index 0000000000..4fc29eff00 --- /dev/null +++ b/examples/cf-secret.cf.pub @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAt96mvMUSXyJSr4jjcfrADkIm+8uqLGp0pHL4zrJLo8o/o8WnQyKl +hK614sAA/XlQmOIZ1KZ8x5Nw3fd/EFxEJ9wugz2iMbQTyRNgyJ4y68wrlBumKDgL +rtVDgb1ozIVh9Q5SEdGuV6MgHwNI7ubaLyuLetn+mlLcJXbzs6V75yWvCdfPfAt+ +aVONUChFRi1HydH34KEmEOgEca8YQW3FxjRYOnzeU81B9C3UGv3aFs8UQtNBvvd5 +15GQGnjimkZ3Ukpp0M/CrpcpoAU7X4AywvZLB3X5/JhZVw4wgFlprIdbbL4wd9ov +Q+4KoG+3BaMf5Y6NKvLRshONh5e3j7V4kQIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/examples/cf2_integration.cf b/examples/cf2_integration.cf new file mode 100644 index 0000000000..2a71a55474 --- /dev/null +++ b/examples/cf2_integration.cf @@ -0,0 +1,62 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Cfagent 2 can be run as a command within cfengine 3 +# so that cfagent 2 AND cfagent 3 can coexist +# +# It does not matter which versions of the daemons are run +# but you must choose version 2 XOR version 3 +# + +body common control + +{ + + # Reads default + # /var/cfengine/inputs/promises.cf + # /var/cfengine/inputs/failsafe.cf + bundlesequence => { "cfengine3", "cfengine2" }; +} + +########################################### + +bundle agent cfengine2 + +{ + commands: + + "/var/cfengine/bin/cfagent"; + +} + +########################################### + +bundle agent cfengine3 + +{ + + # Stuff + commands: + + "/var/cfengine/bin/cf-agent"; +} diff --git a/examples/cf_version_after.cf b/examples/cf_version_after.cf new file mode 100644 index 0000000000..8e007a2100 --- /dev/null +++ b/examples/cf_version_after.cf @@ -0,0 +1,16 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + reports: + "This will be skipped on older or equal versions" + if => cf_version_after("3.15"); + "This will be skipped on newer versions" + unless => cf_version_after("3.15"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This will be skipped on older or equal versions +#@ ``` +#+end_src diff --git a/examples/cf_version_at.cf b/examples/cf_version_at.cf new file mode 100644 index 0000000000..74ce09d047 --- /dev/null +++ b/examples/cf_version_at.cf @@ -0,0 +1,16 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + reports: + "This will be skipped if version is not the same" + if => cf_version_at("3"); + "This will be skipped if version is the same" + unless => cf_version_at("3"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This will be skipped if version is not the same +#@ ``` +#+end_src diff --git a/examples/cf_version_before.cf b/examples/cf_version_before.cf new file mode 100644 index 0000000000..eb5691a65f --- /dev/null +++ b/examples/cf_version_before.cf @@ -0,0 +1,16 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + reports: + "This will be skipped on newer or equal versions" + if => cf_version_before("3.15"); + "This will be skipped on older versions" + unless => cf_version_before("3.15"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This will be skipped on older versions +#@ ``` +#+end_src diff --git a/examples/cf_version_between.cf b/examples/cf_version_between.cf new file mode 100644 index 0000000000..5e06afeb14 --- /dev/null +++ b/examples/cf_version_between.cf @@ -0,0 +1,16 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + reports: + "This will be skipped on versions outside this inclusive range" + if => cf_version_between("3.15", "4"); + "This will be skipped if version is within this inclusive range" + unless => cf_version_between("3.15", "4"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This will be skipped on versions outside this inclusive range +#@ ``` +#+end_src diff --git a/examples/cf_version_maximum.cf b/examples/cf_version_maximum.cf new file mode 100644 index 0000000000..5daf1ec059 --- /dev/null +++ b/examples/cf_version_maximum.cf @@ -0,0 +1,16 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + reports: + "This will be skipped on newer versions" + if => cf_version_maximum("3.15"); + "This will be skipped on older or equal versions" + unless => cf_version_maximum("3.15"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This will be skipped on older or equal versions +#@ ``` +#+end_src diff --git a/examples/cf_version_minimum.cf b/examples/cf_version_minimum.cf new file mode 100644 index 0000000000..429f183c71 --- /dev/null +++ b/examples/cf_version_minimum.cf @@ -0,0 +1,16 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + reports: + "This will be skipped on older versions" + if => cf_version_minimum("3.15"); + "This will be skipped on newer or equal versions" + unless => cf_version_minimum("3.15"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This will be skipped on older versions +#@ ``` +#+end_src diff --git a/examples/change_detect.cf b/examples/change_detect.cf new file mode 100644 index 0000000000..fb1de041c2 --- /dev/null +++ b/examples/change_detect.cf @@ -0,0 +1,65 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Change detect +# +######################################################## + +#[%+%] + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp/web" # Directory to monitor for changes. + + changes => detect_all_change, + depth_search => recurse("inf"); +} + +######################################################### + +body changes detect_all_change + +{ + report_changes => "all"; + update_hashes => "true"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} diff --git a/examples/changedbefore.cf b/examples/changedbefore.cf new file mode 100644 index 0000000000..11dadc2640 --- /dev/null +++ b/examples/changedbefore.cf @@ -0,0 +1,46 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + classes: + + "do_it" and => { changedbefore("/tmp/earlier","/tmp/later") }; + + reports: + + do_it:: + + "Earlier than later!"; + +} + diff --git a/examples/chdir.cf b/examples/chdir.cf new file mode 100644 index 0000000000..3a31b14335 --- /dev/null +++ b/examples/chdir.cf @@ -0,0 +1,46 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +body contain cd(dir) +{ + chdir => "${dir}"; + useshell => "useshell"; +} + +bundle agent example +{ + commands: + + "/bin/pwd" + contain => cd("/tmp"); +} + diff --git a/examples/class-automatic-canonificiation.cf b/examples/class-automatic-canonificiation.cf new file mode 100644 index 0000000000..f4cde2cc8c --- /dev/null +++ b/examples/class-automatic-canonificiation.cf @@ -0,0 +1,52 @@ +# This example shows how classes are automatically canonified when they are +# defined and that you must explicitly canonify when verifying classes. + +#+begin_src cfengine3 +bundle agent main +{ + classes: + + "my-illegal-class"; + + reports: + + # We search to see what class was defined: + "$(with)" with => join( " ", classesmatching( "my.illegal.class" ) ); + + # We see that the illegal class is explicitly not defined. + "my-illegal-class is NOT defined (as expected, its invalid)" + unless => "my-illegal-class"; + + # We see the canonified form of the illegal class is defined. + "my_illegal_class is defined" + if => canonify("my-illegal-class"); + + # Note, if takes expressisons, you couldn't do that if it were + # automatically canonified. Here I canonify the string using with, and use + # it as part of the expression which contains an invalid classcharacter, but + # its desireable for constructing expressions. + + "Slice and dice using `with`" + with => canonify( "my-illegal-class" ), + if => "linux|$(with)"; + +} +#+end_src + +#+begin_src policy_description +#@ First we promise to define `my-illegal-class`. When the promise is actuated +#@ it is automatically canonified and defined. This automatic canonification is +#@ logged in verbose logs (`verbose: Class identifier 'my-illegal-class' contains illegal characters - canonifying`). +#@ Next several reports prove which form of the class was defined. The last +#@ report shows how `if` takes a class expression, and if you are checking a class +#@ that contains invalid characters you must canonify it. +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: my_illegal_class +#@ R: my-illegal-class is NOT defined (as expected, its invalid) +#@ R: my_illegal_class is defined +#@ R: Slice and dice using `with` +#@ ``` +#+end_src diff --git a/examples/classes_context_applies_multiple_promises.cf b/examples/classes_context_applies_multiple_promises.cf new file mode 100644 index 0000000000..6f1cbdcad6 --- /dev/null +++ b/examples/classes_context_applies_multiple_promises.cf @@ -0,0 +1,36 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + reports: + "This promise is not restricted."; + + any:: + "Neither is this promise restricted, 'any' is always defined."; + + linux:: + "This promise is restricted to hosts that have the class 'linux' defined."; + + "This promise is also restricted to hosts that have the class 'linux' defined."; + + linux.cfengine_4:: + "This promise is restricted to hosts that have both the 'linux' class AND the 'cfengine_4' class."; + + !any:: + "This promise will never be actuated."; + + vars: + "Message" string => "Hello World!"; + + reports: + "And this promise is again unrestricted"; +} +#+end_src +#+begin_src example_output +#@ ``` +#@ R: This promise is not restricted. +#@ R: Neither is this promise restricted, 'any' is always defined. +#@ R: This promise is restricted to hosts that have the class 'linux' defined. +#@ R: This promise is also restricted to hosts that have the class 'linux' defined. +#@ R: And this promise is again unrestricted +#@ ``` +#+end_src diff --git a/examples/classes_global.cf b/examples/classes_global.cf new file mode 100644 index 0000000000..a1395d4233 --- /dev/null +++ b/examples/classes_global.cf @@ -0,0 +1,66 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "g","tryclasses_1", "tryclasses_2" }; + +} + +################################# + +bundle common g +{ + classes: + + "one" expression => "any"; + + "client_network" expression => iprange("128.39.89.0/24"); +} + +################################# + +bundle agent tryclasses_1 +{ + classes: + + "two" expression => "any"; +} + +################################# + +bundle agent tryclasses_2 +{ + classes: + + "three" expression => "any"; + + reports: + + one.three.!two:: + + "Success"; +} + + +################################# diff --git a/examples/classesmatching.cf b/examples/classesmatching.cf new file mode 100644 index 0000000000..4f9564f345 --- /dev/null +++ b/examples/classesmatching.cf @@ -0,0 +1,59 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent main +{ + classes: + "example_one"; + "example_two" + meta => { "plus", "defined_from=$(this.bundle)" }; + "example_three" + meta => { "plus", "defined_from=$(this.bundle)" }; + + vars: + "cfengine_classes" + slist => sort( classesmatching("cfengine"), lex); + + "example_with_plus" + slist => sort( classesmatching("example.*", "plus"), lex); + + reports: + # you may find this list of all classes interesting but it + # produces different output every time, so it's commented out here + # "All classes = '$(all)'"; + + "Classes matching 'cfengine' = '$(cfengine_classes)'"; + + # this should produce no output + "Classes matching 'example.*' with the 'plus' tag = $(example_with_plus)"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Classes matching 'cfengine' = 'cfengine' +#@ R: Classes matching 'example.*' with the 'plus' tag = example_three +#@ R: Classes matching 'example.*' with the 'plus' tag = example_two +#@ ``` +#+end_src diff --git a/examples/classfiltercsv.cf b/examples/classfiltercsv.cf new file mode 100644 index 0000000000..8fc0a90e81 --- /dev/null +++ b/examples/classfiltercsv.cf @@ -0,0 +1,85 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo 'ClassExpr,Sort,Token,Value' > /tmp/classfiltercsv.csv +#@ echo '# This is a comment' >> /tmp/classfiltercsv.csv +#@ echo 'any,A,net.ipv4.ip_forward,ANYVALUE' >> /tmp/classfiltercsv.csv +#@ echo 'example_class1,z,net.ipv4.ip_forward,ANYVALUE' >> /tmp/classfiltercsv.csv +#@ echo 'example_class2,a,net.ipv4.ip_forward,127.0.0.3' >> /tmp/classfiltercsv.csv +#@ echo 'not_defined,Z,net.ipv4.ip_forward,NOT_DEFINED' >> /tmp/classfiltercsv.csv +#@ echo 'example_class3,1,net.ipv4.ip_forward,127.0.0.4' >> /tmp/classfiltercsv.csv +#@ echo 'also_undefined,0,net.ipv4.ip_forward,NOT_DEFINED' >> /tmp/classfiltercsv.csv +#@ sed -i 's/$/\r/' /tmp/classfiltercsv.csv +#@ ``` +#+end_src + +############################################################################### + +#+begin_src cfengine3 +bundle agent example_classfiltercsv +{ + classes: + "example_class1"; + "example_class2"; + "example_class3"; + + vars: + "data_file" string => "/tmp/classfiltercsv.csv"; + "d" data => classfiltercsv($(data_file), "true", 0, 1); + + reports: + "Filtered data: $(with)" with => string_mustache("{{%-top-}}", d); +} +bundle agent __main__ +{ + methods: + "example_classfiltercsv"; +} +#+end_src +#+begin_src example_output +#@ ``` +#@ R: Filtered data: [ +#@ { +#@ "Sort": "1", +#@ "Token": "net.ipv4.ip_forward", +#@ "Value": "127.0.0.4" +#@ }, +#@ { +#@ "Sort": "A", +#@ "Token": "net.ipv4.ip_forward", +#@ "Value": "ANYVALUE" +#@ }, +#@ { +#@ "Sort": "a", +#@ "Token": "net.ipv4.ip_forward", +#@ "Value": "127.0.0.3" +#@ }, +#@ { +#@ "Sort": "z", +#@ "Token": "net.ipv4.ip_forward", +#@ "Value": "ANYVALUE" +#@ } +#@ ] +#@ ``` +#+end_src diff --git a/examples/classmatch.cf b/examples/classmatch.cf new file mode 100644 index 0000000000..98286e6d64 --- /dev/null +++ b/examples/classmatch.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "do_it" and => { classmatch("cfengine_3.*"), "any" }; + "have_hardclass_nonesuch" expression => classmatch("nonesuchclass_sodonttryit", hardclass); + reports: + + do_it:: + + "Host matches pattern"; + + have_hardclass_nonesuch:: + + "Host has that really weird hardclass"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Host matches pattern +#@ ``` +#+end_src diff --git a/examples/classvar_convergence.cf b/examples/classvar_convergence.cf new file mode 100644 index 0000000000..a63d3a88b6 --- /dev/null +++ b/examples/classvar_convergence.cf @@ -0,0 +1,61 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "tryclasses_1" }; +} + +################################# + +bundle agent tryclasses_1 +{ + vars: + + "x" string => "one"; + + "y" slist => { "linux", "Friday" }; + + classes: + + "three" and => { "$(x)", "two" }; + + "four" or => { @(y) }; + + "one" expression => "any"; + "two" expression => "any"; + + reports: + + three:: + + "Evaluated true"; + + four:: + + "List substitution works"; + +} + + +################################# diff --git a/examples/commands.cf b/examples/commands.cf new file mode 100644 index 0000000000..d5671945de --- /dev/null +++ b/examples/commands.cf @@ -0,0 +1,47 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "my_commands" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + + +bundle agent my_commands +{ + commands: + + Sunday.Hr04.Min05_10.myhost:: + + "/usr/bin/update_db"; + + any:: + + "/etc/mysql/start" + + contain => setuid("mysql"); + +} + + diff --git a/examples/compare.cf b/examples/compare.cf new file mode 100644 index 0000000000..a6f624d1c5 --- /dev/null +++ b/examples/compare.cf @@ -0,0 +1,60 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Testing some variable/class definitions - note scope +# +# Use browser -f promise_output_agent.html to view +# + +body common control + +{ + bundlesequence => { "test" }; +} + +########################################################### + +bundle agent test + +{ + classes: + + "ok" expression => isgreaterthan("1","0"); + + reports: + + ok:: + + "Assertion is true"; + + !ok:: + + "Assertion is false"; + +} + +#+begin_src example_output +#@ ``` +#@ R: Assertion is true +#@ ``` +#+end_src diff --git a/examples/complicated_cpu_bound_benchmark.cf b/examples/complicated_cpu_bound_benchmark.cf new file mode 100644 index 0000000000..222f2dc103 --- /dev/null +++ b/examples/complicated_cpu_bound_benchmark.cf @@ -0,0 +1,1297 @@ +body common control +{ + bundlesequence => { "benchmark" }; +} + +bundle agent benchmark +{ + vars: + # Determines how many times each example is run + "n" int => "10"; + + # Range: 1, 2, 3, ..., n + "i" slist => { expandrange("[1-$(n)]", "1") }; + + methods: + "accumulated" + usebundle => accumulated_bench("$(i)"); + "ago" + usebundle => ago_bench("$(i)"); + "and" + usebundle => and_bench("$(i)"); + "basename" + usebundle => basename_bench("$(i)"); + "bundlesmatching" + usebundle => bundlesmatching_bench("$(i)"); + "bundlestate" + usebundle => bundlestate_bench("$(i)"); + "callstack_callers" + usebundle => callstack_callers_bench("$(i)"); + "callstack_promisers" + usebundle => callstack_promisers_bench("$(i)"); + "canonify" + usebundle => canonify_bench("$(i)"); + "canonifyuniquely" + usebundle => canonifyuniquely_bench("$(i)"); + "cf_version_after" + usebundle => cf_version_after_bench("$(i)"); + "cf_version_at" + usebundle => cf_version_at_bench("$(i)"); + "cf_version_before" + usebundle => cf_version_before_bench("$(i)"); + "cf_version_between" + usebundle => cf_version_between_bench("$(i)"); + "cf_version_maximum" + usebundle => cf_version_maximum_bench("$(i)"); + "cf_version_minimum" + usebundle => cf_version_minimum_bench("$(i)"); + "classesmatching" + usebundle => classesmatching_bench("$(i)"); + "classify" + usebundle => classify_bench("$(i)"); + "classmatch" + usebundle => classmatch_bench("$(i)"); + "concat" + usebundle => concat_bench("$(i)"); + "countclassesmatching" + usebundle => countclassesmatching_bench("$(i)"); + "data_expand" + usebundle => data_expand_bench("$(i)"); + "data_regextract" + usebundle => data_regextract_bench("$(i)"); + "difference" + usebundle => difference_bench("$(i)"); + "dirname" + usebundle => dirname_bench("$(i)"); + "escape" + usebundle => escape_bench("$(i)"); + "eval" + usebundle => eval_bench("$(i)"); + "every" + usebundle => every_bench("$(i)"); + "expandrange" + usebundle => expandrange_bench("$(i)"); + "filter" + usebundle => filter_bench("$(i)"); + "format" + usebundle => format_bench("$(i)"); + "getclassmetatags" + usebundle => getclassmetatags_bench("$(i)"); + "getindices" + usebundle => getindices_bench("$(i)"); + "getvalues" + usebundle => getvalues_bench("$(i)"); + "getvariablemetatags" + usebundle => getvariablemetatags_bench("$(i)"); + "grep" + usebundle => grep_bench("$(i)"); + "hash" + usebundle => hash_bench("$(i)"); + "hash_to_int" + usebundle => hash_to_int_bench("$(i)"); + "ifelse" + usebundle => ifelse_bench("$(i)"); + "intersection" + usebundle => intersection_bench("$(i)"); + "isgreaterthan" + usebundle => isgreaterthan_bench("$(i)"); + "islessthan" + usebundle => islessthan_bench("$(i)"); + "isvariable" + usebundle => isvariable_bench("$(i)"); + "join" + usebundle => join_bench("$(i)"); + "laterthan" + usebundle => laterthan_bench("$(i)"); + "length" + usebundle => length_bench("$(i)"); + "maparray" + usebundle => maparray_bench("$(i)"); + "mapdata" + usebundle => mapdata_bench("$(i)"); + "maplist" + usebundle => maplist_bench("$(i)"); + "max" + usebundle => max_bench("$(i)"); + "mean" + usebundle => mean_bench("$(i)"); + "mergedata" + usebundle => mergedata_bench("$(i)"); + "min" + usebundle => min_bench("$(i)"); + "none" + usebundle => none_bench("$(i)"); + "not" + usebundle => not_bench("$(i)"); + "now" + usebundle => now_bench("$(i)"); + "nth" + usebundle => nth_bench("$(i)"); + "on" + usebundle => on_bench("$(i)"); + "or" + usebundle => or_bench("$(i)"); + "parseintarray" + usebundle => parseintarray_bench("$(i)"); + "parsejson" + usebundle => parsejson_bench("$(i)"); + "parserealarray" + usebundle => parserealarray_bench("$(i)"); + "parsestringarray" + usebundle => parsestringarray_bench("$(i)"); + "parsestringarrayidx" + usebundle => parsestringarrayidx_bench("$(i)"); + "product" + usebundle => product_bench("$(i)"); + "random" + usebundle => randomint_bench("$(i)"); + "regarray" + usebundle => regarray_bench("$(i)"); + "regcmp" + usebundle => regcmp_bench("$(i)"); + "regex_replace" + usebundle => regex_replace_bench("$(i)"); + "regextract" + usebundle => regextract_bench("$(i)"); + "reglist" + usebundle => reglist_bench("$(i)"); + "reverse" + usebundle => reverse_bench("$(i)"); + "shuffle" + usebundle => shuffle_bench("$(i)"); + "some" + usebundle => some_bench("$(i)"); + "sort" + usebundle => sort_bench("$(i)"); + "splitstring" + usebundle => splitstring_bench("$(i)"); + "storejson" + usebundle => storejson_bench("$(i)"); + "strcmp" + usebundle => strcmp_bench("$(i)"); + "strftime" + usebundle => strftime_bench("$(i)"); + "string_downcase" + usebundle => string_downcase_bench("$(i)"); + "string_head" + usebundle => string_head_bench("$(i)"); + "string_length" + usebundle => string_length_bench("$(i)"); + "string_mustache" + usebundle => string_mustache_bench("$(i)"); + "string_replace" + usebundle => string_replace_bench("$(i)"); + "string_reverse" + usebundle => string_reverse_bench("$(i)"); + "string_split" + usebundle => string_split_bench("$(i)"); + "string_tail" + usebundle => string_tail_bench("$(i)"); + "string_upcase" + usebundle => string_upcase_bench("$(i)"); + "sublist" + usebundle => sublist_bench("$(i)"); + "sum" + usebundle => sum_bench("$(i)"); + "translatepath" + usebundle => translatepath_bench("$(i)"); + "unique" + usebundle => unique_bench("$(i)"); + "validdata" + usebundle => validdata_bench("$(i)"); + "validjson" + usebundle => validjson_bench("$(i)"); + "variablesmatching" + usebundle => variablesmatching_bench("$(i)"); + "variablesmatching_as_data" + usebundle => variablesmatching_as_data_bench("$(i)"); + "variance" + usebundle => variance_bench("$(i)"); + "done" + usebundle => print_benchmark_done_msg(); +} + +bundle agent accumulated_bench(i) +{ + vars: + "test" + int => accumulated("$(i)", "$(i)", "$(i)", "$(i)", "$(i)", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent ago_bench(i) +{ + vars: + "test" + int => ago("$(i)", "$(i)", "$(i)", "$(i)", "$(i)", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent and_bench(i) +{ + classes: + "test_1" + expression => and("any_$(i)", "any_$(i)"); + "test_2" + expression => and("any_$(i)", "!any_$(i)"); + "test_3" + expression => and("!any_$(i)", "any_$(i)"); + "test_4" + expression => and("!any_$(i)", "!any_$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent basename_bench(i) +{ + vars: + "test_1" + string => basename("/tmp/test_$(i).txt"); + "test_2" + string => basename("/tmp/test_$(i).txt", ".txt"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent bundlesmatching_bench(i) +{ + vars: + "test" + slist => bundlesmatching(".*"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent bundlestate_bench(i) +{ + vars: + "test" + data => bundlestate("benchmark"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent callstack_callers_bench(i) +{ + vars: + "test" + data => callstack_callers(); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent callstack_promisers_bench(i) +{ + vars: + "test" + slist => callstack_promisers(); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent canonify_bench(i) +{ + vars: + "test" + string => canonify("/home/root/test-$(i).txt"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent canonifyuniquely_bench(i) +{ + vars: + "test" + string => canonifyuniquely("/home/root/test-$(i).txt"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent cf_version_after_bench(i) +{ + classes: + "test" + expression => cf_version_after("3.$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent cf_version_at_bench(i) +{ + classes: + "test" + expression => cf_version_at("3.$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent cf_version_before_bench(i) +{ + classes: + "test" + expression => cf_version_before("3.$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent cf_version_between_bench(i) +{ + classes: + "test" + expression => cf_version_between("3.$(i)", "4"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent cf_version_maximum_bench(i) +{ + classes: + "test" + expression => cf_version_maximum("3.$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent cf_version_minimum_bench(i) +{ + classes: + "test" + expression => cf_version_minimum("3.$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent classesmatching_bench(i) +{ + vars: + "test" + slist => classesmatching(".*$(i).*"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent classify_bench(i) +{ + classes: + "test" + expression => classify("/home/root/test-$(i).txt"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent classmatch_bench(i) +{ + classes: + "test" + expression => classmatch(".*$(i).*", "hardclass"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent concat_bench(i) +{ + vars: + "test" + string => concat("file", "_$(i)", ".txt"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent countclassesmatching_bench(i) +{ + vars: + "test" + int => countclassesmatching(".*$(i).*"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent data_expand_bench(i) +{ + vars: + "json" + data => '{ "i": $(i) }'; + "expanded" + data => data_expand("json"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent data_regextract_bench(i) +{ + vars: + "parsed" + data => data_regextract("^(?...)(...)(..)-(?...)-(..).*", + "abcdef12-345-$(i)andsoon"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent difference_bench(i) +{ + vars: + "a" + slist => { "a", "b", "c", "d", "e", "f", "$(i)" }; + "b" + slist => { "a", "c", "e", "g", "i", "k" }; + "diff" + slist => difference(a, b); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent dirname_bench(i) +{ + vars: + "dir" + string => dirname("/dir/dir_$(i)/file"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent escape_bench(i) +{ + vars: + "node" + string => format("%d", eval("($(i) - 1) % 256")); + "ip" + string => "192.168.1.$(node)"; + "escaped" + string => escape("$(ip)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent eval_bench(i) +{ + vars: + "hyp" + string => eval("sqrt( ( ($(i) * $(i)) + ($(i) * $(i)) ) )", "math", "infix"); + "same" + string => eval("$(i) == sqrt($(i) * $(i))", "class", "infix"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent every_bench(i) +{ + vars: + "test" + slist => { "/var/cfengine/bin/cf-agent", "/var/cfengine/bin/cf-execd", "/var/cfengine/bin/rpmvercmp", "/var/cfengine/bin/cf-$(i)" }; + classes: + "yes" + expression => every("/var/cfengine/bin/.*", test); + "no" + expression => every("/var/cfengine/bin/cf-.*", test); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent expandrange_bench(i) +{ + vars: + "range" + slist=> { expandrange("[0-$(i)]", "1") }; + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent filter_bench(i) +{ + vars: + "natural" + slist => { expandrange("[0-$(i)]", "1") }; + "odd" + slist => filter("[0-9]*[02468]", natural, "true", "false", "inf"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent format_bench(i) +{ + vars: + "formatted" + string => format("num: %s", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent getclassmetatags_bench(i) +{ + classes: + "myclass" + expression => "any", + meta => { "tag_$(i)" }; + vars: + "metatags" + slist => getclassmetatags("myclass"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent getindices_bench(i) +{ + vars: + "test[i$(i)]" + string => "v$(i)"; + "indices" + slist => getindices(test); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent getvalues_bench(i) +{ + vars: + "test[i$(i)]" + string => "v$(i)"; + "values" + slist => getvalues(test); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent getvariablemetatags_bench(i) +{ + vars: + "myvar" + string => "hello", + meta => { "tag_$(i)" }; + "metatags" + slist => getvariablemetatags(myvar); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent grep_bench(i) +{ + vars: + "mylist" + slist => { "One", "Two", "Three", "Four", "Five", "T$(i)" }; + "tlist" + slist => grep("T.*", mylist); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent hash_bench(i) +{ + vars: + "md5" string => hash("Cfengine $(i) is not cryptic","md5"); + "sha256" string => hash("Cfengine $(i) is not cryptic","sha256"); + "sha384" string => hash("Cfengine $(i) is not cryptic","sha384"); + "sha512" string => hash("Cfengine $(i) is not cryptic","sha512"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent hash_to_int_bench(i) +{ + vars: + "a" int => hash_to_int(0, "$(i)", "hello"); + "b" int => hash_to_int(0, "$(i)", "world"); + "c" int => hash_to_int(0, "$(i)", "$(sys.key_digest)"); + "d" int => hash_to_int(0, "$(i)", "$(sys.policy_hub)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent ifelse_bench(i) +{ + vars: + "outcome" + string => ifelse(regcmp("[0-9]*[02468]", "$(i)"), "is odd", + regcmp("[0-9]", "$(i)"), "is less than 10", + "is even"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent intersection_bench(i) +{ + vars: + "a" + slist => { "a", "b", "c", "d", "e", "f", "$(i)" }; + "b" + slist => { "a", "c", "e", "g", "i", "k", "$(i)" }; + "inter" + slist => intersection(a, b); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent isgreaterthan_bench(i) +{ + classes: + "indeed" + expression => isgreaterthan("5", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent islessthan_bench(i) +{ + classes: + "indeed" + expression => islessthan("5", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent isvariable_bench(i) +{ + vars: + "defined_$(i)" + string => "this var is defined"; + classes: + "test_1" + expression => isvariable("defined_$(i)"); + "test_2" + expression => isvariable("undefined_$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent join_bench(i) +{ + vars: + "list" + slist => { "a", "b", "c", "d", "e", "$(i)" }; + "joined_list" + string => join(", ", list); + "dat" + data => '[ "a", "b", "c", "d", "e", "$(i)" ]'; + "joined_data" + string => join("->", dat); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent laterthan_bench(i) +{ + classes: + "test" + expression => laterthan("$(i)", "$(i)", "$(i)", "$(i)", "$(i)", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent length_bench(i) +{ + vars: + "list" + slist => { expandrange("[0-$(i)]", "1") }; + "len" + int => length(list); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent maparray_bench(i) +{ + vars: + "arr[0]" + string => "a"; + "arr[1]" + string => "b"; + "arr[2]" + string => "c"; + "arr[3]" + string => "d"; + "arr[4]" + string => "e"; + "arr[5]" + string => "$(i)"; + "mapped" + slist => maparray("key=$(this.k), val=$(this.v)", arr); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent mapdata_bench(i) +{ + vars: + "dat" + data => '{ "name": "alice", "age": $(i) }'; + "map_json" + data => mapdata("json", '{ "key": "$(this.k)", "value": "$(this.v)" }', dat); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent maplist_bench(i) +{ + vars: + "list" + slist => { "a", "b", "c", "d", "e", "$(i)" }; + "mapped" + slist => maplist("Element: $(this)", list); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent max_bench(i) +{ + vars: + "alpha" + slist => { "a", "c", "b", "d", "f", "$(i)" }; + "numer" + ilist => { 1, 3, 2, 4, 6, "$(i)" }; + "alpha_max" + string => max(alpha, "lex"); + "numer_max" + string => max(numer, "int"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent mean_bench(i) +{ + vars: + "list" + slist => { "a", "1", "b", "2", "f", "$(i)" }; + "mean" + real => mean(list); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent mergedata_bench(i) +{ + vars: + "d1" + data => '{ "name": "alice" }'; + "d2" + data => '{ "age": $(i) }'; + "data" + data => mergedata(d1, d2); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent min_bench(i) +{ + vars: + "alpha" + slist => { "a", "c", "b", "d", "f", "$(i)" }; + "numer" + ilist => { 1, 3, 2, 4, 6, "$(i)" }; + "alpha_min" + string => min(alpha, "lex"); + "numer_min" + string => min(numer, "int"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent none_bench(i) +{ + vars: + "list" + slist => { "a", "b", "c", "$(i)" }; + classes: + "test_2" + expression => none("[0-9]", list); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent not_bench(i) +{ + classes: + "myclass_$(i)" + expression => "any"; + "yes" + expression => not("!myclass_$(i)"); + "no" + expression => not("myclass_$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent now_bench(i) +{ + vars: + "epoch" + int => now(); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent nth_bench(i) +{ + vars: + "list" + slist => { "a", "b", "c", "d", "e", "$(i)" }; + "first" + string => nth(list, "0"); + "second" + string => nth(list, "1"); + "third" + string => nth(list, "2"); + "forth" + string => nth(list, "3"); + "fifth" + string => nth(list, "4"); + "sixth" + string => nth(list, "5"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent on_bench(i) +{ + vars: + "epoch" + int => on("$(i)", "$(i)", "$(i)", "$(i)", "$(i)", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent or_bench(i) +{ + classes: + "myclass_$(i)" + expression => "any"; + "test_1" + expression => or("myclass_$(i)", "myclass_$(i)"); + "test_2" + expression => or("myclass_$(i)", "!myclass_$(i)"); + "test_3" + expression => or("!myclass_$(i)", "myclass_$(i)"); + "test_3" + expression => or("!myclass_$(i)", "!myclass_$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent parseintarray_bench(i) +{ + vars: + # Define data inline for convenience + "table" + string => + "1:2 + 3:4 + $(i):6"; + "dim" + int => parseintarray("items", "$(table)", "\s*#[^\n]*", ":", "1000", "200000"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent parsejson_bench(i) +{ + vars: + "json" + string => '{ "name": "alice", "age": $(i) }'; + "data" + data => parsejson("$(json)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent parserealarray_bench(i) +{ + vars: + "table" + string => + "1.1:2.2 + 3.3:4.4 + $(i).5:6.6"; + "dim" + int => parserealarray("items", "$(table)", "\s*#[^\n]*", ":", "1000", "200000"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent parsestringarray_bench(i) +{ + vars: + "table" + string => + "a:b + c:d + $(i):f"; + "dim" + int => parsestringarray("items", "$(table)", "\s*#[^\n]*", ":", "1000", "200000"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent parsestringarrayidx_bench(i) +{ + vars: + "table" + string => + "one: a + two: b + $(i): c"; + "dim" + int => parsestringarrayidx("items", "$(table)", "\s*#[^\n]*", ":", "1000", "200000"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent product_bench(i) +{ + vars: + "series" + rlist => { "1.1", "1.2", "2.3", "3.$(i)" }; + "prod" + real => product(series); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent randomint_bench(i) +{ + vars: + "rand" + int => randomint("0", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent regarray_bench(i) +{ + vars: + "array[0]" + string => "a1"; + "array[1]" + string => "a2"; + "array[2]" + string => "b$(i)"; + classes: + "yes" + expression => regarray(array, "[ab][123]"); + "no" + expression => regarray(array, "[cd][456]"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent regcmp_bench(i) +{ + classes: + "odd" + expression => regcmp("[0-9]*[02468]", "$(i)"); + "par" + expression => regcmp("[0-9]*[13579]", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent regex_replace_bench(i) +{ + vars: + "str" + string => regex_replace("$(i)", "[0-9]*[02468]", "odd", "i"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent regextract_bench(i) +{ + classes: + "ok" + expression => regextract("abc([0-9]+)def", "abc$(i)def", "arr"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent reglist_bench(i) +{ + vars: + "test" + slist => { "a", "$(i)", "c" }; + classes: + "yes" + expression => reglist("@(test)", "[0-9]*[02468]"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent reverse_bench(i) +{ + vars: + "test" + slist => { "a", "b", "c", "1", "2", "$(i)" }; + "reversed" + slist => reverse(test); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent shuffle_bench(i) +{ + vars: + "test" + slist => { "a", "b", "c", "1", "2", "3" }; + "shuffled" + slist => shuffle(test, "seed_$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent some_bench(i) +{ + vars: + "test" + slist => { "a", "b", "c", "1", "$(i)", "3" }; + classes: + "yes" + expression => some("[0-9]*[02468]", test); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent sort_bench(i) +{ + vars: + "test" + slist => { "1", "2", "3", "4", "5", "$(i)" }; + "shuffled" + slist => shuffle(test, "seed_$(i)"); + "lex" + slist => sort(shuffled, "lex"); + "int" + slist => sort(shuffled, "int"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent splitstring_bench(i) +{ + vars: + "test" + string => "1.5;2.3;$(i).0"; + "split" + slist => splitstring("$(test)", ";", "inf"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent storejson_bench(i) +{ + vars: + "dat" + data => '{ "name": "alice", "age": $(i) }'; + "json" + string => storejson(dat); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent strcmp_bench(i) +{ + classes: + "same" + expression => strcmp("test$(i)", "test$(i)"); + "diff" + expression => strcmp("$(i)test", "test$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent strftime_bench(i) +{ + vars: + "at_time" + string => strftime("localtime", "%Y-%m-%d %T", "$(i)"); + "gmt_at_time" + string => strftime("gmtime", "%Y-%m-%d %T", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_downcase_bench(i) +{ + vars: + "downcase" + string => string_downcase("AbC dEf_$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_head_bench(i) +{ + vars: + "str" + string => "a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, 1, 2, 3, 4, 5, 6, 7, 8, 9"; + "head" + string => string_head("$(str)", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_length_bench(i) +{ + vars: + "len" + int => string_length("Hello world $(i)!"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_mustache_bench(i) +{ + vars: + "deserts" + data => parsejson('{"deserts":{"Africa":"Sahara","Asia":"Gobi"}}'); + "data" + string => string_mustache("from container $(i): deserts = {{%deserts}}from container: {{#deserts}}The desert {{.}} is in {{@}}. {{/deserts}}", deserts); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_replace_bench(i) +{ + vars: + "str" + string => string_replace("This is a string $(i)", "string", "thing"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_reverse_bench(i) +{ + vars: + "str" + string => string_reverse("abcdefghijklmnopqrstuvwxyz$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_split_bench(i) +{ + vars: + "split" + slist => string_split("a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z", ",", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_tail_bench(i) +{ + vars: + "tail" + string => string_tail("a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent string_upcase_bench(i) +{ + vars: + "upcase" + string => string_upcase("AbC dEf_$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent sublist_bench(i) +{ + vars: + "list" + slist => { "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", + "o", "p", "q", "r", "s", "t", "u", + "v", "x", "y", "z" }; + "sub_head" + slist => sublist(list, "head", "$(i)"); + "sub_tail" + slist => sublist(list, "tail", "$(i)"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent sum_bench(i) +{ + vars: + "nums" + ilist => { "1", "2", "3", "4", "5", "$(i)" }; + "num_sum" + real => sum(nums); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent translatepath_bench(i) +{ + vars: + "path" + string => translatepath("/a/b/c/file_$(i).txt"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent unique_bench(i) +{ + vars: + "uni" + slist => { "one", "two", "three", + "1", "2", "3", + "one", "$(i)", "two" }; + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent validdata_bench(i) +{ + classes: + "is_valid" + expression => validdata('{ "name": "alice", "age": $(i) }', "JSON"); + "not_valid" + expression => validdata('{ "name": "alice", "age": $(i) ]', "JSON"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent validjson_bench(i) +{ + classes: + "is_valid" + expression => validjson('{ "name": "alice", "age": $(i) }'); + "not_valid" + expression => validjson('{ "name": "alice", "age": $(i) ]'); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent variablesmatching_bench(i) +{ + vars: + "matches" + slist => variablesmatching(".*$(i).*", "source=agent"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent variablesmatching_as_data_bench(i) +{ + vars: + "matches" + data => variablesmatching_as_data(".*$(i).*", "source=agent"); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent variance_bench(i) +{ + vars: + "nums" + ilist => { "1", "2", "3", "$(i) " }; + "vari" + real => variance(nums); + reports: + "Benching '$(this.bundle)' ($(i)/$(benchmark.n)) ..."; +} + +bundle agent print_benchmark_done_msg() +{ + reports: + "********** BENCHMARK DONE **********"; +} diff --git a/examples/const.cf b/examples/const.cf new file mode 100644 index 0000000000..1403c7ec17 --- /dev/null +++ b/examples/const.cf @@ -0,0 +1,64 @@ +#+begin_src cfengine3 +bundle agent __main__ +# @brief Example illustrating cosnt vars +{ + vars: + "example_file" + string => "/tmp/const-vars.txt"; + + files: + "$(example_file)" + create => "true", + content => concat("CFEngine const vars$(const.n)", + "before const.at $(const.at) after const.at$(const.n)", + "before const.dollar $(const.dollar) after const.dollar$(const.n)", + "before const.dirsep $(const.dirsep) after const.dirsep$(const.n)", + "before const.linesep $(const.linesep) after const.linesep$(const.n)", + "before const.endl$(const.endl) after const.endl$(const.n)", + "before const.n$(const.n) after const.n$(const.n)", + "before const.r $(const.r) after const.r$(const.n)", + "before const.t $(const.t) after const.t$(const.n)"); + + reports: + "const vars available: $(with)" + with => storejson( variablesmatching_as_data( "default:const\..*" ) ); + + "$(example_file):" + printfile => cat( "$(example_file)" ); +} +body printfile cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: const vars available: { +#@ "default:const.at": "@", +#@ "default:const.dirsep": "/", +#@ "default:const.dollar": "$", +#@ "default:const.endl": "\n", +#@ "default:const.linesep": "\n", +#@ "default:const.n": "\n", +#@ "default:const.r": "\r", +#@ "default:const.t": "\t" +#@ } +#@ R: /tmp/const-vars.txt: +#@ R: CFEngine const vars +#@ R: before const.at @ after const.at +#@ R: before const.dollar $ after const.dollar +#@ R: before const.dirsep / after const.dirsep +#@ R: before const.linesep +#@ R: after const.linesep +#@ R: before const.endl +#@ R: after const.endl +#@ R: before const.n +#@ R: after const.n +#@ R: before const.r after const.r +#@ R: before const.t after const.t +#@ ``` +#+end_src diff --git a/examples/container_iteration.cf b/examples/container_iteration.cf new file mode 100644 index 0000000000..2d674129b9 --- /dev/null +++ b/examples/container_iteration.cf @@ -0,0 +1,57 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + "x" data => parsejson('[ + { "one": "a" }, + { "two": "b" }, + { "three": "c" } +]'); + + # get the numeric indices of x: 0, 1, 2 + "xi" slist => getindices(x); + + # for each xi, make a variable xpiece_$(xi) so we'll have + # xpiece_0, xpiece_1, xpiece_2. Each xpiece will have that + # particular element of the list x. + "xpiece_$(xi)" string => format("%S", "x[$(xi)]"); + + reports: + "$(xi): $(xpiece_$(xi))"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: 0: {"one":"a"} +#@ R: 1: {"two":"b"} +#@ R: 2: {"three":"c"} +#@ ``` +#+end_src diff --git a/examples/container_key_iteration.cf b/examples/container_key_iteration.cf new file mode 100644 index 0000000000..a01da5c7dc --- /dev/null +++ b/examples/container_key_iteration.cf @@ -0,0 +1,73 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + "animals" data => parsejson(' + { + "dog": { "legs": 4, "tail": true, "names": [ "Fido", "Cooper", "Sandy" ] }, + "cat": { "legs": 4, "tail": true, "names": [ "Fluffy", "Snowball", "Tabby" ] }, + "dolphin": { "legs": 0, "tail": true, "names": [ "Flipper", "Duffy" ] }, + "hamster": { "legs": 4, "tail": true, "names": [ "Skullcrusher", "Kimmy", "Fluffadoo" ] }, + }'); + + "keys_unsorted" slist => getindices("animals"); + "keys" slist => sort(keys_unsorted, "lex"); + + "animals_$(keys)" data => mergedata("animals[$(keys)]"); + + methods: + # pass the container and a key inside it + "any" usebundle => analyze(@(animals), $(keys)); +} + +bundle agent analyze(animals, a) +{ + vars: + "names" slist => getvalues("animals[$(a)][names]"); + "names_str" string => format("%S", names); + + reports: + "$(this.bundle): possible names for animal '$(a)': $(names_str)"; + "$(this.bundle): describe animal '$(a)' => name = $(a), legs = $(animals[$(a)][legs]), tail = $(animals[$(a)][tail])"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: analyze: possible names for animal 'cat': { "Fluffy", "Snowball", "Tabby" } +#@ R: analyze: describe animal 'cat' => name = cat, legs = 4, tail = true +#@ R: analyze: possible names for animal 'dog': { "Fido", "Cooper", "Sandy" } +#@ R: analyze: describe animal 'dog' => name = dog, legs = 4, tail = true +#@ R: analyze: possible names for animal 'dolphin': { "Flipper", "Duffy" } +#@ R: analyze: describe animal 'dolphin' => name = dolphin, legs = 0, tail = true +#@ R: analyze: possible names for animal 'hamster': { "Skullcrusher", "Kimmy", "Fluffadoo" } +#@ R: analyze: describe animal 'hamster' => name = hamster, legs = 4, tail = true +#@ ``` +#+end_src diff --git a/examples/control_expand.cf b/examples/control_expand.cf new file mode 100644 index 0000000000..0168ce0d41 --- /dev/null +++ b/examples/control_expand.cf @@ -0,0 +1,91 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +bundle common g +{ + classes: + + "softclass" expression => "any"; + + vars: + + "bundle" slist => { "bundle1", "bundle2", @(g.extra) }; + + any:: + + # default extra + + "extra" slist => { "bundle3" }, + policy => "overridable"; + + softclass:: + + "extra" slist => { "bundle3", "bundle4" }, + policy => "overridable"; + +} + +############################################################## + +body common control +{ + bundlesequence => { @(g.bundle) }; +} + +############################################################## + +bundle agent bundle1 +{ + vars: + + "var1" string => "anything"; + "bundle" slist => { @(g.bundle) }; + + reports: + "$(bundle)"; +} + +bundle agent bundle2 +{ + classes: + + "ok" expression => isvariable("bundle1.var1"); + + reports: + + ok:: + + "Success"; +} + +bundle agent bundle3 +{ + reports: + "Success extra..."; +} + +bundle agent bundle4 +{ + reports: + "Success extra more..."; +} diff --git a/examples/copy.cf b/examples/copy.cf new file mode 100644 index 0000000000..375b4d6611 --- /dev/null +++ b/examples/copy.cf @@ -0,0 +1,40 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "copy" }; +} + +bundle agent copy +{ + files: + "/tmp/testfile1-cop" copy_from => mycopy("/tmp/testfile1", "gudea"); + "/tmp/testfile2-cop" copy_from => mycopy("/tmp/testfile2", "gudea"); +} + +body copy_from mycopy(from,server) +{ + source => "$(from)"; + #servers => { "$(server)" }; +} + diff --git a/examples/copy_classes.cf b/examples/copy_classes.cf new file mode 100644 index 0000000000..688024a27b --- /dev/null +++ b/examples/copy_classes.cf @@ -0,0 +1,126 @@ +body common control +{ + bundlesequence => { "copy_file"}; + + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +###################################################### + +bundle agent copy_file() +{ + files: + + "/tmp/non_existent" + copy_from => local_cp_compare("/non_existent"), + # perms => m("777"), + action => warn_only, + classes => check_promises("NE_NE"); + + + "/tmp/non_existent" + copy_from => local_cp_compare("/tmp/existent_file"), + # perms => m("777"), + action => warn_only, + classes => check_promises("NE_E"); + + + "/tmp/existent_file" + copy_from => local_cp_compare("/tmp/existent_file"), + # perms => m("777"), + action => warn_only, + classes => check_promises("E_E"); + + "/tmp/existent_file" + copy_from => local_cp_compare("/tmp/different_file"), + # perms => m("777"), + action => warn_only, + classes => check_promises("D_E"); + + "/tmp/existent_file" + copy_from => local_cp_compare("/non_existent"), + # perms => m("777"), + action => warn_only, + classes => check_promises("E_NE"); + + reports: + NE_NE_kept:: + "NE_NE_kept"; + NE_NE_repaired:: + "NE_NE_repaired"; + NE_NE_failed:: + "NE_NE_failed"; + NE_NE_denied:: + "NE_NE_denied"; + NE_NE_timeout:: + "NE_NE_timeout"; + + NE_E_kept:: + "NE_E_kept"; + NE_E_repaired:: + "NE_E_repaired"; + NE_E_failed:: + "NE_E_failed"; + NE_E_denied:: + "NE_E_denied"; + NE_E_timeout:: + "NE_E_timeout"; + + + E_E_kept:: + "E_E_kept"; + E_E_repaired:: + "E_E_repaired"; + E_E_failed:: + "E_E_failed"; + E_E_denied:: + "E_E_denied"; + E_E_timeout:: + "E_E_timeout"; + + + D_E_kept:: + "D_E_kept"; + D_E_repaired:: + "D_E_repaired"; + D_E_failed:: + "D_E_failed"; + D_E_denied:: + "D_E_denied"; + D_E_timeout:: + "D_E_timeout"; + + E_NE_kept:: + "E_NE_kept"; + E_NE_repaired:: + "E_NE_repaired"; + E_NE_failed:: + "E_NE_failed"; + E_NE_denied:: + "E_NE_denied"; + E_NE_timeout:: + "E_NE_timeout"; +} + +###################################################### + +body classes check_promises(prom) +{ + promise_kept => { "$(prom)_kept" }; + promise_repaired => { "$(prom)_repaired" }; + + repair_failed => { "$(prom)_failed" }; + repair_denied => { "$(prom)_denied" }; + repair_timeout => { "$(prom)_timeout" }; +} + +###################################################### + +body copy_from local_cp_compare(from) +{ + source => "$(from)"; + verify => "true"; + compare => "hash"; +} + + diff --git a/examples/copy_copbl.cf b/examples/copy_copbl.cf new file mode 100644 index 0000000000..d2269f4950 --- /dev/null +++ b/examples/copy_copbl.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "mycopy" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent mycopy +{ + files: + "/tmp/test_plain" + +#@ Path and name of the file we wish to copy to + + comment => "/tmp/test_plain promises to be an up-to-date copy of /bin/echo to demonstrate copying a local file", + copy_from => local_cp("$(sys.workdir)/bin/file"); + +#@ Copy locally from path/filename + + "/tmp/test_remote_plain" + comment => "/tmp/test_plain_remote promises to be a copy of cfengine://serverhost.example.org/repo/config-files/motd", + copy_from => secure_cp("/repo/config-files/motd", "serverhost.example.org"); +} +#@ Copy remotely from path/filename and specified host diff --git a/examples/copy_edit.cf b/examples/copy_edit.cf new file mode 100644 index 0000000000..5f89d55b69 --- /dev/null +++ b/examples/copy_edit.cf @@ -0,0 +1,118 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Copy and edit convergently +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; + version => "1.2.3"; +} + +######################################################## + +bundle agent example + +{ + vars: + + "source" string => "/tmp"; + "dest" string => "/tmp"; + + files: + + "/$(dest)/staging-file" + + comment => "Copy from source to buffer", + copy_from => cp("$(source)/source-template"), + classes => satisfied("copy_ok"); + + copy_ok:: + + "/$(dest)/final-file" + + comment => "Build a file template and expand keys", + edit_line => myedits("/$(dest)/staging-file"), + edit_defaults => empty; + +} + +######################################################### + +body copy_from cp(from) + +{ + source => "$(from)"; + compare => "mtime"; + type_check => "true"; +} + +######################################################## + +bundle edit_line myedits(f) + +{ + insert_lines: + + "$(f)" + + comment => "Populate empty file", + insert_type => "file"; + + replace_patterns: + + "TEMPLATE_HOST_KEY" + + comment => "Replace a place-marker with the name of this host", + replace_with => rp("$(sys.host)"); + +} + +######################################################## + +body replace_with rp(x) + +{ + replace_value => "$(x)"; + occurrences => "all"; +} + +######################################################### + +body classes satisfied(x) +{ + promise_repaired => { "$(x)" }; + persist_time => "0"; +} + +####################################################### + +body edit_defaults empty + +{ + empty_file_before_editing => "true"; +} diff --git a/examples/copydir_copbl.cf b/examples/copydir_copbl.cf new file mode 100644 index 0000000000..0709cbe054 --- /dev/null +++ b/examples/copydir_copbl.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "my_recursive_copy" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent my_recursive_copy +{ + files: + + "/home/mark/tmp/test_dir" + + copy_from => local_cp("$(sys.workdir)/bin/."), + depth_search => recurse("inf"); + + "/home/mark/tmp/test_dir" + + copy_from => secure_cp("$(sys.workdir)/bin","serverhost"), + depth_search => recurse("inf"); + +} + + diff --git a/examples/copylinks.cf b/examples/copylinks.cf new file mode 100644 index 0000000000..b3f3a53d79 --- /dev/null +++ b/examples/copylinks.cf @@ -0,0 +1,81 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test copy with link/copy exceptions +# +######################################################## + +######################################################## + +body common control + +{ + bundlesequence => { "example" }; + + version => "1.2.3"; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp/test_to" + + copy_from => mycopy("/home/mark/tmp/test_from"), + perms => system, + move_obstructions => "true", + depth_search => recurse("inf"); + +} + +######################################################### + +body perms system + +{ + mode => "0644"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + +######################################################### + +body copy_from mycopy(from) + +{ + source => "$(from)"; + #copylink_patterns => { ".*" }; # copy all links + linkcopy_patterns => { ".*" }; # copy all links + #copy_backup => "timestamp"; +} + diff --git a/examples/countclassesmatching.cf b/examples/countclassesmatching.cf new file mode 100644 index 0000000000..37a7b6886c --- /dev/null +++ b/examples/countclassesmatching.cf @@ -0,0 +1,44 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + # this is anchored, so you need .* to match multiple things + "num" int => countclassesmatching("cfengine"); + "hardcount" int => countclassesmatching(".*", "hardclass"); + reports: + "Found $(num) classes matching"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found 1 classes matching +#@ ``` +#+end_src diff --git a/examples/countlinesmatching.cf b/examples/countlinesmatching.cf new file mode 100644 index 0000000000..c00e1ca74b --- /dev/null +++ b/examples/countlinesmatching.cf @@ -0,0 +1,44 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + # typically there is only one root user + "no" int => countlinesmatching("root:.*","/etc/passwd"); + + reports: + "Found $(no) lines matching"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found 1 lines matching +#@ ``` +#+end_src diff --git a/examples/create_filedir.cf b/examples/create_filedir.cf new file mode 100644 index 0000000000..c06b03bce2 --- /dev/null +++ b/examples/create_filedir.cf @@ -0,0 +1,70 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test create files +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp/test_plain" +#@ The promiser specifies the path and name of the file. + perms => system, + create => "true"; + +#@ The `perms` attribute sets the file permissions as defined in the `system` +#@ body below. The `create` attribute makes sure that the files exists. If it +#@ doesn't, CFEngine will create it. + + "/home/mark/tmp/test_dir/." + + perms => system, + create => "true"; +#@ The trailing `/.` in the filename tells CFEngine that the promiser is a +#@ directory. +} + +######################################################### + +body perms system + +{ + mode => "0640"; +} +#@ This body sets permissions to "0640" + + +######################################################### + diff --git a/examples/createdb.cf b/examples/createdb.cf new file mode 100644 index 0000000000..ab81d8b844 --- /dev/null +++ b/examples/createdb.cf @@ -0,0 +1,67 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "databases" }; +} + +bundle agent databases + +{ + databases: + + "knowledge_bank/topics" + + database_operation => "create", + database_type => "sql", + database_columns => { + "demo_name,varchar,256", + "demo_comment,varchar,1024", + "demo_id,varchar,256", + "demo_type,varchar,256", + "demo_extra,varchar,26" + }, + + database_server => myserver; +} + +################################################ + + +body database_server myserver +{ + none:: + db_server_owner => "postgres"; + db_server_password => ""; + db_server_host => "localhost"; + db_server_type => "postgres"; + db_server_connection_db => "postgres"; + any:: + db_server_owner => "root"; + db_server_password => ""; + db_server_host => "localhost"; + db_server_type => "mysql"; + db_server_connection_db => "mysql"; +} + diff --git a/examples/customize_by_named_list.cf b/examples/customize_by_named_list.cf new file mode 100644 index 0000000000..a7cf9dcacf --- /dev/null +++ b/examples/customize_by_named_list.cf @@ -0,0 +1,97 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test - copy from a single directory of spec files +# generating multiple directories and special +# edits. +# +######################################################## + +body common control + +{ + bundlesequence => { "virtualhosts" }; + version => "1.2.3"; +} + +######################################################## + +bundle agent virtualhosts + +{ + vars: + + + "vmbase" string => "/home/mark/tmp/vm"; + "source_files" string => "/home/mark/tmp/src"; + + # list of hosts to create + + "hostlist" slist => { + "host1", + "host2", + "host3", + "host4", + "host5", + "host6", + "host7", + "host8", + "host9" + }; + + + + + + ################### or just a new file to the dir ################ + # + # "hostlist" slist => { SelectFilesIn("$(source_files)",".*") } + # + ################################################################## + + files: + + "$(vmbase)/$(hostlist)/config_for_$(hostlist).vm" + + copy_from => buildvm("$(source_files)/template_$(hostlist)"); + + + + # + # Now edit config .e.g. edit in $(ipadr[$(hostlist)]) for each + # + +} + +######################################################### +# library template +######################################################### + +body copy_from buildvm(from) + +{ + source => "$(from)"; + copy_backup => "true"; #/false/timestamp +} + diff --git a/examples/data_expand.cf b/examples/data_expand.cf new file mode 100644 index 0000000000..d0542e39cf --- /dev/null +++ b/examples/data_expand.cf @@ -0,0 +1,50 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo '{ "$(main.x)": "$(main.y)" }' > /tmp/expand.json +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent main +{ + vars: + "x" string => "the expanded x"; + "y" string => "the expanded y"; + + "read" data => readjson("/tmp/expand.json", inf); + "expanded" data => data_expand(read); + + "expanded_str" string => format("%S", expanded); + + reports: + "$(this.bundle): the x and y references expanded to $(expanded_str)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: main: the x and y references expanded to {"the expanded x":"the expanded y"} +#@ ``` +#+end_src diff --git a/examples/data_readstringarray.cf b/examples/data_readstringarray.cf new file mode 100644 index 0000000000..286c9d7e5e --- /dev/null +++ b/examples/data_readstringarray.cf @@ -0,0 +1,68 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo a,b,c > /tmp/cfe_array +#@ echo "# This is a comment" >> /tmp/cfe_array +#@ echo d,e,f >> /tmp/cfe_array +#@ echo g,h,i >> /tmp/cfe_array +#@ echo "# This is another comment" >> /tmp/cfe_array +#@ echo j,k,l >> /tmp/cfe_array +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + # The comment regex warrents an explination: + # # matches the character # literally + # [^\n]* match a single character not including the newline character + # between zero and unlimited times, as many times as possible + "bykey" data => data_readstringarray("/tmp/cfe_array","#[^\n]*",",",10,400); + "byint" data => data_readstringarrayidx("/tmp/cfe_array","#[^\n]*",",",10,400); + + "bykey_str" string => format("%S", bykey); + "byint_str" string => format("%S", byint); + reports: + "By key: $(bykey_str)"; + "specific element by key a, offset 0: '$(bykey[a][0])'"; + "By int offset: $(byint_str)"; + "specific element by int offset 2, 0: '$(byint[2][0])'"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: By key: {"a":["b","c"],"d":["e","f"],"g":["h","i"],"j":["k","l"]} +#@ R: specific element by key a, offset 0: 'b' +#@ R: By int offset: [["a","b","c"],["d","e","f"],["g","h","i"],["j","k","l"]] +#@ R: specific element by int offset 2, 0: 'g' +#@ ``` +#+end_src diff --git a/examples/data_regextract.cf b/examples/data_regextract.cf new file mode 100644 index 0000000000..31b0e293ff --- /dev/null +++ b/examples/data_regextract.cf @@ -0,0 +1,80 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +##+begin_src prep +#@ ``` +#@ printf "[general]\n" > /tmp/instance.cfg +#@ printf "guid = 9CB197F0-4569-446A-A987-1DDEC1205F6B\n" >> /tmp/instance.cfg +#@ printf "port=5308" >> /tmp/instance.cfg +#@ ``` +#+end_src +############################################################################## +#+begin_src cfengine3 +bundle agent main +{ + vars: + # the returned data container is a key-value map: + + # the whole matched string is put in key "0" + # the first three characters are put in key "name1" + # the next three characters go into key "2" (the capture has no name) + # the next two characters go into key "3" (the capture has no name) + # then the dash is ignored + # then three characters are put in key "name2" + # then another dash is ignored + # the next three characters go into key "5" (the capture has no name) + # anything else is ignored + + "parsed" data => data_regextract("^(?...)(...)(..)-(?...)-(..).*", "abcdef12-345-67andsoon"); + "parsed_str" string => format("%S", parsed); + + # Illustrating multiline regular expression + + "instance_guid_until_end_of_string" + data => data_regextract( "^guid\s?+=\s?+(?.*)$", + readfile( "/tmp/instance.cfg", 200)); + + "instance_guid" + data => data_regextract( "^guid\s+=\s+(?[^\n]*)", + readfile( "/tmp/instance.cfg", 200)); + + "instance_port" + data => data_regextract( "^port\s?+=\s?+(?[^\n]*)", + readfile( "/tmp/instance.cfg", 200)); + + reports: + "$(this.bundle): parsed[0] '$(parsed[0])' parses into: $(parsed_str)"; + "$(this.bundle): instance_guid_until_end_of_string[value] '$(instance_guid_until_end_of_string[value])'"; + "$(this.bundle): instance_guid[value] '$(instance_guid[value])'"; + "$(this.bundle): instance_port[value] '$(instance_port[value])'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: main: parsed[0] 'abcdef12-345-67andsoon' parses into: {"0":"abcdef12-345-67andsoon","2":"def","3":"12","5":"67","name1":"abc","name2":"345"} +#@ R: main: instance_guid_until_end_of_string[value] '9CB197F0-4569-446A-A987-1DDEC1205F6B +#@ port=5308' +#@ R: main: instance_guid[value] '9CB197F0-4569-446A-A987-1DDEC1205F6B' +#@ R: main: instance_port[value] '5308' +#@ ``` +#+end_src diff --git a/examples/data_sysctlvalues.cf b/examples/data_sysctlvalues.cf new file mode 100644 index 0000000000..d2c56a8e71 --- /dev/null +++ b/examples/data_sysctlvalues.cf @@ -0,0 +1,935 @@ +#+begin_src cfengine3 +bundle agent inventory_sysctl +# @brief Inventory each sysctl variable +{ + vars: + # Get the complete set of sysctl variables and values as a data structure + "_sysctl_d" + data => data_sysctlvalues(), + unless => isvariable( _sysctl_d ); + + # Get the sysctl variable names + "_sysctl_i" + slist => getindices( "_sysctl_d" ); + + # Define a variable tagged for inventory for each sysctl variable + "sysctl[$(_sysctl_i)]" + string => "$(_sysctl_d[$(_sysctl_i)])", + meta => { "inventory", "attribute_name=Kernel Tunable $(_sysctl_i)" }; + + reports: + # Show what data_sysctlvalues() returned. + "data_sysctlvalues() returned:$(with)" + with => storejson( @(_sysctl_d) ); + +} +bundle agent __main__ +{ + methods: "inventory_sysctl"; + +} +#+end_src +############################################################################### +#+begin_src mock_example_output +#@ ``` +#@ R: data_sysctlvalues() returned:{ +#@ "abi.vsyscall32": "1", +#@ "crypto.fips_enabled": "0", +#@ "debug.exception-trace": "1", +#@ "debug.kprobes-optimization": "1", +#@ "debug.panic_on_rcu_stall": "0", +#@ "dev.hpet.max-user-freq": "64", +#@ "dev.mac_hid.mouse_button2_keycode": "97", +#@ "dev.mac_hid.mouse_button3_keycode": "100", +#@ "dev.mac_hid.mouse_button_emulation": "0", +#@ "dev.parport.default.spintime": "500", +#@ "dev.parport.default.timeslice": "200", +#@ "dev.raid.speed_limit_max": "200000", +#@ "dev.raid.speed_limit_min": "1000", +#@ "dev.scsi.logging_level": "0", +#@ "fs.aio-max-nr": "65536", +#@ "fs.aio-nr": "0", +#@ "fs.binfmt_misc.status": "enabled", +#@ "fs.dentry-state": "43676\t27378\t45\t0\t479\t0", +#@ "fs.dir-notify-enable": "1", +#@ "fs.epoll.max_user_watches": "99020", +#@ "fs.file-max": "47380", +#@ "fs.file-nr": "1472\t0\t47380", +#@ "fs.inode-nr": "47116\t21990", +#@ "fs.inode-state": "47116\t21990\t0\t0\t0\t0\t0", +#@ "fs.inotify.max_queued_events": "16384", +#@ "fs.inotify.max_user_instances": "128", +#@ "fs.inotify.max_user_watches": "8192", +#@ "fs.lease-break-time": "45", +#@ "fs.leases-enable": "1", +#@ "fs.may_detach_mounts": "0", +#@ "fs.mount-max": "100000", +#@ "fs.mqueue.msg_default": "10", +#@ "fs.mqueue.msg_max": "10", +#@ "fs.mqueue.msgsize_default": "8192", +#@ "fs.mqueue.msgsize_max": "8192", +#@ "fs.mqueue.queues_max": "256", +#@ "fs.negative-dentry-limit": "0", +#@ "fs.nr_open": "1048576", +#@ "fs.overflowgid": "65534", +#@ "fs.overflowuid": "65534", +#@ "fs.pipe-max-size": "1048576", +#@ "fs.pipe-user-pages-hard": "0", +#@ "fs.pipe-user-pages-soft": "16384", +#@ "fs.protected_hardlinks": "1", +#@ "fs.protected_symlinks": "1", +#@ "fs.quota.allocated_dquots": "0", +#@ "fs.quota.cache_hits": "0", +#@ "fs.quota.drops": "0", +#@ "fs.quota.free_dquots": "0", +#@ "fs.quota.lookups": "0", +#@ "fs.quota.reads": "0", +#@ "fs.quota.syncs": "0", +#@ "fs.quota.warnings": "1", +#@ "fs.quota.writes": "0", +#@ "fs.suid_dumpable": "0", +#@ "fs.xfs.age_buffer_centisecs": "1500", +#@ "fs.xfs.error_level": "3", +#@ "fs.xfs.filestream_centisecs": "3000", +#@ "fs.xfs.inherit_noatime": "1", +#@ "fs.xfs.inherit_nodefrag": "1", +#@ "fs.xfs.inherit_nodump": "1", +#@ "fs.xfs.inherit_nosymlinks": "0", +#@ "fs.xfs.inherit_sync": "1", +#@ "fs.xfs.irix_sgid_inherit": "0", +#@ "fs.xfs.irix_symlink_mode": "0", +#@ "fs.xfs.panic_mask": "0", +#@ "fs.xfs.rotorstep": "1", +#@ "fs.xfs.speculative_prealloc_lifetime": "300", +#@ "fs.xfs.stats_clear": "0", +#@ "fs.xfs.xfsbufd_centisecs": "100", +#@ "fs.xfs.xfssyncd_centisecs": "3000", +#@ "kernel.acct": "4\t2\t30", +#@ "kernel.acpi_video_flags": "0", +#@ "kernel.auto_msgmni": "0", +#@ "kernel.bootloader_type": "114", +#@ "kernel.bootloader_version": "2", +#@ "kernel.cad_pid": "1", +#@ "kernel.cap_last_cap": "36", +#@ "kernel.compat-log": "1", +#@ "kernel.core_pattern": "core", +#@ "kernel.core_pipe_limit": "0", +#@ "kernel.core_uses_pid": "1", +#@ "kernel.ctrl-alt-del": "0", +#@ "kernel.dmesg_restrict": "0", +#@ "kernel.domainname": "(none)", +#@ "kernel.ftrace_dump_on_oops": "0", +#@ "kernel.ftrace_enabled": "1", +#@ "kernel.hardlockup_all_cpu_backtrace": "0", +#@ "kernel.hardlockup_panic": "1", +#@ "kernel.hostname": "hub.example.com", +#@ "kernel.hotplug": "", +#@ "kernel.hung_task_check_count": "4194304", +#@ "kernel.hung_task_panic": "0", +#@ "kernel.hung_task_timeout_secs": "120", +#@ "kernel.hung_task_warnings": "10", +#@ "kernel.io_delay_type": "0", +#@ "kernel.kexec_load_disabled": "0", +#@ "kernel.keys.gc_delay": "300", +#@ "kernel.keys.maxbytes": "20000", +#@ "kernel.keys.maxkeys": "200", +#@ "kernel.keys.persistent_keyring_expiry": "259200", +#@ "kernel.keys.root_maxbytes": "25000000", +#@ "kernel.keys.root_maxkeys": "1000000", +#@ "kernel.kptr_restrict": "0", +#@ "kernel.max_lock_depth": "1024", +#@ "kernel.modprobe": "/sbin/modprobe", +#@ "kernel.modules_disabled": "0", +#@ "kernel.msg_next_id": "-1", +#@ "kernel.msgmax": "8192", +#@ "kernel.msgmnb": "16384", +#@ "kernel.msgmni": "32000", +#@ "kernel.ngroups_max": "65536", +#@ "kernel.nmi_watchdog": "1", +#@ "kernel.ns_last_pid": "3240", +#@ "kernel.numa_balancing": "0", +#@ "kernel.numa_balancing_scan_delay_ms": "1000", +#@ "kernel.numa_balancing_scan_period_max_ms": "60000", +#@ "kernel.numa_balancing_scan_period_min_ms": "1000", +#@ "kernel.numa_balancing_scan_size_mb": "256", +#@ "kernel.numa_balancing_settle_count": "4", +#@ "kernel.osrelease": "3.10.0-1127.el7.x86_64", +#@ "kernel.ostype": "Linux", +#@ "kernel.overflowgid": "65534", +#@ "kernel.overflowuid": "65534", +#@ "kernel.panic": "0", +#@ "kernel.panic_on_io_nmi": "0", +#@ "kernel.panic_on_oops": "1", +#@ "kernel.panic_on_stackoverflow": "0", +#@ "kernel.panic_on_unrecovered_nmi": "0", +#@ "kernel.panic_on_warn": "0", +#@ "kernel.perf_cpu_time_max_percent": "25", +#@ "kernel.perf_event_max_sample_rate": "100000", +#@ "kernel.perf_event_mlock_kb": "516", +#@ "kernel.perf_event_paranoid": "2", +#@ "kernel.pid_max": "32768", +#@ "kernel.poweroff_cmd": "/sbin/poweroff", +#@ "kernel.print-fatal-signals": "0", +#@ "kernel.printk": "7\t4\t1\t7", +#@ "kernel.printk_delay": "0", +#@ "kernel.printk_ratelimit": "5", +#@ "kernel.printk_ratelimit_burst": "10", +#@ "kernel.pty.max": "4096", +#@ "kernel.pty.nr": "1", +#@ "kernel.pty.reserve": "1024", +#@ "kernel.random.boot_id": "422886c9-8219-4749-9610-981bdeb5e509", +#@ "kernel.random.entropy_avail": "3071", +#@ "kernel.random.poolsize": "4096", +#@ "kernel.random.read_wakeup_threshold": "64", +#@ "kernel.random.urandom_min_reseed_secs": "60", +#@ "kernel.random.uuid": "f9544436-b0a1-43b7-a36b-3b8da50e066a", +#@ "kernel.random.write_wakeup_threshold": "896", +#@ "kernel.randomize_va_space": "2", +#@ "kernel.real-root-dev": "0", +#@ "kernel.sched_autogroup_enabled": "0", +#@ "kernel.sched_cfs_bandwidth_slice_us": "5000", +#@ "kernel.sched_child_runs_first": "0", +#@ "kernel.sched_domain.cpu0.domain0.busy_factor": "32", +#@ "kernel.sched_domain.cpu0.domain0.busy_idx": "2", +#@ "kernel.sched_domain.cpu0.domain0.cache_nice_tries": "1", +#@ "kernel.sched_domain.cpu0.domain0.flags": "559", +#@ "kernel.sched_domain.cpu0.domain0.forkexec_idx": "0", +#@ "kernel.sched_domain.cpu0.domain0.idle_idx": "0", +#@ "kernel.sched_domain.cpu0.domain0.imbalance_pct": "117", +#@ "kernel.sched_domain.cpu0.domain0.max_interval": "4", +#@ "kernel.sched_domain.cpu0.domain0.max_newidle_lb_cost": "37143", +#@ "kernel.sched_domain.cpu0.domain0.min_interval": "2", +#@ "kernel.sched_domain.cpu0.domain0.name": "MC", +#@ "kernel.sched_domain.cpu0.domain0.newidle_idx": "0", +#@ "kernel.sched_domain.cpu0.domain0.wake_idx": "0", +#@ "kernel.sched_domain.cpu1.domain0.busy_factor": "32", +#@ "kernel.sched_domain.cpu1.domain0.busy_idx": "2", +#@ "kernel.sched_domain.cpu1.domain0.cache_nice_tries": "1", +#@ "kernel.sched_domain.cpu1.domain0.flags": "559", +#@ "kernel.sched_domain.cpu1.domain0.forkexec_idx": "0", +#@ "kernel.sched_domain.cpu1.domain0.idle_idx": "0", +#@ "kernel.sched_domain.cpu1.domain0.imbalance_pct": "117", +#@ "kernel.sched_domain.cpu1.domain0.max_interval": "4", +#@ "kernel.sched_domain.cpu1.domain0.max_newidle_lb_cost": "16770", +#@ "kernel.sched_domain.cpu1.domain0.min_interval": "2", +#@ "kernel.sched_domain.cpu1.domain0.name": "MC", +#@ "kernel.sched_domain.cpu1.domain0.newidle_idx": "0", +#@ "kernel.sched_domain.cpu1.domain0.wake_idx": "0", +#@ "kernel.sched_latency_ns": "12000000", +#@ "kernel.sched_migration_cost_ns": "500000", +#@ "kernel.sched_min_granularity_ns": "10000000", +#@ "kernel.sched_nr_migrate": "32", +#@ "kernel.sched_rr_timeslice_ms": "100", +#@ "kernel.sched_rt_period_us": "1000000", +#@ "kernel.sched_rt_runtime_us": "950000", +#@ "kernel.sched_schedstats": "0", +#@ "kernel.sched_shares_window_ns": "10000000", +#@ "kernel.sched_time_avg_ms": "1000", +#@ "kernel.sched_tunable_scaling": "1", +#@ "kernel.sched_wakeup_granularity_ns": "15000000", +#@ "kernel.seccomp.actions_avail": "kill trap errno trace allow", +#@ "kernel.seccomp.actions_logged": "kill trap errno trace", +#@ "kernel.sem": "250\t32000\t32\t128", +#@ "kernel.sem_next_id": "-1", +#@ "kernel.shm_next_id": "-1", +#@ "kernel.shm_rmid_forced": "0", +#@ "kernel.shmall": "18446744073692774399", +#@ "kernel.shmmax": "18446744073692774399", +#@ "kernel.shmmni": "4096", +#@ "kernel.softlockup_all_cpu_backtrace": "0", +#@ "kernel.softlockup_panic": "0", +#@ "kernel.stack_tracer_enabled": "0", +#@ "kernel.sysctl_writes_strict": "1", +#@ "kernel.sysrq": "16", +#@ "kernel.tainted": "0", +#@ "kernel.threads-max": "3777", +#@ "kernel.timer_migration": "1", +#@ "kernel.traceoff_on_warning": "0", +#@ "kernel.unknown_nmi_panic": "0", +#@ "kernel.usermodehelper.bset": "4294967295\t31", +#@ "kernel.usermodehelper.inheritable": "4294967295\t31", +#@ "kernel.version": "#1 SMP Tue Mar 31 23:36:51 UTC 2020", +#@ "kernel.watchdog": "1", +#@ "kernel.watchdog_cpumask": "0-1", +#@ "kernel.watchdog_thresh": "10", +#@ "kernel.yama.ptrace_scope": "0", +#@ "net.core.bpf_jit_enable": "1", +#@ "net.core.bpf_jit_harden": "1", +#@ "net.core.bpf_jit_kallsyms": "0", +#@ "net.core.busy_poll": "0", +#@ "net.core.busy_read": "0", +#@ "net.core.default_qdisc": "pfifo_fast", +#@ "net.core.dev_weight": "64", +#@ "net.core.dev_weight_rx_bias": "1", +#@ "net.core.dev_weight_tx_bias": "1", +#@ "net.core.message_burst": "10", +#@ "net.core.message_cost": "5", +#@ "net.core.netdev_budget": "300", +#@ "net.core.netdev_max_backlog": "1000", +#@ "net.core.netdev_rss_key": "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00", +#@ "net.core.netdev_tstamp_prequeue": "1", +#@ "net.core.optmem_max": "20480", +#@ "net.core.rmem_default": "212992", +#@ "net.core.rmem_max": "212992", +#@ "net.core.rps_sock_flow_entries": "0", +#@ "net.core.somaxconn": "128", +#@ "net.core.warnings": "1", +#@ "net.core.wmem_default": "212992", +#@ "net.core.wmem_max": "212992", +#@ "net.core.xfrm_acq_expires": "30", +#@ "net.core.xfrm_aevent_etime": "10", +#@ "net.core.xfrm_aevent_rseqth": "2", +#@ "net.core.xfrm_larval_drop": "1", +#@ "net.ipv4.cipso_cache_bucket_size": "10", +#@ "net.ipv4.cipso_cache_enable": "1", +#@ "net.ipv4.cipso_rbm_optfmt": "0", +#@ "net.ipv4.cipso_rbm_strictvalid": "1", +#@ "net.ipv4.conf.all.accept_local": "0", +#@ "net.ipv4.conf.all.accept_redirects": "1", +#@ "net.ipv4.conf.all.accept_source_route": "0", +#@ "net.ipv4.conf.all.arp_accept": "0", +#@ "net.ipv4.conf.all.arp_announce": "0", +#@ "net.ipv4.conf.all.arp_filter": "0", +#@ "net.ipv4.conf.all.arp_ignore": "0", +#@ "net.ipv4.conf.all.arp_notify": "0", +#@ "net.ipv4.conf.all.bootp_relay": "0", +#@ "net.ipv4.conf.all.disable_policy": "0", +#@ "net.ipv4.conf.all.disable_xfrm": "0", +#@ "net.ipv4.conf.all.force_igmp_version": "0", +#@ "net.ipv4.conf.all.forwarding": "0", +#@ "net.ipv4.conf.all.igmpv2_unsolicited_report_interval": "10000", +#@ "net.ipv4.conf.all.igmpv3_unsolicited_report_interval": "1000", +#@ "net.ipv4.conf.all.log_martians": "0", +#@ "net.ipv4.conf.all.mc_forwarding": "0", +#@ "net.ipv4.conf.all.medium_id": "0", +#@ "net.ipv4.conf.all.promote_secondaries": "1", +#@ "net.ipv4.conf.all.proxy_arp": "0", +#@ "net.ipv4.conf.all.proxy_arp_pvlan": "0", +#@ "net.ipv4.conf.all.route_localnet": "0", +#@ "net.ipv4.conf.all.rp_filter": "1", +#@ "net.ipv4.conf.all.secure_redirects": "1", +#@ "net.ipv4.conf.all.send_redirects": "1", +#@ "net.ipv4.conf.all.shared_media": "1", +#@ "net.ipv4.conf.all.src_valid_mark": "0", +#@ "net.ipv4.conf.all.tag": "0", +#@ "net.ipv4.conf.default.accept_local": "0", +#@ "net.ipv4.conf.default.accept_redirects": "1", +#@ "net.ipv4.conf.default.accept_source_route": "0", +#@ "net.ipv4.conf.default.arp_accept": "0", +#@ "net.ipv4.conf.default.arp_announce": "0", +#@ "net.ipv4.conf.default.arp_filter": "0", +#@ "net.ipv4.conf.default.arp_ignore": "0", +#@ "net.ipv4.conf.default.arp_notify": "0", +#@ "net.ipv4.conf.default.bootp_relay": "0", +#@ "net.ipv4.conf.default.disable_policy": "0", +#@ "net.ipv4.conf.default.disable_xfrm": "0", +#@ "net.ipv4.conf.default.force_igmp_version": "0", +#@ "net.ipv4.conf.default.forwarding": "0", +#@ "net.ipv4.conf.default.igmpv2_unsolicited_report_interval": "10000", +#@ "net.ipv4.conf.default.igmpv3_unsolicited_report_interval": "1000", +#@ "net.ipv4.conf.default.log_martians": "0", +#@ "net.ipv4.conf.default.mc_forwarding": "0", +#@ "net.ipv4.conf.default.medium_id": "0", +#@ "net.ipv4.conf.default.promote_secondaries": "1", +#@ "net.ipv4.conf.default.proxy_arp": "0", +#@ "net.ipv4.conf.default.proxy_arp_pvlan": "0", +#@ "net.ipv4.conf.default.route_localnet": "0", +#@ "net.ipv4.conf.default.rp_filter": "1", +#@ "net.ipv4.conf.default.secure_redirects": "1", +#@ "net.ipv4.conf.default.send_redirects": "1", +#@ "net.ipv4.conf.default.shared_media": "1", +#@ "net.ipv4.conf.default.src_valid_mark": "0", +#@ "net.ipv4.conf.default.tag": "0", +#@ "net.ipv4.conf.eth0.accept_local": "0", +#@ "net.ipv4.conf.eth0.accept_redirects": "1", +#@ "net.ipv4.conf.eth0.accept_source_route": "0", +#@ "net.ipv4.conf.eth0.arp_accept": "0", +#@ "net.ipv4.conf.eth0.arp_announce": "0", +#@ "net.ipv4.conf.eth0.arp_filter": "0", +#@ "net.ipv4.conf.eth0.arp_ignore": "0", +#@ "net.ipv4.conf.eth0.arp_notify": "0", +#@ "net.ipv4.conf.eth0.bootp_relay": "0", +#@ "net.ipv4.conf.eth0.disable_policy": "0", +#@ "net.ipv4.conf.eth0.disable_xfrm": "0", +#@ "net.ipv4.conf.eth0.force_igmp_version": "0", +#@ "net.ipv4.conf.eth0.forwarding": "0", +#@ "net.ipv4.conf.eth0.igmpv2_unsolicited_report_interval": "10000", +#@ "net.ipv4.conf.eth0.igmpv3_unsolicited_report_interval": "1000", +#@ "net.ipv4.conf.eth0.log_martians": "0", +#@ "net.ipv4.conf.eth0.mc_forwarding": "0", +#@ "net.ipv4.conf.eth0.medium_id": "0", +#@ "net.ipv4.conf.eth0.promote_secondaries": "1", +#@ "net.ipv4.conf.eth0.proxy_arp": "0", +#@ "net.ipv4.conf.eth0.proxy_arp_pvlan": "0", +#@ "net.ipv4.conf.eth0.route_localnet": "0", +#@ "net.ipv4.conf.eth0.rp_filter": "1", +#@ "net.ipv4.conf.eth0.secure_redirects": "1", +#@ "net.ipv4.conf.eth0.send_redirects": "1", +#@ "net.ipv4.conf.eth0.shared_media": "1", +#@ "net.ipv4.conf.eth0.src_valid_mark": "0", +#@ "net.ipv4.conf.eth0.tag": "0", +#@ "net.ipv4.conf.eth1.accept_local": "0", +#@ "net.ipv4.conf.eth1.accept_redirects": "1", +#@ "net.ipv4.conf.eth1.accept_source_route": "0", +#@ "net.ipv4.conf.eth1.arp_accept": "0", +#@ "net.ipv4.conf.eth1.arp_announce": "0", +#@ "net.ipv4.conf.eth1.arp_filter": "0", +#@ "net.ipv4.conf.eth1.arp_ignore": "0", +#@ "net.ipv4.conf.eth1.arp_notify": "0", +#@ "net.ipv4.conf.eth1.bootp_relay": "0", +#@ "net.ipv4.conf.eth1.disable_policy": "0", +#@ "net.ipv4.conf.eth1.disable_xfrm": "0", +#@ "net.ipv4.conf.eth1.force_igmp_version": "0", +#@ "net.ipv4.conf.eth1.forwarding": "0", +#@ "net.ipv4.conf.eth1.igmpv2_unsolicited_report_interval": "10000", +#@ "net.ipv4.conf.eth1.igmpv3_unsolicited_report_interval": "1000", +#@ "net.ipv4.conf.eth1.log_martians": "0", +#@ "net.ipv4.conf.eth1.mc_forwarding": "0", +#@ "net.ipv4.conf.eth1.medium_id": "0", +#@ "net.ipv4.conf.eth1.promote_secondaries": "1", +#@ "net.ipv4.conf.eth1.proxy_arp": "0", +#@ "net.ipv4.conf.eth1.proxy_arp_pvlan": "0", +#@ "net.ipv4.conf.eth1.route_localnet": "0", +#@ "net.ipv4.conf.eth1.rp_filter": "1", +#@ "net.ipv4.conf.eth1.secure_redirects": "1", +#@ "net.ipv4.conf.eth1.send_redirects": "1", +#@ "net.ipv4.conf.eth1.shared_media": "1", +#@ "net.ipv4.conf.eth1.src_valid_mark": "0", +#@ "net.ipv4.conf.eth1.tag": "0", +#@ "net.ipv4.conf.lo.accept_local": "0", +#@ "net.ipv4.conf.lo.accept_redirects": "1", +#@ "net.ipv4.conf.lo.accept_source_route": "1", +#@ "net.ipv4.conf.lo.arp_accept": "0", +#@ "net.ipv4.conf.lo.arp_announce": "0", +#@ "net.ipv4.conf.lo.arp_filter": "0", +#@ "net.ipv4.conf.lo.arp_ignore": "0", +#@ "net.ipv4.conf.lo.arp_notify": "0", +#@ "net.ipv4.conf.lo.bootp_relay": "0", +#@ "net.ipv4.conf.lo.disable_policy": "1", +#@ "net.ipv4.conf.lo.disable_xfrm": "1", +#@ "net.ipv4.conf.lo.force_igmp_version": "0", +#@ "net.ipv4.conf.lo.forwarding": "0", +#@ "net.ipv4.conf.lo.igmpv2_unsolicited_report_interval": "10000", +#@ "net.ipv4.conf.lo.igmpv3_unsolicited_report_interval": "1000", +#@ "net.ipv4.conf.lo.log_martians": "0", +#@ "net.ipv4.conf.lo.mc_forwarding": "0", +#@ "net.ipv4.conf.lo.medium_id": "0", +#@ "net.ipv4.conf.lo.promote_secondaries": "0", +#@ "net.ipv4.conf.lo.proxy_arp": "0", +#@ "net.ipv4.conf.lo.proxy_arp_pvlan": "0", +#@ "net.ipv4.conf.lo.route_localnet": "0", +#@ "net.ipv4.conf.lo.rp_filter": "0", +#@ "net.ipv4.conf.lo.secure_redirects": "1", +#@ "net.ipv4.conf.lo.send_redirects": "1", +#@ "net.ipv4.conf.lo.shared_media": "1", +#@ "net.ipv4.conf.lo.src_valid_mark": "0", +#@ "net.ipv4.conf.lo.tag": "0", +#@ "net.ipv4.fib_multipath_hash_policy": "0", +#@ "net.ipv4.fwmark_reflect": "0", +#@ "net.ipv4.icmp_echo_ignore_all": "0", +#@ "net.ipv4.icmp_echo_ignore_broadcasts": "1", +#@ "net.ipv4.icmp_errors_use_inbound_ifaddr": "0", +#@ "net.ipv4.icmp_ignore_bogus_error_responses": "1", +#@ "net.ipv4.icmp_msgs_burst": "50", +#@ "net.ipv4.icmp_msgs_per_sec": "1000", +#@ "net.ipv4.icmp_ratelimit": "1000", +#@ "net.ipv4.icmp_ratemask": "6168", +#@ "net.ipv4.igmp_max_memberships": "20", +#@ "net.ipv4.igmp_max_msf": "10", +#@ "net.ipv4.igmp_qrv": "2", +#@ "net.ipv4.inet_peer_maxttl": "600", +#@ "net.ipv4.inet_peer_minttl": "120", +#@ "net.ipv4.inet_peer_threshold": "65664", +#@ "net.ipv4.ip_default_ttl": "64", +#@ "net.ipv4.ip_dynaddr": "0", +#@ "net.ipv4.ip_early_demux": "1", +#@ "net.ipv4.ip_forward": "0", +#@ "net.ipv4.ip_forward_use_pmtu": "0", +#@ "net.ipv4.ip_local_port_range": "32768\t60999", +#@ "net.ipv4.ip_local_reserved_ports": "", +#@ "net.ipv4.ip_no_pmtu_disc": "0", +#@ "net.ipv4.ip_nonlocal_bind": "0", +#@ "net.ipv4.ipfrag_high_thresh": "4194304", +#@ "net.ipv4.ipfrag_low_thresh": "3145728", +#@ "net.ipv4.ipfrag_max_dist": "64", +#@ "net.ipv4.ipfrag_secret_interval": "600", +#@ "net.ipv4.ipfrag_time": "30", +#@ "net.ipv4.neigh.default.anycast_delay": "100", +#@ "net.ipv4.neigh.default.app_solicit": "0", +#@ "net.ipv4.neigh.default.base_reachable_time": "30", +#@ "net.ipv4.neigh.default.base_reachable_time_ms": "30000", +#@ "net.ipv4.neigh.default.delay_first_probe_time": "5", +#@ "net.ipv4.neigh.default.gc_interval": "30", +#@ "net.ipv4.neigh.default.gc_stale_time": "60", +#@ "net.ipv4.neigh.default.gc_thresh1": "128", +#@ "net.ipv4.neigh.default.gc_thresh2": "512", +#@ "net.ipv4.neigh.default.gc_thresh3": "1024", +#@ "net.ipv4.neigh.default.locktime": "100", +#@ "net.ipv4.neigh.default.mcast_solicit": "3", +#@ "net.ipv4.neigh.default.proxy_delay": "80", +#@ "net.ipv4.neigh.default.proxy_qlen": "64", +#@ "net.ipv4.neigh.default.retrans_time": "100", +#@ "net.ipv4.neigh.default.retrans_time_ms": "1000", +#@ "net.ipv4.neigh.default.ucast_solicit": "3", +#@ "net.ipv4.neigh.default.unres_qlen": "31", +#@ "net.ipv4.neigh.default.unres_qlen_bytes": "65536", +#@ "net.ipv4.neigh.eth0.anycast_delay": "100", +#@ "net.ipv4.neigh.eth0.app_solicit": "0", +#@ "net.ipv4.neigh.eth0.base_reachable_time": "30", +#@ "net.ipv4.neigh.eth0.base_reachable_time_ms": "30000", +#@ "net.ipv4.neigh.eth0.delay_first_probe_time": "5", +#@ "net.ipv4.neigh.eth0.gc_stale_time": "60", +#@ "net.ipv4.neigh.eth0.locktime": "100", +#@ "net.ipv4.neigh.eth0.mcast_solicit": "3", +#@ "net.ipv4.neigh.eth0.proxy_delay": "80", +#@ "net.ipv4.neigh.eth0.proxy_qlen": "64", +#@ "net.ipv4.neigh.eth0.retrans_time": "100", +#@ "net.ipv4.neigh.eth0.retrans_time_ms": "1000", +#@ "net.ipv4.neigh.eth0.ucast_solicit": "3", +#@ "net.ipv4.neigh.eth0.unres_qlen": "31", +#@ "net.ipv4.neigh.eth0.unres_qlen_bytes": "65536", +#@ "net.ipv4.neigh.eth1.anycast_delay": "100", +#@ "net.ipv4.neigh.eth1.app_solicit": "0", +#@ "net.ipv4.neigh.eth1.base_reachable_time": "30", +#@ "net.ipv4.neigh.eth1.base_reachable_time_ms": "30000", +#@ "net.ipv4.neigh.eth1.delay_first_probe_time": "5", +#@ "net.ipv4.neigh.eth1.gc_stale_time": "60", +#@ "net.ipv4.neigh.eth1.locktime": "100", +#@ "net.ipv4.neigh.eth1.mcast_solicit": "3", +#@ "net.ipv4.neigh.eth1.proxy_delay": "80", +#@ "net.ipv4.neigh.eth1.proxy_qlen": "64", +#@ "net.ipv4.neigh.eth1.retrans_time": "100", +#@ "net.ipv4.neigh.eth1.retrans_time_ms": "1000", +#@ "net.ipv4.neigh.eth1.ucast_solicit": "3", +#@ "net.ipv4.neigh.eth1.unres_qlen": "31", +#@ "net.ipv4.neigh.eth1.unres_qlen_bytes": "65536", +#@ "net.ipv4.neigh.lo.anycast_delay": "100", +#@ "net.ipv4.neigh.lo.app_solicit": "0", +#@ "net.ipv4.neigh.lo.base_reachable_time": "30", +#@ "net.ipv4.neigh.lo.base_reachable_time_ms": "30000", +#@ "net.ipv4.neigh.lo.delay_first_probe_time": "5", +#@ "net.ipv4.neigh.lo.gc_stale_time": "60", +#@ "net.ipv4.neigh.lo.locktime": "100", +#@ "net.ipv4.neigh.lo.mcast_solicit": "3", +#@ "net.ipv4.neigh.lo.proxy_delay": "80", +#@ "net.ipv4.neigh.lo.proxy_qlen": "64", +#@ "net.ipv4.neigh.lo.retrans_time": "100", +#@ "net.ipv4.neigh.lo.retrans_time_ms": "1000", +#@ "net.ipv4.neigh.lo.ucast_solicit": "3", +#@ "net.ipv4.neigh.lo.unres_qlen": "31", +#@ "net.ipv4.neigh.lo.unres_qlen_bytes": "65536", +#@ "net.ipv4.ping_group_range": "1\t0", +#@ "net.ipv4.route.error_burst": "5000", +#@ "net.ipv4.route.error_cost": "1000", +#@ "net.ipv4.route.gc_elasticity": "8", +#@ "net.ipv4.route.gc_interval": "60", +#@ "net.ipv4.route.gc_min_interval": "0", +#@ "net.ipv4.route.gc_min_interval_ms": "500", +#@ "net.ipv4.route.gc_thresh": "-1", +#@ "net.ipv4.route.gc_timeout": "300", +#@ "net.ipv4.route.max_size": "2147483647", +#@ "net.ipv4.route.min_adv_mss": "256", +#@ "net.ipv4.route.min_pmtu": "552", +#@ "net.ipv4.route.mtu_expires": "600", +#@ "net.ipv4.route.redirect_load": "20", +#@ "net.ipv4.route.redirect_number": "9", +#@ "net.ipv4.route.redirect_silence": "20480", +#@ "net.ipv4.tcp_abort_on_overflow": "0", +#@ "net.ipv4.tcp_adv_win_scale": "1", +#@ "net.ipv4.tcp_allowed_congestion_control": "cubic reno", +#@ "net.ipv4.tcp_app_win": "31", +#@ "net.ipv4.tcp_autocorking": "1", +#@ "net.ipv4.tcp_available_congestion_control": "cubic reno", +#@ "net.ipv4.tcp_base_mss": "512", +#@ "net.ipv4.tcp_challenge_ack_limit": "1000", +#@ "net.ipv4.tcp_congestion_control": "cubic", +#@ "net.ipv4.tcp_dsack": "1", +#@ "net.ipv4.tcp_early_retrans": "3", +#@ "net.ipv4.tcp_ecn": "2", +#@ "net.ipv4.tcp_fack": "1", +#@ "net.ipv4.tcp_fastopen": "0", +#@ "net.ipv4.tcp_fastopen_key": "00000000-00000000-00000000-00000000", +#@ "net.ipv4.tcp_fin_timeout": "60", +#@ "net.ipv4.tcp_frto": "2", +#@ "net.ipv4.tcp_invalid_ratelimit": "500", +#@ "net.ipv4.tcp_keepalive_intvl": "75", +#@ "net.ipv4.tcp_keepalive_probes": "9", +#@ "net.ipv4.tcp_keepalive_time": "7200", +#@ "net.ipv4.tcp_limit_output_bytes": "262144", +#@ "net.ipv4.tcp_low_latency": "0", +#@ "net.ipv4.tcp_max_orphans": "2048", +#@ "net.ipv4.tcp_max_ssthresh": "0", +#@ "net.ipv4.tcp_max_syn_backlog": "128", +#@ "net.ipv4.tcp_max_tw_buckets": "2048", +#@ "net.ipv4.tcp_mem": "11517\t15356\t23034", +#@ "net.ipv4.tcp_min_snd_mss": "48", +#@ "net.ipv4.tcp_min_tso_segs": "2", +#@ "net.ipv4.tcp_moderate_rcvbuf": "1", +#@ "net.ipv4.tcp_mtu_probing": "0", +#@ "net.ipv4.tcp_no_metrics_save": "0", +#@ "net.ipv4.tcp_notsent_lowat": "-1", +#@ "net.ipv4.tcp_orphan_retries": "0", +#@ "net.ipv4.tcp_reordering": "3", +#@ "net.ipv4.tcp_retrans_collapse": "1", +#@ "net.ipv4.tcp_retries1": "3", +#@ "net.ipv4.tcp_retries2": "15", +#@ "net.ipv4.tcp_rfc1337": "0", +#@ "net.ipv4.tcp_rmem": "4096\t87380\t3868000", +#@ "net.ipv4.tcp_sack": "1", +#@ "net.ipv4.tcp_slow_start_after_idle": "1", +#@ "net.ipv4.tcp_stdurg": "0", +#@ "net.ipv4.tcp_syn_retries": "6", +#@ "net.ipv4.tcp_synack_retries": "5", +#@ "net.ipv4.tcp_syncookies": "1", +#@ "net.ipv4.tcp_thin_dupack": "0", +#@ "net.ipv4.tcp_thin_linear_timeouts": "0", +#@ "net.ipv4.tcp_timestamps": "1", +#@ "net.ipv4.tcp_tso_win_divisor": "3", +#@ "net.ipv4.tcp_tw_recycle": "0", +#@ "net.ipv4.tcp_tw_reuse": "0", +#@ "net.ipv4.tcp_window_scaling": "1", +#@ "net.ipv4.tcp_wmem": "4096\t16384\t3868000", +#@ "net.ipv4.tcp_workaround_signed_windows": "0", +#@ "net.ipv4.udp_mem": "11331\t15109\t22662", +#@ "net.ipv4.udp_rmem_min": "4096", +#@ "net.ipv4.udp_wmem_min": "4096", +#@ "net.ipv4.xfrm4_gc_thresh": "32768", +#@ "net.ipv6.anycast_src_echo_reply": "0", +#@ "net.ipv6.bindv6only": "0", +#@ "net.ipv6.conf.all.accept_dad": "0", +#@ "net.ipv6.conf.all.accept_ra": "1", +#@ "net.ipv6.conf.all.accept_ra_defrtr": "1", +#@ "net.ipv6.conf.all.accept_ra_pinfo": "1", +#@ "net.ipv6.conf.all.accept_ra_rt_info_max_plen": "0", +#@ "net.ipv6.conf.all.accept_ra_rtr_pref": "1", +#@ "net.ipv6.conf.all.accept_redirects": "1", +#@ "net.ipv6.conf.all.accept_source_route": "0", +#@ "net.ipv6.conf.all.autoconf": "1", +#@ "net.ipv6.conf.all.dad_transmits": "1", +#@ "net.ipv6.conf.all.disable_ipv6": "0", +#@ "net.ipv6.conf.all.enhanced_dad": "1", +#@ "net.ipv6.conf.all.force_mld_version": "0", +#@ "net.ipv6.conf.all.force_tllao": "0", +#@ "net.ipv6.conf.all.forwarding": "0", +#@ "net.ipv6.conf.all.hop_limit": "64", +#@ "net.ipv6.conf.all.keep_addr_on_down": "0", +#@ "net.ipv6.conf.all.max_addresses": "16", +#@ "net.ipv6.conf.all.max_desync_factor": "600", +#@ "net.ipv6.conf.all.mc_forwarding": "0", +#@ "net.ipv6.conf.all.mldv1_unsolicited_report_interval": "10000", +#@ "net.ipv6.conf.all.mldv2_unsolicited_report_interval": "1000", +#@ "net.ipv6.conf.all.mtu": "1280", +#@ "net.ipv6.conf.all.ndisc_notify": "0", +#@ "net.ipv6.conf.all.optimistic_dad": "0", +#@ "net.ipv6.conf.all.proxy_ndp": "0", +#@ "net.ipv6.conf.all.regen_max_retry": "3", +#@ "net.ipv6.conf.all.router_probe_interval": "60", +#@ "net.ipv6.conf.all.router_solicitation_delay": "1", +#@ "net.ipv6.conf.all.router_solicitation_interval": "4", +#@ "net.ipv6.conf.all.router_solicitations": "3", +#@ "net.ipv6.conf.all.temp_prefered_lft": "86400", +#@ "net.ipv6.conf.all.temp_valid_lft": "604800", +#@ "net.ipv6.conf.all.use_optimistic": "0", +#@ "net.ipv6.conf.all.use_tempaddr": "0", +#@ "net.ipv6.conf.default.accept_dad": "1", +#@ "net.ipv6.conf.default.accept_ra": "1", +#@ "net.ipv6.conf.default.accept_ra_defrtr": "1", +#@ "net.ipv6.conf.default.accept_ra_pinfo": "1", +#@ "net.ipv6.conf.default.accept_ra_rt_info_max_plen": "0", +#@ "net.ipv6.conf.default.accept_ra_rtr_pref": "1", +#@ "net.ipv6.conf.default.accept_redirects": "1", +#@ "net.ipv6.conf.default.accept_source_route": "0", +#@ "net.ipv6.conf.default.autoconf": "1", +#@ "net.ipv6.conf.default.dad_transmits": "1", +#@ "net.ipv6.conf.default.disable_ipv6": "0", +#@ "net.ipv6.conf.default.enhanced_dad": "1", +#@ "net.ipv6.conf.default.force_mld_version": "0", +#@ "net.ipv6.conf.default.force_tllao": "0", +#@ "net.ipv6.conf.default.forwarding": "0", +#@ "net.ipv6.conf.default.hop_limit": "64", +#@ "net.ipv6.conf.default.keep_addr_on_down": "0", +#@ "net.ipv6.conf.default.max_addresses": "16", +#@ "net.ipv6.conf.default.max_desync_factor": "600", +#@ "net.ipv6.conf.default.mc_forwarding": "0", +#@ "net.ipv6.conf.default.mldv1_unsolicited_report_interval": "10000", +#@ "net.ipv6.conf.default.mldv2_unsolicited_report_interval": "1000", +#@ "net.ipv6.conf.default.mtu": "1280", +#@ "net.ipv6.conf.default.ndisc_notify": "0", +#@ "net.ipv6.conf.default.optimistic_dad": "0", +#@ "net.ipv6.conf.default.proxy_ndp": "0", +#@ "net.ipv6.conf.default.regen_max_retry": "3", +#@ "net.ipv6.conf.default.router_probe_interval": "60", +#@ "net.ipv6.conf.default.router_solicitation_delay": "1", +#@ "net.ipv6.conf.default.router_solicitation_interval": "4", +#@ "net.ipv6.conf.default.router_solicitations": "3", +#@ "net.ipv6.conf.default.temp_prefered_lft": "86400", +#@ "net.ipv6.conf.default.temp_valid_lft": "604800", +#@ "net.ipv6.conf.default.use_optimistic": "0", +#@ "net.ipv6.conf.default.use_tempaddr": "0", +#@ "net.ipv6.conf.eth0.accept_dad": "1", +#@ "net.ipv6.conf.eth0.accept_ra": "1", +#@ "net.ipv6.conf.eth0.accept_ra_defrtr": "1", +#@ "net.ipv6.conf.eth0.accept_ra_pinfo": "1", +#@ "net.ipv6.conf.eth0.accept_ra_rt_info_max_plen": "0", +#@ "net.ipv6.conf.eth0.accept_ra_rtr_pref": "1", +#@ "net.ipv6.conf.eth0.accept_redirects": "1", +#@ "net.ipv6.conf.eth0.accept_source_route": "0", +#@ "net.ipv6.conf.eth0.autoconf": "1", +#@ "net.ipv6.conf.eth0.dad_transmits": "1", +#@ "net.ipv6.conf.eth0.disable_ipv6": "0", +#@ "net.ipv6.conf.eth0.enhanced_dad": "1", +#@ "net.ipv6.conf.eth0.force_mld_version": "0", +#@ "net.ipv6.conf.eth0.force_tllao": "0", +#@ "net.ipv6.conf.eth0.forwarding": "0", +#@ "net.ipv6.conf.eth0.hop_limit": "64", +#@ "net.ipv6.conf.eth0.keep_addr_on_down": "0", +#@ "net.ipv6.conf.eth0.max_addresses": "16", +#@ "net.ipv6.conf.eth0.max_desync_factor": "600", +#@ "net.ipv6.conf.eth0.mc_forwarding": "0", +#@ "net.ipv6.conf.eth0.mldv1_unsolicited_report_interval": "10000", +#@ "net.ipv6.conf.eth0.mldv2_unsolicited_report_interval": "1000", +#@ "net.ipv6.conf.eth0.mtu": "1500", +#@ "net.ipv6.conf.eth0.ndisc_notify": "0", +#@ "net.ipv6.conf.eth0.optimistic_dad": "0", +#@ "net.ipv6.conf.eth0.proxy_ndp": "0", +#@ "net.ipv6.conf.eth0.regen_max_retry": "3", +#@ "net.ipv6.conf.eth0.router_probe_interval": "60", +#@ "net.ipv6.conf.eth0.router_solicitation_delay": "1", +#@ "net.ipv6.conf.eth0.router_solicitation_interval": "4", +#@ "net.ipv6.conf.eth0.router_solicitations": "3", +#@ "net.ipv6.conf.eth0.temp_prefered_lft": "86400", +#@ "net.ipv6.conf.eth0.temp_valid_lft": "604800", +#@ "net.ipv6.conf.eth0.use_optimistic": "0", +#@ "net.ipv6.conf.eth0.use_tempaddr": "0", +#@ "net.ipv6.conf.eth1.accept_dad": "1", +#@ "net.ipv6.conf.eth1.accept_ra": "0", +#@ "net.ipv6.conf.eth1.accept_ra_defrtr": "0", +#@ "net.ipv6.conf.eth1.accept_ra_pinfo": "0", +#@ "net.ipv6.conf.eth1.accept_ra_rt_info_max_plen": "0", +#@ "net.ipv6.conf.eth1.accept_ra_rtr_pref": "0", +#@ "net.ipv6.conf.eth1.accept_redirects": "1", +#@ "net.ipv6.conf.eth1.accept_source_route": "0", +#@ "net.ipv6.conf.eth1.autoconf": "1", +#@ "net.ipv6.conf.eth1.dad_transmits": "1", +#@ "net.ipv6.conf.eth1.disable_ipv6": "0", +#@ "net.ipv6.conf.eth1.enhanced_dad": "1", +#@ "net.ipv6.conf.eth1.force_mld_version": "0", +#@ "net.ipv6.conf.eth1.force_tllao": "0", +#@ "net.ipv6.conf.eth1.forwarding": "0", +#@ "net.ipv6.conf.eth1.hop_limit": "64", +#@ "net.ipv6.conf.eth1.keep_addr_on_down": "0", +#@ "net.ipv6.conf.eth1.max_addresses": "16", +#@ "net.ipv6.conf.eth1.max_desync_factor": "600", +#@ "net.ipv6.conf.eth1.mc_forwarding": "0", +#@ "net.ipv6.conf.eth1.mldv1_unsolicited_report_interval": "10000", +#@ "net.ipv6.conf.eth1.mldv2_unsolicited_report_interval": "1000", +#@ "net.ipv6.conf.eth1.mtu": "1500", +#@ "net.ipv6.conf.eth1.ndisc_notify": "0", +#@ "net.ipv6.conf.eth1.optimistic_dad": "0", +#@ "net.ipv6.conf.eth1.proxy_ndp": "0", +#@ "net.ipv6.conf.eth1.regen_max_retry": "3", +#@ "net.ipv6.conf.eth1.router_probe_interval": "60", +#@ "net.ipv6.conf.eth1.router_solicitation_delay": "1", +#@ "net.ipv6.conf.eth1.router_solicitation_interval": "4", +#@ "net.ipv6.conf.eth1.router_solicitations": "3", +#@ "net.ipv6.conf.eth1.temp_prefered_lft": "86400", +#@ "net.ipv6.conf.eth1.temp_valid_lft": "604800", +#@ "net.ipv6.conf.eth1.use_optimistic": "0", +#@ "net.ipv6.conf.eth1.use_tempaddr": "0", +#@ "net.ipv6.conf.lo.accept_dad": "-1", +#@ "net.ipv6.conf.lo.accept_ra": "1", +#@ "net.ipv6.conf.lo.accept_ra_defrtr": "1", +#@ "net.ipv6.conf.lo.accept_ra_pinfo": "1", +#@ "net.ipv6.conf.lo.accept_ra_rt_info_max_plen": "0", +#@ "net.ipv6.conf.lo.accept_ra_rtr_pref": "1", +#@ "net.ipv6.conf.lo.accept_redirects": "1", +#@ "net.ipv6.conf.lo.accept_source_route": "0", +#@ "net.ipv6.conf.lo.autoconf": "1", +#@ "net.ipv6.conf.lo.dad_transmits": "1", +#@ "net.ipv6.conf.lo.disable_ipv6": "0", +#@ "net.ipv6.conf.lo.enhanced_dad": "1", +#@ "net.ipv6.conf.lo.force_mld_version": "0", +#@ "net.ipv6.conf.lo.force_tllao": "0", +#@ "net.ipv6.conf.lo.forwarding": "0", +#@ "net.ipv6.conf.lo.hop_limit": "64", +#@ "net.ipv6.conf.lo.keep_addr_on_down": "0", +#@ "net.ipv6.conf.lo.max_addresses": "16", +#@ "net.ipv6.conf.lo.max_desync_factor": "600", +#@ "net.ipv6.conf.lo.mc_forwarding": "0", +#@ "net.ipv6.conf.lo.mldv1_unsolicited_report_interval": "10000", +#@ "net.ipv6.conf.lo.mldv2_unsolicited_report_interval": "1000", +#@ "net.ipv6.conf.lo.mtu": "65536", +#@ "net.ipv6.conf.lo.ndisc_notify": "0", +#@ "net.ipv6.conf.lo.optimistic_dad": "0", +#@ "net.ipv6.conf.lo.proxy_ndp": "0", +#@ "net.ipv6.conf.lo.regen_max_retry": "3", +#@ "net.ipv6.conf.lo.router_probe_interval": "60", +#@ "net.ipv6.conf.lo.router_solicitation_delay": "1", +#@ "net.ipv6.conf.lo.router_solicitation_interval": "4", +#@ "net.ipv6.conf.lo.router_solicitations": "3", +#@ "net.ipv6.conf.lo.temp_prefered_lft": "86400", +#@ "net.ipv6.conf.lo.temp_valid_lft": "604800", +#@ "net.ipv6.conf.lo.use_optimistic": "0", +#@ "net.ipv6.conf.lo.use_tempaddr": "-1", +#@ "net.ipv6.fwmark_reflect": "0", +#@ "net.ipv6.icmp.ratelimit": "1000", +#@ "net.ipv6.idgen_delay": "1", +#@ "net.ipv6.idgen_retries": "3", +#@ "net.ipv6.ip6frag_high_thresh": "4194304", +#@ "net.ipv6.ip6frag_low_thresh": "3145728", +#@ "net.ipv6.ip6frag_secret_interval": "600", +#@ "net.ipv6.ip6frag_time": "60", +#@ "net.ipv6.ip_nonlocal_bind": "0", +#@ "net.ipv6.mld_max_msf": "64", +#@ "net.ipv6.mld_qrv": "2", +#@ "net.ipv6.neigh.default.anycast_delay": "100", +#@ "net.ipv6.neigh.default.app_solicit": "0", +#@ "net.ipv6.neigh.default.base_reachable_time": "30", +#@ "net.ipv6.neigh.default.base_reachable_time_ms": "30000", +#@ "net.ipv6.neigh.default.delay_first_probe_time": "5", +#@ "net.ipv6.neigh.default.gc_interval": "30", +#@ "net.ipv6.neigh.default.gc_stale_time": "60", +#@ "net.ipv6.neigh.default.gc_thresh1": "128", +#@ "net.ipv6.neigh.default.gc_thresh2": "512", +#@ "net.ipv6.neigh.default.gc_thresh3": "1024", +#@ "net.ipv6.neigh.default.locktime": "0", +#@ "net.ipv6.neigh.default.mcast_solicit": "3", +#@ "net.ipv6.neigh.default.proxy_delay": "80", +#@ "net.ipv6.neigh.default.proxy_qlen": "64", +#@ "net.ipv6.neigh.default.retrans_time": "1000", +#@ "net.ipv6.neigh.default.retrans_time_ms": "1000", +#@ "net.ipv6.neigh.default.ucast_solicit": "3", +#@ "net.ipv6.neigh.default.unres_qlen": "31", +#@ "net.ipv6.neigh.default.unres_qlen_bytes": "65536", +#@ "net.ipv6.neigh.eth0.anycast_delay": "100", +#@ "net.ipv6.neigh.eth0.app_solicit": "0", +#@ "net.ipv6.neigh.eth0.base_reachable_time": "30", +#@ "net.ipv6.neigh.eth0.base_reachable_time_ms": "30000", +#@ "net.ipv6.neigh.eth0.delay_first_probe_time": "5", +#@ "net.ipv6.neigh.eth0.gc_stale_time": "60", +#@ "net.ipv6.neigh.eth0.locktime": "0", +#@ "net.ipv6.neigh.eth0.mcast_solicit": "3", +#@ "net.ipv6.neigh.eth0.proxy_delay": "80", +#@ "net.ipv6.neigh.eth0.proxy_qlen": "64", +#@ "net.ipv6.neigh.eth0.retrans_time": "1000", +#@ "net.ipv6.neigh.eth0.retrans_time_ms": "1000", +#@ "net.ipv6.neigh.eth0.ucast_solicit": "3", +#@ "net.ipv6.neigh.eth0.unres_qlen": "31", +#@ "net.ipv6.neigh.eth0.unres_qlen_bytes": "65536", +#@ "net.ipv6.neigh.eth1.anycast_delay": "100", +#@ "net.ipv6.neigh.eth1.app_solicit": "0", +#@ "net.ipv6.neigh.eth1.base_reachable_time": "30", +#@ "net.ipv6.neigh.eth1.base_reachable_time_ms": "30000", +#@ "net.ipv6.neigh.eth1.delay_first_probe_time": "5", +#@ "net.ipv6.neigh.eth1.gc_stale_time": "60", +#@ "net.ipv6.neigh.eth1.locktime": "0", +#@ "net.ipv6.neigh.eth1.mcast_solicit": "3", +#@ "net.ipv6.neigh.eth1.proxy_delay": "80", +#@ "net.ipv6.neigh.eth1.proxy_qlen": "64", +#@ "net.ipv6.neigh.eth1.retrans_time": "1000", +#@ "net.ipv6.neigh.eth1.retrans_time_ms": "1000", +#@ "net.ipv6.neigh.eth1.ucast_solicit": "3", +#@ "net.ipv6.neigh.eth1.unres_qlen": "31", +#@ "net.ipv6.neigh.eth1.unres_qlen_bytes": "65536", +#@ "net.ipv6.neigh.lo.anycast_delay": "100", +#@ "net.ipv6.neigh.lo.app_solicit": "0", +#@ "net.ipv6.neigh.lo.base_reachable_time": "30", +#@ "net.ipv6.neigh.lo.base_reachable_time_ms": "30000", +#@ "net.ipv6.neigh.lo.delay_first_probe_time": "5", +#@ "net.ipv6.neigh.lo.gc_stale_time": "60", +#@ "net.ipv6.neigh.lo.locktime": "0", +#@ "net.ipv6.neigh.lo.mcast_solicit": "3", +#@ "net.ipv6.neigh.lo.proxy_delay": "80", +#@ "net.ipv6.neigh.lo.proxy_qlen": "64", +#@ "net.ipv6.neigh.lo.retrans_time": "1000", +#@ "net.ipv6.neigh.lo.retrans_time_ms": "1000", +#@ "net.ipv6.neigh.lo.ucast_solicit": "3", +#@ "net.ipv6.neigh.lo.unres_qlen": "31", +#@ "net.ipv6.neigh.lo.unres_qlen_bytes": "65536", +#@ "net.ipv6.route.gc_elasticity": "9", +#@ "net.ipv6.route.gc_interval": "30", +#@ "net.ipv6.route.gc_min_interval": "0", +#@ "net.ipv6.route.gc_min_interval_ms": "500", +#@ "net.ipv6.route.gc_thresh": "1024", +#@ "net.ipv6.route.gc_timeout": "60", +#@ "net.ipv6.route.max_size": "16384", +#@ "net.ipv6.route.min_adv_mss": "1220", +#@ "net.ipv6.route.mtu_expires": "600", +#@ "net.ipv6.xfrm6_gc_thresh": "32768", +#@ "net.netfilter.nf_log.0": "NONE", +#@ "net.netfilter.nf_log.1": "NONE", +#@ "net.netfilter.nf_log.10": "NONE", +#@ "net.netfilter.nf_log.11": "NONE", +#@ "net.netfilter.nf_log.12": "NONE", +#@ "net.netfilter.nf_log.2": "NONE", +#@ "net.netfilter.nf_log.3": "NONE", +#@ "net.netfilter.nf_log.4": "NONE", +#@ "net.netfilter.nf_log.5": "NONE", +#@ "net.netfilter.nf_log.6": "NONE", +#@ "net.netfilter.nf_log.7": "NONE", +#@ "net.netfilter.nf_log.8": "NONE", +#@ "net.netfilter.nf_log.9": "NONE", +#@ "net.netfilter.nf_log_all_netns": "0", +#@ "net.unix.max_dgram_qlen": "512", +#@ "sunrpc.max_resvport": "1023", +#@ "sunrpc.min_resvport": "665", +#@ "sunrpc.nfs_debug": "0x0000", +#@ "sunrpc.nfsd_debug": "0x0000", +#@ "sunrpc.nlm_debug": "0x0000", +#@ "sunrpc.rpc_debug": "0x0000", +#@ "sunrpc.tcp_fin_timeout": "15", +#@ "sunrpc.tcp_max_slot_table_entries": "65536", +#@ "sunrpc.tcp_slot_table_entries": "2", +#@ "sunrpc.transports": "tcp 1048576\nudp 32768", +#@ "sunrpc.udp_slot_table_entries": "16", +#@ "user.max_ipc_namespaces": "1888", +#@ "user.max_mnt_namespaces": "1888", +#@ "user.max_net_namespaces": "1888", +#@ "user.max_pid_namespaces": "1888", +#@ "user.max_user_namespaces": "0", +#@ "user.max_uts_namespaces": "1888", +#@ "vm.admin_reserve_kbytes": "8192", +#@ "vm.block_dump": "0", +#@ "vm.dirty_background_bytes": "0", +#@ "vm.dirty_background_ratio": "10", +#@ "vm.dirty_bytes": "0", +#@ "vm.dirty_expire_centisecs": "3000", +#@ "vm.dirty_ratio": "30", +#@ "vm.dirty_writeback_centisecs": "500", +#@ "vm.drop_caches": "0", +#@ "vm.extfrag_threshold": "500", +#@ "vm.hugepages_treat_as_movable": "0", +#@ "vm.hugetlb_shm_group": "0", +#@ "vm.laptop_mode": "0", +#@ "vm.legacy_va_layout": "0", +#@ "vm.lowmem_reserve_ratio": "256\t256\t32", +#@ "vm.max_map_count": "65530", +#@ "vm.memory_failure_early_kill": "0", +#@ "vm.memory_failure_recovery": "1", +#@ "vm.min_free_kbytes": "2816", +#@ "vm.min_slab_ratio": "5", +#@ "vm.min_unmapped_ratio": "1", +#@ "vm.mmap_min_addr": "4096", +#@ "vm.mmap_rnd_bits": "28", +#@ "vm.mmap_rnd_compat_bits": "8", +#@ "vm.nr_hugepages": "0", +#@ "vm.nr_hugepages_mempolicy": "0", +#@ "vm.nr_overcommit_hugepages": "0", +#@ "vm.nr_pdflush_threads": "0", +#@ "vm.numa_zonelist_order": "default", +#@ "vm.oom_dump_tasks": "1", +#@ "vm.oom_kill_allocating_task": "0", +#@ "vm.overcommit_kbytes": "0", +#@ "vm.overcommit_memory": "0", +#@ "vm.overcommit_ratio": "50", +#@ "vm.page-cluster": "3", +#@ "vm.panic_on_oom": "0", +#@ "vm.percpu_pagelist_fraction": "0", +#@ "vm.stat_interval": "1", +#@ "vm.swappiness": "30", +#@ "vm.user_reserve_kbytes": "14160", +#@ "vm.vfs_cache_pressure": "100", +#@ "vm.zone_reclaim_mode": "0" +#@ } +#@ ``` +#+end_src diff --git a/examples/datastate.cf b/examples/datastate.cf new file mode 100644 index 0000000000..8caa8d542c --- /dev/null +++ b/examples/datastate.cf @@ -0,0 +1,65 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { holder, test }; +} + +bundle common holder +{ + classes: + "holderclass" expression => "any"; # will be global + + vars: + "s" string => "Hello!"; + "d" data => parsejson('[4,5,6]'); + "list" slist => { "element1", "element2" }; +} + +bundle agent test +{ + vars: + "state" data => datastate(); + + # all the variables in bundle "holder" defined as of the execution of datastate() will be here + "holderstate" string => format("%S", "state[vars][holder]"); + # all the classes defined as of the execution of datastate() will be here + "allclasses" slist => getindices("state[classes]"); + + classes: + "have_holderclass" expression => some("holderclass", allclasses); + + reports: + "holder vars = $(holderstate)"; + have_holderclass:: + "I have the holder class"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: holder vars = {"d":[4,5,6],"list":["element1","element2"],"s":"Hello!"} +#@ R: I have the holder class +#@ ``` +#+end_src diff --git a/examples/def.json b/examples/def.json new file mode 100644 index 0000000000..5b8b32e45e --- /dev/null +++ b/examples/def.json @@ -0,0 +1,9 @@ +{ + "vars": { + "example_augment_string_override": "defined in def.json", + "example_augment_list_override": [ "defined", "in", "def.json"], + "example_augment_structured_override": { + "key1": "defined in def.json" + } + } +} diff --git a/examples/defaults.cf b/examples/defaults.cf new file mode 100644 index 0000000000..97c2ba5491 --- /dev/null +++ b/examples/defaults.cf @@ -0,0 +1,90 @@ + +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Default values for variables and parameters, introduced 3.4.0 +# + +########################################################### + +bundle agent main + +{ + defaults: + + # We can have default values even if variables are not defined at all. + # This is equivalent to a variable definition, so not particularly useful. + + "X" string => "I am a default value"; + "Y" slist => { "I am a default list item 1", "I am a default list item 2" }; + + methods: + + # More useful, defaults if parameters are passed to a param bundle + + "example" usebundle => mymethod("","bbb"); + + reports: + + "The default value of X is $(X)"; + "The default value of Y is $(Y)"; +} + +########################################################### + +bundle agent mymethod(a,b) + +{ + vars: + + "no_return" string => "ok"; # readfile("/dont/exist","123"); + + defaults: + + "a" string => "AAAAAAAAA", if_match_regex => ""; + + "b" string => "BBBBBBBBB", if_match_regex => ""; + + "no_return" string => "no such file"; + + reports: + + "The value of a is $(a)"; + "The value of b is $(b)"; + + "The value of no_return is $(no_return)"; + +} + +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The value of a is AAAAAAAAA +#@ R: The value of b is bbb +#@ R: The value of no_return is ok +#@ R: The default value of X is I am a default value +#@ R: The default value of Y is I am a default list item 1 +#@ R: The default value of Y is I am a default list item 2 +#@ ``` +#+end_src + diff --git a/examples/defaults2.cf b/examples/defaults2.cf new file mode 100644 index 0000000000..72f2c4fba6 --- /dev/null +++ b/examples/defaults2.cf @@ -0,0 +1,68 @@ + +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Default values for variables and parameters, introduced 3.4.0 +# + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "Y" slist => { "I am list item 1", "I am list item 2" }; + + methods: + + # More useful, defaults if parameters are passed to a param bundle + + "example" usebundle => mymethod(@(example.Y)); + +} + +########################################################### + +bundle agent mymethod(list) + +{ + defaults: + + "list" slist => { "1", "2", "3"}, if_match_regex => ".*list item.*"; + # "list" string => "1" , if_match_regex => ".*list item.*"; + + + reports: + + "The value of list is $(list)"; + +} + + diff --git a/examples/defaults3.cf b/examples/defaults3.cf new file mode 100644 index 0000000000..dee49f5489 --- /dev/null +++ b/examples/defaults3.cf @@ -0,0 +1,28 @@ + + +body common control +{ + bundlesequence => { "main" }; +} + +bundle agent main +{ + methods: + + "example" usebundle => test("one","x","","$(four)"); + +} + + +bundle agent test(a,b,c,d) +{ + defaults: + + "a" string => "default a", if_match_regex => ""; + "b" string => "default b", if_match_regex => "x"; + "c" string => "default c", if_match_regex => ""; + "d" string => "default d", if_match_regex => "\$\([a-zA-Z0-9_.]+\)"; + + reports: + "a = '$(a)', b = '$(b)', c = '$(c)' d = '$(d)'"; +} diff --git a/examples/definitions.cf b/examples/definitions.cf new file mode 100644 index 0000000000..ca5e892b32 --- /dev/null +++ b/examples/definitions.cf @@ -0,0 +1,105 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Testing some variable/class definitions - note scope +# +# Use browser -f promise_output_agent.html to view +# + +body common control + +{ + bundlesequence => { "mycommon", "assign" }; +} + +########################################################### + +bundle common mycommon + +{ + classes: + + "global_class" expression => "any"; + + "another_global" xor => { "any", "linux", "solaris"}; +} + +########################################################### + +bundle agent assign + +{ + vars: + + "scalar" int => "16k"; + + # "xxx" string => readfile( "/home/mark/tmp/testfile" , "33" ); + + "ran" int => randomint(4,88); + + # "yyy" slist => { readstringlist("/home/mark/tmp/testlist","#[a-zA-Z0-9 ]*","[^a-zA-Z0-9]",15,4000) }; + # "zzz" slist => { readstringlist("/home/mark/tmp/testlist2","#[^\n]*",",",5,4000) }; + # "aaa" ilist => { readintlist("/home/mark/tmp/testilist","#[a-zA-Z0-9 ]*",",",10,4000) }; + + "dim_array" int => readstringarray("array_name","/etc/passwd","#[^\n]*",":",10,4000); + + + classes: + + # Standard aliasing + + "myclass" or => { "solaris", "linux" }; + + # got_array is a class that says whether the read was successful + # array_name[] is the lval + + # Create a distribution + + "my_dist" dist => { "10", "20", "30", "40" }; + + # + # Now like "alerts" in cf2 + # + + reports: + + "Dimension of passwd array $(dim_array)"; + + "Read item from list: $(yyy)"; + + # Any kind of rule can define classes on exit + + "Read this file: [$(xxx)] ..." + classes => persist("alertclass","20"); + +} + +###################################################################### + +body classes persist(class,time) + +{ + promise_repaired => { "$(class)" }; + persist_time => "$(time)"; + timer_policy => "absolute"; +} diff --git a/examples/deletelines.cf b/examples/deletelines.cf new file mode 100644 index 0000000000..2d9245a42e --- /dev/null +++ b/examples/deletelines.cf @@ -0,0 +1,90 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + +body common control +{ + bundlesequence => { "example" }; +} + + + +bundle agent example +{ + files: + + "/tmp/resolv.conf" # test on "/tmp/resolv.conf" # + + create => "true", + edit_line => resolver, + edit_defaults => def; + +} + + +####################################################### +# For the library +####################################################### + +bundle edit_line resolver + +{ + vars: + + "search" slist => { "search iu.hio.no cfengine.com", "nameserver 128.39.89.10" }; + + delete_lines: + + "search.*"; + + insert_lines: + + "$(search)" location => end; +} + +####################################################### + +body edit_defaults def +{ + empty_file_before_editing => "false"; + edit_backup => "false"; + max_file_size => "100000"; +} + +######################################################## + +body location start + +{ + # If not line to match, applies to whole text body + before_after => "before"; +} + +######################################################## + +body location end + +{ + # If not line to match, applies to whole text body + before_after => "after"; +} diff --git a/examples/depends_on.cf b/examples/depends_on.cf new file mode 100644 index 0000000000..aaaec1f03a --- /dev/null +++ b/examples/depends_on.cf @@ -0,0 +1,18 @@ + + +body common control +{ + bundlesequence => { "one" }; +} + +bundle agent one +{ + reports: + + "two" + depends_on => { "handle_one" }; + + "one" + handle => "handle_one"; + +} diff --git a/examples/depends_on2.cf b/examples/depends_on2.cf new file mode 100644 index 0000000000..d276e530b3 --- /dev/null +++ b/examples/depends_on2.cf @@ -0,0 +1,50 @@ + + +body common control +{ + bundlesequence => { "main" }; +} + +bundle agent main +{ + methods: + + "any" usebundle => one; + + "any" usebundle => mars:two; + +} + +bundle agent one +{ + reports: + + "two" + depends_on => { "handle_one" }; + + "one" + handle => "handle_one"; + +} + +body file control +{ + namespace => "mars"; +} + + +bundle agent two +{ + reports: + + "marstwo" + handle => "mars_two", + depends_on => { "handle_one" }; + + # Currently bugged -> CFE-3206 + # "marsone" + # handle => "handle_one"; + + "marsthree" + depends_on => { "default.handle_one", "mars_two" }; +} diff --git a/examples/difference.cf b/examples/difference.cf new file mode 100644 index 0000000000..792ab3a9ea --- /dev/null +++ b/examples/difference.cf @@ -0,0 +1,66 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "a" slist => { 1,2,3,"x" }; + "b" slist => { "x" }; + + # normal usage + "diff_between_a_and_b" slist => difference(a, b); + "diff_between_a_and_b_str" string => join(",", diff_between_a_and_b); + + # NOTE: advanced usage! + "mylist1" slist => { "a", "b" }; + "mylist2" slist => { "a", "b" }; + "$(mylist1)_str" string => join(",", $(mylist1)); + + # Here we're going to really call difference(a,a) then difference(a,b) then difference(b,a) then difference(b,b) + # We create a new variable for each difference!!! + "diff_$(mylist1)_$(mylist2)" slist => difference($(mylist1), $(mylist2)); + "diff_$(mylist1)_$(mylist2)_str" string => join(",", "diff_$(mylist1)_$(mylist2)"); + + reports: + # normal usage + "The difference between lists a and b is '$(diff_between_a_and_b_str)'"; + + # NOTE: advanced usage results! + "The difference of list '$($(mylist1)_str)' with '$($(mylist2)_str)' is '$(diff_$(mylist1)_$(mylist2)_str)'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The difference between lists a and b is '1,2,3' +#@ R: The difference of list '1,2,3,x' with '1,2,3,x' is '' +#@ R: The difference of list '1,2,3,x' with 'x' is '1,2,3' +#@ R: The difference of list 'x' with '1,2,3,x' is '' +#@ R: The difference of list 'x' with 'x' is '' +#@ ``` +#+end_src diff --git a/examples/dirname.cf b/examples/dirname.cf new file mode 100644 index 0000000000..2c7f3a29e1 --- /dev/null +++ b/examples/dirname.cf @@ -0,0 +1,42 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "apache_dir" string => dirname("/etc/apache2/httpd.conf"); + reports: + "apache conf dir = $(apache_dir)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: apache conf dir = /etc/apache2 +#@ ``` +#+end_src diff --git a/examples/disable.cf b/examples/disable.cf new file mode 100644 index 0000000000..ed54193ad5 --- /dev/null +++ b/examples/disable.cf @@ -0,0 +1,42 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "my_disable" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent my_disable +{ + + files: + + "/home/mark/tmp/test_create" + rename => disable; + + "/home/mark/tmp/rotate_my_log" + rename => rotate("4"); + +} + + diff --git a/examples/disable_and_rotate_files.cf b/examples/disable_and_rotate_files.cf new file mode 100644 index 0000000000..1220fe4745 --- /dev/null +++ b/examples/disable_and_rotate_files.cf @@ -0,0 +1,68 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Disabling / rotating files +# +####################################################### + +body common control + +{ + bundlesequence => { "example" }; +} + + +############################################ + +bundle agent example + +{ + files: + + "/home/mark/tmp/test_create" + + comment => "this rule does something", + rename => disable; + + "/home/mark/tmp/rotateme" + + rename => rotate("4"); +} + +############################################ + +body rename disable + +{ + disable => "true"; + disable_suffix => "_blownaway"; +} + +############################################ + +body rename rotate(level) + +{ + rotate => "$(level)"; +} diff --git a/examples/diskfree.cf b/examples/diskfree.cf new file mode 100644 index 0000000000..7660d3b878 --- /dev/null +++ b/examples/diskfree.cf @@ -0,0 +1,49 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + "has_space" expression => isgreaterthan($(free), 0); + + vars: + "free" int => diskfree("/tmp"); + + reports: + has_space:: + "The filesystem has free space"; + !has_space:: + "The filesystem has NO free space"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The filesystem has free space +#@ ``` +#+end_src diff --git a/examples/dollar.cf b/examples/dollar.cf new file mode 100644 index 0000000000..328edeabe9 --- /dev/null +++ b/examples/dollar.cf @@ -0,0 +1,49 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + classes: + + "some" expression => "any"; + + reports: + + some:: + + "The value of $(const.dollar)(const.dollar) is $(const.dollar)"; + + "But the value of \$(dollar) is \$(dollar)"; + +} + diff --git a/examples/edit.empty_before_use.cf b/examples/edit.empty_before_use.cf new file mode 100644 index 0000000000..fa74032b2f --- /dev/null +++ b/examples/edit.empty_before_use.cf @@ -0,0 +1,54 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + files: + + "/tmp/example-edit.empty_before_use.one" create => "true"; + "/tmp/example-edit.empty_before_use.two" create => "true"; + + "/tmp/example-edit.empty_before_use.one" + edit_line => show_edit_empty_before_use, + edit_defaults => my_empty_file_before_editing; + + "/tmp/example-edit.empty_before_use.two" + edit_line => show_edit_empty_before_use; + +} + +bundle edit_line show_edit_empty_before_use +{ + reports: + "$(with)" + with => concat( "The promise to edit '$(edit.filename)' was ", + "instructed to ignore any pre-existing content." ), + if => strcmp( "true", "$(edit.empty_before_use)"); + + "$(with)" + with => concat( "The promise to edit '$(edit.filename)' was ", + "not instructed to ignore any pre-existing content."), + if => strcmp( "false", "$(edit.empty_before_use)"); + + "$(with)" + with => concat ( "This version of CFEngine does not know if the", + "edit operation is expected to ignore pre-existing ", + "content the variable 'edit.empty_before_use' does ", + "not exist"), + unless => isvariable ( "edit.empty_before_use" ); + +} + +body edit_defaults my_empty_file_before_editing +{ + empty_file_before_editing => "true"; # The variable + # edit.empty_before_use allows this + # to be known from within an + # edit_line bundle. +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The promise to edit '/tmp/example-edit.empty_before_use.one' was instructed to ignore any pre-existing content. +#@ R: The promise to edit '/tmp/example-edit.empty_before_use.two' was not instructed to ignore any pre-existing content. +#@ ``` +#+end_src diff --git a/examples/edit.filename.cf b/examples/edit.filename.cf new file mode 100644 index 0000000000..d98eec5e18 --- /dev/null +++ b/examples/edit.filename.cf @@ -0,0 +1,54 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + files: + + "/tmp/example-edit.filename.txt" content => "Hello World!"; + + "/tmp/example-edit.filename.txt" + edit_line => show_edit_filename; + +} + +bundle edit_line show_edit_filename +{ + reports: + "$(with)" + with => concat( "I found the string 'World' in the file being ", + "edited ('$(edit.filename)')"), + if => strcmp( "false", "$(edit.empty_before_use)"); # It's probably + # useless to probe + # the content of the + # file if you are + # ignoring + # pre-existing + # content. + + "$(with)" + with => concat( "It's probably not very useful to inspect content", + "that is being thrown away." ), + if => strcmp( "true", "$(edit.empty_before_use)"); + + "$(with)" + with => concat ( "This version of CFEngine does not know if the", + "edit operation is expected to ignore pre-existing ", + "content the variable 'edit.empty_before_use' does ", + "not exist"), + unless => isvariable ( "edit.empty_before_use" ); + +} + +body edit_defaults my_empty_file_before_editing +{ + empty_file_before_editing => "true"; # The variable + # edit.empty_before_use allows this + # to be known from within an + # edit_line bundle. +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: I found the string 'World' in the file being edited ('/tmp/example-edit.filename.txt') +#@ ``` +#+end_src diff --git a/examples/edit_column_files.cf b/examples/edit_column_files.cf new file mode 100644 index 0000000000..7fc167bd8c --- /dev/null +++ b/examples/edit_column_files.cf @@ -0,0 +1,113 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# File editing +# +# Normal ordering: +# - delete +# - replace | column_edit +# - insert +# +###################################################################### + + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "bun" }; +} + +######################################################## + +bundle agent bun + +{ + vars: + + "userset" slist => { "one", "two", "three" }; + + files: + + # Make a copy of the password file + + "/home/mark/tmp/passwd" + + create => "true", + edit_line => set_user_field("mark","6","/home/dir"); + + "/home/mark/tmp/group" + + create => "true", + edit_line => append_user_field("wheel","4","@(bun.userset)"); + +} + +######################################################## + +bundle edit_line set_user_field(user,field,val) +{ + field_edits: + + "$(user).*" + + # Set field of the file to parameter + + edit_field => col(":","$(field)","$(val)","set"); +} + +######################################################## + +bundle edit_line append_user_field(user,field,allusers) +{ + vars: + + "val" slist => { @(allusers) }; + + field_edits: + + "$(user).*" + + # Set field of the file to parameter + + edit_field => col(":","$(field)","$(val)","alphanum"); + +} + +######################################## +# Bodies +######################################## + +body edit_field col(split,col,newval,method) + +{ + field_separator => "$(split)"; + select_field => "$(col)"; + value_separator => ","; + field_value => "$(newval)"; + field_operation => "$(method)"; + extend_fields => "true"; + allow_blank_fields => "true"; +} + diff --git a/examples/edit_comment_lines.cf b/examples/edit_comment_lines.cf new file mode 100644 index 0000000000..7d441e4a6c --- /dev/null +++ b/examples/edit_comment_lines.cf @@ -0,0 +1,94 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# File editing +# +# Normal ordering: +# - delete +# - replace | column_edit +# - insert +# +###################################################################### + + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + + files: + + "/home/mark/tmp/cf3_test" + + create => "true", + edit_line => myedit("second"); +} + +######################################################## + +bundle edit_line myedit(parameter) +{ + vars: + + "edit_variable" string => "private edit variable is $(parameter)"; + + + replace_patterns: + + # replace shell comments with C comments + + "#(.*)" + + replace_with => C_comment, + select_region => MySection("New section"); + +} + +######################################## +# Bodies +######################################## + +body replace_with C_comment + +{ + replace_value => "/* $(match.1) */"; # backreference 0 + occurrences => "all"; # first, last all +} + +######################################################## + +body select_region MySection(x) + +{ + select_start => "\[$(x)\]"; + select_end => "\[.*\]"; +} diff --git a/examples/edit_deletenotmatch.cf b/examples/edit_deletenotmatch.cf new file mode 100644 index 0000000000..a9af940657 --- /dev/null +++ b/examples/edit_deletenotmatch.cf @@ -0,0 +1,69 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test editfile +# +######################################################## + +# +# This assumes a file format like: +# +# [section 1] +# +# lines.... +# +# [section 2] +# +# lines... etc + + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/tmp/passwd_excerpt" + + create => "true", + edit_line => MarkNRoot; +} + +######################################################## + +bundle edit_line MarkNRoot +{ + delete_lines: + + "mark.*|root.*" not_matching => "true"; + +} + diff --git a/examples/edit_insert_fuzzylines.cf b/examples/edit_insert_fuzzylines.cf new file mode 100644 index 0000000000..4fb55e2058 --- /dev/null +++ b/examples/edit_insert_fuzzylines.cf @@ -0,0 +1,78 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +####################################################### +# +# Insert a number of lines with vague whitespace +# +####################################################### + +body common control + +{ + any:: + + bundlesequence => { "insert" }; +} + + +####################################################### + +bundle agent insert + +{ + vars: + + "v" string => " One potato"; + + files: + + "/tmp/test_insert" + + create => "true", + edit_line => Insert("$(insert.v)"); + +} + +####################################################### +# For the library +####################################################### + +bundle edit_line Insert(name) + +{ + insert_lines: + + " $(name)" + + whitespace_policy => { "ignore_leading", "ignore_embedded" }; + +} + +####################################################### + +body edit_defaults empty + +{ + empty_file_before_editing => "true"; +} diff --git a/examples/edit_insert_lines.cf b/examples/edit_insert_lines.cf new file mode 100644 index 0000000000..e0bf3b9bd0 --- /dev/null +++ b/examples/edit_insert_lines.cf @@ -0,0 +1,82 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +####################################################### +# +# Insert a number of lines +# +####################################################### + +body common control + +{ + any:: + + bundlesequence => { "insert" }; +} + + +####################################################### + +bundle agent insert + +{ + vars: + + "v" string => " + One potato + Two potato + Three potatoe + Four + "; + + files: + + "/tmp/test_insert" + + create => "true", + edit_line => Insert("$(insert.v)"), + edit_defaults => empty; + +} + +####################################################### +# For the library +####################################################### + +bundle edit_line Insert(name) + +{ + insert_lines: + + "Begin$(const.n)$(name)$(const.n)End"; + +} + +####################################################### + +body edit_defaults empty + +{ + empty_file_before_editing => "false"; +} diff --git a/examples/edit_insert_lines_silly.cf b/examples/edit_insert_lines_silly.cf new file mode 100644 index 0000000000..d86ef4a795 --- /dev/null +++ b/examples/edit_insert_lines_silly.cf @@ -0,0 +1,82 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +####################################################### +# +# Insert a number of lines +# +####################################################### + +body common control + +{ + any:: + + bundlesequence => { "insert" }; +} + + +####################################################### + +bundle agent insert + +{ + vars: + + "v" slist => { + "One potato", + "Two potato", + "Three potatoe", + "Four" + }; + + files: + + "/tmp/test_insert" + + create => "true", + edit_line => Insert("@(insert.v)"); + # edit_defaults => empty; + +} + +####################################################### +# For the library +####################################################### + +bundle edit_line Insert(name) + +{ + insert_lines: + + "$(name)"; + +} + +####################################################### + +body edit_defaults empty + +{ + empty_file_before_editing => "true"; +} diff --git a/examples/edit_passwd_file.cf b/examples/edit_passwd_file.cf new file mode 100644 index 0000000000..bc25760727 --- /dev/null +++ b/examples/edit_passwd_file.cf @@ -0,0 +1,91 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "addpasswd" }; +} + +bundle agent addpasswd +{ + vars: + + # want to set these values by the names of their array keys + + "pwd[mark]" string => "mark:x:1000:100:Mark Burgess:/home/mark:/bin/bash"; + "pwd[fred]" string => "fred:x:1001:100:Right Said:/home/fred:/bin/bash"; + "pwd[jane]" string => "jane:x:1002:100:Jane Doe:/home/jane:/bin/bash"; + + files: + + + "/tmp/passwd" + + create => "true", + edit_line => append_users_starting("addpasswd.pwd"); + +} + +############################################################ +# Library stuff +############################################################ + +bundle edit_line append_users_starting(v) + +{ + vars: + + "index" slist => getindices("$(v)"); + + classes: + + "add_$(index)" not => userexists("$(index)"); + + insert_lines: + + "$($(v)[$(index)])" + + if => "add_$(index)"; + +} + +############################################################ + +bundle edit_line append_groups_starting(v) + +{ + vars: + + "index" slist => getindices("$(v)"); + + classes: + + "add_$(index)" not => groupexists("$(index)"); + + insert_lines: + + "$($(v)[$(index)])" + + if => "add_$(index)"; + +} diff --git a/examples/edit_passwd_file_basic.cf b/examples/edit_passwd_file_basic.cf new file mode 100644 index 0000000000..d6c105efe3 --- /dev/null +++ b/examples/edit_passwd_file_basic.cf @@ -0,0 +1,49 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "edit_passwd" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent edit_passwd +{ + + vars: + + "userset" slist => { "user1", "user2", "user3" }; + + files: + + "/etc/passwd" + edit_line => + set_user_field("mark","7","/set/this/shell"); + + + "/etc/group" + edit_line => + append_user_field("root","4","@(main.userset)"); + +} + diff --git a/examples/edit_replace_string.cf b/examples/edit_replace_string.cf new file mode 100644 index 0000000000..17e5c93152 --- /dev/null +++ b/examples/edit_replace_string.cf @@ -0,0 +1,94 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# File editing +# +# Normal ordering: +# - delete +# - replace | column_edit +# - insert +# +###################################################################### + + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + + files: + + "/tmp/replacestring" + + create => "true", + edit_line => myedit("second"); +} + +######################################################## + +bundle edit_line myedit(parameter) +{ + vars: + + "edit_variable" string => "private edit variable is $(parameter)"; + + + replace_patterns: + + # replace shell comments with C comments + + "puppet" + + replace_with => With("cfengine 3"); + +} + +######################################## +# Bodies +######################################## + +body replace_with With(x) + +{ + replace_value => "$(x)"; + occurrences => "first"; +} + +######################################## + +body select_region MySection(x) + +{ + select_start => "\[$(x)\]"; + select_end => "\[.*\]"; +} + diff --git a/examples/edit_sectioned_file.cf b/examples/edit_sectioned_file.cf new file mode 100644 index 0000000000..ec38265a0e --- /dev/null +++ b/examples/edit_sectioned_file.cf @@ -0,0 +1,121 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test editfile +# +######################################################## + +# +# This assumes a file format like: +# +# [section 1] +# +# lines.... +# +# [section 2] +# +# lines... etc + + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + vars: + + "v1" string => "numerical backreference"; + + files: + + "/home/mark/tmp/cf3_test" + + create => "true", + edit_line => AppendIfNoLine("cfengine tcp 5308","second"); + + +} + +######################################################## + +bundle edit_line AppendIfNoLine(parameter,two) +{ + vars: + + "list" slist => { "1", "2", "3" }; + + insert_lines: + + "$(parameter) and $(two)-$(list)" location => append; + + "NEW Special-insert!!!" select_region => MySection("New section"); + "set variable = value" select_region => MySection("New section"); + + + "/home/mark/tmp/insert" insert_type => "file", + expand_scalars => "true", + select_region => MySection("New section"); + + delete_lines: + + # "l.*"; + # "NEW.*" select_region => MySection("New section"); + + # Delete LinesStarting in file + +} + +######################################################## + +body location append + +{ + # If not line to match, applies to whole text body + before_after => "after"; +} + +######################################################## + +body location after(x) + +{ + # If not line to match, applies to whole text body + #select_line_matching => "$(x)"; + before_after => "after"; +} + +######################################################## + +body select_region MySection(x) + +{ + select_start => "\[$(x)\]"; + select_end => "\[.*\]"; +} diff --git a/examples/edit_setvar.cf b/examples/edit_setvar.cf new file mode 100644 index 0000000000..c7e21da9fd --- /dev/null +++ b/examples/edit_setvar.cf @@ -0,0 +1,74 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Edit variable = value in a text file +# +####################################################### + +body common control + +{ + bundlesequence => { "example" }; +} + + +####################################################### + +bundle agent example + +{ + vars: + + "v[variable_1]" string => "value_1"; + "v[variable_2]" string => "value_2"; + + files: + + "/tmp/test_setvar" + + edit_line => setvars_v1("testsetvar.v"); + +} + +####################################################### +# For the library +####################################################### + +bundle edit_line setvars_v1(contexted_array_name) + +{ + vars: + + "parameter_name" slist => getindices("$(contexted_array_name)"); + + delete_lines: + + "$(parameter_name).*"; + + insert_lines: + + "$(parameter_name) = $($(contexted_array_name)[$(parameter_name)])"; + +} + diff --git a/examples/edit_template.cf b/examples/edit_template.cf new file mode 100644 index 0000000000..e08bca448b --- /dev/null +++ b/examples/edit_template.cf @@ -0,0 +1,21 @@ + +body common control +{ + bundlesequence => { "main" }; +} + +bundle agent main +{ + vars: + + "here" string => "/home/a10004/LapTop/cfengine/core/examples"; + + "list" slist => { "one", "two", "three" }; + + files: + + "/tmp/output" + + create => "true", + edit_template => "$(here)/input.edittemplate"; +} diff --git a/examples/edit_triggerclass.cf b/examples/edit_triggerclass.cf new file mode 100644 index 0000000000..d53486db39 --- /dev/null +++ b/examples/edit_triggerclass.cf @@ -0,0 +1,102 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +####################################################### +# +# Insert a number of lines and trigger a followup if edited +# +####################################################### + +body common control + +{ + any:: + + bundlesequence => { "insert" }; +} + + +####################################################### + +bundle agent insert + +{ + vars: + + "v" string => " + One potato + Two potato + Three potahto + Four + "; + + files: + + "/tmp/test_insert" + + edit_line => Insert("$(insert.v)"), + edit_defaults => empty, + classes => trigger("edited"); + + commands: + + edited:: + + "/bin/echo make bananas"; + + reports: + + edited:: + + "The potatoes are bananas"; + +} + +####################################################### +# For the library +####################################################### + +bundle edit_line Insert(name) + +{ + insert_lines: + + "Begin$(const.n) $(name)$(const.n)End"; + +} + +####################################################### + +body edit_defaults empty + +{ + empty_file_before_editing => "true"; +} + +####################################################### + +body classes trigger(x) + +{ + promise_repaired => { "$(x)" }; +} diff --git a/examples/edit_xml.cf b/examples/edit_xml.cf new file mode 100644 index 0000000000..611a736595 --- /dev/null +++ b/examples/edit_xml.cf @@ -0,0 +1,89 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +############################################################################### +#+begin_src cfengine3 +# for more info: http://cfengine.com/docs/master/reference-promise-types-edit_xml.html + +body common control +{ + bundlesequence => { run }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent run +{ + vars: + "file" string => "$(this.promise_filename).txt"; + methods: + "rm" usebundle => rmxml; + "make" usebundle => makexml; + "use" usebundle => maintainxml; + "report" usebundle => reportxml; +} + +bundle agent rmxml +{ + files: + "$(run.file)" delete => tidy; +} + +bundle agent makexml +{ + files: + "$(run.file)" + comment => "Create xml file", + create => "true", + edit_defaults => empty, + edit_xml => xml_insert_tree_nopath('cfe_alias'); +} + +bundle agent reportxml +{ + vars: + "data" string => readfile($(run.file), 4k); + reports: + "Final XML is $(data)"; +# R: Final XML is +# newhosttextnewalias + +} + +bundle agent maintainxml +{ + files: + "$(run.file)" + comment => "Maintain xml file: set the Alias node", + create => "false", + edit_xml => xml_set_value("newalias","/Host/Alias"); + + "$(run.file)" + comment => "Maintain xml file: replace a Host node with a name attribute = cfe_host", + create => "false", + edit_xml => xml_set_value("newhosttext","/Host[@name='cfe_host']"); + + "$(run.file)" + comment => "Maintain xml file: replace a Host node's name attribute", + create => "false", + edit_xml => xml_set_attribute("name", "newhostname", "/Host"); +} +#+end_src diff --git a/examples/ensure_line_present_prepend_append.cf b/examples/ensure_line_present_prepend_append.cf new file mode 100644 index 0000000000..75005cee03 --- /dev/null +++ b/examples/ensure_line_present_prepend_append.cf @@ -0,0 +1,41 @@ +# Note that this example assumes that the masterfiles policy framework is installed +# in inputs in your user's working directory (e.g. /var/cfengine/inputs for root). + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "line_prepend", "line_append" , "output" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + + +bundle agent line_prepend +{ + files: + "/tmp/test_line_prepend" + edit_line => prepend_if_no_line("I am the first line"), + create => "true"; +} + + +bundle agent line_append +{ + files: + "/tmp/test_line_append" + edit_line => append_if_no_line("I am the last line"), + create => "true"; +} + +bundle agent output +{ + vars: + "prepend_file" string => readfile("/tmp/test_line_prepend", 1000); + "append_file" string => readfile("/tmp/test_line_append", 1000); + + reports: + "Contents of /tmp/test_line_prepend:"; + "$(prepend_file)"; + "Contents of /tmp/test_line_append:"; + "$(append_file)"; +} +#+end_src diff --git a/examples/env.cf b/examples/env.cf new file mode 100644 index 0000000000..111bafc5a9 --- /dev/null +++ b/examples/env.cf @@ -0,0 +1,17 @@ + +body common control +{ + bundlesequence => { "one" }; +} + +body agent control +{ + environment => { "A=123", "B=456", "PGK_PATH=/tmp"}; +} + +bundle agent one +{ + commands: + + "/usr/bin/env"; +} diff --git a/examples/epimenides.cf b/examples/epimenides.cf new file mode 100644 index 0000000000..7503e871d1 --- /dev/null +++ b/examples/epimenides.cf @@ -0,0 +1,39 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "g" }; +} + +bundle common g +{ + vars: + + "head" string => "Swallow my ${tail}"; + "tail" string => "behind my "; + + reports: + + "Go $(head)"; +} diff --git a/examples/escape.cf b/examples/escape.cf new file mode 100644 index 0000000000..2ff234e723 --- /dev/null +++ b/examples/escape.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "ip" string => "10.123.321.250"; + "escaped" string => escape($(ip)); + + reports: + "escaped $(ip) = $(escaped)"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: escaped 10.123.321.250 = 10\.123\.321\.250 +#@ ``` +#+end_src diff --git a/examples/eval.cf b/examples/eval.cf new file mode 100644 index 0000000000..b537d1fdf9 --- /dev/null +++ b/examples/eval.cf @@ -0,0 +1,80 @@ +#+begin_src cfengine3 +body common control +{ + bundlesequence => { run }; +} +body agent control +{ + inform => "true"; +} +bundle agent run +{ + vars: + "values[0]" string => "x"; # bad + "values[1]" string => "+ 200"; # bad + "values[2]" string => "200 + 100"; + "values[3]" string => "200 - 100"; + "values[4]" string => "- - -"; # bad + "values[5]" string => "2 + 3 - 1"; + "values[6]" string => ""; # 0 + "values[7]" string => "3 / 0"; # inf but not an error + "values[8]" string => "3^3"; + # "values[9]" string => "-1^2.1"; # 'nan' or '-nan' (on some platforms) + "values[10]" string => "sin(20)"; + "values[11]" string => "cos(20)"; + "values[19]" string => "20 % 3"; # remainder + "values[20]" string => "sqrt(0.2)"; + "values[21]" string => "ceil(3.5)"; + "values[22]" string => "floor(3.4)"; + "values[23]" string => "abs(-3.4)"; + "values[24]" string => "-3.4 == -3.4"; + "values[25]" string => "-3.400000 == -3.400001"; + "values[26]" string => "e"; + "values[27]" string => "pi"; + "values[28]" string => "100m"; # 100 million + "values[29]" string => "100k"; # 100 thousand + + "indices" slist => sort( getindices("values"), int); + + "eval[$(indices)]" string => eval("$(values[$(indices)])", "math", "infix"); + + reports: + "math/infix eval('$(values[$(indices)])') = '$(eval[$(indices)])'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ info: eval error: expression could not be parsed (input 'x') +#@ info: eval error: expression could not be parsed (input '+ 200') +#@ info: eval error: expression could not be parsed (input '- - -') +#@ R: math/infix eval('x') = '' +#@ R: math/infix eval('+ 200') = '' +#@ R: math/infix eval('200 + 100') = '300.000000' +#@ R: math/infix eval('200 - 100') = '100.000000' +#@ R: math/infix eval('- - -') = '' +#@ R: math/infix eval('2 + 3 - 1') = '4.000000' +#@ R: math/infix eval('') = '0.000000' +#@ R: math/infix eval('3 / 0') = 'inf' +#@ R: math/infix eval('3^3') = '27.000000' +#@ R: math/infix eval('sin(20)') = '0.912945' +#@ R: math/infix eval('cos(20)') = '0.408082' +#@ R: math/infix eval('20 % 3') = '2.000000' +#@ R: math/infix eval('sqrt(0.2)') = '0.447214' +#@ R: math/infix eval('ceil(3.5)') = '4.000000' +#@ R: math/infix eval('floor(3.4)') = '3.000000' +#@ R: math/infix eval('abs(-3.4)') = '3.400000' +#@ R: math/infix eval('-3.4 == -3.4') = '1.000000' +#@ R: math/infix eval('-3.400000 == -3.400001') = '0.000000' +#@ R: math/infix eval('e') = '2.718282' +#@ R: math/infix eval('pi') = '3.141593' +#@ R: math/infix eval('100m') = '100000000.000000' +#@ R: math/infix eval('100k') = '100000.000000' +#@ info: eval error: expression could not be parsed (input 'x') +#@ info: eval error: expression could not be parsed (input '+ 200') +#@ info: eval error: expression could not be parsed (input '- - -') +#@ info: eval error: expression could not be parsed (input 'x') +#@ info: eval error: expression could not be parsed (input '+ 200') +#@ info: eval error: expression could not be parsed (input '- - -') +#@ ``` +#+end_src diff --git a/examples/every.cf b/examples/every.cf new file mode 100644 index 0000000000..58e9c50cf6 --- /dev/null +++ b/examples/every.cf @@ -0,0 +1,117 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test + +{ + classes: + "every_dot_star" expression => every(".*", test); + "every_dot" expression => every(".", test); + "every_number" expression => every("[0-9]", test); + + "every2_dot_star" expression => every(".*", test2); + "every2_dot" expression => every(".", test2); + "every2_number" expression => every("[0-9]", test2); + + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; + + "test2" data => parsejson('[1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three",]'); + + reports: + "The test list is $(test)"; + + every_dot_star:: + "every() test passed: every element matches '.*'"; + !every_dot_star:: + "every() test failed: not every element matches '.*'"; + every_number:: + "every() test failed: every element matches '[0-9]'"; + !every_number:: + "every() test passed: not every element matches '[0-9]'"; + every_dot:: + "every() test failed: every element matches '.'"; + !every_dot:: + "every() test passed: not every element matches '.'"; + + "The test2 list is $(test2)"; + every2_dot_star:: + "every() test2 passed: every element matches '.*'"; + !every2_dot_star:: + "every() test2 failed: not every element matches '.*'"; + every2_number:: + "every() test2 failed: every element matches '[0-9]'"; + !every2_number:: + "every() test2 passed: not every element matches '[0-9]'"; + every2_dot:: + "every() test2 failed: every element matches '.'"; + !every2_dot:: + "every() test2 passed: not every element matches '.'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The test list is 1 +#@ R: The test list is 2 +#@ R: The test list is 3 +#@ R: The test list is one +#@ R: The test list is two +#@ R: The test list is three +#@ R: The test list is long string +#@ R: The test list is four +#@ R: The test list is fix +#@ R: The test list is six +#@ R: every() test passed: every element matches '.*' +#@ R: every() test passed: not every element matches '[0-9]' +#@ R: every() test passed: not every element matches '.' +#@ R: The test2 list is 1 +#@ R: The test2 list is 2 +#@ R: The test2 list is 3 +#@ R: The test2 list is one +#@ R: The test2 list is two +#@ R: The test2 list is three +#@ R: The test2 list is long string +#@ R: The test2 list is four +#@ R: The test2 list is fix +#@ R: The test2 list is six +#@ R: every() test2 passed: every element matches '.*' +#@ R: every() test2 passed: not every element matches '[0-9]' +#@ R: every() test2 passed: not every element matches '.' +#@ ``` +#+end_src diff --git a/examples/exec_args.cf b/examples/exec_args.cf new file mode 100644 index 0000000000..152ba55eea --- /dev/null +++ b/examples/exec_args.cf @@ -0,0 +1,48 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Test the symmetry between using / not using args => +# Locks should prevent these from being run twice, all else being equal. +# + +body common control +{ + bundlesequence => { "main" }; +} + +bundle agent main +{ + + vars: + + "testlist" slist => { "apple", "banana", "carrot" }; + + commands: + + "/bin/echo test1 $(testlist)"; + + "/bin/echo test1" + + args => "$(testlist)"; + +} diff --git a/examples/exec_in_sequence.cf b/examples/exec_in_sequence.cf new file mode 100644 index 0000000000..6c302c1d3f --- /dev/null +++ b/examples/exec_in_sequence.cf @@ -0,0 +1,81 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test execution +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + vars: + + "size" int => "46k"; + "rand" int => randomint("33","$(size)"); + + commands: + + "/bin/echo" + args => "Hello world - $(size)/$(rand)", + contain => standard, + classes => cdefine("followup","alert"); + + followup:: + + "/bin/ls" + contain => standard; + + reports: + + alert:: + + "What happened?"; + +} + +###################################################################### + +body contain standard + +{ + exec_owner => "mark"; + useshell => "useshell"; +} + +###################################################################### + +body classes cdefine(class,alert) + +{ + promise_repaired => { "$(class)" }; + repair_failed => { "$(alert)" }; +} diff --git a/examples/execd.cf b/examples/execd.cf new file mode 100644 index 0000000000..e638ee875a --- /dev/null +++ b/examples/execd.cf @@ -0,0 +1,38 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { }; +} + +body executor control + +{ + mailto => "MARK@iu.hio.no"; + mailfrom => "cfengine@domain.tld"; + smtpserver => "MAIL-out.hio.no"; + mailmaxlines => "50"; + schedule => { "Min00_05", "Min30_35" }; + + exec_command => "/var/cfengine/bin/cf-agent"; +} diff --git a/examples/execresult.cf b/examples/execresult.cf new file mode 100644 index 0000000000..b78b7eb01c --- /dev/null +++ b/examples/execresult.cf @@ -0,0 +1,82 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ rm -rf /tmp/testhere +#@ mkdir -p /tmp/testhere +#@ touch /tmp/testhere/a +#@ touch /tmp/testhere/b +#@ touch /tmp/testhere/c +#@ touch /tmp/testhere/d +#@ touch /tmp/testhere/e +#@ echo "#!/usr/bin/env sh" >/tmp/testhere/echo-stdout-and-stderr +#@ echo "echo stderr >&2" >>/tmp/testhere/echo-stdout-and-stderr +#@ echo "echo stdout" >>/tmp/testhere/echo-stdout-and-stderr +#@ chmod +x /tmp/testhere/echo-stdout-and-stderr +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "my_result" + string => execresult("/bin/ls /tmp/testhere", noshell); + + "my_result_with_stdout_and_stderr" + string => execresult("/tmp/testhere/echo-stdout-and-stderr", noshell); + + "my_result_with_stdout" + string => execresult("/tmp/testhere/echo-stdout-and-stderr 2>/dev/null", useshell); + + "my_result_with_stderr" + string => execresult("/tmp/testhere/echo-stdout-and-stderr 1>/dev/null", useshell); + + reports: + "/bin/ls /tmp/testhere returned '$(my_result)'"; + "my_result_with_stdout_and_stderr == '$(my_result_with_stdout_and_stderr)'"; + "my_result_with_stdout == '$(my_result_with_stdout)'"; + "my_result_with_stderr == '$(my_result_with_stderr)'"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /bin/ls /tmp/testhere returned 'a +#@ b +#@ c +#@ d +#@ e +#@ echo-stdout-and-stderr' +#@ R: my_result_with_stdout_and_stderr == 'stderr +#@ stdout' +#@ R: my_result_with_stdout == 'stdout' +#@ R: my_result_with_stderr == 'stderr' +#@ ``` +#+end_src diff --git a/examples/execresult_as_data.cf b/examples/execresult_as_data.cf new file mode 100644 index 0000000000..f9b3fc2f41 --- /dev/null +++ b/examples/execresult_as_data.cf @@ -0,0 +1,29 @@ +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "my_data" + data => execresult_as_data("echo 'hello'", "useshell", "both"); + "my_json_string" + string => storejson(my_data); + + reports: + "echo 'hello' returned '$(my_json_string)'"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: echo 'hello' returned '{ +#@ "exit_code": 0, +#@ "output": "hello" +#@ }' +#@ ``` +#+end_src diff --git a/examples/expand.cf b/examples/expand.cf new file mode 100644 index 0000000000..df926e12bf --- /dev/null +++ b/examples/expand.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "component" slist => { "cf-monitord", "cf-serverd", "cf-execd" }; + + processes: + + "$(component)" restart_class => canonify("start_$(component)"); + + commands: + + "/bin/echo /var/cfengine/bin/$(component)" + + if => canonify("start_$(component)"); + + +} + diff --git a/examples/failedcommand.cf b/examples/failedcommand.cf new file mode 100644 index 0000000000..ca03c36b05 --- /dev/null +++ b/examples/failedcommand.cf @@ -0,0 +1,31 @@ +body common control +{ + bundlesequence => { "cmdtest" }; +} + +bundle agent cmdtest +{ + files: + "/tmp/test" + copy_from => copy("/etc/passwd"); + + + "/tmp/test" + classes => example, + transformer => "/bin/grep -q lkajfo999999 $(this.promiser)"; + + reports: + hasfailed:: + "The files-promise failed!"; +} + +body classes example +{ + failed_returncodes => { "1" }; + repair_failed => { "hasfailed" }; +} + +body copy_from copy(file) +{ + source => "$(file)"; +} diff --git a/examples/file_change_detection.cf b/examples/file_change_detection.cf new file mode 100644 index 0000000000..5af7ba9d04 --- /dev/null +++ b/examples/file_change_detection.cf @@ -0,0 +1,88 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple backgrounding +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +body agent control + +{ + agentaccess => { "mark", "root" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp" -> "me" + + changes => tripwire, + depth_search => recurse("inf"), + action => background; + + "/home/mark/LapTop/words" -> "you" + + changes => tripwire, + depth_search => recurse("inf"); + +} + + +######################################################### + +body changes tripwire + +{ + hash => "md5"; + report_changes => "content"; + update_hashes => "true"; +} + +######################################################### + +body action background + +{ + background => "true"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} diff --git a/examples/file_hash.cf b/examples/file_hash.cf new file mode 100644 index 0000000000..3fc1ce94c6 --- /dev/null +++ b/examples/file_hash.cf @@ -0,0 +1,64 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo 1 > /tmp/1 +#@ echo 2 > /tmp/2 +#@ echo 3 > /tmp/3 +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example + +{ + vars: + + "md5" string => file_hash("/tmp/1","md5"); + "sha256" string => file_hash("/tmp/2","sha256"); + "sha384" string => hash("/tmp/3","sha384"); + "sha512" string => hash("/tmp/3","sha512"); + + reports: + + "'1\n' hashed to: md5 $(md5)"; + "'2\n' hashed to: sha256 $(sha256)"; + "'3\n' hashed to: sha384 $(sha384)"; + "'3\n' hashed to: sha512 $(sha512)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: '1\n' hashed to: md5 b026324c6904b2a9cb4b88d6d61c81d1 +#@ R: '2\n' hashed to: sha256 53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 +#@ R: '3\n' hashed to: sha384 54f7379844b41bf513c0557a7195ca96a8ac90d0f8cc87d3607ef7ab593a7c61732759387afaabaf72ca2c0bd599373e +#@ R: '3\n' hashed to: sha512 48b3c46b24db82059b5c87603066cf8d2165837d66e268286feb384644c808c06edf99aeaca0d879f4ee6ec70ebfaa0b98d5b77c12f7c0a68de3f7302dec6e21 +#@ ``` +#+end_src diff --git a/examples/file_owner_list_template.cf b/examples/file_owner_list_template.cf new file mode 100644 index 0000000000..19378897a1 --- /dev/null +++ b/examples/file_owner_list_template.cf @@ -0,0 +1,60 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# List substitution in bodies +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + vars: + + "usernames" slist => { "one", "two", "three" }; + + files: + + "/home/mark/tmp/test_plain" + + perms => users("@(usernames)"), + create => "true"; + +} + +######################################################### + +body perms users(x) + +{ + mode => "0640"; + owners => { @(x) }; +} diff --git a/examples/fileexists.cf b/examples/fileexists.cf new file mode 100644 index 0000000000..008cee6c9e --- /dev/null +++ b/examples/fileexists.cf @@ -0,0 +1,49 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + # this.promise_filename has the currently-executed file, so it + # better exist! + "exists" expression => fileexists($(this.promise_filename)); + "exists_etc_passwd" expression => fileexists("/etc/passwd"); + + reports: + + exists:: + + "I exist! I mean, file exists!"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: I exist! I mean, file exists! +#@ ``` +#+end_src diff --git a/examples/filenames.cf b/examples/filenames.cf new file mode 100644 index 0000000000..0ed8372349 --- /dev/null +++ b/examples/filenames.cf @@ -0,0 +1,47 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# filenames +# + +body common control + +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + files: + + + "/etc/passwd" + + create => "true"; + + "C:\etc\passwd" + create => "true"; + + "etc/passwd" + create => "true"; +} diff --git a/examples/fileperms.cf b/examples/fileperms.cf new file mode 100644 index 0000000000..585dfaf519 --- /dev/null +++ b/examples/fileperms.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control + +{ + + bundlesequence => { + "main" + }; + +} + +######################################################### + +bundle agent main + +{ + + files: + + "/tmp/foo" perms => p("at","0750"); + +} + +######################################################### + +body perms p(user,mode) + +{ + owners => { "$(user)" }; + mode => "$(mode)"; +} diff --git a/examples/files-content-if-fileexists.cf b/examples/files-content-if-fileexists.cf new file mode 100644 index 0000000000..cde6527bae --- /dev/null +++ b/examples/files-content-if-fileexists.cf @@ -0,0 +1,23 @@ +#+begin_src prep +#@ ``` +#@ rm -f /tmp/hello && touch /tmp/hello +#@ ``` +#+end_src +#+begin_src cfengine3 +body agent control +{ + inform => "true"; +} +bundle agent __main__ +{ + files: + "/tmp/hello" + content => "Hello, CFEngine", + if => fileexists("/tmp/hello"); +} +#+end_src +#+begin_src example_output +#@ ``` +#@ info: Updated file '/tmp/hello' with content 'Hello, CFEngine' +#@ ``` +#+end_src diff --git a/examples/files-content-with.cf b/examples/files-content-with.cf new file mode 100644 index 0000000000..6034773507 --- /dev/null +++ b/examples/files-content-with.cf @@ -0,0 +1,24 @@ +#+begin_src prep +#@ ``` +#@ test -e /tmp/hello && rm /tmp/hello +#@ ``` +#+end_src +#+begin_src cfengine3 +body agent control +{ + inform => "true"; +} +bundle agent __main__ +{ + files: + "/tmp/hello" + content => "Output from stat: $(with)", + with => execresult( 'stat -c "%U" /', "useshell" ); +} +#+end_src +#+begin_src example_output +#@ ``` +#@ info: Created file '/tmp/hello', mode 0600 +#@ info: Updated file '/tmp/hello' with content 'Output from stat: root' +#@ ``` +#+end_src diff --git a/examples/files-content.cf b/examples/files-content.cf new file mode 100644 index 0000000000..0adfd9b06d --- /dev/null +++ b/examples/files-content.cf @@ -0,0 +1,23 @@ +#+begin_src prep +#@ ``` +#@ test -e /tmp/hello && rm -f /tmp/hello +#@ ``` +#+end_src +#+begin_src cfengine3 +body agent control +{ + inform => "true"; +} +bundle agent __main__ +{ + files: + "/tmp/hello" + content => "Hello, CFEngine"; +} +#+end_src +#+begin_src example_output +#@ ``` +#@ info: Created file '/tmp/hello', mode 0600 +#@ info: Updated file '/tmp/hello' with content 'Hello, CFEngine' +#@ ``` +#+end_src diff --git a/examples/files-create-class-expression.cf b/examples/files-create-class-expression.cf new file mode 100644 index 0000000000..70f43bada9 --- /dev/null +++ b/examples/files-create-class-expression.cf @@ -0,0 +1,23 @@ +#+begin_src prep +#@ ``` +#@ test -e /tmp/linux && rm /tmp/linux +#@ ``` +#+end_src +#+begin_src cfengine3 +body agent control +{ + inform => "true"; +} +bundle agent __main__ +{ + files: + linux:: + "/tmp/linux" + create => "true"; +} +#+end_src +#+begin_src example_output +#@ ``` +#@ info: Created file '/tmp/linux', mode 0600 +#@ ``` +#+end_src diff --git a/examples/files_auto_define.cf b/examples/files_auto_define.cf new file mode 100644 index 0000000000..679d0bf5ec --- /dev/null +++ b/examples/files_auto_define.cf @@ -0,0 +1,49 @@ +# Example illustrating how body agent control files_auto_define works. +#+begin_src prep +#@ ``` +#@ # Ensure that the files used in the example do not exist before running the example +#@ rm -f /tmp/example_files_auto_define.txt +#@ rm -f /tmp/source_file.txt +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body agent control +{ + inform => "true"; # So that we can easily see class definition + files_auto_define => { ".*" }; # Trigger for any copied file +} +bundle agent main +{ + + files: + "/tmp/source_file.txt" + content => "Hello World!"; + + "/tmp/example_files_auto_define.txt" + copy_from => local_dcp( "/tmp/source_file.txt" ); + + reports: + "Defined '$(with)', the canonified form of 'auto_/tmp/example_files_auto_define.txt'" + with => canonify( "auto_/tmp/example_files_auto_define.txt"), + if => canonify( "auto_/tmp/example_files_auto_define.txt"); +} +# Copied from the standard library to make self-contained. +body copy_from local_dcp(from) +{ + source => "$(from)"; + compare => "digest"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ info: Created file '/tmp/source_file.txt', mode 0600 +#@ info: Updated file '/tmp/source_file.txt' with content 'Hello World!' +#@ info: Copied file '/tmp/source_file.txt' to '/tmp/example_files_auto_define.txt.cfnew' (mode '600') +#@ info: Moved '/tmp/example_files_auto_define.txt.cfnew' to '/tmp/example_files_auto_define.txt' +#@ info: Updated file '/tmp/example_files_auto_define.txt' from 'localhost:/tmp/source_file.txt' +#@ info: Auto defining class 'auto__tmp_example_files_auto_define_txt' +#@ R: Defined 'auto__tmp_example_files_auto_define_txt', the canonified form of 'auto_/tmp/example_files_auto_define.txt' +#@ ``` +#+end_src diff --git a/examples/files_content.cf b/examples/files_content.cf new file mode 100644 index 0000000000..285ce4a4a5 --- /dev/null +++ b/examples/files_content.cf @@ -0,0 +1,46 @@ +#+begin_src cfengine3 +bundle agent example_file_content +# @brief Example showing files content +{ + vars: + "my_content" + string => "Hello from var!"; + + files: + "/tmp/hello_string" + create => "true", + content => "Hello from string!"; + + "/tmp/hello_var" + create => "true", + content => "$(my_content)"; + + reports: + "/tmp/hello_string" + printfile => cat( $(this.promiser) ); + "/tmp/hello_var" + printfile => cat( $(this.promiser) ); +} + +body printfile cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle agent __main__ +{ + methods: "example_file_content"; +} +############################################################################### +#+end_src +#+begin_src example_output +#@ ``` +#@ R: /tmp/hello_string +#@ R: Hello from string! +#@ R: /tmp/hello_var +#@ R: Hello from var! +#@ ``` +#+end_src diff --git a/examples/files_depth_search_include_basedir.cf b/examples/files_depth_search_include_basedir.cf new file mode 100644 index 0000000000..52e35dafa9 --- /dev/null +++ b/examples/files_depth_search_include_basedir.cf @@ -0,0 +1,131 @@ +#+begin_src prep +#@ ``` +#@ rm -rf /tmp/CFE-3217 +#@ mkdir -p /tmp/CFE-3217/test-delete-nobasedir/one/two/three +#@ mkdir -p /tmp/CFE-3217/test-delete/one/two/three +#@ mkdir -p /tmp/CFE-3217/test-perms/one/two/three +#@ mkdir -p /tmp/CFE-3217/test-perms-nobasedir/one/two/three +#@ touch /tmp/CFE-3217/test-delete-nobasedir/one/two/three/file +#@ touch /tmp/CFE-3217/test-delete/one/two/three/file +#@ touch /tmp/CFE-3217/test-perms/one/two/three/file +#@ touch /tmp/CFE-3217/test-perms-nobasedir/one/two/three/file +#@ touch /tmp/CFE-3217/test-delete-nobasedir/file +#@ touch /tmp/CFE-3217/test-delete/file +#@ touch /tmp/CFE-3217/test-perms/file +#@ touch /tmp/CFE-3217/test-perms-nobasedir/file +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent main +# @brief Example showing how to promise permissions recursively and promise a directory tree is empty. It illustrates the behavior of `include_basedir` in `depth_search` bodies and that the delete ignores `include_basedir`. +{ + files: + "/tmp/CFE-3217/test-delete/." -> { "CFE-3217", "CFE-3218" } + depth_search => aggressive("true"), + file_select => all, + delete => tidy, + comment => "include_basedir => 'true' will not result in thd promised directory being removed."; + + "/tmp/CFE-3217/test-delete-nobasedir/." + depth_search => aggressive("false"), + file_select => all, + delete => tidy, + comment => "include_basedir => 'false' will not result in thd promised directory being removed."; + + "/tmp/CFE-3217/test-perms/." + perms => m(555), + depth_search => aggressive("true"), + file_select => all, + comment => "include_basedir => 'true' results in thd promised directory having permissions managed as well."; + + "/tmp/CFE-3217/test-perms-nobasedir/." -> { "CFE-3217" } + perms => m(555), + depth_search => aggressive("false"), + file_select => all, + comment => "include_basedir => 'false' results in thd promised directory not having permissions managed."; + + reports: + + "delete => tidy"; + "/tmp/CFE-3217/test-delete present despite include_basedir => 'true'" + if => isdir("/tmp/CFE-3217/test-delete"); + "/tmp/CFE-3217/test-delete-nobasedir present as expected with include_basedir => 'false'" + if => isdir("/tmp/CFE-3217/test-delete-nobasedir"); + "/tmp/CFE-3217/test-delete absent, unexpectedly" + unless => isdir("/tmp/CFE-3217/test-delete"); + "/tmp/CFE-3217/test-delete-nobasedir absent, unexpectedly" + unless => isdir("/tmp/CFE-3217/test-delete-nobasedir"); + + + "perms => m(555)"; + "/tmp/CFE-3217/test-perms $(with), as expected with include_basedir => 'true'" + with => filestat( "/tmp/CFE-3217/test-perms", modeoct ), + if => strcmp( filestat( "/tmp/CFE-3217/test-perms", modeoct ), "40555" ); + + "/tmp/CFE-3217/test-perms-nobasedir $(with), not 555, as expected with include_basedir => 'false'" + with => filestat( "/tmp/CFE-3217/test-perms-nobasedir", modeoct ), + unless => strcmp( filestat( "/tmp/CFE-3217/test-perms-nobasedir", modeoct ), "40555" ); +} + +body depth_search aggressive(include_basedir) +# @brief Search for files recursively from promiser traversing synmlinks and filesystem boundaries. +{ + depth => "inf"; + # exclude_dirs => { @(exclude_dirs) }; + include_basedir => "$(include_basedir)"; + # include_dirs => { @(include_dirs) }; + # inherit_from => "$(inherit_from)"; + # meta => "$(meta)"; meta attribute inside the depth_search body? It's not documented. TODO!? + rmdeadlinks => "false"; # Depth search removes dead links, this seems like something that should be in delete body. TODO!? + traverse_links => "true"; + xdev => "true"; + +} + +#@ Inlined bodies from the stdlib in the Masterfiles Policy Framework + +body file_select all +# @brief Select all file system entries +{ + leaf_name => { ".*" }; + file_result => "leaf_name"; +} + +body delete tidy +# @brief Delete the file and remove empty directories +# and links to directories +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +body perms m(mode) +# @brief Set the file mode +# @param mode The new mode +{ + mode => "$(mode)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ info: Deleted file '/tmp/CFE-3217/test-delete/./one/two/three/file' +#@ info: Deleted directory '/tmp/CFE-3217/test-delete/./one/two/three' +#@ info: Deleted directory '/tmp/CFE-3217/test-delete/./one/two' +#@ info: Deleted directory '/tmp/CFE-3217/test-delete/./one' +#@ info: Deleted file '/tmp/CFE-3217/test-delete/./file' +#@ info: Deleted file '/tmp/CFE-3217/test-delete-nobasedir/./one/two/three/file' +#@ info: Deleted directory '/tmp/CFE-3217/test-delete-nobasedir/./one/two/three' +#@ info: Deleted directory '/tmp/CFE-3217/test-delete-nobasedir/./one/two' +#@ info: Deleted directory '/tmp/CFE-3217/test-delete-nobasedir/./one' +#@ info: Deleted file '/tmp/CFE-3217/test-delete-nobasedir/./file' +#@ info: Object '/tmp/CFE-3217/test-perms-nobasedir/./file' had permission 0664, changed it to 0555 +#@ R: delete => tidy +#@ R: /tmp/CFE-3217/test-delete present despite include_basedir => 'true' +#@ R: /tmp/CFE-3217/test-delete-nobasedir present as expected with include_basedir => 'false' +#@ R: perms => m(555) +#@ R: /tmp/CFE-3217/test-perms 40555, as expected with include_basedir => 'true' +#@ R: /tmp/CFE-3217/test-perms-nobasedir 40775, not 555, as expected with include_basedir => 'false' +#@ ``` +#+end_example diff --git a/examples/files_transformer.cf b/examples/files_transformer.cf new file mode 100644 index 0000000000..11a8e56a26 --- /dev/null +++ b/examples/files_transformer.cf @@ -0,0 +1,64 @@ +#+begin_src prep +#@ ``` +#@ # Make sure that none of the example files exist to begin with +#@ rm -f /tmp/example-files-transformer.txt +#@ rm -f /tmp/example-files-transformer.txt.gz +#@ rm -f /tmp/this-file-does-not-exist-to-be-transformed.txt +#@ rm -f /tmp/this-file-does-not-exist-to-be-transformed.txt.gz +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent main +{ + vars: + "gzip_path" string => ifelse( isexecutable("/bin/gzip"), "/bin/gzip", + "/usr/bin/gzip" ); + files: + linux:: + "/tmp/example-files-transformer.txt" + content => "Hello World"; + + "/tmp/example-files-transformer.txt" + transformer => "$(gzip_path) $(this.promiser)"; + + # The transformer in the following promise results in the promised file + # being absent on completion. Note: It is the expectation and + # responsibility of the transformer itself that the transformation results + # in the promised file no longer existing. + + "/tmp/example-files-transformer.txt" + transformer => "$(gzip_path) $(this.promiser)"; + + # Since this file does not exist, the transformer will not be triggered + # and neither the text file nor a gzip file will exist + "/tmp/this-file-does-not-exist-to-be-transformed.txt" + transformer => "$(gzip_path) $(this.promiser)"; + + reports: + "/tmp/example-files-transformer.txt $(with)" + with => ifelse( fileexists( "/tmp/example-files-transformer.txt"), "exists", + "does not exist"); + + "/tmp/example-files-transformer.txt.gz $(with)" + with => ifelse( fileexists( "/tmp/example-files-transformer.txt.gz"), "exists", + "does not exist"); + + "/tmp/this-file-does-not-exist-to-be-transformed.txt $(with)" + with => ifelse( fileexists( "/tmp/this-file-does-not-exist-to-be-transformed.txt"), "exists", + "does not exist"); + + "/tmp/this-file-does-not-exist-to-be-transformed.txt.gz $(with)" + with => ifelse( fileexists( "/tmp/this-file-does-not-exist-to-be-transformed.txt.gz"), "exists", + "does not exist"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /tmp/example-files-transformer.txt does not exist +#@ R: /tmp/example-files-transformer.txt.gz exists +#@ R: /tmp/this-file-does-not-exist-to-be-transformed.txt does not exist +#@ R: /tmp/this-file-does-not-exist-to-be-transformed.txt.gz does not exist +#@ ``` +#+end_src diff --git a/examples/filesexist.cf b/examples/filesexist.cf new file mode 100644 index 0000000000..a1c5f3f241 --- /dev/null +++ b/examples/filesexist.cf @@ -0,0 +1,57 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control + +{ + bundlesequence => { "example" }; +} + +bundle agent example + +{ + vars: + + "mylist" slist => { "/tmp/a", "/tmp/b", "/tmp/c" }; + + classes: + + "exists" expression => filesexist("@(mylist)"); + + reports: + + exists:: + + "All files exist"; + + !exists:: + + "Not all files exist"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Not all files exist +#@ ``` +#+end_src diff --git a/examples/filesexist2.cf b/examples/filesexist2.cf new file mode 100644 index 0000000000..a08d3b7e05 --- /dev/null +++ b/examples/filesexist2.cf @@ -0,0 +1,64 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "mylist" slist => { "/tmp/a", "/tmp/b", "/tmp/c" }; + + + methods: + + "any" usebundle => crystal_meth(@(example.mylist)); + +} + +bundle agent crystal_meth(x) + +{ + classes: + + "exists" expression => filesexist("@(x)"); + + reports: + + exists:: + + "File does exist"; + + !exists:: + + "Does not yet exist"; + +} + diff --git a/examples/filesize.cf b/examples/filesize.cf new file mode 100644 index 0000000000..4e8cb9428c --- /dev/null +++ b/examples/filesize.cf @@ -0,0 +1,48 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { example }; +} + +bundle agent example +{ + vars: + # my own size! + "exists" int => filesize("$(this.promise_filename)"); + "nexists" int => filesize("/etc/passwdx"); + + reports: + "File size $(exists)"; + "Does not exist: $(nexists)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: File size 301 +#@ R: Does not exist: $(nexists) +#@ ``` +#+end_src diff --git a/examples/filestat.cf b/examples/filestat.cf new file mode 100644 index 0000000000..c9c917ecc0 --- /dev/null +++ b/examples/filestat.cf @@ -0,0 +1,77 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo 1234567890 > FILE.txt +#@ chmod 0755 FILE.txt +#@ chown 0 FILE.txt +#@ chgrp 0 FILE.txt +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "file" string => "$(this.promise_filename).txt"; + methods: + "fileinfo" usebundle => fileinfo("$(file)"); +} +bundle agent fileinfo(f) +{ + vars: + # use the full list if you want to see all the attributes! + # "fields" slist => splitstring("size,gid,uid,ino,nlink,ctime,atime,mtime,mode,modeoct,permstr,permoct,type,devno,dev_minor,dev_major,basename,dirname,linktarget,linktarget_shallow", ",", 999); + + # ino (inode number), ctime (creation time), + # devno/dev_minor/dev_major (device numbers) were omitted but + # they are all integers + + "fields" slist => splitstring("size,gid,uid,nlink,mode,modeoct,permstr,permoct,type,basename", ",", 999); + + "stat[$(f)][$(fields)]" string => filestat($(f), $(fields)); + + reports: + "$(this.bundle): file $(stat[$(f)][basename]) has $(fields) = $(stat[$(f)][$(fields)])"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: fileinfo: file filestat.cf.txt has size = 11 +#@ R: fileinfo: file filestat.cf.txt has gid = 0 +#@ R: fileinfo: file filestat.cf.txt has uid = 0 +#@ R: fileinfo: file filestat.cf.txt has nlink = 1 +#@ R: fileinfo: file filestat.cf.txt has mode = 33261 +#@ R: fileinfo: file filestat.cf.txt has modeoct = 100755 +#@ R: fileinfo: file filestat.cf.txt has permstr = -rwxr-xr-x +#@ R: fileinfo: file filestat.cf.txt has permoct = 755 +#@ R: fileinfo: file filestat.cf.txt has type = regular file +#@ R: fileinfo: file filestat.cf.txt has basename = filestat.cf.txt +#@ ``` +#+end_src diff --git a/examples/filter.cf b/examples/filter.cf new file mode 100644 index 0000000000..29d23a1a10 --- /dev/null +++ b/examples/filter.cf @@ -0,0 +1,98 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "one", "two", "three", + }; + + "test2" data => parsejson('[1,2,3, "ab", "c"]'); + + "test_filtergrep" slist => filter("[0-9]", test, "true", "false", 999); + "test_exact1" slist => filter("one", test, "false", "false", 999); + "test_exact2" slist => filter(".", test, "false", "false", 999); + "test_invert" slist => filter("[0-9]", test, "true", "true", 999); + "test_max2" slist => filter(".*", test, "true", "false", 2); + "test_max0" slist => filter(".*", test, "true", "false", 0); + "test_grep" slist => grep("[0-9]", test); + + "test2_filtergrep" slist => filter("[0-9]", test2, "true", "false", 999); + "test2_exact1" slist => filter("one", test2, "false", "false", 999); + "test2_exact2" slist => filter(".", test2, "false", "false", 999); + "test2_invert" slist => filter("[0-9]", test2, "true", "true", 999); + "test2_max2" slist => filter(".*", test2, "true", "false", 2); + "test2_max0" slist => filter(".*", test2, "true", "false", 0); + "test2_grep" slist => grep("[0-9]", test2); + + "todo" slist => { "test", "test2", "test_filtergrep", "test_exact1", + "test_exact2", "test_invert", "test_max2", + "test_max0", "test_grep", "test2_filtergrep", + "test2_exact1", "test2_exact2", + "test2_invert", "test2_max2", "test2_max0", + "test2_grep"}; + + "$(todo)_str" string => format("%S", $(todo)); + "tests" slist => { "test", "test2" }; + + reports: + "The $(tests) list is $($(tests)_str)"; + "The grepped list (only single digits from $(tests)) is $($(tests)_grep_str)"; + "The filter-grepped list (only single digits from $(tests)) is $($(tests)_grep_str)"; + "The filter-exact list, looking for only 'one' in $(tests), is $($(tests)_exact1_str)"; + "This list should be empty, the '.' is not literally in the list $(tests): $($(tests)_exact2_str)"; + "The filter-invert list, looking for non-digits in $(tests), is $($(tests)_invert_str)"; + "The filter-bound list, matching at most 2 items from the whole list $(tests), is $($(tests)_max2_str)"; + "This list should be empty because 0 elements of $(tests) were requested: $($(tests)_max0_str)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The test list is { "1", "2", "3", "one", "two", "three", "long string", "one", "two", "three" } +#@ R: The test2 list is [1,2,3,"ab","c"] +#@ R: The grepped list (only single digits from test) is { "1", "2", "3" } +#@ R: The grepped list (only single digits from test2) is { "1", "2", "3" } +#@ R: The filter-grepped list (only single digits from test) is { "1", "2", "3" } +#@ R: The filter-grepped list (only single digits from test2) is { "1", "2", "3" } +#@ R: The filter-exact list, looking for only 'one' in test, is { "one", "one" } +#@ R: The filter-exact list, looking for only 'one' in test2, is { } +#@ R: This list should be empty, the '.' is not literally in the list test: { } +#@ R: This list should be empty, the '.' is not literally in the list test2: { } +#@ R: The filter-invert list, looking for non-digits in test, is { "one", "two", "three", "long string", "one", "two", "three" } +#@ R: The filter-invert list, looking for non-digits in test2, is { "ab", "c" } +#@ R: The filter-bound list, matching at most 2 items from the whole list test, is { "1", "2" } +#@ R: The filter-bound list, matching at most 2 items from the whole list test2, is { "1", "2" } +#@ R: This list should be empty because 0 elements of test were requested: { } +#@ R: This list should be empty because 0 elements of test2 were requested: { } +#@ ``` +#+end_src diff --git a/examples/findfiles.cf b/examples/findfiles.cf new file mode 100644 index 0000000000..723aea9d13 --- /dev/null +++ b/examples/findfiles.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + "findtmp" slist => findfiles("/[tT][mM][pP]"); + # or find all .txt files under /tmp, up to 6 levels deep... + # "findtmp" slist => findfiles("/tmp/**/*.txt"); + reports: + "All files that match '/[tT][mM][pP]' = $(findtmp)"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: All files that match '/[tT][mM][pP]' = /tmp +#@ ``` +#+end_src diff --git a/examples/findfiles_up.cf b/examples/findfiles_up.cf new file mode 100644 index 0000000000..f667c8d218 --- /dev/null +++ b/examples/findfiles_up.cf @@ -0,0 +1,34 @@ +#+begin_src prep +#@ ``` +#@ mkdir -p /tmp/repo/.git/ +#@ touch /tmp/repo/.git/config +#@ mkdir -p /tmp/repo/submodule/.git/ +#@ touch /tmp/repo/submodule/.git/config +#@ mkdir -p /tmp/repo/submodule/some/place/deep/within/my/repo/ +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent __main__ +{ + vars: + "path" # path to search up from + string => "/tmp/repo/submodule/some/place/deep/within/my/repo/"; + "glob" # glob pattern matching filename + string => ".git/config"; + "level" # how far to search + int => "inf"; + "configs" + data => findfiles_up("$(path)", "$(glob)", "$(level)"); + reports: + "Submodules '$(glob)' is located in '$(configs[0])'"; + "Parents '$(glob)' is located in '$(configs[1])'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Submodules '.git/config' is located in '/tmp/repo/submodule/.git/config' +#@ R: Parents '.git/config' is located in '/tmp/repo/.git/config' +#@ ``` +#+end_src diff --git a/examples/fix_names.cf b/examples/fix_names.cf new file mode 100644 index 0000000000..5b97823cc8 --- /dev/null +++ b/examples/fix_names.cf @@ -0,0 +1,77 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# File editing +# +# Normal ordering: +# - delete +# - replace | column_edit +# - insert +# +###################################################################### + + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + + files: + + "/home/mark/LapTop/Cfengine3/trunk/src/.*\.[ch]" + + edit_line => change_name; +} + +######################################################## + +bundle edit_line change_name +{ + replace_patterns: + + "Verbose\(" + + replace_with => With("CfOut(cf_verbose,\"\","); + +} + +######################################## +# Bodies +######################################## + +body replace_with With(x) + +{ + replace_value => "$(x)"; + occurrences => "all"; +} + diff --git a/examples/format.cf b/examples/format.cf new file mode 100644 index 0000000000..de0eee95cd --- /dev/null +++ b/examples/format.cf @@ -0,0 +1,66 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "run" }; +} + +bundle agent run +{ + vars: + "v" string => "2.5.6"; + "vlist" slist => splitstring($(v), "\.", 3); + "padded" string => format("%04d%04d%04d", nth("vlist", 0), nth("vlist", 1), nth("vlist", 2)); + "a" string => format("%10.10s", "x"); + "b" string => format("%-10.10s", "x"); + "c" string => format("%04d", 1); + "d" string => format("%07.2f", 1); + "e" string => format("hello my name is %s %s", "Inigo", "Montoya"); + + "container" data => parsejson('{ "x": "y", "z": true }'); + + "packed" string => format("slist = %S, container = %S", vlist, container); + + reports: + "version $(v) => padded $(padded)"; + "%10.10s on 'x' => '$(a)'"; + "%-10.10s on 'x' => '$(b)'"; + "%04d on '1' => '$(c)'"; + "%07.2f on '1' => '$(d)'"; + "you killed my father... => '$(e)'"; + "$(packed)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: version 2.5.6 => padded 000200050006 +#@ R: %10.10s on 'x' => ' x' +#@ R: %-10.10s on 'x' => 'x ' +#@ R: %04d on '1' => '0001' +#@ R: %07.2f on '1' => '0001.00' +#@ R: you killed my father... => 'hello my name is Inigo Montoya' +#@ R: slist = { "2", "5", "6" }, container = {"x":"y","z":true} +#@ ``` +#+end_src diff --git a/examples/function-return-types.cf b/examples/function-return-types.cf new file mode 100644 index 0000000000..ebdbeef8fa --- /dev/null +++ b/examples/function-return-types.cf @@ -0,0 +1,95 @@ +#+begin_src prep +#@ ``` +#@ printf "one\ntwo\nthree\n" > /tmp/list.txt +#@ printf "1\n2\n3\n" >> /tmp/list.txt +#@ printf "1.0\n2.0\n3.0" >> /tmp/list.txt +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent example_function_return_types +# @brief Example showing function return types +{ + + classes: + "this_file_exists" expression => fileexists( $(this.promise_filename) ); + + vars: + "my_string" string => concat( "Promises you cannot keep", + " are no better than lies"); + + "my_list_of_strings" + slist => readstringlist( "/tmp/list.txt", # File to read + "", # Don't ignore any lines + "\n", # Split on newlines + inf, # Extract as many entries as possible + inf); # Read in as much data as possible + + "my_list_of_integers" + ilist => readintlist( "/tmp/list.txt", # File to read + "^(\D+)|(\d+[^\n]+)", # Ignore any lines that are not integers + "\n", # Split on newlines + inf, # Maximum number of entries + inf); # Maximum number of bytes to read + + "my_list_of_reals" + rlist => readreallist( "/tmp/list.txt", # File to read + "^(\D+)", # Ignore any lines that are not digits + "\n", # Split on newlines + inf, # Maximum number of entries + inf); # Maximum number of bytes to read + + "my_integer" int => string_length( $(my_string) ); + + "my_real" real => sum( my_list_of_integers ); + + "my_data" data => mergedata( '{ "Hello": "world!" }' ); + + reports: + "my_string: '$(my_string)'"; + "my_list_of_strings includes '$(my_list_of_strings)'"; + "my_list_of_integers includes '$(my_list_of_integers)'"; + "my_list_of_reals includes '$(my_list_of_reals)'"; + "my_integer: '$(my_integer)'"; + "my_real: '$(my_real)'"; + "my_data: '$(with)'" + with => string_mustache( "{{%-top-}}", my_data ); + + this_file_exists:: + "This file exists."; + +} +bundle agent __main__ +{ + methods: "example_function_return_types"; +} +#+end_src +#+begin_src example_output +#@ ``` +#@ R: my_string: 'Promises you cannot keep are no better than lies' +#@ R: my_list_of_strings includes 'one' +#@ R: my_list_of_strings includes 'two' +#@ R: my_list_of_strings includes 'three' +#@ R: my_list_of_strings includes '1' +#@ R: my_list_of_strings includes '2' +#@ R: my_list_of_strings includes '3' +#@ R: my_list_of_strings includes '1.0' +#@ R: my_list_of_strings includes '2.0' +#@ R: my_list_of_strings includes '3.0' +#@ R: my_list_of_integers includes '1' +#@ R: my_list_of_integers includes '2' +#@ R: my_list_of_integers includes '3' +#@ R: my_list_of_reals includes '1' +#@ R: my_list_of_reals includes '2' +#@ R: my_list_of_reals includes '3' +#@ R: my_list_of_reals includes '1.0' +#@ R: my_list_of_reals includes '2.0' +#@ R: my_list_of_reals includes '3.0' +#@ R: my_integer: '48' +#@ R: my_real: '6.000000' +#@ R: my_data: '{ +#@ "Hello": "world!" +#@ }' +#@ R: This file exists. +#@ ``` +#+end_src diff --git a/examples/getclassmetatags.cf b/examples/getclassmetatags.cf new file mode 100644 index 0000000000..c816205047 --- /dev/null +++ b/examples/getclassmetatags.cf @@ -0,0 +1,54 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + "c" expression => "any", meta => { "mytag", "other=once", "other=twice" }; + + vars: + "ctags" slist => getclassmetatags("c"); + "othertag_values" slist => getclassmetatags("c", "other"); + + reports: + "Found tags: $(ctags)"; + "Found tags for key 'other': $(othertag_values)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found tags: source=promise +#@ R: Found tags: mytag +#@ R: Found tags: other=once +#@ R: Found tags: other=twice +#@ R: Found tags for key 'other': once +#@ R: Found tags for key 'other': twice +#@ ``` +#+end_src diff --git a/examples/getenv.cf b/examples/getenv.cf new file mode 100644 index 0000000000..56d73977fa --- /dev/null +++ b/examples/getenv.cf @@ -0,0 +1,56 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "myvar" string => getenv("EXAMPLE","2048"); + + classes: + + "isdefined" not => strcmp("$(myvar)",""); + + reports: + + isdefined:: + + "The EXAMPLE environment variable is $(myvar)"; + + !isdefined:: + + "The environment variable EXAMPLE does not exist"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The EXAMPLE environment variable is getenv.cf +#@ ``` +#+end_src diff --git a/examples/getfields.cf b/examples/getfields.cf new file mode 100644 index 0000000000..7e1a6666f9 --- /dev/null +++ b/examples/getfields.cf @@ -0,0 +1,57 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "no" int => getfields("root:.*","/etc/passwd",":","userdata"); + + reports: + "Found $(no) lines matching"; + "root's handle = $(userdata[1])"; + "root's passwd = ... forget it!"; + "root's uid = $(userdata[3])"; + # uncomment this if you want to see the HOMEDIR field + #"root's homedir = $(userdata[6])"; + # uncomment this if you want to see the GID field + #"root's gid = $(userdata[4])"; + # uncomment this if you want to see the GECOS field + #"root's name = $(userdata[5])"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found 1 lines matching +#@ R: root's handle = root +#@ R: root's passwd = ... forget it! +#@ R: root's uid = 0 +#@ ``` +#+end_src diff --git a/examples/getgid.cf b/examples/getgid.cf new file mode 100644 index 0000000000..3d9978e9c3 --- /dev/null +++ b/examples/getgid.cf @@ -0,0 +1,48 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + linux|solaris|hpux:: + "gid" int => getgid("root"); + freebsd|darwin|openbsd:: + "gid" int => getgid("wheel"); + aix:: + "gid" int => getgid("system"); + + reports: + "root's gid is $(gid)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: root's gid is 0 +#@ ``` +#+end_src diff --git a/examples/getindices.cf b/examples/getindices.cf new file mode 100644 index 0000000000..c6f97522ed --- /dev/null +++ b/examples/getindices.cf @@ -0,0 +1,64 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "ps[relayhost]" string => "[mymailrelay]:587"; + "ps[mydomain]" string => "iu.hio.no"; + "ps[smtp_sasl_auth_enable]" string => "yes"; + "ps[smtp_sasl_password_maps]" string => "hash:/etc/postfix/sasl-passwd"; + "ps[smtp_sasl_security_options]" string => ""; + "ps[smtp_use_tls]" string => "yes"; + "ps[default_privs]" string => "mailman"; + "ps[inet_protocols]" string => "all"; + "ps[inet_interfaces]" string => "127.0.0.1"; + + "parameter_name" slist => getindices("ps"); + "parameter_name_sorted" slist => sort(parameter_name, lex); + + reports: + + "Found key $(parameter_name_sorted)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found key default_privs +#@ R: Found key inet_interfaces +#@ R: Found key inet_protocols +#@ R: Found key mydomain +#@ R: Found key relayhost +#@ R: Found key smtp_sasl_auth_enable +#@ R: Found key smtp_sasl_password_maps +#@ R: Found key smtp_sasl_security_options +#@ R: Found key smtp_use_tls +#@ ``` +#+end_src diff --git a/examples/getindices_and_values.cf b/examples/getindices_and_values.cf new file mode 100644 index 0000000000..e5934b68c7 --- /dev/null +++ b/examples/getindices_and_values.cf @@ -0,0 +1,50 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; +} + + +####################################################### + +bundle agent example + +{ + vars: + + "v[index_1]" string => "value_1"; + "v[index_2]" string => "value_2"; + + "x" slist => getindices("v"); + "y" slist => getvalues("v"); + + reports: + + "All indices:"; + " Found index: $(x) with value \"$(v[$(x)])\""; + "All values:"; + " Found value: $(y)"; +} diff --git a/examples/getmacaddress.cf b/examples/getmacaddress.cf new file mode 100644 index 0000000000..b9d2fc08d1 --- /dev/null +++ b/examples/getmacaddress.cf @@ -0,0 +1,65 @@ + +body common control +{ + bundlesequence => { "get_mac_adr" }; +} + +############################################################## + +bundle agent get_mac_adr +{ + vars: + + linux:: + "interface" string => execresult("/sbin/ifconfig eth0","noshell"); + + solaris:: + "interface" string => execresult("/usr/sbin/ifconfig bge0","noshell"); + + freebsd:: + "interface" string => execresult("/sbin/ifconfig le0","noshell"); + + darwin:: + "interface" string => execresult("/sbin/ifconfig en0","noshell"); + + classes: + + linux:: + + "ok" expression => regextract( + ".*HWaddr ([^\s]+).*(\n.*)*", + "$(interface)", + "mac" + ); + + solaris:: + + "ok" expression => regextract( + ".*ether ([^\s]+).*(\n.*)*", + "$(interface)", + "mac" + ); + + freebsd:: + + "ok" expression => regextract( + ".*ether ([^\s]+).*(\n.*)*", + "$(interface)", + "mac" + ); + + darwin:: + + "ok" expression => regextract( + "(?s).*ether ([^\s]+).*(\n.*)*", + "$(interface)", + "mac" + ); + + reports: + + ok:: + + "MAC address is $(mac[1])"; + +} diff --git a/examples/getregistry.cf b/examples/getregistry.cf new file mode 100644 index 0000000000..d3eb3c13bd --- /dev/null +++ b/examples/getregistry.cf @@ -0,0 +1,41 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "reg" }; +} + +bundle agent reg +{ + vars: + + windows:: + + "value" string => registryvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS\Cfengine","value3"); + + reports: + + "Value extracted: $(value)"; + +} diff --git a/examples/getuid.cf b/examples/getuid.cf new file mode 100644 index 0000000000..a5076eeeff --- /dev/null +++ b/examples/getuid.cf @@ -0,0 +1,44 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "uid" int => getuid("root"); + + reports: + "root's uid is $(uid)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: root's uid is 0 +#@ ``` +#+end_src diff --git a/examples/getuserinfo.cf b/examples/getuserinfo.cf new file mode 100644 index 0000000000..9a516f042e --- /dev/null +++ b/examples/getuserinfo.cf @@ -0,0 +1,43 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + # note the results here will vary depending on your platform + "me" data => getuserinfo(); # the current user's info + "root" data => getuserinfo("root"); # the "root" user's info (usually UID 0) + "uid0" data => getuserinfo(0); # lookup user info for UID 0 (usually "root") + + # sys.user_data has the information for the user that started the agent + "out" string => format("I am '%s', root shell is '%s', and the agent was started by %S", "$(me[description])", "$(root[shell])", "sys.user_data"); + + reports: + "$(out)"; +} +#+end_src diff --git a/examples/getusers.cf b/examples/getusers.cf new file mode 100644 index 0000000000..faa37a0450 --- /dev/null +++ b/examples/getusers.cf @@ -0,0 +1,65 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + # The getusers function takes two filtering arguments: exclude_names and + # exclude_ids, both a comma separated list of usernames and user IDs + # respectively. + + # To get users with a uid 1000 and greater we generate a list of uids from + # 0 to 999 and convert it into a comma separated string used to filter the + # list of users. + + "users_with_uid_gt_999" + slist => getusers( "", join( ",", expandrange( "[0-999]", 1 ) ) ); + + # Here we get a list of users except usernames nfsnobody and vagrant as + # well as any users with uid 8 or 9 + + "users_except_nfsnobody_and_vagrant_and_uid_8_and_9" + slist => getusers( "nfsnobody,vagrant", "8,9" ); + + # Here we get a list of all users by not filtering any + "allusers" slist => getusers("",""); + "root_list" slist => { "root" }; + # this will get just the root users out of the full user list + "justroot" slist => intersection(allusers, root_list); + + reports: + "Found just the root user: $(justroot)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found just the root user: root +#@ ``` +#+end_src diff --git a/examples/getvalues.cf b/examples/getvalues.cf new file mode 100644 index 0000000000..298b540738 --- /dev/null +++ b/examples/getvalues.cf @@ -0,0 +1,63 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "v[index_1]" string => "value_1"; + "v[index_2]" string => "value_2"; + + "values" slist => getvalues("v"); + "values_sorted" slist => sort(values, lex); + + # works with data containers too + "d" data => parsejson('{ "k": [ 1, 2, 3, "a", "b", "c" ] }'); + + "cvalues" slist => getvalues("d[k]"); + "cvalues_sorted" slist => sort(cvalues, lex); + + reports: + "Found values: $(values_sorted)"; + "Found container values: $(cvalues_sorted)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found values: value_1 +#@ R: Found values: value_2 +#@ R: Found container values: 1 +#@ R: Found container values: 2 +#@ R: Found container values: 3 +#@ R: Found container values: a +#@ R: Found container values: b +#@ R: Found container values: c +#@ ``` +#+end_src diff --git a/examples/getvariablemetatags.cf b/examples/getvariablemetatags.cf new file mode 100644 index 0000000000..a251d7e164 --- /dev/null +++ b/examples/getvariablemetatags.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "v" string => "myvalue", meta => { "mytag", "other=once", "other=twice" }; + "vtags" slist => getvariablemetatags("example.v"); + "othertag_values" slist => getvariablemetatags("example.v", "other"); + + reports: + "Found tags: $(vtags)"; + "Found tags for key 'other': $(othertag_values)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found tags: source=promise +#@ R: Found tags: mytag +#@ R: Found tags: other=once +#@ R: Found tags: other=twice +#@ R: Found tags for key 'other': once +#@ R: Found tags for key 'other': twice +#@ ``` +#+end_src diff --git a/examples/global_list_expansion.cf b/examples/global_list_expansion.cf new file mode 100644 index 0000000000..c295421588 --- /dev/null +++ b/examples/global_list_expansion.cf @@ -0,0 +1,62 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Show access of external lists. +# +# - to pass lists globally, use a parameter to dereference them +# + +body common control +{ + bundlesequence => { hardening(@(va.tmpdirs)) }; +} + +######################################################### + +bundle common va +{ + vars: + + "tmpdirs" slist => { "/tmp", "/var/tmp", "/usr/tmp" }; + +} + +########################################################## + +bundle agent hardening(x) +{ + classes: + + "ok" expression => "any"; + + vars: + + "other" slist => { "/tmp", "/var/tmp" }; + + reports: + + ok:: + + "Do $(x)"; + "Other: $(other)"; +} diff --git a/examples/global_list_expansion_2.cf b/examples/global_list_expansion_2.cf new file mode 100644 index 0000000000..be31985219 --- /dev/null +++ b/examples/global_list_expansion_2.cf @@ -0,0 +1,61 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Show access of external lists. +# + +body common control +{ + bundlesequence => { hardening }; +} + +######################################################### + +bundle common va +{ + vars: + + "tmpdirs" slist => { "/tmp", "/var/tmp", "/usr/tmp" }; + +} + +########################################################## + +bundle agent hardening +{ + classes: + + "ok" expression => "any"; + + vars: + + "other" slist => { "/tmp", "/var/tmp" }; + "x" slist => { @(va.tmpdirs) }; + + reports: + + ok:: + + "Do $(x)"; + "Other: $(other)"; +} diff --git a/examples/grep.cf b/examples/grep.cf new file mode 100644 index 0000000000..5d9421a8ef --- /dev/null +++ b/examples/grep.cf @@ -0,0 +1,58 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + + "mylist" slist => { "One", "Two", "Three", "Four", "Five" }; + "Tlist" slist => grep("T.*","mylist"); + "empty_list" slist => grep("ive","mylist"); + + "datalist" data => parsejson('[1,2,3, "Tab", "chive"]'); + "data_Tlist" slist => grep("T.*","datalist"); + "data_empty_list" slist => grep("ive","datalist"); + + "todo" slist => { "mylist", "Tlist", "empty_list", "datalist", "data_Tlist", "data_empty_list" }; + "$(todo)_str" string => format("%S", $(todo)); + + reports: + "$(todo): $($(todo)_str)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: mylist: { "One", "Two", "Three", "Four", "Five" } +#@ R: Tlist: { "Two", "Three" } +#@ R: empty_list: { } +#@ R: datalist: [1,2,3,"Tab","chive"] +#@ R: data_Tlist: { "Tab" } +#@ R: data_empty_list: { } +#@ ``` +#+end_src diff --git a/examples/groupexists.cf b/examples/groupexists.cf new file mode 100644 index 0000000000..6274b89883 --- /dev/null +++ b/examples/groupexists.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example +{ + classes: + "gname" expression => groupexists("sys"); + "gid" expression => groupexists("0"); + + reports: + gname:: + "Group exists by name"; + gid:: + "Group exists by id"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Group exists by name +#@ R: Group exists by id +#@ ``` +#+end_src diff --git a/examples/guest_environment_kvm.cf b/examples/guest_environment_kvm.cf new file mode 100644 index 0000000000..27ee8322be --- /dev/null +++ b/examples/guest_environment_kvm.cf @@ -0,0 +1,88 @@ +# +# Management of KVM/QEMU virtual machines +# Assumes you already have a disk image (with an OS installed) for the machine on creation +# + + +body common control +{ + bundlesequence => { "kvm_create" }; +} + + +bundle agent kvm_create +{ + + guest_environments: + "my_host_machine" + environment_resources => mykvm("my_host_machine", "x86_64", "1", "1048576", "/var/lib/libvirt/images/ubuntu104-64-clone.img"), + environment_type => "kvm", + environment_state => "create", + environment_host => "ubuntu"; +} + + +bundle agent kvm_suspend +{ + guest_environments: + "my_host_machine" + environment_type => "kvm", + environment_state => "suspended", + environment_host => "ubuntu"; +} + + +bundle agent kvm_run +{ + guest_environments: + "my_host_machine" + environment_type => "kvm", + environment_state => "running", + environment_host => "ubuntu"; +} + + +bundle agent kvm_delete +{ + guest_environments: + "my_host_machine" + environment_type => "kvm", + environment_state => "delete", + environment_host => "ubuntu"; +} + + +body environment_resources mykvm(name, arch, cpu_count, mem_kb, disk_file) +{ + env_spec => + " + $(name) + $(mem_kb) + $(mem_kb) + $(cpu_count) + + hvm + + + + + + + destroy + restart + restart + + /usr/bin/kvm + + + + + + + + + + +"; +} + diff --git a/examples/hash.cf b/examples/hash.cf new file mode 100644 index 0000000000..ebbda900e3 --- /dev/null +++ b/examples/hash.cf @@ -0,0 +1,56 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example + +{ + vars: + + "md5" string => hash("Cfengine is not cryptic","md5"); + "sha256" string => hash("Cfengine is not cryptic","sha256"); + "sha384" string => hash("Cfengine is not cryptic","sha384"); + "sha512" string => hash("Cfengine is not cryptic","sha512"); + + reports: + + "Hashed to: md5 $(md5)"; + "Hashed to: sha256 $(sha256)"; + "Hashed to: sha384 $(sha384)"; + "Hashed to: sha512 $(sha512)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Hashed to: md5 2036af0ee58d6d9dffcc6507af92664f +#@ R: Hashed to: sha256 e2fb1927976bfe1ea3987c1a731c75e8ac1453d22a21811dc352db5e62d3f73c +#@ R: Hashed to: sha384 b348c0b83ccd9ee12673f5daaba3ee5f49c42906540936bb16cf9d2001ed502b8c56f6e36b8389ab596febb529aab17f +#@ R: Hashed to: sha512 29ce0883afbe7740bb2a016735499ae5a0a9b067539018ce6bb2c309a7e885c2d7da64744956e9f151bc72ec8dc19f85efd85eb0a73cbf1e829a15ac9ac35358 +#@ ``` +#+end_src diff --git a/examples/hash_to_int.cf b/examples/hash_to_int.cf new file mode 100644 index 0000000000..03d31b9a0d --- /dev/null +++ b/examples/hash_to_int.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "hello" int => hash_to_int(0, 1000, "hello"); + "world" int => hash_to_int(0, 1000, "world"); + + # Hash can vary on hostkey or policy hub: + "hour" int => hash_to_int(0, 24, "$(sys.key_digest)"); + "minute" int => hash_to_int(0, 60, "$(sys.policy_hub)"); + + reports: + "'hello' hashed to: $(hello)"; + "'world' hashed to: $(world)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: 'hello' hashed to: 172 +#@ R: 'world' hashed to: 760 +#@ ``` +#+end_src diff --git a/examples/hashcomment.cf b/examples/hashcomment.cf new file mode 100644 index 0000000000..e48687d877 --- /dev/null +++ b/examples/hashcomment.cf @@ -0,0 +1,73 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# Comment lines +# +###################################################################### + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp/comment_test" + + create => "true", + edit_line => comment_lines_matching; +} + +######################################################## + +bundle edit_line comment_lines_matching +{ + vars: + + "regexes" slist => { "one.*", "two.*", "four.*" }; + + replace_patterns: + + "^($(regexes))$" + replace_with => comment("# "); +} + +######################################## +# Bodies +######################################## + +body replace_with comment(c) + +{ + replace_value => "$(c) $(match.1)"; + occurrences => "all"; +} + diff --git a/examples/hashmatch.cf b/examples/hashmatch.cf new file mode 100644 index 0000000000..2e3f8a2e4d --- /dev/null +++ b/examples/hashmatch.cf @@ -0,0 +1,46 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + classes: + + "matches" expression => hashmatch("/etc/passwd","md5","c5068b7c2b1707f8939b283a2758a691"); + + reports: + + matches:: + + "File has correct version"; + +} + diff --git a/examples/hashuncomment.cf b/examples/hashuncomment.cf new file mode 100644 index 0000000000..1b289b4e59 --- /dev/null +++ b/examples/hashuncomment.cf @@ -0,0 +1,72 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# Uncomment lines +# +###################################################################### + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "example" }; +} + +# try this on some test data like + +# one +# two +# mark one +#mark two + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp/comment_test" + + create => "true", + edit_line => uncomment_lines_matching("\s*mark.*","#"); +} + +######################################################## + +bundle edit_line uncomment_lines_matching(regex,comment) +{ + replace_patterns: + + "#($(regex))$" replace_with => uncomment; +} + +######################################################## + +body replace_with uncomment +{ + replace_value => "$(match.1)"; + occurrences => "all"; +} + diff --git a/examples/helloworld.cf b/examples/helloworld.cf new file mode 100644 index 0000000000..f6a802b06f --- /dev/null +++ b/examples/helloworld.cf @@ -0,0 +1,29 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +bundle agent main +{ + files: + "/tmp/hello_world" + create => "true", + content => "Hello, CFEngine!$(const.n)"; +} diff --git a/examples/host2ip.cf b/examples/host2ip.cf new file mode 100644 index 0000000000..02d23d273b --- /dev/null +++ b/examples/host2ip.cf @@ -0,0 +1,49 @@ + +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# host2ip function +# +####################################################### + +body common control + +{ + bundlesequence => { "example" }; +} + +####################################################### + +bundle agent example + +{ + vars: + + "ip" string => host2ip("www.google.com"); + + reports: + + "IP address $(ip)"; + +} diff --git a/examples/hostrange.cf b/examples/hostrange.cf new file mode 100644 index 0000000000..79301a5166 --- /dev/null +++ b/examples/hostrange.cf @@ -0,0 +1,69 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + + "range" string => "1-32"; + "hostname_f" string => execresult( "hostname -f", useshell); + "hostname_s" string => execresult( "hostname -s", useshell); + "hostname" string => execresult( "hostname", useshell); + + classes: + + "hostgroup_alpha_no_leading_zeros" expression => hostrange("host", $(range) ); + "hostgroup_alpha_leading_zeros" expression => hostrange("host", "00$(range)" ); + "hostgroup_alpha_UPPERCASE_prefix" expression => hostrange("HOST", "0$(range)" ); + + reports: + "sys.fqhost = '$(sys.fqhost)'"; + "sys.uqhost = '$(sys.uqhost)'"; + "hostname -f = '$(hostname_f)'"; + "hostname -s = '$(hostname_s)'"; + "hostname = '$(hostname)'"; + + hostgroup_alpha_no_leading_zeros:: + + "This host is within the alpha host group range (host$(range))"; + + hostgroup_alpha_leading_zeros:: + "This host is within the alpha host group range (host00$(range)) (NOTE: Leading zeros and prefix capitalization is insignificant)"; + + hostgroup_alpha_UPPERCASE_prefix:: + "This host is within the alpha host group range (HOST0$(range)) (NOTE: Leading zeros and prefix capitalization is insignificant)"; +} +#+end_src +############################################################################### +#+begin_src static_example_output +#@ ``` +#@ R: sys.fqhost = 'host001.example.com' +#@ R: sys.uqhost = 'host001' +#@ R: hostname -f = 'HOST001.example.com' +#@ R: hostname -s = 'HOST001' +#@ R: hostname = 'HOST001' +#@ R: This host is within the alpha host group range (host1-32) +#@ R: This host is within the alpha host group range (host001-32) (NOTE: Leading zeros and prefix capitalization is insignificant) +#@ R: This host is within the alpha host group range (HOST01-32) (NOTE: Leading zeros and prefix capitalization is insignificant) +#@ ``` +#+end_src diff --git a/examples/hostsseen.cf b/examples/hostsseen.cf new file mode 100644 index 0000000000..f764a77c56 --- /dev/null +++ b/examples/hostsseen.cf @@ -0,0 +1,39 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example + +{ + vars: + + "myhosts" slist => { hostsseen("inf","lastseen","address") }; + + reports: + "Found client/peer: $(myhosts)"; + +} diff --git a/examples/hostswithclass.cf b/examples/hostswithclass.cf new file mode 100644 index 0000000000..cd230e3d9a --- /dev/null +++ b/examples/hostswithclass.cf @@ -0,0 +1,20 @@ +body common control +{ + bundlesequence => { "example" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + + +bundle agent example +{ + vars: + + am_policy_hub:: + "host_list" slist => hostswithclass( "debian", "name" ); + + files: + am_policy_hub:: + "/tmp/master_config.cfg" + edit_line => insert_lines("host=$(host_list)"), + create => "true"; +} diff --git a/examples/hub.cf b/examples/hub.cf new file mode 100644 index 0000000000..6a82c42d28 --- /dev/null +++ b/examples/hub.cf @@ -0,0 +1,87 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Remote value from hub +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-d2] +# cf-agent -f runtest_2.cf +# +# Notice that the same file configures all parts of cfengine + +######################################################## + +body common control + +{ + bundlesequence => { "example" }; + + version => "1.2.3"; +} + +######################################################## + +bundle agent example + +{ + vars: + + "remote_value" string => hubknowledge("monitoring"); + + reports: + + "Global knowledge: $(remote_value)"; +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowusers => { "mark" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "discovered value" + + handle => "monitoring", + resource_type => "literal", + admit => { "127.0.0.1" }; + +} diff --git a/examples/id.cf b/examples/id.cf new file mode 100644 index 0000000000..6779ef204b --- /dev/null +++ b/examples/id.cf @@ -0,0 +1,110 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test of promise references +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; + + version => "1.2.3"; +} + +######################################################## + +bundle agent example + +{ + vars: + + + classes: + + + files: + + "/home/mark/tmp/testcopy" + + copy_from => mycopy("/home/mark/LapTop/words","127.0.0.1"), + perms => system, + depth_search => recurse("inf"); + +} + +######################################################### + +body perms system + +{ + mode => "0644"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + +######################################################### + +body copy_from mycopy(from,server) + +{ + source => "$(from)"; + servers => { "$(server)" }; + copy_backup => "true"; #/false/timestamp + purge => "false"; + type_check => "true"; + force_ipv4 => "true"; +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "/home/mark/LapTop" + + handle => "update_rule", + admit => { "127.0.0.1" }; +} diff --git a/examples/inform.cf b/examples/inform.cf new file mode 100644 index 0000000000..f47d69830a --- /dev/null +++ b/examples/inform.cf @@ -0,0 +1,16 @@ +# The inform constraint can be used to select which commands promises +# generate output in INFO log level. INFO log level, is intendeted for +# events which alter the state of the system, such as editing files, users, +# etc. Since commands promises CAN modify the system, they create INFO +# messages by default. However, not all commands promises do change the +# system, so we let the policy writer customize this. + +bundle agent main +{ + commands: + "/bin/touch /tmp/module_cache" + inform => "true"; # Default + "/bin/cat /tmp/module_cache" + inform => "false", # Read-only promise, no INFO logging wanted + module => "true"; +} diff --git a/examples/inherit.cf b/examples/inherit.cf new file mode 100644 index 0000000000..a2127d583f --- /dev/null +++ b/examples/inherit.cf @@ -0,0 +1,98 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# Inheritance of classes +# +###################################################################### + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "parent" }; +} + +######################################################## + +bundle agent parent + +{ + classes: + + "parent_private" expression => "any"; + + methods: + + "any" usebundle => child, + inherit => "true"; + + files: + + "/tmp/bla" + + create => "true", + edit_line => edit_child, + edit_defaults => inherit_all; + +} + +######################################################## + +bundle agent child + +{ + reports: + + parent_private:: + + "Parent class is inherited"; + + !parent_private:: + + "Parent class is not inherited"; + +} + +# + +bundle edit_line edit_child +{ + reports: + + parent_private:: + + "Parent editclass is inherited"; + + !parent_private:: + + "Parent editclass is not inherited"; + +} + +# + +body edit_defaults inherit_all +{ + inherit => "true"; +} diff --git a/examples/inherit_from.cf b/examples/inherit_from.cf new file mode 100644 index 0000000000..2882b7189a --- /dev/null +++ b/examples/inherit_from.cf @@ -0,0 +1,55 @@ +# This example illustrates the use of inherit_from to inherit body attribute +# values from another body. +############################################################################### +#+begin_src cfengine3 +bundle agent __main__ +{ + files: + "$(this.promise_filename).txt" + content => "Hello World$(const.n)2$(const.n)3$(const.n)4$(const.n)half-way +6$(const.n)7$(const.n)8$(const.n)9$(const.n)Byeeeeeee", + create => "true"; + + reports: + "The first 3 lines of this file are:" + printfile => head_n( "$(this.promise_filename).txt", "3" ); + + "The whole file contains:" + printfile => cat( "$(this.promise_filename).txt" ); +} + +body printfile head_n(file, lines) +{ + # Sets file_to_print the same as cat + inherit_from => cat( $(file) ); + + # Overrides number_of_lines from cat + number_of_lines => "$(lines)"; +} +body printfile cat(file) +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The first 3 lines of this file are: +#@ R: Hello World +#@ R: 2 +#@ R: 3 +#@ R: The whole file contains: +#@ R: Hello World +#@ R: 2 +#@ R: 3 +#@ R: 4 +#@ R: half-way +#@ R: 6 +#@ R: 7 +#@ R: 8 +#@ R: 9 +#@ R: Byeeeeeee +#@ ``` +#+end_src + + diff --git a/examples/inherit_from_classes.cf b/examples/inherit_from_classes.cf new file mode 100644 index 0000000000..5e1debb83c --- /dev/null +++ b/examples/inherit_from_classes.cf @@ -0,0 +1,52 @@ +# This example illustrates the use of inherit_from to inherit body attribute +# values from another body. +############################################################################### +#+begin_src cfengine3 +bundle agent __main__ +{ + commands: + "/bin/true" + handle => "some meaningful string", + classes => my_set_some_extra_fancy_classes( "$(this.promiser)", + "$(this.handle)", + "some_class_to_handle_dependencies" ); + + "/bin/false" + handle => "some meaningless string", + classes => my_set_some_extra_fancy_classes( "$(this.promiser)", + "$(this.handle)", + "some_class_to_handle_dependencies" ); + + reports: + "Defined classes:$(const.n)$(with)" + with => join( "$(const.n)", sort( classesmatching( "some_.*"), "lex" )); +} + +body classes my_set_some_extra_fancy_classes(x, y, z) +# @brief In addition to the classes set by `set_some_fancy_classes` define `z` when the promise is repaired +{ + inherit_from => set_some_fancy_classes( $(x), $(y) ); + promise_repaired => { "some_fancy_class_${x}_${y}_repaired", $(z) }; +} + +body classes set_some_fancy_classes(x, y) +# @brief Define a class prefixed with `some_fancy_class_` followed by expansion +# of `x`_`y` and suffixed with the promise outcome for each promise outcome. +{ + promise_kept => { "some_fancy_class_${x}_${y}_kept" }; + promise_repaired => { "some_fancy_class_${x}_${y}_repaired" }; + repair_failed => { "some_fancy_class_${x}_${y}_notkept" }; + repair_denied => { "some_fancy_class_${x}_${y}_notkept" }; + repair_timeout => { "some_fancy_class_${x}_${y}_notkept" }; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ error: Finished command related to promiser '/bin/false' -- an error occurred, returned 1 +#@ R: Defined classes: +#@ some_class_to_handle_dependencies +#@ some_fancy_class__bin_false_some_meaningless_string_notkept +#@ some_fancy_class__bin_true_some_meaningful_string_repaired +#@ ``` +#+end_src diff --git a/examples/inline-json.cf b/examples/inline-json.cf new file mode 100644 index 0000000000..0d633592be --- /dev/null +++ b/examples/inline-json.cf @@ -0,0 +1,59 @@ +#+begin_src cfengine3 +bundle agent example_inline_json +# @brief Example illustrating inline json +{ + vars: + "json_multi_line" data => '{ + "CFEngine Champions": [ + { + "Name": "Aleksey Tsalolikhin", + "Year": "2011" + }, + { + "Name": "Ted Zlatanov", + "Year": "2013" + } + ] +}'; + + + "json_single_line" data => '[{"key1":"value1"},{"key2":"value2"}]'; + + reports: + "Data container defined from json_multi_line: $(with)" + with => storejson( @(json_multi_line) ); + + "Data container defined from json_single_line: $(with)" + with => storejson( @(json_single_line) ); +} +bundle agent __main__ +{ + methods: + "example_inline_json"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Data container defined from json_multi_line: { +#@ "CFEngine Champions": [ +#@ { +#@ "Name": "Aleksey Tsalolikhin", +#@ "Year": "2011" +#@ }, +#@ { +#@ "Name": "Ted Zlatanov", +#@ "Year": "2013" +#@ } +#@ ] +#@ } +#@ R: Data container defined from json_single_line: [ +#@ { +#@ "key1": "value1" +#@ }, +#@ { +#@ "key2": "value2" +#@ } +#@ ] +#@ ``` +#+end_src diff --git a/examples/inline-yaml.cf b/examples/inline-yaml.cf new file mode 100644 index 0000000000..231b3fe890 --- /dev/null +++ b/examples/inline-yaml.cf @@ -0,0 +1,58 @@ +#+begin_src cfengine3 +bundle agent example_inline_yaml +# @brief Example illustrating inline yaml +{ + vars: + # YAML requires "---" header (followed by newline) + # NOTE \n is not interpreted as a newline, instead use $(const.n) + + "yaml_multi_line" data => '--- +- "CFEngine Champions": + - Name: "Aleksey Tsalolikhin" + Year: 2011 + - Name: "Ted Zlatanov" + Year : 2013'; + + + "yaml_single_line" data => '---$(const.n)- key1: value1$(const.n)- key2: value2'; + + reports: + "Data container defined from yaml_multi_line: $(with)" + with => storejson( @(yaml_multi_line) ); + + "Data container defined from yaml_single_line: $(with)" + with => storejson( @(yaml_single_line) ); +} +bundle agent __main__ +{ + methods: + "example_inline_yaml"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Data container defined from yaml_multi_line: [ +#@ { +#@ "CFEngine Champions": [ +#@ { +#@ "Name": "Aleksey Tsalolikhin", +#@ "Year": 2011 +#@ }, +#@ { +#@ "Name": "Ted Zlatanov", +#@ "Year": 2013 +#@ } +#@ ] +#@ } +#@ ] +#@ R: Data container defined from yaml_single_line: [ +#@ { +#@ "key1": "value1" +#@ }, +#@ { +#@ "key2": "value2" +#@ } +#@ ] +#@ ``` +#+end_src diff --git a/examples/insert_users.cf b/examples/insert_users.cf new file mode 100644 index 0000000000..99433fc759 --- /dev/null +++ b/examples/insert_users.cf @@ -0,0 +1,134 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Insert users into the passwd file of a system by +# extracting named users from a master file - repeat +# for /etc/shadow +# +######################################################## + +body common control + +{ + bundlesequence => { "updateusers" }; +} + +######################################################## + +bundle agent updateusers + +{ + vars: + + # Set $(testing) to "" for production + + "testing" string => "/home/mark/tmp"; + "tmp" string => "$(testing)/etc/passwd_tmp"; + + "extract_users" slist => { "mark", "root", "at", "www-run" }; + + files: + + # + # Take the passwed entries from source and add them to real_passwd + # + + "$(tmp)" + + create => "true", + edit_line => SelectUsers("$(testing)/masterfiles/passwd","@(this.extract_users)"); + + # + # Intermediate file - should be secure - not in /tmp + # + + "$(testing)/etc/passwd" + + edit_line => ReplaceUsers("$(tmp)","@(this.extract_users)"); + + # + + "$(testing)/home/$(extract_users)/." + + create => "true", + perms => userdir("$(extract_users)"); +} + + + + + + +######################################################## +# Library stuff +######################################################## + +body perms userdir(u) +{ + mode => "755"; + owners => { "$(u)" }; + groups => { "users" }; +} + +######################################################## + +bundle edit_line SelectUsers(f,l) +{ + insert_lines: + + "$(f)" + + insert_type => "file", + insert_select => keep("@(l)"); +} + +######################################################## + +bundle edit_line ReplaceUsers(f,l) +{ + delete_lines: + + "$(f)" + delete_select => discard("@(l)"); + + insert_lines: + + "$(f)" + + insert_type => "file"; +} + +######################################################## + +body insert_select keep(s) +{ + insert_if_startwith_from_list => { @(s) }; +} + +######################################################## + +body delete_select discard(s) +{ + delete_if_not_startwith_from_list => { @(s) }; +} diff --git a/examples/int.cf b/examples/int.cf new file mode 100644 index 0000000000..ef372dc62b --- /dev/null +++ b/examples/int.cf @@ -0,0 +1,21 @@ +#+begin_src cfengine3 +bundle agent main +{ + vars: + "data" data => '{"acft_name": "A320neo", + "engine_num": "2", + "price_in_USD": "123.456789k"}'; + + "engines" int => int("$(data[engine_num])"); + "ballpark_price" int => int("$(data[price_in_USD])"); + + reports: + "A320neo has $(engines) engines and costs about $(ballpark_price) USD."; +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: A320neo has 2 engines and costs about 123456 USD. +#@ ``` +#+end_src diff --git a/examples/intarray.cf b/examples/intarray.cf new file mode 100644 index 0000000000..7dd0dc25e6 --- /dev/null +++ b/examples/intarray.cf @@ -0,0 +1,43 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "dim_array" + + int => readstringarray("array_name","/tmp/array","#[^\n]*",":",10,4000); + + +} + diff --git a/examples/intersection.cf b/examples/intersection.cf new file mode 100644 index 0000000000..ece7c2e99b --- /dev/null +++ b/examples/intersection.cf @@ -0,0 +1,54 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "a" slist => { 1,2,3,"x" }; + "b" slist => { "x" }; + + "mylist1" slist => { "a", "b" }; + "mylist2" slist => { "a", "b" }; + "$(mylist1)_str" string => join(",", $(mylist1)); + + "int_$(mylist1)_$(mylist2)" slist => intersection($(mylist1), $(mylist2)); + "int_$(mylist1)_$(mylist2)_str" string => join(",", "int_$(mylist1)_$(mylist2)"); + + reports: + "The intersection of list '$($(mylist1)_str)' with '$($(mylist2)_str)' is '$(int_$(mylist1)_$(mylist2)_str)'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The intersection of list '1,2,3,x' with '1,2,3,x' is '1,2,3,x' +#@ R: The intersection of list '1,2,3,x' with 'x' is 'x' +#@ R: The intersection of list 'x' with '1,2,3,x' is 'x' +#@ R: The intersection of list 'x' with 'x' is 'x' +#@ ``` +#+end_src diff --git a/examples/inventory_cpu.cf b/examples/inventory_cpu.cf new file mode 100644 index 0000000000..8734152f56 --- /dev/null +++ b/examples/inventory_cpu.cf @@ -0,0 +1,35 @@ +# Demo on how to extract the first "model name" field from /proc/cpuinfo +# into a variable. +# Can be used for inventory information in CFEngine Nova and above. + +body common control +{ + bundlesequence => { "inventory" }; +} + +### + +bundle common inventory +{ + vars: + "cpuinfo" string => execresult("/bin/grep \"model name\" /proc/cpuinfo", "noshell"); + + got_model_name:: + "cpu" string => "$(myarray[1])"; + + classes: + + "got_model_name" expression => regextract( + "model\s+name\s+:\s+([^\n]*)\n?.*", + "$(cpuinfo)", + "myarray" + ); + + reports: + got_model_name:: + "model name is \"$(myarray[1])\""; + + !got_model_name:: + "Did not match CPU model name"; +} + diff --git a/examples/ip2host.cf b/examples/ip2host.cf new file mode 100644 index 0000000000..414b7528eb --- /dev/null +++ b/examples/ip2host.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "reverse_lookup" }; +} + +bundle agent reverse_lookup +{ + vars: + "local4" string => ip2host("127.0.0.1"); + + # this will be localhost on some systems, ip6-localhost on others... + "local6" string => ip2host("::1"); + + reports: + _cfe_output_testing:: + "we got local4" if => isvariable("local4"); + + !_cfe_output_testing:: + "local4 is $(local4)"; + "local6 is $(local6)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: we got local4 +#@ ``` +#+end_src diff --git a/examples/iprange.cf b/examples/iprange.cf new file mode 100644 index 0000000000..d08f697ec2 --- /dev/null +++ b/examples/iprange.cf @@ -0,0 +1,50 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + classes: + + "adhoc_group_1" expression => iprange("192.168.1.50-140"); + "adhoc_group_2" expression => iprange("192.168.1.0/24"); + + reports: + + adhoc_group_1:: + + "Some numerology"; + + adhoc_group_2:: + + "The masked warriors"; +} + diff --git a/examples/irange.cf b/examples/irange.cf new file mode 100644 index 0000000000..ec8c32b904 --- /dev/null +++ b/examples/irange.cf @@ -0,0 +1,59 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "cleanup" }; +} + +bundle agent cleanup +{ + files: + # This will not delete the parent + + "/home/mark/tmp/testcopy" + delete => tidyfiles, + file_select => changed_within_1_year, + depth_search => recurse; + + #Now delete the parent. + + "/home/mark/tmp/testcopy" + delete => tidyfiles; +} + +body delete tidyfiles +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +body file_select changed_within_1_year +{ + mtime => irange(ago(1,0,0,0,0,0),now); + file_result => "mtime"; +} + +body depth_search recurse +{ + depth => "inf"; +} diff --git a/examples/isdir.cf b/examples/isdir.cf new file mode 100644 index 0000000000..db3ec14bf4 --- /dev/null +++ b/examples/isdir.cf @@ -0,0 +1,48 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "isdir" expression => isdir("/"); + + reports: + + isdir:: + + "Directory exists.."; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Directory exists.. +#@ ``` +#+end_src diff --git a/examples/isexecutable.cf b/examples/isexecutable.cf new file mode 100644 index 0000000000..ff06e24368 --- /dev/null +++ b/examples/isexecutable.cf @@ -0,0 +1,44 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "yes" expression => isexecutable("/bin/ls"); + reports: + yes:: + "/bin/ls is an executable file"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /bin/ls is an executable file +#@ ``` +#+end_src diff --git a/examples/isgreaterthan.cf b/examples/isgreaterthan.cf new file mode 100644 index 0000000000..c34c3df627 --- /dev/null +++ b/examples/isgreaterthan.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "ok" expression => isgreaterthan("1","0"); + + reports: + + ok:: + + "Assertion is true"; + + !ok:: + + "Assertion is false"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Assertion is true +#@ ``` +#+end_src diff --git a/examples/isipinsubnet.cf b/examples/isipinsubnet.cf new file mode 100644 index 0000000000..e3cbd34208 --- /dev/null +++ b/examples/isipinsubnet.cf @@ -0,0 +1,47 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +############################################################################### +#+begin_src cfengine3 +bundle agent main +{ + classes: + + "in_192" expression => isipinsubnet("192.0.0.0/8", "192.1.2.3"); + "in_192_2_2_2" expression => isipinsubnet("192.2.2.0/24", "192.1.2.3"); + + reports: + + in_192:: + "The address 192.1.2.3 is in the 192/8 subnet"; + !in_192_2_2_2:: + "The address 192.1.2.3 is not in the 192.2.2/24 subnet"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The address 192.1.2.3 is in the 192/8 subnet +#@ R: The address 192.1.2.3 is not in the 192.2.2/24 subnet +#@ ``` +#+end_src diff --git a/examples/islessthan.cf b/examples/islessthan.cf new file mode 100644 index 0000000000..aaddd9892e --- /dev/null +++ b/examples/islessthan.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + classes: + + "ok" expression => islessthan("0","1"); + + reports: + + ok:: + + "Assertion is true"; + + !ok:: + + "Assertion is false"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Assertion is true +#@ ``` +#+end_src diff --git a/examples/islink.cf b/examples/islink.cf new file mode 100644 index 0000000000..52a12dba40 --- /dev/null +++ b/examples/islink.cf @@ -0,0 +1,54 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ ln -fs /tmp/cfe_testhere.txt /tmp/link +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "islink" expression => islink("/tmp/link"); + + reports: + + islink:: + + "It's a link."; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: It's a link. +#@ ``` +#+end_src diff --git a/examples/isnewerthan.cf b/examples/isnewerthan.cf new file mode 100644 index 0000000000..eef78f9dbf --- /dev/null +++ b/examples/isnewerthan.cf @@ -0,0 +1,54 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ touch -t '200102031234.56' /tmp/earlier +#@ touch -t '200202031234.56' /tmp/later +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "do_it" and => { isnewerthan("/tmp/later","/tmp/earlier"), "cfengine" }; + + reports: + + do_it:: + + "/tmp/later is older than /tmp/earlier"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /tmp/later is older than /tmp/earlier +#@ ``` +#+end_src diff --git a/examples/isplain.cf b/examples/isplain.cf new file mode 100644 index 0000000000..a97f92742d --- /dev/null +++ b/examples/isplain.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "fileisplain" expression => isplain("/etc/passwd"); + "dirisnotplain" not => isplain("/"); + + reports: + + fileisplain:: + "/etc/passwd is plain.."; + + dirisnotplain:: + "/ is not plain.."; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /etc/passwd is plain.. +#@ R: / is not plain.. +#@ ``` +#+end_src diff --git a/examples/isreadable.cf b/examples/isreadable.cf new file mode 100644 index 0000000000..25fc7d7832 --- /dev/null +++ b/examples/isreadable.cf @@ -0,0 +1,25 @@ +#+begin_src prep +#@ ``` +#@ echo "Hello CFEngine!" > /tmp/foo.txt +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent __main__ +{ + vars: + "filename" + string => "/tmp/foo.txt"; + "timeout" + int => "3"; + reports: + "File '$(filename)' is readable" + if => isreadable("$(filename)", "$(timeout)"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: File '/tmp/foo.txt' is readable +#@ ``` +#+end_src diff --git a/examples/isvariable.cf b/examples/isvariable.cf new file mode 100644 index 0000000000..af7acc36b8 --- /dev/null +++ b/examples/isvariable.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "bla" string => "xyz.."; + + classes: + + "exists" expression => isvariable("bla"); + + reports: + + exists:: + + "Variable exists: \"$(bla)\".."; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Variable exists: "xyz..".. +#@ ``` +#+end_src diff --git a/examples/iteration.cf b/examples/iteration.cf new file mode 100644 index 0000000000..ecc1e11b6a --- /dev/null +++ b/examples/iteration.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Nested iteration +# + +body common control +{ + bundlesequence => {"x"}; +} + +bundle agent x +{ + vars: + + "list1" slist => { "a", "b", "c" }; + + "list2" slist => { "1", "2", "3", "4" }; + + "list3" slist => { "x", "y", "z" }; + + reports: + "Hello $(list1) $(list2) $(list3)"; +} + diff --git a/examples/join.cf b/examples/join.cf new file mode 100644 index 0000000000..310c081041 --- /dev/null +++ b/examples/join.cf @@ -0,0 +1,56 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + + "mylist" slist => { "one", "two", "three", "four", "five" }; + "datalist" data => parsejson('[1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three",]'); + + "mylist_str" string => format("%S", mylist); + "datalist_str" string => format("%S", datalist); + "myscalar" string => join("->", mylist); + "datascalar" string => join("->", datalist); + + reports: + "Concatenated $(mylist_str): $(myscalar)"; + "Concatenated $(datalist_str): $(datascalar)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Concatenated { "one", "two", "three", "four", "five" }: one->two->three->four->five +#@ R: Concatenated [1,2,3,"one","two","three","long string","four","fix","six","one","two","three"]: 1->2->3->one->two->three->long string->four->fix->six->one->two->three +#@ ``` +#+end_src diff --git a/examples/kill_process_running_wrong_user.cf b/examples/kill_process_running_wrong_user.cf new file mode 100644 index 0000000000..2e2d3c1e13 --- /dev/null +++ b/examples/kill_process_running_wrong_user.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent main +{ + processes: + + # Any /usr/local/web/tomcat-logviewer processes not + # running as buildsrv should be killed on sight. + + "/usr/local/web/tomcat-logviewer" -> { "security" } + process_select => not_running_as("buildsrv"), + signals => { "kill" }, + comment => "It is against the security policy for this + service to run under the wrong user id."; +} + +body process_select not_running_as(owner) +# @brief select processes that are not running as the expected owner +# @param owner +{ + process_owner => { $(owner) }; + process_result => "!process_owner"; +} +#+end_src +############################################################################### +#+begin_src static_example_output +#@ ``` +#@ info: Signalled 'kill' (9) to process 7211 (root 7211 7199 7211 0.0 0.1 100908 0 596 1 15:26 00:06 00:00:00 /usr/local/web/tomcat-logviewer 500) +#@ ``` +#+end_src diff --git a/examples/lastnode.cf b/examples/lastnode.cf new file mode 100644 index 0000000000..ab2567f598 --- /dev/null +++ b/examples/lastnode.cf @@ -0,0 +1,59 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "yes" }; +} + +bundle agent yes +{ + vars: + + "path1" string => "/one/two/last1"; + "path2" string => "one:two:last2"; + "path4" string => "/one/two/"; + + "last1" string => lastnode("$(path1)","/"); + "last2" string => lastnode("$(path2)",":"); + + "last3" string => lastnode("$(path2)","/"); + "last4" string => lastnode("$(path4)","/"); + + reports: + "Last / node in / path '$(path1)' = '$(last1)'"; + "Last : node in : path '$(path2)' = '$(last2)'"; + "Last / node in : path '$(path2)' = '$(last3)'"; + "Last / node in /-terminated path '$(path4)' = '$(last4)'"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Last / node in / path '/one/two/last1' = 'last1' +#@ R: Last : node in : path 'one:two:last2' = 'last2' +#@ R: Last / node in : path 'one:two:last2' = 'one:two:last2' +#@ R: Last / node in /-terminated path '/one/two/' = '' +#@ ``` +#+end_src diff --git a/examples/ldap.cf b/examples/ldap.cf new file mode 100644 index 0000000000..06df601cd6 --- /dev/null +++ b/examples/ldap.cf @@ -0,0 +1,77 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# + +body common control +{ + bundlesequence => { "ldap" , "followup"}; +} + +################################################################################################### +# NOTE!! relying on LDAP or other network data without validation is EXTREMELY dangerous. +# You could destroy a system by assuming that the service will respond with a +# sensible result. Cfengine does not recommend reliance on network services in configuration. +################################################################################################### + +bundle agent ldap +{ + vars: + + # Get the first matching value for "uid" + + "value" string => ldapvalue("ldap://eternity.iu.hio.no","dc=cfengine,dc=com","(sn=User)","uid","subtree","none"); + + # Geta all matching values for "uid" - should be a single record match + + "list" slist => ldaplist("ldap://eternity.iu.hio.no","dc=cfengine,dc=com","(sn=User)","uid","subtree","none"); + + classes: + + "gotdata" expression => ldaparray("myarray","ldap://eternity.iu.hio.no","dc=cfengine,dc=com","(uid=mark)","subtree","none"); + + "found" expression => regldap("ldap://eternity.iu.hio.no","dc=cfengine,dc=com","(sn=User)","uid","subtree","jon.*","none"); + + reports: + + "LDAP VALUE $(value) found"; + "LDAP LIST VALUE $(list)"; + + gotdata:: + + "Found specific entry data ...$(ldap.myarray[uid]),$(ldap.myarray[gecos]), etc"; + + found:: + + "Matched regex"; + +} + +bundle agent followup + +{ + reports: + + "Different bundle ...$(ldap.myarray[uid]),$(ldap.myarray[gecos]), etc"; + +} diff --git a/examples/length.cf b/examples/length.cf new file mode 100644 index 0000000000..61f0a6ef30 --- /dev/null +++ b/examples/length.cf @@ -0,0 +1,55 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test + +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; + + "length" int => length("test"); + "test_str" string => join(",", "test"); + + reports: + "The test list is $(test_str)"; + "The test list has $(length) elements"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The test list is 1,2,3,one,two,three,long string,four,fix,six,one,two,three +#@ R: The test list has 13 elements +#@ ``` +#+end_src diff --git a/examples/linking.cf b/examples/linking.cf new file mode 100644 index 0000000000..addcd7e1b1 --- /dev/null +++ b/examples/linking.cf @@ -0,0 +1,86 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# File editing +# +# Normal ordering: +# - delete +# - replace | column_edit +# - insert +# +###################################################################### + + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + # Make a copy of the password file + + "/home/mark/tmp/passwd" + + link_from => linkdetails("/etc/passwd"), + move_obstructions => "true"; + + + "/home/mark/tmp/linktest" + + link_from => linkchildren("/usr/local/sbin"); + + + #child links +} + +######################################################### + +body link_from linkdetails(tofile) + +{ + source => "$(tofile)"; + link_type => "symlink"; + when_no_source => "force"; # kill +} + +######################################################### + +body link_from linkchildren(tofile) + +{ + source => "$(tofile)"; + link_type => "symlink"; + when_no_source => "force"; # kill + link_children => "true"; + when_linking_children => "if_no_such_file"; # "override_file"; +} + diff --git a/examples/literal_server.cf b/examples/literal_server.cf new file mode 100644 index 0000000000..e991f2df5e --- /dev/null +++ b/examples/literal_server.cf @@ -0,0 +1,109 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Remote value from server connection to cfServer +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-d2] +# cf-agent -f runtest_2.cf +# +# Notice that the same file configures all parts of cfengine + +######################################################## + +body common control + +{ + bundlesequence => { "example" }; + + version => "1.2.3"; +} + +######################################################## + +bundle agent example + +{ + vars: + + "encrypt" string => "yes"; + + "x" string => "scalar2"; + "remote1" string => remotescalar("test_scalar1","127.0.0.1","$(encrypt)"); + "remote2" string => remotescalar("test_scalar2","127.0.0.1","$(encrypt)"); + "remote3" string => remotescalar("test_scalar3","127.0.0.1","$(encrypt)"); + "remote_error" string => remotescalar("test_$(x)","127.0.0.2","$(encrypt)"); + + reports: + + "Receive value $(remote1), $(remote2), $(remote3)"; + + "And an error gives: $(remote_error)"; +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowusers => { "mark" }; +} + +######################################################### + +bundle server access_rules() + +{ + vars: + + "localvar" string => "literal string"; + + access: + + "Embed a $(localvar) for remote access" + + handle => "test_scalar1", + resource_type => "literal", + admit => { "127.0.0.1" }; + + + "Mary had a little lamb whose fleece was white as snow and everywhere that Mary went she wore it lovingly" + + handle => "test_scalar2", + resource_type => "literal", + admit => { "127.0.0.1" }; + + "/etc/passwd" + handle => "test_scalar3", + admit => { "127.0.0.1" }; +} diff --git a/examples/local_group_present.cf b/examples/local_group_present.cf new file mode 100644 index 0000000000..4716797231 --- /dev/null +++ b/examples/local_group_present.cf @@ -0,0 +1,17 @@ +body file control +{ + # This policy uses parts of the standard library. + inputs => { "$(sys.libdir)/paths.cf" }; +} + +bundle agent main +{ + classes: + "group_cfengineers_absent" + not => groupexists("cfengineers"); + + commands: + linux.group_cfengineers_absent:: + "$(paths.groupadd)" + args => "cfengineers"; +} diff --git a/examples/local_user_password.cf b/examples/local_user_password.cf new file mode 100644 index 0000000000..535c7cf915 --- /dev/null +++ b/examples/local_user_password.cf @@ -0,0 +1,21 @@ +body file control +{ + # This policy uses parts of the standard library. + inputs => { "$(sys.libdir)/users.cf" }; +} + +bundle agent main +{ + vars: + # This is the hashed password for 'vagrant' + debian_8:: + "root_hash" + string => "$6$1nRTeNoE$DpBSe.eDsuZaME0EydXBEf.DAwuzpSoIJhkhiIAPgRqVKlmI55EONfvjZorkxNQvK2VFfMm9txx93r2bma/4h/"; + + users: + linux:: + "root" + policy => "present", + password => hashed_password( $(root_hash) ), + if => isvariable("root_hash"); +} diff --git a/examples/local_user_secondary_group_member.cf b/examples/local_user_secondary_group_member.cf new file mode 100644 index 0000000000..a5c513cd6d --- /dev/null +++ b/examples/local_user_secondary_group_member.cf @@ -0,0 +1,8 @@ +bundle agent main +{ + users: + linux:: + "jill" + policy => "present", + groups_secondary => { "cfengineers" }; +} diff --git a/examples/local_users_absent.cf b/examples/local_users_absent.cf new file mode 100644 index 0000000000..f6c8dc3c99 --- /dev/null +++ b/examples/local_users_absent.cf @@ -0,0 +1,10 @@ +bundle agent main +{ + vars: + "users" slist => { "jack", "jill" }; + + users: + linux:: + "$(users)" + policy => "absent"; +} diff --git a/examples/local_users_locked.cf b/examples/local_users_locked.cf new file mode 100644 index 0000000000..b3a6f30f1c --- /dev/null +++ b/examples/local_users_locked.cf @@ -0,0 +1,10 @@ +bundle agent main +{ + vars: + "users" slist => { "jack", "jill" }; + + users: + linux:: + "$(users)" + policy => "locked"; +} diff --git a/examples/local_users_present.cf b/examples/local_users_present.cf new file mode 100644 index 0000000000..80ac180561 --- /dev/null +++ b/examples/local_users_present.cf @@ -0,0 +1,31 @@ +body file control +{ + # This policy uses parts of the standard library. + inputs => { "$(sys.libdir)/files.cf" }; +} + +bundle agent main +{ + vars: + "users" slist => { "jack", "jill" }; + "skel" string => "/etc/skel"; + + users: + linux:: + "$(users)" + home_dir => "/home/$(users)", + policy => "present", + home_bundle => home_skel( $(users), $(skel) ); +} + +bundle agent home_skel(user, skel) +# @brief Initialize a user's home directory with the contents of skel +# @param user The user's home directory to create +# @param skel The directory to seed the user's home with +{ + files: + "/home/$(user)/." + create => "true", + copy_from => seed_cp( $(skel) ), + depth_search => recurse( "inf" ); +} diff --git a/examples/locate_files_and_compress.cf b/examples/locate_files_and_compress.cf new file mode 100644 index 0000000000..9d7935135d --- /dev/null +++ b/examples/locate_files_and_compress.cf @@ -0,0 +1,66 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Compressing files +# +####################################################### + +body common control + +{ + bundlesequence => { "example" }; +} + +############################################ + +bundle agent example + +{ + files: + + "/home/mark/tmp/testcopy" + + file_select => pdf_files, + transformer => "/usr/bin/gzip $(this.promiser)", + depth_search => recurse("inf"); + +} + +############################################ + +body file_select pdf_files + +{ + leaf_name => { ".*.pdf" , ".*.fdf" }; + file_result => "leaf_name"; +} + +############################################ + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + diff --git a/examples/log_private.cf b/examples/log_private.cf new file mode 100644 index 0000000000..a71937b67c --- /dev/null +++ b/examples/log_private.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "software" slist => { "/root/xyz", "/tmp/xyz" }; + + files: + + "$(software)" + + create => "true", + action => logme("$(software)"); + +} + +# + +body action logme(x) +{ + log_kept => "/tmp/private_keptlog.log"; + log_failed => "/tmp/private_faillog.log"; + log_repaired => "/tmp/private_replog.log"; + log_string => "$(sys.date) $(x) promise status"; +} diff --git a/examples/loops.cf b/examples/loops.cf new file mode 100644 index 0000000000..e73cce22d1 --- /dev/null +++ b/examples/loops.cf @@ -0,0 +1,48 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "component" slist => { "cf-monitord", "cf-serverd", "cf-execd" }; + + "array[cf-monitord]" string => "The monitor"; + "array[cf-serverd]" string => "The server"; + "array[cf-execd]" string => "The executor, not executioner"; + + reports: + "/bin/echo $(component) is $(array[$(component)])"; + +} + diff --git a/examples/lsdir.cf b/examples/lsdir.cf new file mode 100644 index 0000000000..81f6a73941 --- /dev/null +++ b/examples/lsdir.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "listfiles" slist => lsdir("/etc", "(p.sswd|gr[ou]+p)", "true"); + "sorted_listfiles" slist => sort(listfiles, "lex"); + + reports: + "files in list: $(sorted_listfiles)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: files in list: /etc/group +#@ R: files in list: /etc/passwd +#@ ``` +#+end_src diff --git a/examples/main.cf b/examples/main.cf new file mode 100644 index 0000000000..d803c10c69 --- /dev/null +++ b/examples/main.cf @@ -0,0 +1,16 @@ +# Example showing how the default bundlesequence runs bundle agent main + +bundle agent main +# @brief main is the default bundlesequence +{ + reports: + "Hello, $(this.bundle) bundle."; +} + +#@ The policy promises to report the name of the current bundle, and produces this output: + +#+begin_src example_output +#@ ``` +#@ R: Hello, main bundle. +#@ ``` +#+end_src diff --git a/examples/main_entry_point.cf b/examples/main_entry_point.cf new file mode 100644 index 0000000000..a38b8c78cb --- /dev/null +++ b/examples/main_entry_point.cf @@ -0,0 +1,22 @@ +#!/var/cfengine/bin/cf-agent -KIf- +# Example showing how bundle __main__ works +# This file can be used as the main entry (`cf-agent -KIf ./main_entry_point.cf`) +# This file can also be included from another policy file containing __main__ + +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/main_library.cf" }; +} + +bundle agent __main__ +{ + methods: + + "a" usebundle => libprint("Hello from $(sys.policy_entry_basename)"); +} + +#+begin_src example_output +#@ ``` +#@ R: Hello from policy.cf +#@ ``` +#+end_src diff --git a/examples/main_library.cf b/examples/main_library.cf new file mode 100644 index 0000000000..9f6460639f --- /dev/null +++ b/examples/main_library.cf @@ -0,0 +1,27 @@ +#!/var/cfengine/bin/cf-agent -KIf- +# Example showing how bundle __main__ works +# This file can be used as the main entry (`cf-agent -KIf ./main_library.cf`) +# This file can also be included from another policy file containing __main__ + +bundle agent libprint(message) +# @brief report `message` +{ + reports: + "Library: $(message)."; +} + +bundle agent __main__ +# @brief Run this bundle (a testsuite) if this file is the entry +# This bundle is special. It is ignored unless it is the policy entry file +{ + methods: + "test" usebundle => libprint( "ok 1 - libprint works" ); +} + +#@ The policy promises to call libprint to report that it works when the policy file is the main entry. + +#+begin_src example_output +#@ ``` +#@ R: Library: ok 1 - libprint works +#@ ``` +#+end_src diff --git a/examples/maparray.cf b/examples/maparray.cf new file mode 100644 index 0000000000..fceb0227d0 --- /dev/null +++ b/examples/maparray.cf @@ -0,0 +1,73 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "run" }; +} + +bundle agent run +{ + vars: + "static[2]" string => "lookup 2"; + "static[two]" string => "lookup two"; + "static[big]" string => "lookup big"; + "static[small]" string => "lookup small"; + + "todo[1]" string => "2"; + "todo[one]" string => "two"; + "todo[3999]" slist => { "big", "small" }; + "map" slist => + maparray("key='$(this.k)', static lookup = '$(static[$(this.v)])', value='$(this.v)'", + todo); + "map_sorted" slist => sort(map, lex); + + "mycontainer" data => parsejson(' +{ + "top": + { + "x": 2, + "y": "big" + } +}'); + "mapc" slist => + maparray("key='$(this.k)', key2='$(this.k[1])', static lookup = '$(static[$(this.v)])', value='$(this.v)'", + mycontainer); + "mapc_str" string => format("%S", mapc); + + reports: + "mapped array: $(map_sorted)"; + "mapped container: $(mapc_str)"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: mapped array: key='1', static lookup = 'lookup 2', value='2' +#@ R: mapped array: key='3999', static lookup = 'lookup big', value='big' +#@ R: mapped array: key='3999', static lookup = 'lookup small', value='small' +#@ R: mapped array: key='one', static lookup = 'lookup two', value='two' +#@ R: mapped container: { "key='top', key2='x', static lookup = 'lookup 2', value='2'", "key='top', key2='y', static lookup = 'lookup big', value='big'" } +#@ ``` +#+end_src diff --git a/examples/mapdata.cf b/examples/mapdata.cf new file mode 100644 index 0000000000..19fe2c16e9 --- /dev/null +++ b/examples/mapdata.cf @@ -0,0 +1,87 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "run" }; +} + +bundle agent run +{ + vars: + "myarray[lookup][big]" string => "lookup big"; + "myarray[lookup][small]" string => "lookup small"; + + # every item must parse as valid JSON when the interpretation is `json` + "mapa_json" data => mapdata("json", '{ "key": "$(this.k)", "key2": "$(this.k[1])", "value": "$(this.v)" }', myarray); + "mapa_json_str" string => format("%S", mapa_json); + + # every item is just a string when the interpretation is `none` + "mapa_none" data => mapdata("none", 'key=$(this.k), level 2 key = $(this.k[1]), value=$(this.v)', myarray); + "mapa_none_str" string => format("%S", mapa_none); + + "mycontainer" data => parsejson(' +{ + "top": + { + "x": 100, + "y": 200 + } +}'); + + # every item must parse as valid JSON when the interpretation is `json` + "mapc_json" data => mapdata("json", '{ "key": "$(this.k)", "key2": "$(this.k[1])", "value": "$(this.v)" }', mycontainer); + "mapc_json_str" string => format("%S", mapc_json); + + # every item is just a string when the interpretation is `none` + "mapc_none" data => mapdata("none", 'key=$(this.k), level 2 key = $(this.k[1]), value=$(this.v)', mycontainer); + "mapc_none_str" string => format("%S", mapc_none); + + reports: + show_example:: + "mapdata/json on classic CFEngine array result: $(mapa_json_str)"; + "mapdata/none on classic CFEngine array result: $(mapa_none_str)"; + "mapdata/json on data container result: $(mapc_json_str)"; + "mapdata/none on data container result: $(mapc_none_str)"; + + any:: + "Note that the output of the above reports is not deterministic,"; + "because the order of the keys returned by mapdata() is not guaranteed."; +} + +#+end_src +############################################################################### +#+begin_src show_example_example_output +#@ ``` +#@ R: mapdata/json on classic CFEngine array result: [{"key":"lookup","key2":"big","value":"lookup big"},{"key":"lookup","key2":"small","value":"lookup small"}] +#@ R: mapdata/none on classic CFEngine array result: ["key=lookup, level 2 key = big, value=lookup big","key=lookup, level 2 key = small, value=lookup small"] +#@ R: mapdata/json on data container result: [{"key":"top","key2":"x","value":"100"},{"key":"top","key2":"y","value":"200"}] +#@ R: mapdata/none on data container result: ["key=top, level 2 key = x, value=100","key=top, level 2 key = y, value=200"] +#@ ``` +#+end_src +#+begin_src show_example_example_output +#@ ``` +#@ R: "Note that the output of the above reports is not deterministic," +#@ R: "because the order of the keys returned by mapdata() is not guaranteed." +#@ ``` +#+end_src diff --git a/examples/mapdata_jsonpipe.cf b/examples/mapdata_jsonpipe.cf new file mode 100644 index 0000000000..c50ea18f30 --- /dev/null +++ b/examples/mapdata_jsonpipe.cf @@ -0,0 +1,78 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "run" }; +} + +bundle agent run +{ + vars: + "tester" data => '{ "x": 100, "y": [ true, "a", "b" ] }'; + + # "jq ." returns the same thing that was passed in + "pipe_passthrough" data => mapdata("json_pipe", '$(def.jq) .', tester); + "pipe_passthrough_str" string => format("%S", pipe_passthrough); + + # "jq .x" returns what was under x wrapped in an array: [100] + "pipe_justx" data => mapdata("json_pipe", '$(def.jq) .x', tester); + "pipe_justx_str" string => format("%S", pipe_justx); + + # "jq .y" returns what was under y wrapped in an array: [[true,"a","b"]] + "pipe_justy" data => mapdata("json_pipe", '$(def.jq) .y', tester); + "pipe_justy_str" string => format("%S", pipe_justy); + + # "jq .y[]" returns each entry under y *separately*: [true,"a","b"] + "pipe_yarray" data => mapdata("json_pipe", '$(def.jq) .y[]', tester); + "pipe_yarray_str" string => format("%S", pipe_yarray); + + # "jq .z" returns null because the key "z" is missing: [null] + "pipe_justz" data => mapdata("json_pipe", '$(def.jq) .z', tester); + "pipe_justz_str" string => format("%S", pipe_justz); + + # "jq" can do math too! and much more! + "pipe_jqmath" data => mapdata("json_pipe", '$(def.jq) 1+2+3', tester); + "pipe_jqmath_str" string => format("%S", pipe_jqmath); + + reports: + "mapdata/json_pipe passthrough result: $(pipe_passthrough_str)"; + "mapdata/json_pipe just x result: $(pipe_justx_str)"; + "mapdata/json_pipe just y result: $(pipe_justy_str)"; + "mapdata/json_pipe array under y result: $(pipe_yarray_str)"; + "mapdata/json_pipe just z result: $(pipe_justz_str)"; + "mapdata/json_pipe math expression result: $(pipe_jqmath_str)"; +} + +#+end_src +############################################################################### +#+begin_src output +#@ ``` +#@ R: mapdata/json_pipe passthrough result: [{"x":100,"y":[true,"a","b"]}] +#@ R: mapdata/json_pipe just x result: [100] +#@ R: mapdata/json_pipe just y result: [[true,"a","b"]] +#@ R: mapdata/json_pipe array under y result: [true,"a","b"] +#@ R: mapdata/json_pipe just z result: [null] +#@ R: mapdata/json_pipe math expression result: [6] +#@ ``` +#+end_src diff --git a/examples/maplist.cf b/examples/maplist.cf new file mode 100644 index 0000000000..11c20354c2 --- /dev/null +++ b/examples/maplist.cf @@ -0,0 +1,63 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle common g +{ + vars: + + "otherlist" slist => { "x", "y", "z" }; +} + +####################################################### + +bundle agent example + +{ + vars: + + "oldlist" slist => { "a", "b", "c" }; + + "newlist1" slist => maplist("Element ($(this))","@(g.otherlist)"); + "newlist2" slist => maplist("Element ($(this))",@(oldlist)); + + reports: + "Transform: $(newlist1)"; + "Transform: $(newlist2)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Transform: Element (x) +#@ R: Transform: Element (y) +#@ R: Transform: Element (z) +#@ R: Transform: Element (a) +#@ R: Transform: Element (b) +#@ R: Transform: Element (c) +#@ ``` +#+end_src diff --git a/examples/max-min-mean-variance.cf b/examples/max-min-mean-variance.cf new file mode 100644 index 0000000000..838779beb2 --- /dev/null +++ b/examples/max-min-mean-variance.cf @@ -0,0 +1,76 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + # the behavior will be the same whether you use a data container or a list + # "mylist" slist => { "foo", "1", "2", "3000", "bar", "10.20.30.40" }; + "mylist" data => parsejson('["foo", "1", "2", "3000", "bar", "10.20.30.40"]'); + "mylist_str" string => format("%S", mylist); + + "max_int" string => max(mylist, "int"); + "max_lex" string => max(mylist, "lex"); + "max_ip" string => max(mylist, "ip"); + + "min_int" string => min(mylist, "int"); + "min_lex" string => min(mylist, "lex"); + "min_ip" string => min(mylist, "ip"); + + "mean" real => mean(mylist); + "variance" real => variance(mylist); + + reports: + "my list is $(mylist_str)"; + + "mean is $(mean)"; + "variance is $(variance) (use eval() to get the standard deviation)"; + + "max int is $(max_int)"; + "max IP is $(max_ip)"; + "max lexicographically is $(max_lex)"; + + "min int is $(min_int)"; + "min IP is $(min_ip)"; + "min lexicographically is $(min_lex)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: my list is ["foo","1","2","3000","bar","10.20.30.40"] +#@ R: mean is 502.200000 +#@ R: variance is 1497376.000000 (use eval() to get the standard deviation) +#@ R: max int is 3000 +#@ R: max IP is 10.20.30.40 +#@ R: max lexicographically is foo +#@ R: min int is bar +#@ R: min IP is 1 +#@ R: min lexicographically is 1 +#@ ``` +#+end_src diff --git a/examples/measure_log.cf b/examples/measure_log.cf new file mode 100644 index 0000000000..5bba071d79 --- /dev/null +++ b/examples/measure_log.cf @@ -0,0 +1,42 @@ +#cop measurements,example + +####################################################### +# +# Test file: log scanner +# +####################################################### + +# +# Look for a file in $STATEDIR/line_counter_measure.log +# +# $STATEDIR = $WORKDIR/state unless overridden at compile time. +# + +bundle monitor watch +{ + measurements: + + "/home/mark/tmp/file" + + handle => "line_counter", + stream_type => "file", + data_type => "counter", + match_value => scan_log("MYLINE.*"), + history_type => "log", + action => sample_rate("0"); + +} + +########################################################## + +body match_value scan_log(x) +{ + select_line_matching => "^$(x)$"; + track_growing_file => "true"; +} + +body action sample_rate(x) +{ + ifelapsed => "$(x)"; + expireafter => "10"; +} diff --git a/examples/measurements.cf b/examples/measurements.cf new file mode 100644 index 0000000000..911c0b068a --- /dev/null +++ b/examples/measurements.cf @@ -0,0 +1,159 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#cop measurements,example + +####################################################### +# +# Test file: +# +# First line +# Blonk blonk bnklkygsuilnm +# +####################################################### + +body common control +{ + bundlesequence => { "report" }; +} + +####################################################### + +body monitor control +{ + forgetrate => "0.7"; + histograms => "true"; +} + +####################################################### + +bundle agent report +{ + reports: + " + Free memory read at $(mon.av_free_memory_watch) + cf_monitord read $(mon.value_monitor_self_watch) + "; +} + +####################################################### + +bundle monitor watch +{ + measurements: + + # Test 1 - extract string matching + + "/home/mark/tmp/testmeasure" + + handle => "blonk_watch", + stream_type => "file", + data_type => "string", + history_type => "weekly", + units => "blonks", + match_value => find_blonks, + action => sample_min("10"); + + # Test 2 - follow a special process over time + # using cfengine's process cache to avoid resampling + + "/var/cfengine/state/cf_rootprocs" + + handle => "monitor_self_watch", + stream_type => "file", + data_type => "int", + history_type => "static", + units => "kB", + match_value => proc_value(".*cf-monitord.*", + "root\s+[0-9.]+\s+[0-9.]+\s+[0-9.]+\s+[0-9.]+\s+([0-9]+).*"); + + # Test 3, discover disk device information + + "/bin/df" + + handle => "free_disk_watch", + stream_type => "pipe", + data_type => "slist", + history_type => "static", + units => "device", + match_value => file_system; + # Update this as often as possible + + # Test 4 + + "/tmp/file" + + handle => "line_counter", + stream_type => "file", + data_type => "counter", + match_value => scanlines("MYLINE.*"), + history_type => "log"; + +} + +########################################################## + +body match_value scanlines(x) +{ + select_line_matching => "^$(x)$"; +} + +########################################################## + +body action sample_min(x) +{ + ifelapsed => "$(x)"; + expireafter => "$(x)"; +} + +########################################################## + +body match_value find_blonks +{ + select_line_number => "2"; + extraction_regex => "Blonk blonk ([blonk]+).*"; +} + +########################################################## + +body match_value free_memory # not willy! +{ + select_line_matching => "MemFree:.*"; + extraction_regex => "MemFree:\s+([0-9]+).*"; +} + +########################################################## + +body match_value proc_value(x,y) +{ + select_line_matching => "$(x)"; + extraction_regex => "$(y)"; +} + +########################################################## + +body match_value file_system +{ + select_line_matching => "/.*"; + extraction_regex => "(.*)"; +} + diff --git a/examples/menu.cf b/examples/menu.cf new file mode 100644 index 0000000000..eb17774e56 --- /dev/null +++ b/examples/menu.cf @@ -0,0 +1,78 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test copy from server connection to cfServer +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-d2] +# cf-agent -f runtest_2.cf +# +# Notice that the same file configures all parts of cfengine + +######################################################## + +body common control +{ + bundlesequence => { "example" }; + version => "1.2.3"; +} + +# cf-runagent -q + +bundle agent example +{ + files: +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowusers => { "mark", "root" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "delta" + resource_type => "query", + admit => { "127.0.0.1" }; + "full" + resource_type => "query", + admit => { "127.0.0.1" }; +} diff --git a/examples/mergedata-last-key-wins.cf b/examples/mergedata-last-key-wins.cf new file mode 100644 index 0000000000..fc75d4702c --- /dev/null +++ b/examples/mergedata-last-key-wins.cf @@ -0,0 +1,30 @@ +#+begin_src cfengine3 +bundle agent mergedata_last_key_wins +# @brief Example illustrating how the last key wins when merging data containers with conflicting keys +{ + vars: + + "one" data => '{ "color": "red", "stuff": [ "one", "two" ], "thing": "one" }'; + "two" data => '{ "color": "blue", "stuff": [ "three" ] }'; + + reports: + "$(with)" with => storejson( mergedata( one, two ) ); + +} +bundle agent __main__ +{ + methods: "mergedata_last_key_wins"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: { +#@ "color": "blue", +#@ "stuff": [ +#@ "three" +#@ ], +#@ "thing": "one" +#@ } +#@ ``` +#+end_src diff --git a/examples/mergedata.cf b/examples/mergedata.cf new file mode 100644 index 0000000000..bfaa830912 --- /dev/null +++ b/examples/mergedata.cf @@ -0,0 +1,122 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test", "test2", "test3" }; +} + +bundle agent test +{ + vars: + "d1" data => parsejson('{ "a": [1,2,3], "b": [] }'); + "d2" data => parsejson('{ "b": [4,5,6] }'); + "d3" data => parsejson('[4,5,6]'); + "list1" slist => { "element1", "element2" }; + "array1[mykey]" slist => { "array_element1", "array_element2" }; + "array2[otherkey]" string => "hello"; + + "merged_d1_d2" data => mergedata("d1", "d2"); + "merged_d1_d3" data => mergedata("d1", "d3"); + "merged_d3_list1" data => mergedata("d3", "list1"); + + "merged_d1_array1" data => mergedata("d1", "array1"); + "merged_d2_array2" data => mergedata("d2", "array2"); + + "merged_d1_wrap_array_d2" data => mergedata("d1", "[ d2 ]"); + "merged_d1_wrap_map_d2" data => mergedata("d1", '{ "newkey": d2 }'); + + "merged_d1_d2_str" string => format("merging %S with %S produced %S", d1, d2, merged_d1_d2); + "merged_d1_wrap_array_d2_str" string => format("merging %S with wrapped [ %S ] produced %S", d1, d2, merged_d1_wrap_array_d2); + "merged_d1_wrap_map_d2_str" string => format('merging %S with wrapped { "newkey": %S produced %S', d1, d2, merged_d1_wrap_map_d2); + "merged_d1_d3_str" string => format("merging %S with %S produced %S", d1, d3, merged_d1_d3); + "merged_d3_list1_str" string => format("merging %S with %S produced %S", d3, list1, merged_d3_list1); + + "merged_d1_array1_str" string => format("merging %S with %s produced %S", d1, array1, merged_d1_array1); + "merged_d2_array2_str" string => format("merging %S with %s produced %S", d2, array2, merged_d2_array2); + reports: + "$(merged_d1_d2_str)"; + "$(merged_d1_wrap_array_d2_str)"; + "$(merged_d1_wrap_map_d2_str)"; + "$(merged_d1_d3_str)"; + "$(merged_d3_list1_str)"; + "$(merged_d1_array1_str)"; + "$(merged_d2_array2_str)"; +} + +bundle agent test2 +{ + vars: + "a" data => parsejson('{ "a": "1" }'), meta => { "mymerge" }; + "b" data => parsejson('{ "b": "2" }'), meta => { "mymerge" }; + "c" data => parsejson('{ "c": "3" }'), meta => { "mymerge" }; + "d" data => parsejson('{ "d": "4" }'), meta => { "mymerge" }; + "todo" slist => variablesmatching(".*", "mymerge"); + + methods: + "go" usebundle => cmerge(@(todo)); # a, b, c, d + + reports: + "$(this.bundle): merged containers with cmerge = $(cmerge.all_str)"; +} + +# note this bundle is in the standard library, in lib/3.6/bundles.cf +bundle agent cmerge(varlist) +{ + vars: + "all" data => parsejson('[]'), policy => "free"; + "all" data => mergedata(all, $(varlist)), policy => "free"; + "all_str" string => format("%S", all), policy => "free"; +} + +bundle agent test3 +{ + vars: + "dest_files" slist => { "/tmp/default.json", "/tmp/epel.json" }; + "template_file" string => "repository.mustache"; + + "process_templates" data => mergedata('{ "$(template_file)" : dest_files }'); + "process__templates_str" string => format("%S", "process_templates"); + + reports: + "$(this.bundle) $(process__templates_str)"; + "$(this.bundle) $(process_templates[$(template_file)])"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: merging {"a":[1,2,3],"b":[]} with {"b":[4,5,6]} produced {"a":[1,2,3],"b":[4,5,6]} +#@ R: merging {"a":[1,2,3],"b":[]} with wrapped [ {"b":[4,5,6]} ] produced {"0":{"b":[4,5,6]},"a":[1,2,3],"b":[]} +#@ R: merging {"a":[1,2,3],"b":[]} with wrapped { "newkey": {"b":[4,5,6]} produced {"a":[1,2,3],"b":[],"newkey":{"b":[4,5,6]}} +#@ R: merging {"a":[1,2,3],"b":[]} with [4,5,6] produced {"0":4,"1":5,"2":6,"a":[1,2,3],"b":[]} +#@ R: merging [4,5,6] with { "element1", "element2" } produced [4,5,6,"element1","element2"] +#@ R: merging {"a":[1,2,3],"b":[]} with array1 produced {"a":[1,2,3],"b":[],"mykey":["array_element1","array_element2"]} +#@ R: merging {"b":[4,5,6]} with array2 produced {"b":[4,5,6],"otherkey":"hello"} +#@ R: test2: merged containers with cmerge = {"a":"1","b":"2","c":"3","d":"4"} +#@ R: test3 {"repository.mustache":["/tmp/default.json","/tmp/epel.json"]} +#@ R: test3 /tmp/default.json +#@ R: test3 /tmp/epel.json +#@ ``` +#+end_src diff --git a/examples/meta.cf b/examples/meta.cf new file mode 100644 index 0000000000..9e76e9d1de --- /dev/null +++ b/examples/meta.cf @@ -0,0 +1,62 @@ + +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Default values for variables and parameters, introduced 3.4.0 +# + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + meta: + + "bundle_version" string => "1.2.3"; + "works_with_cfengine" string => "3.4.0"; + + vars: + + "testvar" string => "x"; + + reports: + "Not a local variable: $(bundle_version)"; + "Not a local variable: $(other_bundle.something)"; + "Meta data (variable): $(example_meta.bundle_version)"; + +} + + +bundle agent other_bundle + +{ + vars: + + "something" string => "I am defined in another bundle"; +} diff --git a/examples/method.cf b/examples/method.cf new file mode 100644 index 0000000000..bef2705955 --- /dev/null +++ b/examples/method.cf @@ -0,0 +1,59 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; + + version => "1.2.3"; +} + +########################################### + +bundle agent example +{ + vars: + + "userlist" slist => { "mark", "jeang", "jonhenrik", "thomas", "eben" }; + + methods: + + "any" usebundle => subtest("$(userlist)"); + +} + +########################################### + +bundle agent subtest(user) + +{ + commands: + + "/bin/echo Fix $(user)"; + + reports: + + "Finished doing stuff for $(user)"; +} + diff --git a/examples/method_polymorph.cf b/examples/method_polymorph.cf new file mode 100644 index 0000000000..d3f437b4ff --- /dev/null +++ b/examples/method_polymorph.cf @@ -0,0 +1,38 @@ +# +# Demonstrates the use of polymorphism to call bundles. +# + +body common control +{ + bundlesequence => { "example" }; +} + +########################################### + +bundle agent example +{ + methods: + + "Patch Group" + + comment => "Apply OS specific patches and modifications", + usebundle => "$(sys.class)_fix"; + +} + +########################################### + +bundle agent linux_fix +{ + reports: + "Fixes for linux"; +} + +########################################### + +bundle agent solaris_android +{ + reports: + "Fixes for android"; +} + diff --git a/examples/method_validate.cf b/examples/method_validate.cf new file mode 100644 index 0000000000..03bcbb7508 --- /dev/null +++ b/examples/method_validate.cf @@ -0,0 +1,72 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; + + version => "1.2.3"; +} + +########################################### + +body agent control + +{ + abortbundleclasses => { "invalid" }; +} + +########################################### + +bundle agent example +{ + vars: + + "userlist" slist => { "xyz", "mark", "jeang", "jonhenrik", "thomas", "eben" }; + + methods: + + "any" usebundle => subtest("$(userlist)"); + +} + +########################################### + +bundle agent subtest(user) + +{ + classes: + + "invalid" not => regcmp("[a-z][a-z][a-z][a-z]","$(user)"); + + reports: + + !invalid:: + + "User name $(user) is valid at 4 letters"; + + invalid:: + + "User name $(user) is invalid"; +} diff --git a/examples/method_var.cf b/examples/method_var.cf new file mode 100644 index 0000000000..9d48783f7e --- /dev/null +++ b/examples/method_var.cf @@ -0,0 +1,47 @@ +# +# Demonstrates the use of variables to call bundles. +# We can then filter variables on classes, for example. +# + +body common control +{ + bundlesequence => { "example" }; +} + +########################################### + +bundle agent test +{ + vars: + "run_bundles" slist => { "test_one", "test_two" }; + "run_a_bundle" string => "test_three"; + + methods: + "any" usebundle => "$(run_bundles)"; + "any" usebundle => "$(run_a_bundle)"; +} + +########################################### + +bundle agent test_one +{ + reports: + "in test_one"; +} + +########################################### + +bundle agent test_two +{ + reports: + "in test_two"; +} + +########################################### + +bundle agent test_three +{ + reports: + "in test_three"; +} + diff --git a/examples/method_var2.cf b/examples/method_var2.cf new file mode 100644 index 0000000000..b4a452b8ad --- /dev/null +++ b/examples/method_var2.cf @@ -0,0 +1,47 @@ +# +# Demonstrates the use of polymorphism to call bundles. +# + +body common control +{ + bundlesequence => { "example" }; +} + +########################################### + +bundle agent example +{ + vars: + "m" slist => { "login", "ssh_keys", "environment" }; + "user" slist => { "diego", "mark", "neil" }; + + methods: + "set of $(m)" usebundle => $(m)("$(user)"); + + +} + +########################################### + +bundle agent login(x) +{ + reports: + "Setup login for $(x)"; +} + +########################################### + +bundle agent ssh_keys(x) +{ + reports: + "Setup ssh keys for $(x)"; +} + +########################################### + +bundle agent environment(x) +{ + reports: + "Setup login environment for $(x)"; +} + diff --git a/examples/missing_ok.cf b/examples/missing_ok.cf new file mode 100644 index 0000000000..63a10595d2 --- /dev/null +++ b/examples/missing_ok.cf @@ -0,0 +1,93 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# This example shows how to use the missing_ok attribute in copy from bodies. If +# the source file is missing, the promise will be seen to be a promise kept +# instead of a failure. The exception is that a remote copy that fails to make a +# connection to the remote server will still be seen as a promise failure. + +#+begin_src cfengine3 +bundle agent main +{ + files: + "/tmp/copied_from_missing_ok" + copy_from => missing_ok( "/var/cfengine/masterfiles/missing" ), + classes => results("bundle", "copy_from_missing_ok"); + + reports: + "$(with)" + with => string_mustache( "{{%-top-}}", sort( classesmatching( "copy_from_.*" ), lex)); +} +body copy_from missing_ok( file_path ) +{ + source => "$(file_path)"; + missing_ok => "true"; + + # Run with these classes to try remote copies + remote_copy_self:: + servers => { "127.0.0.1" }; + + remote_copy_policy_hub:: + servers => { $(sys.policy_hub) }; +} +body classes results(scope, class_prefix) +{ + scope => "$(scope)"; + + promise_kept => { "$(class_prefix)_reached", + "$(class_prefix)_kept" }; + + promise_repaired => { "$(class_prefix)_reached", + "$(class_prefix)_repaired" }; + + repair_failed => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_failed" }; + + repair_denied => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_denied" }; + + repair_timeout => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_timeout" }; +} +#+end_src +#@ In the above example `/tmp/copied_from_missing_ok` promises to be a copy of the local file +#@ `/var/cfengine/masterfiles/missing`. In the `missing_ok` `copy_from` body +#@ `missing_ok` is set to true. This causes the promise to be considered kept if +#@ the source file is missing. The `results` classes body is used to define bundle +#@ scoped classes prefixed with `copy_from_missing_ok`. The `reports` promise +#@ outputs a sorted list of the classes defined starting with `copy_from_`. +#+begin_src example_output +#@ ``` +#@ R: [ +#@ "copy_from_missing_ok_kept", +#@ "copy_from_missing_ok_reached" +#@ ] +#@ ``` +#+end_src +#@ We can see in the output that the class defined for copying a local file that +#@ does not exist is seen to be a promise kept. diff --git a/examples/module_exec.cf b/examples/module_exec.cf new file mode 100644 index 0000000000..07a40ef809 --- /dev/null +++ b/examples/module_exec.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Test module execution as class function +# + +body common control + +{ + bundlesequence => { "example" }; +} + +################################################################### + +bundle agent example + +{ + classes: + + "done" or => { usemodule("module:getusers","") }; + + commands: + + "/bin/echo promiser text" args => "test $(user)"; +} diff --git a/examples/module_exec_2.cf b/examples/module_exec_2.cf new file mode 100644 index 0000000000..1258f6ecca --- /dev/null +++ b/examples/module_exec_2.cf @@ -0,0 +1,73 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Test module execution as class function +# + +body common control + +{ + bundlesequence => { "def", "example" }; +} + +################################################################### + +bundle agent def + +{ + commands: + + "$(sys.workdir)/modules/module_name" module => "true"; + + reports: + + # + # Each module forms a private context with its name as id + # + + module_class:: + + "Module set variable $(module_name.myscalar)"; + +} + +################################################################### + +bundle agent example + +{ + vars: + + "mylist" slist => { @(module_name.mylist) }; + + reports: + + # + # Each module forms a private context with its name as id + # + + module_class:: + + "Module set variable $(mylist)"; + +} diff --git a/examples/monitord.cf b/examples/monitord.cf new file mode 100644 index 0000000000..4e1e88941a --- /dev/null +++ b/examples/monitord.cf @@ -0,0 +1,43 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################### + +body monitor control +{ + #version => "1.2.3.4"; + + forgetrate => "0.7"; + + histograms => "true"; + + tcpdump => "false"; + + tcpdumpcommand => "/usr/sbin/tcpdump -i eth1 -n -t -v"; + + # on linux + + linux:: + + # sensor => readfile("/proc/cpu/temperature"); +} + diff --git a/examples/mount_fs.cf b/examples/mount_fs.cf new file mode 100644 index 0000000000..bd2d69c241 --- /dev/null +++ b/examples/mount_fs.cf @@ -0,0 +1,58 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# +# cfengine 3 +# +# cf-agent -f ./cftest.cf -K +# + +body common control + +{ + bundlesequence => { "mounts" }; +} + +# + +bundle agent mounts + +{ + storage: + + "/mnt" mount => nfs("slogans.iu.hio.no","/home"); + +} + +###################################################################### + +body mount nfs(server,source) + +{ + mount_type => "nfs"; + mount_source => "$(source)"; + mount_server => "$(server)"; + #mount_options => { "rw" }; + edit_fstab => "true"; + unmount => "true"; +} diff --git a/examples/multipassvars.cf b/examples/multipassvars.cf new file mode 100644 index 0000000000..d962fcf32c --- /dev/null +++ b/examples/multipassvars.cf @@ -0,0 +1,35 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + any:: + "tempdir" string => execresult("/bin/mktemp -d", "noshell"); + reports: + "tempdir: ${tempdir}"; +} diff --git a/examples/multiple_outcomes.cf b/examples/multiple_outcomes.cf new file mode 100644 index 0000000000..604d023dba --- /dev/null +++ b/examples/multiple_outcomes.cf @@ -0,0 +1,105 @@ +# Note: This is NOT an automatically tested example. The difference between +# dry-run and normal run output makes it problematic to do so. + +#+begin_src cfengine3 +bundle agent main +{ + meta: + "description" string => " +Illustrate how a promise can have multiple outcomes"; + + vars: + !windows:: + "repaired_and_kept" + string => "/tmp/repaired_and_not_kept.txt"; + + files: + # We make sure our test file is absent so that we + # can show consistent outcomes. + "$(repaired_and_kept)" + handle => "testfile_absent", + delete => tidy; + + "$(repaired_and_kept)" + handle => "test_outcomes", + depends_on => { "testfile_absent" }, + create => "true", + edit_template => "$(this.promsie_filename).broken_mustache", + template_method => "mustache", + classes => results("bundle", "outcome"); + + vars: + "result_classes" + slist => classesmatching("outcome_.*"); + + "s_result_classes" + slist => sort( result_classes, "lex"); + + reports: + outcome_repaired:: + "My promise was repaired because the file was created."; + + outcome_not_kept:: + "My promise was not kept because the template failed to render."; + + outcome_reached:: + "My promise was reached because it was actuated. Any o"; + + DEBUG:: + "Found class: '$(s_result_classes)'"; +} + +body delete tidy +# @brief This body was inlined from the standard +# library in the masterfiles policy framework +# repository at commit +# 6244c16e6934546d069052f953e597c4b95747df +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +body classes results(scope, class_prefix) +# @brief This body was inlined from the standard +# library in the masterfiles policy framework +# repository at commit +# 206a8b2e6bf277c3df9b57bf5c535b0b6dba963a +{ + scope => "$(scope)"; + + promise_kept => { "$(class_prefix)_reached", + "$(class_prefix)_kept" }; + + promise_repaired => { "$(class_prefix)_reached", + "$(class_prefix)_repaired" }; + + repair_failed => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_failed" }; + + repair_denied => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_denied" }; + + repair_timeout => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_timeout" }; +} +#+end_src + +######################### +#+begin_src do_not_test_example_output +#@ ``` +#@ error: Template file '$(this.promsie_filename).broken_mustache' could not be opened for reading +#@ R: My promise was repaired because the file was created. +#@ R: My promise was not kept because the template failed to render. +#@ R: Found class: 'outcome_error' +#@ R: Found class: 'outcome_failed' +#@ R: Found class: 'outcome_not_kept' +#@ R: Found class: 'outcome_reached' +#@ R: Found class: 'outcome_repaired' +#@ ``` +#+end_src diff --git a/examples/mustache_classes.cf b/examples/mustache_classes.cf new file mode 100644 index 0000000000..c4fe623307 --- /dev/null +++ b/examples/mustache_classes.cf @@ -0,0 +1,52 @@ +# Example showing how sections are rendered using cfengine classes + +# Being a logicless templating system, mustache is not able to leverage +# CFEngine's powerful class expression logic. Only singular classes can be used +# to conditionally render a block in mustache. This example shows how you can +# define a singular cfengine class based on a complex expression, and then use +# that singular class for conditional rendering in a template. + +#+begin_src cfengine3 +bundle agent main +{ + classes: + + "known_day_of_week" + expression => "(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)"; + + vars: + + "rendered" + string => string_mustache( +"{{#classes.known_day_of_week}}I recognize the day of the week.{{/classes.known_day_of_week}} +{{^classes.class_you_are_looking_for}} +The class you are looking for is not defined. +{{/classes.class_you_are_looking_for}}", + datastate()); + reports: + "$(rendered)"; + +} +#+end_src + +#+begin_src policy_description +#@ Here we define the class `known_day_of_week` as long as there is a class +#@ representing a known day. Then we render the value of the string variable +#@ "rendered" using `string_mustache()` with a template that includes a section +#@ that is conditional when `classes.known_day_of_week` is `true` and another section +#@ when `classes.class_you_are_looking_for` is not defined based on the data +#@ provided from `datastate()` which is the default set of data to use for mustache +#@ templates when explicit data is not provided. Finally we report the variable to +#@ see the rendered template. +#+end_src +#+begin_src example_output +#@ ``` +#@ R: I recognize the day of the week. +#@ The class you are looking for is not defined. +#@ +#@ ``` +#+end_src +#+begin_src output_description +#@ We can see in the output that the conditional text was rendered as expected. +#@ Try adjusting the template or the class expression. +#+end_src diff --git a/examples/mustache_comments.cf b/examples/mustache_comments.cf new file mode 100644 index 0000000000..1c6b9b92b0 --- /dev/null +++ b/examples/mustache_comments.cf @@ -0,0 +1,19 @@ +# Example showing how comments are not rendered + +#+begin_src cfengine3 +bundle agent main +{ + reports: + "$(with)" + with => string_mustache("Render this.{{! But not +this}}", + datastate()); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: Render this. +#@ ``` +#+end_src + diff --git a/examples/mustache_extension_compact_json.cf b/examples/mustache_extension_compact_json.cf new file mode 100644 index 0000000000..87a4d8dc99 --- /dev/null +++ b/examples/mustache_extension_compact_json.cf @@ -0,0 +1,22 @@ +#+begin_src cfengine3 +bundle agent main +{ + vars: + "msg" string => "Hello World"; + + "report" + string => string_mustache("{{$-top-}}", bundlestate( $(this.bundle) ) ), + unless => isvariable( $(this.promiser) ); # Careful to not include + # ourselves so we only grab the + # state of the first pass. + + reports: + "$(report)"; +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: {"msg":"Hello World"} +#@ ``` +#+end_src diff --git a/examples/mustache_extension_expand_key.cf b/examples/mustache_extension_expand_key.cf new file mode 100644 index 0000000000..8a2202d10a --- /dev/null +++ b/examples/mustache_extension_expand_key.cf @@ -0,0 +1,14 @@ +#+begin_src cfengine3 +bundle agent main +{ + reports: + "$(with)" + with => string_mustache("datastate() provides {{#-top-}} {{{@}}}{{/-top-}}", datastate() ); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: datastate() provides classes vars +#@ ``` +#+end_src diff --git a/examples/mustache_extension_multiline_json.cf b/examples/mustache_extension_multiline_json.cf new file mode 100644 index 0000000000..2b15b643e7 --- /dev/null +++ b/examples/mustache_extension_multiline_json.cf @@ -0,0 +1,17 @@ +#+begin_src cfengine3 +bundle agent main +{ + reports: + "$(with)" + with => string_mustache( "{{%mykey}}", + '{ "mykey": { "msg": "Hello World" } }' ); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: { +#@ "msg": "Hello World" +#@ } +#@ ``` +#+end_src diff --git a/examples/mustache_extension_top.cf b/examples/mustache_extension_top.cf new file mode 100644 index 0000000000..a669cbab4b --- /dev/null +++ b/examples/mustache_extension_top.cf @@ -0,0 +1,28 @@ +# Example showing how -top- gives access to the entire data provided to a mustache template. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + "data" data => '{ "key": "value", "list": [ "1", "2" ] }'; + + reports: + "-top- contains$(const.n)$(with)" + with => string_mustache("{{%-top-}}", data ); +} +#+end_src + +############################################################################### + +#+begin_src example_output +#@ ``` +#@ R: -top- contains +#@ { +#@ "key": "value", +#@ "list": [ +#@ "1", +#@ "2" +#@ ] +#@ } +#@ ``` +#+end_src diff --git a/examples/mustache_sections_empty_list.cf b/examples/mustache_sections_empty_list.cf new file mode 100644 index 0000000000..4171459912 --- /dev/null +++ b/examples/mustache_sections_empty_list.cf @@ -0,0 +1,21 @@ +# Example showing how variables are rendered for empty lists. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + "data" data => '{ "list": [ ] }'; + + reports: + "The list contains $(with)." + with => string_mustache("{{#list}} {{{.}}}{{/list}}", + data); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: The list contains . +#@ ``` +#+end_src + diff --git a/examples/mustache_sections_inverted.cf b/examples/mustache_sections_inverted.cf new file mode 100644 index 0000000000..9151f5216f --- /dev/null +++ b/examples/mustache_sections_inverted.cf @@ -0,0 +1,18 @@ +# Example showing how inverted sections are rendered + +#+begin_src cfengine3 +bundle agent main +{ + reports: + "CFEngine $(with)" + with => string_mustache("{{^classes.enterprise_edition}}Community{{/classes.enterprise_edition}}{{#classes.enterprise_edition}}Enterprise{{/classes.example}}", + datastate()); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: CFEngine Community +#@ ``` +#+end_src + diff --git a/examples/mustache_sections_non_empty_list.cf b/examples/mustache_sections_non_empty_list.cf new file mode 100644 index 0000000000..8b7477315f --- /dev/null +++ b/examples/mustache_sections_non_empty_list.cf @@ -0,0 +1,21 @@ +# Example showing how variables are rendered for non empty lists. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + "data" data => '{ "list": [ "1", "2", "3" ] }'; + + reports: + "$(with)" + with => string_mustache("{{#list}} {{{.}}}{{/list}}", + data); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: 1 2 3 +#@ ``` +#+end_src + diff --git a/examples/mustache_sections_non_false_value.cf b/examples/mustache_sections_non_false_value.cf new file mode 100644 index 0000000000..6ebfa28294 --- /dev/null +++ b/examples/mustache_sections_non_false_value.cf @@ -0,0 +1,21 @@ +# Example showing how sections are rendered for non-false + +#+begin_src cfengine3 +bundle agent main +{ + vars: + "data" data => parsejson('{ "classes": { "example": true } }'); + + reports: + "$(with)" + with => string_mustache("{{#classes.example}}This is an example.{{/classes.example}}", + data); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: This is an example. +#@ ``` +#+end_src + diff --git a/examples/mustache_set_delimiters.cf b/examples/mustache_set_delimiters.cf new file mode 100644 index 0000000000..f56441d826 --- /dev/null +++ b/examples/mustache_set_delimiters.cf @@ -0,0 +1,21 @@ +# Example showing how delimeters can be customized + +#+begin_src cfengine3 +bundle agent main +{ + vars: + "data" data => '{ "key": "value of key in data" }'; + + reports: + "$(with)" + with => string_mustache("{{=<% %>=}}key has <% key %>", + data); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: key has value of key in data +#@ ``` +#+end_src + diff --git a/examples/mustache_template_motd.cf b/examples/mustache_template_motd.cf new file mode 100644 index 0000000000..bd3816d4db --- /dev/null +++ b/examples/mustache_template_motd.cf @@ -0,0 +1,123 @@ +bundle agent env_classification +# @brief Classify environment +{ + vars: + # We use presence of key files to know the hosts environment + "environment_semaphores" slist => { "/etc/prod", "/etc/staging" }; + "environment" + string => ifelse( filesexist( @(environment_semaphores) ), "incoherent", + fileexists("/etc/prod"), "production", + fileexists("/etc/staging"), "staging", + "unknown" ); + "env_issue_msg" + string => ifelse( strcmp( "incoherent", $(environment) ), + "WARNING: Environment incoherent (multiple environment semaphores)", + strcmp( "unknown", $(environment) ), + "WARNING: Environment unknown (missing environment semaphores)", + "Host environment classified as $(environment)"); + + # This is to extract the cfengine role ( hub or client ) + "cf_role" + string => ifelse( "policy_server", "Policy Server", "Policy Client"); + + classes: + + # We define a class for the selected environment. It's useful for making + # decisions in other policies + + "env_$(environment)" + expression => "any", + scope => "namespace"; + +} +bundle agent env_info +# @brief Relevant environment information +{ + vars: + ## Based on the environment we define different contacts. + "admin_contact" + slist => { "admin@acme.com", "oncall@acme.com" }, + if => strcmp( $(env_classification.environment), "production" ); + + "admin_contact" + slist => { "dev@acme.com" }, + if => strcmp( $(env_classification.environment), "staging" ); + + "admin_contact" + slist => { "root@localhost" }, + if => strcmp( $(env_classification.environment), "unknown" ); + + ## This is to extract the available package updates status + "updates_available" + data => packageupdatesmatching(".*", ".*", ".*", ".*"); + "count_updates" int => length("updates_available"); + + classes: + + # We define a class indicating there are updates available, it might be + # useful for various different policies. + + "have_updates_available" + expression => isgreaterthan( $(count_updates), 0), + scope => "namespace"; + +} +bundle agent motd { + + vars: + "motd_path" string => "/etc/motd"; + + # It's considered best practice to prepare a data container holding the + # information you need to render the template instead of relying on + # current datastate() + + # First we extract currently defined classes from datastate(), then we + # construct the template data. + + "_state" data => datastate(), + if => not( isvariable ( $(this.promiser) ) ); # Limit recursive growth + # and multiple calls to + # datastate() over + # multiple passes. + + "template_data" + data => mergedata('{ "fqhost": "$(sys.fqhost)", + "primary_ip": "$(sys.ipv4)", + "cf_version": "$(sys.cf_version)", + "issue_msg": "$(env_classification.env_issue_msg)", + "cf_role": "$(env_classification.cf_role)", + "count_updates": "$(env_info.count_updates)", + "contacts": env_info.admin_contact, + "classes": _state[classes] + }'); + + files: + "$(motd_path)" + create => "true", + template_method => "inline_mustache", + template_data => @(template_data), + edit_template_string => '# Managed by CFEngine +{{{issue_msg}}} + *** + *** Welcome to {{{fqhost}}} + *** + +* *** * CFEngine Role: {{{cf_role}}} +* *** * CFEngine Version:{{{cf_version}}} +* *** * +* * Host IP: {{{primary_ip}}} + *** {{#classes.have_updates_available}}{{{count_updates}}} package updates available.{{/classes.have_updates_available}}{{^classes.have_updates_available}}No package updates available or status unknown.{{/classes.have_updates_available}} + * * + * * + * * + For support contact:{{#contacts}} + - {{{.}}}{{/contacts}}$(const.n)'; + +} +bundle agent __main__ +{ + methods: + "env_classification"; + "env_info"; + "motd"; +} diff --git a/examples/mustache_variables.cf b/examples/mustache_variables.cf new file mode 100644 index 0000000000..72e083fc99 --- /dev/null +++ b/examples/mustache_variables.cf @@ -0,0 +1,28 @@ +# Example showing how sections are rendered. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + "data" data => '{ "key": "Hello World & 3>2!" }'; + + reports: + "$(with)" + with => string_mustache( +"{{key}} +{{{key}}} +{{&key}} +Missing '{{missing}}' varibles render empty strings.", + data); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: Hello World & 3>2! +#@ Hello World & 3>2! +#@ Hello World & 3>2! +#@ Missing '' varibles render empty strings. +#@ ``` +#+end_src + diff --git a/examples/namespace_bodies.cf b/examples/namespace_bodies.cf new file mode 100644 index 0000000000..dc2f18b16b --- /dev/null +++ b/examples/namespace_bodies.cf @@ -0,0 +1,54 @@ +bundle agent __main__ +{ + methods: + "example_space:main"; +} +body file control +{ + namespace => "example_space"; +} + +bundle agent main +{ + + reports: + # Use the 'first_line' printfile body from the current namespace + "Specifying a body without explict namespace assumes the same namespace.$(const.n)Show me the first 1 line of this file" + printfile => first_line( $(this.promise_filename) ); + + # Use the 'first_two_lines' printfile body from the 'default' namespace + "Forgetting to prefix bodies with 'default:' is a common mistake when using the standard library.$(const.n)Show me the first 2 line of this file" + printfile => default:first_two_lines( $(this.promise_filename) ); + +} + +body printfile first_line(file) +# @brief Report the first 1 lines of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "1"; +} +body file control +{ + namespace => "default"; +} +body printfile first_two_lines(file) +# @brief Report the first 2 lines of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "2"; +} +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Specifying a body without explict namespace assumes the same namespace. +#@ Show me the first 1 line of this file +#@ R: bundle agent __main__ +#@ R: Forgetting to prefix bodies with 'default:' is a common mistake when using the standard library. +#@ Show me the first 2 line of this file +#@ R: bundle agent __main__ +#@ R: { +#@ ``` +#+end_src diff --git a/examples/namespace_classes.cf b/examples/namespace_classes.cf new file mode 100644 index 0000000000..38681afd55 --- /dev/null +++ b/examples/namespace_classes.cf @@ -0,0 +1,79 @@ +bundle agent __main__ +{ + methods: + "mynamespace:my_bundle"; + + reports: + + a_bundle_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I see 'a_bundle_scoped_class_in_my_namespaced_bundle::'"; + + !a_bundle_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I do not see 'a_bundle_scoped_class_in_my_namespaced_bundle::'"; + + a_namespace_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I see 'a_namespace_scoped_class_in_my_namespaced_bundle::'"; + + !a_namespace_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I do not see 'a_namespace_scoped_class_in_my_namespaced_bundle::'"; + + mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I see 'mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle::'"; + + !mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I do not see 'mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle::'"; + + mynamespace:a_namespace_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I see 'mynamespace:a_namespace_scoped_class_in_my_namespaced_bundle::'"; +} + +body file control +{ + namespace => "mynamespace"; +} + +bundle agent my_bundle +{ + classes: + "a_bundle_scoped_class_in_my_namespaced_bundle"; + + "a_namespace_scoped_class_in_my_namespaced_bundle" + scope => "namespace"; + + reports: + + a_bundle_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I see 'a_bundle_scoped_class_in_my_namespaced_bundle::'"; + + !a_bundle_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I do not see 'a_bundle_scoped_class_in_my_namespaced_bundle::'"; + + a_namespace_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I see 'a_namespace_scoped_class_in_my_namespaced_bundle::'"; + + !a_namespace_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I do not see 'a_namespace_scoped_class_in_my_namespaced_bundle::'"; + + mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I see 'mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle::'"; + + !mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I do not see 'mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle::'"; + + mynamespace:a_namespace_scoped_class_in_my_namespaced_bundle:: + "In $(this.namespace):$(this.bundle) I see 'mynamespace:a_namespace_scoped_class_in_my_namespaced_bundle::'"; +} +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: In mynamespace:my_bundle I see 'a_bundle_scoped_class_in_my_namespaced_bundle::' +#@ R: In mynamespace:my_bundle I see 'a_namespace_scoped_class_in_my_namespaced_bundle::' +#@ R: In mynamespace:my_bundle I see 'mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle::' +#@ R: In mynamespace:my_bundle I see 'mynamespace:a_namespace_scoped_class_in_my_namespaced_bundle::' +#@ R: In default:main I do not see 'a_bundle_scoped_class_in_my_namespaced_bundle::' +#@ R: In default:main I do not see 'a_namespace_scoped_class_in_my_namespaced_bundle::' +#@ R: In default:main I do not see 'mynamespace:a_bundle_scoped_class_in_my_namespaced_bundle::' +#@ R: In default:main I see 'mynamespace:a_namespace_scoped_class_in_my_namespaced_bundle::' +#@ ``` +#+end_src + diff --git a/examples/namespace_declaration.cf b/examples/namespace_declaration.cf new file mode 100644 index 0000000000..1a85e5e5d8 --- /dev/null +++ b/examples/namespace_declaration.cf @@ -0,0 +1,64 @@ +# By default we are in the default namespace + +bundle agent __main__ +{ + methods: + "Main in my_namespace namespace" + usebundle => my_namespace:main; + + "Main in your_namespace namespace" + usebundle => your_namespace:main; + + "my_bundle in default namespace" + usebundle => my_bundle; + + reports: + "Inside $(this.namespace):$(this.bundle)"; +} + +body file control +# From here until the next namespace declaration all bundles and bodies are +# defined in my_namespace. +{ + namespace => "my_namespace"; +} + +bundle agent main +{ + reports: + "Inside $(this.namespace):$(this.bundle)"; +} + +body file control +# From here until the next namespace declaration all bundles and bodies are +# defined in your_namespace. +{ + namespace => "your_namespace"; +} + +bundle agent main +{ + reports: + "Inside $(this.namespace):$(this.bundle)"; +} + +body file control +# From here until the next namespace declaration we return to the default namespace. +{ + namespace => "default"; +} + +bundle agent my_bundle +{ + reports: + "Inside $(this.namespace):$(this.bundle)"; +} +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Inside my_namespace:main +#@ R: Inside your_namespace:main +#@ R: Inside default:my_bundle +#@ R: Inside default:main +#@ ``` +#+end_src diff --git a/examples/namespace_hard_classes.cf b/examples/namespace_hard_classes.cf new file mode 100644 index 0000000000..4a8bd578fd --- /dev/null +++ b/examples/namespace_hard_classes.cf @@ -0,0 +1,40 @@ +bundle agent __main__ +{ + methods: + "example:my_bundle"; + + reports: + cfengine:: + "From the '$(this.namespace)' namespace the class expression 'cfengine::' evaluates true"; + + default:cfengine:: + "From the '$(this.namespace)' namespace the class expression 'default:cfengine::' evaluates true"; + + "The class 'cfengine' has tags: $(with)" + with => join( ", ", getclassmetatags( "cfengine" ) ); +} + +body file control +{ + namespace => "example"; +} + +bundle agent my_bundle +{ + reports: + cfengine:: + "From the '$(this.namespace)' namespace the class expression 'cfengine::' evaluates true"; + + default:cfengine:: + "From the '$(this.namespace)' namespace the class expression 'default:cfengine::' evaluates true"; +} +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: From the 'example' namespace the class expression 'cfengine::' evaluates true +#@ R: From the 'example' namespace the class expression 'default:cfengine::' evaluates true +#@ R: From the 'default' namespace the class expression 'cfengine::' evaluates true +#@ R: From the 'default' namespace the class expression 'default:cfengine::' evaluates true +#@ R: The class 'cfengine' has tags: inventory, attribute_name=none, source=agent, hardclass +#@ ``` +#+end_src diff --git a/examples/namespace_methods-usebundle.cf b/examples/namespace_methods-usebundle.cf new file mode 100644 index 0000000000..fe1d8f7d7d --- /dev/null +++ b/examples/namespace_methods-usebundle.cf @@ -0,0 +1,39 @@ +bundle agent __main__ +{ + methods: + # Call the bundle named main within the example_space namespace. + "example_space:main"; +} +body file control +{ + namespace => "example_space"; +} + +bundle agent main +{ + methods: + # Call the bundle 'my_bundle' within the current namespace + "When not specified, we assume you are refering to a bundle or body within the same namespace" + usebundle => my_bundle( "Called 'my_bundle' from $(this.namespace):$(this.bundle) (the same namespace)."); + + # Call the bundle 'my_bundle' from the 'example_space' namespace + "When explicitly specified, the policy reader has less congnitive burden" + usebundle => example_space:my_bundle( "Called 'example_space:my_bundle' $(this.namespace):$(this.bundle) (the same namespace)."); +} + +bundle agent my_bundle(string) +{ + reports: + "In $(this.namespace):$(this.bundle)" + handle => "$(string)"; + "$(string)"; +} +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: In example_space:my_bundle +#@ R: Called 'my_bundle' from example_space:main (the same namespace). +#@ R: In example_space:my_bundle +#@ R: Called 'example_space:my_bundle' example_space:main (the same namespace). +#@ ``` +#+end_src diff --git a/examples/namespace_special_var_exception.cf b/examples/namespace_special_var_exception.cf new file mode 100644 index 0000000000..c4ba9cc83b --- /dev/null +++ b/examples/namespace_special_var_exception.cf @@ -0,0 +1,23 @@ +bundle agent __main__ +{ + methods: "special_variables_example:demo"; +} +body file control +{ + namespace => "special_variables_example"; +} +bundle agent demo +{ + reports: + "Special Variables live in the default namespace but don't have to be fully qualified when referenced ..."; + "In $(this.namespace):$(this.bundle) $(const.dollar)(sys.cf_version_major) == $(sys.cf_version_major)"; + "In $(this.namespace):$(this.bundle) $(default:const.dollar)(default:sys.cf_version_major) == $(default:sys.cf_version_major)"; +} +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Special Variables live in the default namespace but don't have to be fully qualified when referenced ... +#@ R: In special_variables_example:demo $(sys.cf_version_major) == 3 +#@ R: In special_variables_example:demo $(default:sys.cf_version_major) == 3 +#@ ``` +#+end_src diff --git a/examples/namespace_var_meta.cf b/examples/namespace_var_meta.cf new file mode 100644 index 0000000000..09d6a95979 --- /dev/null +++ b/examples/namespace_var_meta.cf @@ -0,0 +1,24 @@ + +body common control +{ + bundlesequence => { "main" }; + version => "0.1"; + inputs => { "namespace_var_meta2.cf"}; +} + +bundle agent main +{ + + classes: + + "abc" expression => "any"; + + + methods: + + "bla" usebundle => fred:example; + + reports: + "remote var: $(fred:example.bundle_version)"; + +} diff --git a/examples/namespace_var_meta2.cf b/examples/namespace_var_meta2.cf new file mode 100644 index 0000000000..63c723372c --- /dev/null +++ b/examples/namespace_var_meta2.cf @@ -0,0 +1,25 @@ + +body file control +{ + namespace => "fred"; +} + + +bundle agent example + +{ + vars: + + "bundle_version" string => "4.5.6"; + + meta: + + "bundle_version" string => "1.2.3"; + "works_with_cfengine" string => "3.4.0"; + + reports: + "Not a local variable: $(bundle_version)"; + "Meta data (variable): $(example_meta.bundle_version)"; + +} + diff --git a/examples/namespace_variable_references.cf b/examples/namespace_variable_references.cf new file mode 100644 index 0000000000..b556f0afbb --- /dev/null +++ b/examples/namespace_variable_references.cf @@ -0,0 +1,25 @@ +bundle agent __main__ +{ + methods: "example:demo"; +} +body file control +{ + namespace => "example"; +} +bundle agent demo +{ + vars: + "color" string => "#f5821f"; + + reports: + "Unqualified: The color is $(color)"; + # ENT-8817 "Bundle-qualified: The color is $(demo.color)"; + "Fully-qualified: The color is $(example:demo.color)"; +} +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Unqualified: The color is #f5821f +#@ R: Fully-qualified: The color is #f5821f +#@ ``` +#+end_src diff --git a/examples/neighbourhood_watch.cf b/examples/neighbourhood_watch.cf new file mode 100644 index 0000000000..cc5282345a --- /dev/null +++ b/examples/neighbourhood_watch.cf @@ -0,0 +1,96 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Change detect +# +######################################################## + +body common control + +{ + bundlesequence => { "neighbourhood_watch" }; +} + +######################################################## + +bundle agent neighbourhood_watch + +{ + vars: + + "neighbours" slist => peers("/var/cfengine/inputs/hostlist","#.*",4); + + files: + + # Redundant cross monitoring ....................................... + + "$(sys.workdir)/nw/$(neighbours)_checksum_digests.db" + + comment => "Watch our peers remote hash tables and keep a local copy", + copy_from => rcp("$(sys.workdir)/checksum_digests.db",$(neighbours)), + depends_on => { "grant_hash_tables" }; + + # Define the actual children to watch over ......................... + + "/usr/bin" + + comment => "Watch over the system binaries - changes are mostly updates", + changes => lay_trip_wire, + depth_search => recurse("inf"), + action => measure; + +} + +######################################################### + +body changes lay_trip_wire +{ + hash => "best"; + report_changes => "content"; + update_hashes => "yes"; +} + +######################################################### + +body copy_from rcp(from,server) + +{ + servers => { "$(server)" }; + source => "$(from)"; + compare => "digest"; + encrypt => "false"; +} + +########################################################## + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + +body action measure +{ + measurement_class => "$(this.promiser) long job scan of /usr"; +} diff --git a/examples/none.cf b/examples/none.cf new file mode 100644 index 0000000000..faceb576a5 --- /dev/null +++ b/examples/none.cf @@ -0,0 +1,103 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + "none11" expression => none("jebadiah", test1); + "none12" expression => none("2", test1); + "none21" expression => none("jebadiah", test2); + "none22" expression => none("2", test2); + + vars: + "test1" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; + + + "test2" data => parsejson('[1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three",]'); + + reports: + "The test1 list is $(test1)"; + none11:: + "none() test1 1 passed"; + !none11:: + "none() test1 1 failed"; + none12:: + "none() test1 2 failed"; + !none12:: + "none() test1 2 passed"; + + "The test2 list is $(test2)"; + none21:: + "none() test2 1 passed"; + !none21:: + "none() test2 1 failed"; + none22:: + "none() test2 2 failed"; + !none22:: + "none() test2 2 passed"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The test1 list is 1 +#@ R: The test1 list is 2 +#@ R: The test1 list is 3 +#@ R: The test1 list is one +#@ R: The test1 list is two +#@ R: The test1 list is three +#@ R: The test1 list is long string +#@ R: The test1 list is four +#@ R: The test1 list is fix +#@ R: The test1 list is six +#@ R: none() test1 1 passed +#@ R: none() test1 2 passed +#@ R: The test2 list is 1 +#@ R: The test2 list is 2 +#@ R: The test2 list is 3 +#@ R: The test2 list is one +#@ R: The test2 list is two +#@ R: The test2 list is three +#@ R: The test2 list is long string +#@ R: The test2 list is four +#@ R: The test2 list is fix +#@ R: The test2 list is six +#@ R: none() test2 1 passed +#@ R: none() test2 2 passed +#@ ``` +#+end_src diff --git a/examples/nth.cf b/examples/nth.cf new file mode 100644 index 0000000000..2e36739cb4 --- /dev/null +++ b/examples/nth.cf @@ -0,0 +1,97 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; + "test_str" string => format("%S", test); + + "test2" data => parsejson("[1, 2, 3, null]"); + "test2_str" string => format("%S", test2); + + "test3" data => parsejson('{ "x": true, "y": "z" }'); + "test3_str" string => format("%S", test3); + + "nth" slist => { 1, 2, 6, 10, 11, 1000 }; + "nth2" slist => getindices(test2); + "nth3" slist => getindices(test3); + + "access[$(nth)]" string => nth(test, $(nth)); + "access[0]" string => nth(test, 0); + + "access2[$(nth2)]" string => nth(test2, $(nth2)); + "access3[$(nth3)]" string => nth(test3, $(nth3)); + + "nth_neg1" string => nth(test, "-1"); + "nth_neg100" string => nth(test, "-100"); # invalid index position requested + + reports: + "The test list is $(test_str)"; + "element #$(nth) of the test list: $(access[$(nth)])"; + "element #0 of the test list: $(access[0])"; + + "The test2 data container is $(test2_str)"; + "element #$(nth2) of the test2 data container: $(access2[$(nth2)])"; + + "The test3 data container is $(test3_str)"; + "element #$(nth3) of the test3 data container: $(access3[$(nth3)])"; + + "The last element of test is $(nth_neg1)"; + "nth_neg100 is not defined, because an invalid index was requested" + if => not( isvariable( nth_neg100 )); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The test list is { "1", "2", "3", "one", "two", "three", "long string", "four", "fix", "six", "one", "two", "three" } +#@ R: element #1 of the test list: 2 +#@ R: element #2 of the test list: 3 +#@ R: element #6 of the test list: long string +#@ R: element #10 of the test list: one +#@ R: element #11 of the test list: two +#@ R: element #0 of the test list: 1 +#@ R: The test2 data container is [1,2,3,null] +#@ R: element #0 of the test2 data container: 1 +#@ R: element #1 of the test2 data container: 2 +#@ R: element #2 of the test2 data container: 3 +#@ R: element #3 of the test2 data container: null +#@ R: The test3 data container is {"x":true,"y":"z"} +#@ R: element #x of the test3 data container: true +#@ R: element #y of the test3 data container: z +#@ R: The last element of test is three +#@ R: nth_neg100 is not defined, because an invalid index was requested +#@ ``` +#+end_src diff --git a/examples/null_config.cf b/examples/null_config.cf new file mode 100644 index 0000000000..7a8ac3d705 --- /dev/null +++ b/examples/null_config.cf @@ -0,0 +1,42 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# The starting point for every configuration +# +####################################################### + +body common control + +{ + bundlesequence => { "example" }; +} + +####################################################### + +bundle agent example + +{ + reports: + "This is a test bundle"; +} diff --git a/examples/orchestrate_chain1.cf b/examples/orchestrate_chain1.cf new file mode 100644 index 0000000000..474f9f2bc0 --- /dev/null +++ b/examples/orchestrate_chain1.cf @@ -0,0 +1,133 @@ +############################################################ +# +# The self-healing tower: Anti-Dominoes +# +# This method works with Enterprise +# +# If you want to test this on localhost, just edit /etc/hosts +# to add host1 host2 host3 host4 as aliases to localhost +# +############################################################ + +body common control +{ + bundlesequence => { "weak_dependency_symphony" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +body server control +{ + allowconnects => { "127.0.0.1" , "::1", @(def.acl) }; + allowallconnects => { "127.0.0.1" , "::1", @(def.acl) }; +} + + +############################################################ + +bundle agent weak_dependency_symphony +{ + methods: + + # We have to seed the beginning by creating the tower + # /tmp/tower_localhost + + host1:: + "tower" usebundle => tier1, + classes => publish_ok("ok_O"); + + host2:: + "tower" usebundle => tier2, + classes => publish_ok("ok_1"); + + host3:: + "tower" usebundle => tier3, + classes => publish_ok("ok_2"); + + host4:: + "tower" usebundle => tier4, + classes => publish_ok("ok_f"); + + classes: + + ok_O:: # Wait for the methods, report on host1 only + + "check1" expression => remoteclassesmatching("ok.*","host2","yes","a"); + "check2" expression => remoteclassesmatching("ok.*","host3","yes","a"); + "check3" expression => remoteclassesmatching("ok.*","host4","yes","a"); + + reports: + + ok_O:: + "tier 1 is ok"; + a_ok_1:: + "tier 2 is ok"; + a_ok_2:: + "tier 3 is ok"; + a_ok_f:: + "tier 4 is ok"; + + ok_O&a_ok_1&a_ok_2&a_ok_f:: + "The Tower is standing"; + + !(ok_O&a_ok_1&a_ok_2&a_ok_f):: + "The Tower is down"; +} + +############################################################ + +bundle agent tier1 +{ + files: + + "/tmp/something_to_do_1" + create => "true"; +} + +bundle agent tier2 +{ + files: + + "/tmp/something_to_do_2" + create => "true"; +} + +bundle agent tier3 +{ + files: + + "/tmp/something_to_do_3" + create => "true"; + +} + +bundle agent tier4 +{ + files: + + "/tmp/something_to_do_4" + create => "true"; +} + +############################################################ + + +bundle server access_rules() +{ + access: + + "ok.*" + resource_type => "context", + admit => { "127.0.0.1" }; + +} + +############################################################ + +body classes publish_ok(x) +{ + promise_repaired => { "$(x)" }; + promise_kept => { "$(x)" }; + cancel_notkept => { "$(x)" }; + persist_time => "2"; +} + diff --git a/examples/orchestrate_chain2.cf b/examples/orchestrate_chain2.cf new file mode 100644 index 0000000000..8a6ad85bd1 --- /dev/null +++ b/examples/orchestrate_chain2.cf @@ -0,0 +1,126 @@ +############################################################ +# +# The self-healing tower: Anti-Dominoes +# +# This method works with either Community of Enterprise +# +# If you want to test this on localhost, just edit /etc/hosts +# to add host1 host2 host3 host4 as aliases to localhost +# +############################################################ + +body common control +{ + bundlesequence => { "weak_dependency_symphony" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +body server control +{ + allowconnects => { "127.0.0.1" , "::1", @(def.acl) }; + allowallconnects => { "127.0.0.1" , "::1", @(def.acl) }; +} + + +############################################################ + +bundle agent weak_dependency_symphony +{ + methods: + + # We have to seed the beginning by creating the tower + # /tmp/tower_localhost + + host1:: + "tower" usebundle => tier1, + classes => publish_ok("ok_O"); + + host2:: + "tower" usebundle => tier2, + classes => publish_ok("ok_1"); + + host3:: + "tower" usebundle => tier3, + classes => publish_ok("ok_2"); + + host4:: + "tower" usebundle => tier4, + classes => publish_ok("ok_f"); + + classes: + + ok_O:: # Wait for the methods, report on host1 only + + "check1" expression => remoteclassesmatching("ok.*","host2","yes","a"); + "check2" expression => remoteclassesmatching("ok.*","host3","yes","a"); + "check3" expression => remoteclassesmatching("ok.*","host4","yes","a"); + + reports: + + ok_O&a_ok_1&a_ok_2&a_ok_f:: + + "The Tower is standing"; + + !(ok_O&a_ok_1&a_ok_2&a_ok_f):: + + "The Tower is down"; +} + +############################################################ + +bundle agent tier1 +{ + files: + + "/tmp/something_to_do_1" + create => "true"; +} + +bundle agent tier2 +{ + files: + + "/tmp/something_to_do_2" + create => "true"; +} + +bundle agent tier3 +{ + files: + + "/tmp/something_to_do_3" + create => "true"; + +} + +bundle agent tier4 +{ + files: + + "/tmp/something_to_do_4" + create => "true"; +} + +############################################################ + + +bundle server access_rules() +{ + access: + + "ok.*" + resource_type => "context", + admit => { "127.0.0.1" }; + +} + +############################################################ + +body classes publish_ok(x) +{ + promise_repaired => { "$(x)" }; + promise_kept => { "$(x)" }; + cancel_notkept => { "$(x)" }; + persist_time => "5"; +} + diff --git a/examples/orchestrate_delay_trigger.cf b/examples/orchestrate_delay_trigger.cf new file mode 100644 index 0000000000..d4c1a202dc --- /dev/null +++ b/examples/orchestrate_delay_trigger.cf @@ -0,0 +1,81 @@ +# +# Time based orchestration +# + +body common control +{ + bundlesequence => { "example" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +########################################### + +bundle common orchestrate +{ + vars: + + # Must have delay < reset_time + + "reset_time" int => "2"; + "delay" int => "1"; +} + +########################################### + +bundle agent example +{ + methods: + + "immediate" usebundle => one, + classes => if_repaired_persist("countdown", "$(orchestrate.delay)"), + if => "!one"; + + "delayed" usebundle => two, + if => "one.!countdown"; + + reports: + + countdown:: + "Counting down ... $(sys.date)"; +} + +########################################### + +bundle agent one +{ + reports: + + "One = $(this.bundle) at $(sys.date)" + classes => if_repaired_persist("$(this.bundle)", "$(orchestrate.reset_time)"), + action => if_elapsed("0"); + + "Set class $(this.bundle)" + if => "$(this.bundle)"; +} + +########################################## + +bundle agent two +{ + reports: + + "Two = $(this.bundle) at $(sys.date)" + classes => if_repaired_persist("$(this.bundle)", "$(orchestrate.reset_time)"), + action => if_elapsed("0"); + + "Set class $(this.bundle)" + if => "$(this.bundle)"; +} + +########################################### + +body classes if_repaired_persist(x,t) +{ + promise_repaired => { "$(x)" }; + persist_time => "$(t)"; +} + +body classes cancel_persist(x) +{ + cancel_repaired => { "$(x)" }; +} diff --git a/examples/orchestrate_dominoes1.cf b/examples/orchestrate_dominoes1.cf new file mode 100644 index 0000000000..2eba58658b --- /dev/null +++ b/examples/orchestrate_dominoes1.cf @@ -0,0 +1,108 @@ +############################################################ +# +# Dominoes +# +# This method works with either Community of Enterprise +# +# If you want to test this on localhost, just edit /etc/hosts +# to add host1 host2 host3 host4 as aliases to localhost +# +############################################################ + +body common control +{ + bundlesequence => { "dominoes_symphony" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +############################################################ + +bundle agent dominoes_symphony +{ + methods: + + # We have to seed the beginning by creating the dominoes + # /tmp/dominoes_localhost + + host1:: + "dominoes" usebundle => hand_over("localhost","host1","overture"); + + host2:: + "dominoes" usebundle => hand_over("host1","host2","first movement"); + + host3:: + "dominoes" usebundle => hand_over("host2","host3","second movement"); + + host4:: + "dominoes" usebundle => hand_over("host3","host4","final movement"), + classes => if_ok("finale"); + + reports: + + finale:: + + "The visitors book of the Dominoes method" + printfile => visitors_book("/tmp/dominoes_host4"); + +} + +############################################################ + +bundle agent hand_over(predecessor,myalias,method) +{ + + # This is a wrapper for the orchestration + + files: + + "/tmp/tip_the_dominoes" + + comment => "Wait for our cue or relay/conductor baton", + copy_from => secure_cp("/tmp/dominoes_$(predecessor)","$(predecessor)"), + classes => if_repaired("cue_action"); + + cue_action:: + + "/tmp/outcome_of_part" + + comment => "One off activity", + create => "true", + edit_line => append_if_no_line("Do something meaningful by calling $(method)"), + classes => if_ok("pass_the_stick"); + + pass_the_stick:: + + "/tmp/tip_the_dominoes" + comment => "Add our signature to the dominoes's tail", + edit_line => append_if_no_line("Knocked over $(myalias) and did: $(method)"); + + "/tmp/dominoes_$(myalias)" + + comment => "Dominoes in position to be beamed up by next agent", + copy_from => local_cp("/tmp/tip_the_dominoes"); + +} + +############################################################ + +bundle server access_rules() +{ + access: + + "/tmp" + + admit => { "127.0.0.1" }; + + "did.*" + resource_type => "context", + admit => { "127.0.0.1" }; + +} + + +body printfile visitors_book(file) +{ + file_to_print => "$(file)"; + number_of_lines => "10"; +} + diff --git a/examples/orchestrate_dominoes2.cf b/examples/orchestrate_dominoes2.cf new file mode 100644 index 0000000000..612cf8165a --- /dev/null +++ b/examples/orchestrate_dominoes2.cf @@ -0,0 +1,140 @@ +############################################################ +# +# Dominoes +# +# This method works with either Community of Enterprise +# +# If you want to test this on localhost, just edit /etc/hosts +# to add host1 host2 host3 host4 as aliases to localhost +# +############################################################ + +body common control +{ + bundlesequence => { "dominoes_symphony" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +############################################################ + +bundle agent dominoes_symphony +{ + methods: + + # We have to seed the beginning by creating the dominoes + # /tmp/dominoes_localhost + + host1:: + "dominoes" usebundle => hand_over("localhost","host1","overture"); + + host2:: + "dominoes" usebundle => hand_over("host1","host2","first_movement"); + + host3:: + "dominoes" usebundle => hand_over("host2","host3","second_movement"); + + host4:: + "dominoes" usebundle => hand_over("host3","host4","final_movement"), + classes => if_ok("finale"); + + reports: + + finale:: + + "The visitors book of the Dominoes method" + printfile => visitors_book("/tmp/dominoes_host4"); + +} + +############################################################ + +bundle agent hand_over(predecessor,myalias,method) +{ + + # This is a wrapper for the orchestration + + files: + + "/tmp/tip_the_dominoes" + + comment => "Wait for our cue or relay/conductor baton", + copy_from => secure_cp("/tmp/dominoes_$(predecessor)","$(predecessor)"), + classes => if_repaired("cue_action"); + + methods: + + cue_action:: + + "the music happens" + + comment => "One off activity", + usebundle => $(method), + classes => if_ok("pass_the_stick"); + + files: + + pass_the_stick:: + + "/tmp/tip_the_dominoes" + comment => "Add our signature to the dominoes's tail", + edit_line => append_if_no_line("Knocked over $(myalias) and did: $(method)"); + + "/tmp/dominoes_$(myalias)" + + comment => "Dominoes in position to be beamed up by next agent", + copy_from => local_cp("/tmp/tip_the_dominoes"); + +} + +############################################################ + +bundle agent overture +{ + reports: + "Singing the overture..."; +} + +bundle agent first_movement +{ + reports: + "Singing the first adagio..."; +} + +bundle agent second_movement +{ + reports: + "Singing second allegro..."; + +} + +bundle agent final_movement +{ + reports: + "Trumpets for the finale"; + +} + +############################################################ + + +bundle server access_rules() +{ + access: + + "/tmp" + + admit => { "127.0.0.1" }; + + "did.*" + resource_type => "context", + admit => { "127.0.0.1" }; + +} + + +body printfile visitors_book(file) +{ + file_to_print => "$(file)"; + number_of_lines => "10"; +} + diff --git a/examples/orchestrate_dragon.cf b/examples/orchestrate_dragon.cf new file mode 100644 index 0000000000..714e84037d --- /dev/null +++ b/examples/orchestrate_dragon.cf @@ -0,0 +1,231 @@ +############################################################ +# +# Chinese Dragon Dancing on a Star +# +# This method works with either Community or Enterprise. +# and uses named signals +# +# If you want to test this on localhost, just edit /etc/hosts +# to add host1 host2 host3 host4 as aliases to localhost +# +############################################################ + +body common control +{ + bundlesequence => { "dragon_symphony" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +############################################################ + +bundle agent dragon_symphony +{ + methods: + + # We have to seed the beginning by creating the dragon + # /tmp/dragon_localhost + + "dragon" usebundle => visit("localhost","host1","chapter1"); + + "dragon" usebundle => visit("host1","host2","chapter2"); + + "dragon" usebundle => visit("host2","host3","chapter3"); + + "dragon" usebundle => visit("host3","host4","chapter4"), + classes => if_ok("finale"); + + reports: + + finale:: + + "The dragon is slain:" + printfile => visitors_book("/tmp/shoo_dragon_host4"); +} + +############################################################ +# Define the +############################################################ + +bundle agent chapter1(x) +{ + # Do something significant here + + reports: + + host1:: + " ----> Breathing fire on $(x)"; +} + +################################ + +bundle agent chapter2(x) +{ + # Do something significant here + + reports: + + host2:: + " ----> Breathing fire on $(x)"; + +} + +################################ + +bundle agent chapter3(x) +{ + # Do something significant here + + reports: + + host3:: + " ----> Breathing fire on $(x)"; + +} + +################################ + +bundle agent chapter4(x) +{ + # Do something significant here + + reports: + + host4:: + " ----> Breathing fire on $(x)"; + +} + +############################################################ +# Orchestration wrappers +############################################################ + +bundle agent visit(predecessor,satellite,method) +{ + + # This is a wrapper for the orchestration will be acted on + # first by the dragon's lair and then by the satellite + + vars: + + "dragons_lair" string => "host0"; + + files: + + # We start in the dragon's lair .. + + "/tmp/unleash_dragon" + + comment => "Unleash the dragon", + rename => to("/tmp/enter_the_dragon"), + classes => if_repaired("dispatch_dragon_$(satellite)"), + if => "$(dragons_lair)"; + + # if we are the dragon's lair, welcome the dragon back, shooed from the satellite + + "/tmp/enter_the_dragon" + + comment => "Returning from a visit to a satellite", + copy_from => secure_cp("/tmp/shoo_dragon_$(predecessor)","$(predecessor)"), + classes => if_repaired("dispatch_dragon_$(satellite)"), + if => "$(dragons_lair)"; + + # If we are a satellite, receive the dragon from its lair + + "/tmp/enter_the_dragon" + comment => "Wait for our cue or relay/conductor baton", + copy_from => secure_cp("/tmp/dragon_$(satellite)","$(dragons_lair)"), + classes => if_repaired("cue_action_on_$(satellite)"), + if => "$(satellite)"; + + methods: + + "check in at home" + comment => "Edit the load balancer?", + usebundle => switch_satellite(" -> Send dragon to $(satellite)"), + classes => if_repaired("send_the_dragon_to_$(satellite)"), + if => "dispatch_dragon_$(satellite)"; + + "dragon visits" + comment => "One off activity that the nodes carry out while the dragon visits", + usebundle => $(method)("$(satellite)"), + classes => if_repaired("send_the_dragon_back_from_$(satellite)"), + if => "cue_action_on_$(satellite)"; + + + files: + + # hub/lair hub signs the book too and schedules the dragon for next satellite + + "/tmp/dragon_$(satellite)" + create => "true", + comment => "Add our signature to the dragon's tail", + edit_line => sign_visitor_book("Dragon returned from $(predecessor)"), + if => "send_the_dragon_to_$(satellite)"; + + # Satellite signs the book and shoos dragon for hub to collect + + "/tmp/shoo_dragon_$(satellite)" + create => "true", + comment => "Add our signature to the dragon's tail", + edit_line => sign_visitor_book("Dragon visited $(satellite) and did: $(method)"), + if => "send_the_dragon_back_from_$(satellite)"; + + reports: + "Done $(satellite)"; + +} + +############################################################ + +bundle agent switch_satellite(name) +{ + files: + + "/tmp/enter_the_dragon" + comment => "Add our signature to the dragon's tail", + edit_line => append_if_no_line("Switch new dragon's target $(name)"); + + reports: + " X Switching new dragon's target $(name)"; +} + + +############################################################ + +bundle edit_line sign_visitor_book(s) +{ + insert_lines: + + "/tmp/enter_the_dragon" + comment => "Import the current visitor's book", + insert_type => "file"; + + "$(s)" comment => "Append this string to the visitor's book"; +} + +############################################################ + + +bundle server access_rules() +{ + access: + + "/tmp" + + admit => { "127.0.0.1" }; + + "did.*" + resource_type => "context", + admit => { "127.0.0.1" }; + +} + +############################################################ + +body printfile visitors_book(file) +{ + file_to_print => "$(file)"; + number_of_lines => "100"; +} + + diff --git a/examples/orchestrate_dragon_load_balancer.cf b/examples/orchestrate_dragon_load_balancer.cf new file mode 100644 index 0000000000..be8f07d44f --- /dev/null +++ b/examples/orchestrate_dragon_load_balancer.cf @@ -0,0 +1,307 @@ +############################################################ +# +# Chinese Dragon Dancing on a Star +# +# This method works with either Community or Enterprise. +# and uses named signals +# +# If you want to test this on localhost, just edit /etc/hosts +# to add host1 host2 host3 host4 as aliases to localhost +# +# Start a switch file /tmp/switch_file with +# host1 ON +# host2 ON +# host3 ON etc + + +############################################################ + +body common control +{ + bundlesequence => { "dragon_symphony" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +############################################################ + +bundle agent dragon_symphony +{ + methods: + + # We have to seed the beginning by creating the dragon + # /tmp/dragon_localhost + + "dragon" usebundle => upgrade_host("localhost","host1","chapter1"); + + "dragon" usebundle => upgrade_host("host1","host2","chapter2"); + + "dragon" usebundle => upgrade_host("host2","host3","chapter3"); + + "dragon" usebundle => upgrade_host("host3","host4","chapter4"), + classes => if_ok("finale"); + + finale:: + + "dragon" usebundle => restore_final("host4"), + comment => "Restore all the deactivated satellites"; + + + reports: + + finale:: + + "The dragon is slain!!!" + printfile => visitors_book("/tmp/shoo_dragon_host4"); + + "And the switch state is..." + printfile => visitors_book("/tmp/switch_file"); +} + +############################################################ +# Define the +############################################################ + +bundle agent chapter1(x) +{ + # Do something significant here + + reports: + + host1:: + " ----> Breathing fire on $(x)"; +} + +################################ + +bundle agent chapter2(x) +{ + # Do something significant here + + reports: + + host2:: + " ----> Breathing fire on $(x)"; + +} + +################################ + +bundle agent chapter3(x) +{ + # Do something significant here + + reports: + + host3:: + " ----> Breathing fire on $(x)"; + +} + +################################ + +bundle agent chapter4(x) +{ + # Do something significant here + + reports: + + host4:: + " ----> Breathing fire on $(x)"; + +} + +############################################################ +# Orchestration wrappers +############################################################ + +bundle agent upgrade_host(predecessor,satellite,method) +{ + + # This is a wrapper for the orchestration will be acted on + # first by the dragon's lair and then by the satellite + + vars: + + "dragons_lair" string => "host0"; + + files: + + # We start in the dragon's lair .. + + "/tmp/unleash_dragon" + + comment => "Unleash the dragon", + rename => to("/tmp/enter_the_dragon"), + classes => if_repaired("dispatch_dragon_$(satellite)"), + if => "$(dragons_lair)"; + + # if we are the dragon's lair, welcome the dragon back, shooed from the satellite + + "/tmp/enter_the_dragon" + + comment => "Returning from a visit to a satellite", + copy_from => secure_cp("/tmp/shoo_dragon_$(predecessor)","$(predecessor)"), + classes => if_repaired("dispatch_dragon_$(satellite)"), + if => "$(dragons_lair)"; + + # If we are a satellite, receive the dragon from its lair + + "/tmp/enter_the_dragon" + comment => "Wait for our cue or relay/conductor baton", + copy_from => secure_cp("/tmp/dragon_$(satellite)","$(dragons_lair)"), + classes => if_repaired("cue_action_on_$(satellite)"), + if => "$(satellite)"; + + methods: + + "check in at home" + comment => "Edit the load balancer?", + usebundle => next_host("$(satellite)","$(predecessor)"), + classes => if_repaired("send_the_dragon_to_$(satellite)"), + if => "dispatch_dragon_$(satellite)"; + + "dragon visits" + comment => "One off activity that the nodes carry out while the dragon visits", + usebundle => $(method)("$(satellite)"), + classes => if_repaired("send_the_dragon_back_from_$(satellite)"), + if => "cue_action_on_$(satellite)"; + + + files: + + # hub/lair hub signs the book too and schedules the dragon for next satellite + + "/tmp/dragon_$(satellite)" + create => "true", + comment => "Add our signature to the dragon's tail", + edit_line => sign_visitor_book("Dragon returned from $(predecessor)"), + if => "send_the_dragon_to_$(satellite)"; + + # Satellite signs the book and shoos dragon for hub to collect + + "/tmp/shoo_dragon_$(satellite)" + create => "true", + comment => "Add our signature to the dragon's tail", + edit_line => sign_visitor_book("Dragon visited $(satellite) and did: $(method)"), + if => "send_the_dragon_back_from_$(satellite)"; + + reports: + + "Done $(satellite)" + if => "$(satellite)"; +} + +############################################################ + +bundle agent next_host(name,pred) +{ + files: + + "/tmp/enter_the_dragon" + comment => "Add our signature to the dragon's tail", + edit_line => append_if_no_line("Switch new dragon's target $(name)"); + + # Edit the switch file + + "/tmp/switch_file" + comment => "Restore whatever is missing and comment out", + edit_line => restore_and_comment("$(name)","$(pred)"), + classes => if_repaired("reload_switch_$(name)"); + + reports: + + !problem:: + + "Returned from $(pred), heading out to $(name)"; + + " X Restoring $(pred) and switching new dragon's target $(name)" + if => "reload_switch_$(name)"; +} + +############################################################ + +bundle agent restore_final(name) +{ + files: + + host0:: + + "/tmp/switch_file" + comment => "Restore whatever is missing and comment out", + edit_line => restore_and_comment("NONE","$(name)"), + classes => if_repaired("reload_switch"); + + reports: + + reload_switch:: + " X Restoring $(name) to tidy up loose ends"; + +} + +############################################################ + +bundle edit_line sign_visitor_book(s) +{ + insert_lines: + + "/tmp/enter_the_dragon" + comment => "Import the current visitor's book", + insert_type => "file"; + + "$(s)" comment => "Append this string to the visitor's book"; +} + +# + +bundle edit_line restore_and_comment(name,pred) +{ + replace_patterns: + + "$(name) ON" + + replace_with => value("$(name) OFF"), + comment => "Uncomment the predecessor to reactivate"; + + "$(pred) OFF" + + replace_with => value("$(pred) ON"), + comment => "Comment out the new host", + classes => if_repaired("show_switch_$(name)"); + + reports: + + !problem:: + + "The state of the switch was:" + printfile => visitors_book("/tmp/switch_file"), + if => "show_switch_$(name)"; + + +} + +############################################################ + + +bundle server access_rules() +{ + access: + + "/tmp" + + admit => { "127.0.0.1" }; + + "did.*" + resource_type => "context", + admit => { "127.0.0.1" }; + +} + +############################################################ + +body printfile visitors_book(file) +{ + file_to_print => "$(file)"; + number_of_lines => "100"; +} + + diff --git a/examples/orchestrate_n_of_m.cf b/examples/orchestrate_n_of_m.cf new file mode 100644 index 0000000000..39ac0a1d41 --- /dev/null +++ b/examples/orchestrate_n_of_m.cf @@ -0,0 +1,62 @@ +############################################################ +# +# Keep a special promise only if at least n or m hosts +# keep a specific promise +# +# This method works with Enterprise CFEngine +# +# If you want to test this on localhost, just edit /etc/hosts +# to add host1 host2 host3 host4 as aliases to localhost +# +############################################################ + +body common control +{ + bundlesequence => { "n_of_m_symphony" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +############################################################ + +bundle agent n_of_m_symphony +{ + vars: + + "count_compliant_hosts" string => hubknowledge("running_myprocess"); + + classes: + + "reboot" expression => isgreaterthan("$(count_compliant_hosts)","20"); + + processes: + + "myprocess" + + comment => "Count this host if a job is matched", + classes => enumerate("running_myprocess"); + + commands: + + reboot:: + + "/bin/shutdown now"; +} + + +####################################################### + +bundle server access_rules() +{ + access: + + "value of my test_scalar, can expand variables here - $(sys.host)" + handle => "test_scalar", + comment => "Grant access to contents of test_scalar VAR", + resource_type => "literal", + admit => { "127.0.0.1" }; + + "running_myprocess" + resource_type => "variable", + admit => { "127.0.0.1" }; + +} diff --git a/examples/orchestration_hostlist.cf b/examples/orchestration_hostlist.cf new file mode 100644 index 0000000000..a3381a394b --- /dev/null +++ b/examples/orchestration_hostlist.cf @@ -0,0 +1,58 @@ +# +# Make list of hosts available to all clients, and use it on clients +# in order to configure a sample monitoring application that checks +# health of all clients. +# +# Sample application configuration file contents below, put this in +# /var/cfengine/masterfiles/template_monitoring on the hub. +# +# --- +# +#monitor_hosts = $(orchestration_hostlist_use.host_string) +# +# --- +# +# Note that your copy_from statement in failsafe.cf or update.cf must +# copy this file also (e.g. not only copy *.cf files in any file_select) + + + +body common control +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; + bundlesequence => { "orchestration_hostlist_update", "orchestration_hostlist_use"}; +} + + +bundle agent orchestration_hostlist_update +{ + vars: + am_policy_hub:: + "host_list" slist => hostsseen("inf", "lastseen", "address"); + + files: + + am_policy_hub:: + "$(sys.workdir)/masterfiles/host_list" + comment => "Write the addresses of all hosts to a file", + handle => "orchestration_hostlit_update_files_host_list_write", + edit_line => insert_lines( "@(orchestration_hostlist_update.host_list)" ), + create => "true", + edit_defaults => empty; +} + + +bundle agent orchestration_hostlist_use +{ + vars: + "host_list" slist => readstringlist("$(sys.workdir)/inputs/host_list", "#.*", "\n", 1000, 40000); + "host_string" string => join(", ", "host_list"); + + files: + "/tmp/monitoring" + comment => "Update application configuration to reflect all hosts on this hub", + handle => "orchestration_read_files_host_list_configuration", + edit_line => expand_template("$(sys.workdir)/inputs/template_monitoring"), + create => "true", + edit_defaults => empty; +} diff --git a/examples/ordering.cf b/examples/ordering.cf new file mode 100644 index 0000000000..30206fab45 --- /dev/null +++ b/examples/ordering.cf @@ -0,0 +1,79 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +################################################################## +# +# cfengine 3 - ordering promises into dependent chains +# +## +# +# cf-agent -f ./cftest.cf -K +# +################################################################## + +body common control + +{ + bundlesequence => { "order" }; +} + +################################################################## + +bundle agent order + +{ + vars: + + "list" slist => { "three", "four" }; + + commands: + + ok_later:: + + "/bin/echo five"; + + otherthing:: + + "/bin/echo six"; + + any:: + + + "/bin/echo one" classes => d("ok_later","otherthing"); + "/bin/echo two"; + "/bin/echo $(list)"; + + preserved_class:: + + "/bin/echo seven"; + +} + +############################################ + +body classes d(if,else) + +{ + promise_repaired => { "$(if)" }; + repair_failed => { "$(else)" }; + persist_time => "0"; +} diff --git a/examples/package_apt.cf b/examples/package_apt.cf new file mode 100644 index 0000000000..e377756334 --- /dev/null +++ b/examples/package_apt.cf @@ -0,0 +1,94 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# to see list of packages type "apt-cache pkgnames" +# to see list of installed packages type "dpkg --get-selections" +# +# Package management +# + +body common control +{ + bundlesequence => { "packages" }; +} + +body agent control +{ + environment => { "DEBIAN_FRONTEND=noninteractive" }; +} + +############################################# + +bundle agent packages +{ + vars: + + # Test the simplest case -- leave everything to the yum smart manager + + "match_package" slist => { + "apache2" + # "apache2-mod_php5", + # "apache2-prefork", + # "php5" + }; + packages: + + "$(match_package)" + + package_policy => "add", + package_method => apt; + +} + +############################################# + +body package_method apt + +{ + any:: + + # ii acpi 0.09-3ubuntu1 + + package_changes => "bulk"; + package_list_command => "/usr/bin/dpkg -l"; + + package_list_name_regex => "ii\s+([^\s]+).*"; + package_list_version_regex => "ii\s+[^\s]+\s+([^\s]+).*"; + + # package_list_arch_regex => "none"; + + package_installed_regex => ".*"; # all reported are installed + + #package_name_convention => "$(name)_$(version)_$(arch)"; + package_name_convention => "$(name)"; + + # Use these only if not using a separate version/arch string + # package_version_regex => ""; + # package_name_regex => ""; + # package_arch_regex => ""; + + package_add_command => "/usr/bin/apt-get --yes install"; + package_delete_command => "/usr/bin/apt-get --yes remove"; + package_update_command => "/usr/bin/apt-get --yes dist-upgrade"; + #package_verify_command => "/bin/rpm -V"; +} + diff --git a/examples/package_bundles.cf b/examples/package_bundles.cf new file mode 100644 index 0000000000..18a3525247 --- /dev/null +++ b/examples/package_bundles.cf @@ -0,0 +1,72 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +#+begin_src cfengine3 +body common control +{ + inputs => { "$(sys.libdir)/packages.cf" }; + bundlesequence => { "runme" }; +} + + +bundle agent runme +{ + methods: + "nozip" usebundle => package_absent("zip"); # delete + "pleasezip" usebundle => package_present("zip"); # add + "latestzip" usebundle => package_latest("zip"); # add/update + + # add a package from a file with a specific selection method, + # version, and architecture + "addfilezip" + usebundle => package_specific_present("/mydir/zip.deb-or-rpm", + "3.0-7", + ifelse("debian", "amd64", + "x86_64")); + + # add a package with a specific selection method, version, and + # architecture + "addzip" + usebundle => package_specific_present("zip", + ifelse("redhat", "3.0-7", + "2.99"), + ifelse("debian", "amd64", + "x86_64")); + + # add or update a package from a file with a specific selection + # method, version, and architecture + "upgradefilezip" + usebundle => package_specific_latest("/mydir/zip.deb-or-rpm", + "3.0-7", + ifelse("debian", "amd64", + "x86_64")); + + # add or update a package with a specific selection method, + # version, and architecture + "upgradezip" + usebundle => package_specific_latest("zip", + "3.0-7", + ifelse("debian", "amd64", + "x86_64")); +} +#+end_src diff --git a/examples/package_freebsd.cf b/examples/package_freebsd.cf new file mode 100644 index 0000000000..ad006bd781 --- /dev/null +++ b/examples/package_freebsd.cf @@ -0,0 +1,86 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# +# Package management +# + +body common control +{ + bundlesequence => { "packages" }; +} + +############################################# + +body agent control +{ + environment => { "PACKAGESITE=ftp://freebsd-src.es.net/pub/FreeBSD/ports/i386/packages-7.2-release/All/" }; +} + +############################################# + +bundle agent packages +{ + vars: + + # Test the simplest case -- leave everything to the yum smart manager + + "match_package" slist => { + "wget-1.11.4" + }; + packages: + + "$(match_package)" + + package_policy => "add", + package_method => freebsd; + +} +############################################# + +body package_method freebsd + +{ + any:: + + package_changes => "individual"; + + # Could use rpm for this + package_list_command => "/usr/sbin/pkg_info"; + + # Remember to escape special characters like | + + package_list_name_regex => "([^-]+).*"; + package_list_version_regex => "[^-]+-([^\s]+).*"; + + package_name_regex => "([^-]+).*"; + package_version_regex => "[^-]+-([^\s]+).*"; + + package_installed_regex => ".*"; + + package_name_convention => "$(name)-$(version)"; + + + package_add_command => "/usr/sbin/pkg_add -r"; + package_delete_command => "/usr/sbin/pkg_delete"; +} diff --git a/examples/package_latest.cf b/examples/package_latest.cf new file mode 100644 index 0000000000..42c5ba9cba --- /dev/null +++ b/examples/package_latest.cf @@ -0,0 +1,24 @@ +# +# Makes sure a package is installed at latest version. +# Does not take any action if the package has already +# reached the version given in package_version. +# + +body common control +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; + bundlesequence => { "example" }; +} + + +bundle agent example +{ + packages: + + "apache2" + package_method => generic, + package_version => "2.2.00", + package_select => ">=", + package_policy => "addupdate"; +} + diff --git a/examples/package_msi_file.cf b/examples/package_msi_file.cf new file mode 100644 index 0000000000..47d238378d --- /dev/null +++ b/examples/package_msi_file.cf @@ -0,0 +1,67 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# MSI package management using file name +# + +body common control +{ + bundlesequence => { "packages" }; +} + +############################################# + +bundle agent packages +{ + vars: + + "match_package" slist => { + "7zip-4.65-x86_64.msi" + }; + packages: + + "$(match_package)" + + package_policy => "add", + package_method => msi_fmatch; + +} + +############################################# + +body package_method msi_fmatch + +{ + package_changes => "individual"; + package_file_repositories => { "$(sys.workdir)\software_updates\windows", "s:\su" }; + + package_installed_regex => ".*"; + + package_name_regex => "^(\S+)-(\d+\.?)+"; + package_version_regex => "^\S+-((\d+\.?)+)"; + package_arch_regex => "^\S+-(\d+\.?)+(^.+)"; + + package_name_convention => "$(name)-$(version)-$(arch).msi"; + + package_add_command => "\"$(sys.winsysdir)\msiexec.exe\" /qn /i"; + package_update_command => "\"$(sys.winsysdir)\msiexec.exe\" /qn /i"; + package_delete_command => "\"$(sys.winsysdir)\msiexec.exe\" /qn /x"; +} diff --git a/examples/package_msi_version.cf b/examples/package_msi_version.cf new file mode 100644 index 0000000000..38c4d74f95 --- /dev/null +++ b/examples/package_msi_version.cf @@ -0,0 +1,67 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# MSI package management using version criteria +# + +body common control +{ + bundlesequence => { "packages" }; +} + +############################################# + +bundle agent packages +{ + vars: + + "match_package" slist => { + "7zip" + }; + packages: + + "$(match_package)" + + package_policy => "update", + package_select => ">=", + package_architectures => { "x86_64" }, + package_version => "3.00", + package_method => msi_vmatch; + +} + +############################################# + +body package_method msi_vmatch + +{ + package_changes => "individual"; + package_file_repositories => { "$(sys.workdir)\software_updates\windows", "s:\su" }; + + package_installed_regex => ".*"; + + package_name_convention => "$(name)-$(version)-$(arch).msi"; + + package_add_command => "\"$(sys.winsysdir)\msiexec.exe\" /qn /i"; + package_update_command => "\"$(sys.winsysdir)\msiexec.exe\" /qn /i"; + package_delete_command => "\"$(sys.winsysdir)\msiexec.exe\" /qn /x"; +} + diff --git a/examples/package_rpm.cf b/examples/package_rpm.cf new file mode 100644 index 0000000000..5c4d7daa94 --- /dev/null +++ b/examples/package_rpm.cf @@ -0,0 +1,85 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Package management +# + +body common control +{ + bundlesequence => { "packages" }; +} + +############################################# + +bundle agent packages +{ + vars: + + "exact_package" slist => { + "apache2", + "kernel-default" + }; + + "version[OpenOffice_org-hyphen]" string => "1.2.3"; + "version[kernel-default]" string => "2.6.27.7-9.1"; + + packages: + + "$(exact_package)" + + package_policy => "verify", + package_method => rpm, + package_select => ">=", + package_architectures => { "x86_64" }, + package_version => "$(version[$(exact_package)])"; + +} + +############################################# + +body package_method rpm + +{ + any:: + + package_changes => "individual"; + + package_list_command => "/bin/rpm -qa --queryformat \"i | repos | %{name} | %{version}-%{release} | %{arch}\n\""; + + # Remember to escape special characters like | + + package_list_name_regex => "[^|]+\|[^|]+\|\s+([^\s|]+).*"; + package_list_version_regex => "[^|]+\|[^|]+\|[^|]+\|\s+([^\s|]+).*"; + package_list_arch_regex => "[^|]+\|[^|]+\|[^|]+\|[^|]+\|\s+([^\s]+).*"; + + package_installed_regex => "i.*"; + + package_name_convention => "$(name).$(arch)"; + + package_add_command => "/bin echo /bin/rpm -i "; + package_delete_command => "/bin/rpm -e --nodeps"; + package_verify_command => "/bin/rpm -V"; + package_noverify_regex => ".*[^\s].*"; + #package_noverify_returncode => "-1"; + +} diff --git a/examples/package_solaris.cf b/examples/package_solaris.cf new file mode 100644 index 0000000000..82aad7f52c --- /dev/null +++ b/examples/package_solaris.cf @@ -0,0 +1,60 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# +# Package management +# + +body common control +{ + bundlesequence => { "packages" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +############################################# + +bundle agent packages +{ + vars: + + "solaris_packages[SMCzlib]" string => "zlib-1.2.3-sol10-sparc-local"; + "admin_file" string => "cfengine_admin_file"; + + "package_names" slist => getindices("solaris_packages"); + + files: + + "/tmp/$(admin_file)" + create => "true", + edit_defaults => empty, + edit_line => create_solaris_admin_file; + + packages: + + "$(package_names)" + + package_policy => "add", + package_method => solaris("$(package_names)", "$(solaris_packages[$(package_names)])", "$(admin_file)"); + +} + diff --git a/examples/package_windows_feature.cf b/examples/package_windows_feature.cf new file mode 100644 index 0000000000..20997f4bfa --- /dev/null +++ b/examples/package_windows_feature.cf @@ -0,0 +1,34 @@ +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + packages: + + Windows_Server_2008_R2:: + "Telnet-Client" + comment => "Install the telnet client", + package_policy => "add", + package_method => windows_feature; + +} + + +body package_method windows_feature +# NOTE: Windows feature names are case-sensitive +{ + package_changes => "individual"; + + package_name_convention => "$(name)"; + package_delete_convention => "$(name)"; + + package_installed_regex => ".*"; + package_list_name_regex => "(.*)"; + package_list_version_regex => "(.*)"; # FIXME: the listing does not give version, so takes name for version too now + + package_add_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Import-Module ServerManager; Add-WindowsFeature -Name\""; + package_delete_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Import-Module ServerManager; Remove-WindowsFeature -confirm:$false -Name\""; + package_list_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Import-Module ServerManager; Get-WindowsFeature | where {$_.installed -eq $True} |foreach {$_.Name}\""; +} diff --git a/examples/package_yum.cf b/examples/package_yum.cf new file mode 100644 index 0000000000..56f61b8185 --- /dev/null +++ b/examples/package_yum.cf @@ -0,0 +1,56 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# +# Package management +# + +body common control +{ + bundlesequence => { "packages" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +############################################# + +bundle agent packages +{ + vars: + + # Test the simplest case -- leave everything to the yum smart manager + + "match_package" slist => { + "apache2", + "apache2-mod_php5", + "apache2-prefork", + "php5" + }; + packages: + + "$(match_package)" + + package_policy => "add", + package_method => yum; + +} + diff --git a/examples/package_zypper.cf b/examples/package_zypper.cf new file mode 100644 index 0000000000..9c74441331 --- /dev/null +++ b/examples/package_zypper.cf @@ -0,0 +1,56 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# +# Package management +# + +body common control +{ + bundlesequence => { "packages" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +############################################# + +bundle agent packages +{ + vars: + + # Test the simplest case -- leave everything to the zypper smart manager + + "match_package" slist => { + "apache2", + "apache2-mod_php5", + "apache2-prefork", + "php5" + }; + packages: + + "$(match_package)" + + package_policy => "add", + package_method => zypper; + +} + diff --git a/examples/packagesmatching.cf b/examples/packagesmatching.cf new file mode 100644 index 0000000000..b07eb7403b --- /dev/null +++ b/examples/packagesmatching.cf @@ -0,0 +1,63 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#[%-%] +body common control + +{ + bundlesequence => { "missing_packages" }; +} +#[%-%] + +########################################### + +#[%+%] +bundle agent missing_packages +{ + vars: + # List of desired packages + "desired" slist => { "mypackage1", "mypackage2" }; + + # Get info on all installed packages + "installed" data => packagesmatching(".*",".*",".*",".*"); + "installed_indices" slist => getindices(installed); + + # Build a simple array of the package names so that we can use + # getvalues to pull a unified list of package names that are installed. + "installed_name[$(installed_indices)]" + string => "$(installed[$(installed_indices)][name])"; + + # Get unified list of installed packages + "installed_names" slist => getvalues("installed_name"); + + # Determine packages that are missing my differencing the list of + # desired packages, against the list of installed packages + "missing_list" slist => difference(desired,installed_names); + + reports: + # Report on packages that are missing, installed + # and what we were looking for + "Missing packages = $(missing_list)"; + "Installed packages = $(installed_names)"; + "Desired packages = $(desired)"; +} +#[%+%] diff --git a/examples/parallel_exec.cf b/examples/parallel_exec.cf new file mode 100644 index 0000000000..4bf6102ef4 --- /dev/null +++ b/examples/parallel_exec.cf @@ -0,0 +1,65 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple backgrounding - try this with background = true and false +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +body agent control + +{ + agentaccess => { "mark", "root" }; +} + +######################################################## + +bundle agent example + +{ + commands: + + "/bin/sleep 10" + action => background; + + "/bin/sleep" + args => "20", + action => background; + +} + +######################################################### + +body action background + +{ + background => "true"; +} diff --git a/examples/parseintarray.cf b/examples/parseintarray.cf new file mode 100644 index 0000000000..e3737426e9 --- /dev/null +++ b/examples/parseintarray.cf @@ -0,0 +1,60 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test(f) +{ + vars: + # Define data inline for convenience + "table" string => + "1:2 + 3:4 + 5:6"; + + "dim" int => parseintarray( + "items", + "$(table)", + "\s*#[^\n]*", + ":", + "1000", + "200000" + ); + + "keys" slist => sort(getindices("items")); + + reports: + "$(keys)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: 1 +#@ R: 3 +#@ R: 5 +#@ ``` +#+end_src diff --git a/examples/parserealarray.cf b/examples/parserealarray.cf new file mode 100644 index 0000000000..a6096393df --- /dev/null +++ b/examples/parserealarray.cf @@ -0,0 +1,57 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent __main__ +{ + vars: + # Define data inline for convenience + "table" + string => "1.0:2.718 +3:4.6692 +5.0:6.82"; + + + "dim" + int => parserealarray( + "items", + "$(table)", + "\s*#[^\n]*", + ":", + "1000", + "200000" + ); + + "keys" slist => sort(getindices("items")); + + reports: + "$(keys) - $(items[$(keys)][1])"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: 1.0 - 2.718 +#@ R: 3 - 4.6692 +#@ R: 5.0 - 6.82 +#@ ``` +#+end_src diff --git a/examples/parsestringarray.cf b/examples/parsestringarray.cf new file mode 100644 index 0000000000..c5abf3fe56 --- /dev/null +++ b/examples/parsestringarray.cf @@ -0,0 +1,39 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + vars: + + ####################################### + # Define data inline for convenience + ####################################### + + "table" + string => "Eulers Number:2.718 +A Feigenbaum constant:4.6692 +Tau (2pi):6.28"; + + ####################################### + + "dim" int => parsestringarray( + "items", + "$(table)", + "\s*#[^\n]*", + ":", + "1000", + "200000" + ); + + "keys" slist => sort(getindices("items")); + + reports: + "$(keys) - $(items[$(keys)][1])"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: A Feigenbaum constant - 4.6692 +#@ R: Eulers Number - 2.718 +#@ R: Tau (2pi) - 6.28 +#@ ``` +#+end_src diff --git a/examples/parsestringarrayidx.cf b/examples/parsestringarrayidx.cf new file mode 100644 index 0000000000..019afab4ce --- /dev/null +++ b/examples/parsestringarrayidx.cf @@ -0,0 +1,62 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test(f) +{ + vars: + # Define data inline for convenience + "table" string => "one: a + two: b + three: c"; + + ####################################### + + "dim" int => parsestringarrayidx( + "items", + "$(table)", + "\s*#[^\n]*", + ":", + "1000", + "200000" + ); + + "keys" slist => getindices("items"); + "sorted_keys" slist => sort(keys, "int"); + + reports: + "item $(sorted_keys) has column 0 = $(items[$(sorted_keys)][0]) and column 1 = $(items[$(sorted_keys)][1])"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: item 0 has column 0 = one and column 1 = a +#@ R: item 1 has column 0 = two and column 1 = b +#@ R: item 2 has column 0 = three and column 1 = c +#@ ``` +#+end_src diff --git a/examples/pathtype.cf b/examples/pathtype.cf new file mode 100644 index 0000000000..b3c2fcf7df --- /dev/null +++ b/examples/pathtype.cf @@ -0,0 +1,76 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +######################################################## +# +# Simple backgrounding +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp" -> "me" + + changes => tripwire, + pathtype => "literal", + depth_search => recurse("inf"); + +} + + +######################################################### + +body changes tripwire + +{ + hash => "md5"; + report_changes => "all"; + update_hashes => "true"; +} + +######################################################### + +body action background + +{ + background => "true"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} diff --git a/examples/pattern_and_edit.cf b/examples/pattern_and_edit.cf new file mode 100644 index 0000000000..457a60a258 --- /dev/null +++ b/examples/pattern_and_edit.cf @@ -0,0 +1,60 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => {"test"}; +} + +######################################################## + +bundle agent example + +{ + + files: + + "/home/(.*)/testfile" + + create => "true", + edit_line => AppendIfNoLine("key_$(match.1)"); + +} + +######################################################## + +bundle edit_line AppendIfNoLine(x) +{ + insert_lines: + + "Line $(x)" location => append; +} + +######################################################## + +body location append + +{ + before_after => "before"; +} diff --git a/examples/peerleader.cf b/examples/peerleader.cf new file mode 100644 index 0000000000..fdc4eb2a52 --- /dev/null +++ b/examples/peerleader.cf @@ -0,0 +1,79 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +#+begin_src prep +#@ ``` +#@ echo alpha > /tmp/cfe_hostlist +#@ echo beta >> /tmp/cfe_hostlist +#@ echo gamma >> /tmp/cfe_hostlist +#@ echo "Set HOSTNAME appropriately beforehand" +#@ echo "$(hostname -f)" | tr 'A-Z' 'a-z' >> /tmp/cfe_hostlist +#@ echo "Delta Delta Delta may I help ya help ya help ya" +#@ echo delta1 >> /tmp/cfe_hostlist +#@ echo delta2 >> /tmp/cfe_hostlist +#@ echo delta3 >> /tmp/cfe_hostlist +#@ echo may1.I.help.ya >> /tmp/cfe_hostlist +#@ echo may2.I.help.ya >> /tmp/cfe_hostlist +#@ echo may3.I.help.ya >> /tmp/cfe_hostlist +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "peers" }; +} + +bundle agent peers +{ + vars: + + "mygroup" slist => peers("/tmp/cfe_hostlist","#.*",4); + + "myleader" string => peerleader("/tmp/cfe_hostlist","#.*",4); + + "all_leaders" slist => peerleaders("/tmp/cfe_hostlist","#.*",4); + + reports: + + # note that the current host name is fourth in the host list, so + # its peer group is the first 4-host group, minus the host itself. + "/tmp/cfe_hostlist mypeer $(mygroup)"; + # note that the current host name is fourth in the host list, so + # the peer leader is "alpha" + "/tmp/cfe_hostlist myleader $(myleader)"; + "/tmp/cfe_hostlist another leader $(all_leaders)"; + "Unable to find my fully qualified hostname $(sys.fqhost) in /tmp/cfe_hostlist. Can't determine peers." + if => not( regline( $(sys.fqhost), "/tmp/cfe_hostlist" ) ); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /tmp/cfe_hostlist mypeer alpha +#@ R: /tmp/cfe_hostlist mypeer beta +#@ R: /tmp/cfe_hostlist mypeer gamma +#@ R: /tmp/cfe_hostlist myleader alpha +#@ R: /tmp/cfe_hostlist another leader alpha +#@ R: /tmp/cfe_hostlist another leader delta1 +#@ R: /tmp/cfe_hostlist another leader may2.I.help.ya +#@ ``` +#+end_src diff --git a/examples/peerleaders.cf b/examples/peerleaders.cf new file mode 100644 index 0000000000..fdc4eb2a52 --- /dev/null +++ b/examples/peerleaders.cf @@ -0,0 +1,79 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +#+begin_src prep +#@ ``` +#@ echo alpha > /tmp/cfe_hostlist +#@ echo beta >> /tmp/cfe_hostlist +#@ echo gamma >> /tmp/cfe_hostlist +#@ echo "Set HOSTNAME appropriately beforehand" +#@ echo "$(hostname -f)" | tr 'A-Z' 'a-z' >> /tmp/cfe_hostlist +#@ echo "Delta Delta Delta may I help ya help ya help ya" +#@ echo delta1 >> /tmp/cfe_hostlist +#@ echo delta2 >> /tmp/cfe_hostlist +#@ echo delta3 >> /tmp/cfe_hostlist +#@ echo may1.I.help.ya >> /tmp/cfe_hostlist +#@ echo may2.I.help.ya >> /tmp/cfe_hostlist +#@ echo may3.I.help.ya >> /tmp/cfe_hostlist +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "peers" }; +} + +bundle agent peers +{ + vars: + + "mygroup" slist => peers("/tmp/cfe_hostlist","#.*",4); + + "myleader" string => peerleader("/tmp/cfe_hostlist","#.*",4); + + "all_leaders" slist => peerleaders("/tmp/cfe_hostlist","#.*",4); + + reports: + + # note that the current host name is fourth in the host list, so + # its peer group is the first 4-host group, minus the host itself. + "/tmp/cfe_hostlist mypeer $(mygroup)"; + # note that the current host name is fourth in the host list, so + # the peer leader is "alpha" + "/tmp/cfe_hostlist myleader $(myleader)"; + "/tmp/cfe_hostlist another leader $(all_leaders)"; + "Unable to find my fully qualified hostname $(sys.fqhost) in /tmp/cfe_hostlist. Can't determine peers." + if => not( regline( $(sys.fqhost), "/tmp/cfe_hostlist" ) ); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /tmp/cfe_hostlist mypeer alpha +#@ R: /tmp/cfe_hostlist mypeer beta +#@ R: /tmp/cfe_hostlist mypeer gamma +#@ R: /tmp/cfe_hostlist myleader alpha +#@ R: /tmp/cfe_hostlist another leader alpha +#@ R: /tmp/cfe_hostlist another leader delta1 +#@ R: /tmp/cfe_hostlist another leader may2.I.help.ya +#@ ``` +#+end_src diff --git a/examples/peers.cf b/examples/peers.cf new file mode 100644 index 0000000000..fdc4eb2a52 --- /dev/null +++ b/examples/peers.cf @@ -0,0 +1,79 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +#+begin_src prep +#@ ``` +#@ echo alpha > /tmp/cfe_hostlist +#@ echo beta >> /tmp/cfe_hostlist +#@ echo gamma >> /tmp/cfe_hostlist +#@ echo "Set HOSTNAME appropriately beforehand" +#@ echo "$(hostname -f)" | tr 'A-Z' 'a-z' >> /tmp/cfe_hostlist +#@ echo "Delta Delta Delta may I help ya help ya help ya" +#@ echo delta1 >> /tmp/cfe_hostlist +#@ echo delta2 >> /tmp/cfe_hostlist +#@ echo delta3 >> /tmp/cfe_hostlist +#@ echo may1.I.help.ya >> /tmp/cfe_hostlist +#@ echo may2.I.help.ya >> /tmp/cfe_hostlist +#@ echo may3.I.help.ya >> /tmp/cfe_hostlist +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "peers" }; +} + +bundle agent peers +{ + vars: + + "mygroup" slist => peers("/tmp/cfe_hostlist","#.*",4); + + "myleader" string => peerleader("/tmp/cfe_hostlist","#.*",4); + + "all_leaders" slist => peerleaders("/tmp/cfe_hostlist","#.*",4); + + reports: + + # note that the current host name is fourth in the host list, so + # its peer group is the first 4-host group, minus the host itself. + "/tmp/cfe_hostlist mypeer $(mygroup)"; + # note that the current host name is fourth in the host list, so + # the peer leader is "alpha" + "/tmp/cfe_hostlist myleader $(myleader)"; + "/tmp/cfe_hostlist another leader $(all_leaders)"; + "Unable to find my fully qualified hostname $(sys.fqhost) in /tmp/cfe_hostlist. Can't determine peers." + if => not( regline( $(sys.fqhost), "/tmp/cfe_hostlist" ) ); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /tmp/cfe_hostlist mypeer alpha +#@ R: /tmp/cfe_hostlist mypeer beta +#@ R: /tmp/cfe_hostlist mypeer gamma +#@ R: /tmp/cfe_hostlist myleader alpha +#@ R: /tmp/cfe_hostlist another leader alpha +#@ R: /tmp/cfe_hostlist another leader delta1 +#@ R: /tmp/cfe_hostlist another leader may2.I.help.ya +#@ ``` +#+end_src diff --git a/examples/postfix.cf b/examples/postfix.cf new file mode 100644 index 0000000000..da5406e397 --- /dev/null +++ b/examples/postfix.cf @@ -0,0 +1,124 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Postfix +# +####################################################### + +body common control + +{ + any:: + + bundlesequence => { + postfix + }; +} + +####################################################### + +bundle agent postfix + +{ + vars: + + "prefix" string => "/home/mark/tmp"; + "smtpserver" string => "mailx.domain.tld"; + "mailrelay" string => "mailx.domain.tld"; + + files: + + "$(prefix)/main.cf" + edit_line => prefix_postfix; + + "$(prefix)/sasl-passwd" + create => "true", + perms => system("0600","root"), + edit_line => AppendIfNSL("$(smtpserver) _$(fqhost):chmsxrcynz4etzefabj9frejizhs22"); + + +} + +####################################################### +# For the library +####################################################### + +bundle edit_line prefix_postfix + +{ + # + # Value have the form NAME = "quoted space separated list" + # + vars: + + "ps[relayhost]" string => "[$(postfix.mailrelay)]:587"; + "ps[mydomain]" string => "iu.hio.no"; + "ps[smtp_sasl_auth_enable]" string => "yes"; + "ps[smtp_sasl_password_maps]" string => "hash:/etc/postfix/sasl-passwd"; + "ps[smtp_sasl_security_options]" string => ""; + "ps[smtp_use_tls]" string => "yes"; + "ps[default_privs]" string => "mailman"; + "ps[inet_protocols]" string => "all"; + "ps[inet_interfaces]" string => "127.0.0.1"; + + "parameter_name" slist => getindices("ps"); + + delete_lines: + + "$(parameter_name).*"; + + insert_lines: + + "$(parameter_name) = $(ps[$(parameter_name)])"; + +} + +######################################################## + +bundle edit_line AppendIfNSL(parameter) +{ + insert_lines: + + "$(parameter)"; # This is default +} + +######################################## +# Library Bodies +######################################## + +body replace_with All(x) + +{ + replace_value => "$(x)"; + occurrences => "all"; +} + +######################################################### + +body perms system(x,owner) + +{ + mode => "0640"; + owners => { "$(owner)", "root" }; +} diff --git a/examples/printfile.cf b/examples/printfile.cf new file mode 100644 index 0000000000..bdc7b2846c --- /dev/null +++ b/examples/printfile.cf @@ -0,0 +1,56 @@ +#+begin_src prep +#@ ``` +#@ echo 'Line 1' > /tmp/example_file.txt +#@ echo 'Line 2' >> /tmp/example_file.txt +#@ echo 'Line 3' >> /tmp/example_file.txt +#@ echo 'Line 4' >> /tmp/example_file.txt +#@ echo 'Line 5' >> /tmp/example_file.txt +#@ echo 'Line 6' >> /tmp/example_file.txt +#@ echo 'Line 7' >> /tmp/example_file.txt +#@ echo 'Line 8' >> /tmp/example_file.txt +#@ echo 'Line 9' >> /tmp/example_file.txt +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "example_file" string => "/tmp/example_file.txt"; + reports: + "First three:" + printfile => first_three("$(example_file)"); + "Last three:" + printfile => last_three("$(example_file)"); +} + +body printfile first_three(file) +{ + file_to_print => "$(file)"; + number_of_lines => "3"; +} + +body printfile last_three(file) +{ + file_to_print => "$(file)"; + number_of_lines => "-3"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: First three: +#@ R: Line 1 +#@ R: Line 2 +#@ R: Line 3 +#@ R: Last three: +#@ R: Line 7 +#@ R: Line 8 +#@ R: Line 9 +#@ ``` +#+end_src diff --git a/examples/process_kill.cf b/examples/process_kill.cf new file mode 100644 index 0000000000..dc47e9cc4c --- /dev/null +++ b/examples/process_kill.cf @@ -0,0 +1,38 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "kill_process" }; +} + + + +bundle agent kill_process +{ + processes: + + "sleep" + + signals => { "term", "kill" }; + +} diff --git a/examples/process_matching.cf b/examples/process_matching.cf new file mode 100644 index 0000000000..de43e0fa32 --- /dev/null +++ b/examples/process_matching.cf @@ -0,0 +1,69 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test processes +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + processes: + + ".*" + + process_select => proc_finder("a.*"), + process_count => up("cfservd"); +} + +######################################################## + +body process_count up(s) + +{ + match_range => "1,10"; # or irange("1","10"); + out_of_range_define => { "$(s)_out_of_control" }; +} + +######################################################## + +body process_select proc_finder(p) + +{ + process_owner => { "avahi", "bin" }; + command => "$(p)"; + pid => "100,199"; + vsize => "0,1000"; + process_result => "command.(process_owner|vsize)"; +} + + diff --git a/examples/process_matching2.cf b/examples/process_matching2.cf new file mode 100644 index 0000000000..854b28e9d6 --- /dev/null +++ b/examples/process_matching2.cf @@ -0,0 +1,61 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test processes +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + processes: + + "sleep" + + process_count => up("sleep"); + + reports: + + sleep_out_of_control:: + + "Out of control"; +} + +######################################################## + +body process_count up(s) + +{ + match_range => "5,10"; # or irange("1","10"); + out_of_range_define => { "$(s)_out_of_control" }; +} + diff --git a/examples/process_matching3.cf b/examples/process_matching3.cf new file mode 100644 index 0000000000..da4d2b686b --- /dev/null +++ b/examples/process_matching3.cf @@ -0,0 +1,66 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test processes +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + processes: + + ".*" + + process_select => proc_finder("a.*"), + process_count => up("cfservd"); +} + +######################################################## + +body process_count up(s) + +{ + match_range => "1,10"; # or irange("1","10"); + out_of_range_define => { "$(s)_out_of_control" }; +} + +######################################################## + +body process_select proc_finder(p) + +{ + stime_range => irange(ago("0","0","0","2","0","0"),now); + process_result => "stime"; +} + + diff --git a/examples/process_restart.cf b/examples/process_restart.cf new file mode 100644 index 0000000000..15f9248995 --- /dev/null +++ b/examples/process_restart.cf @@ -0,0 +1,49 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "process_restart" }; +} + +######################################################### + +bundle agent process_restart +{ + vars: + + "component" slist => { + "cf-monitord", + "cf-serverd", + "cf-execd" + }; + processes: + + "$(component)" + restart_class => canonify("start_$(component)"); + + commands: + + "/var/cfengine/bin/$(component)" + if => canonify("start_$(component)"); + +} diff --git a/examples/process_restart_basic.cf b/examples/process_restart_basic.cf new file mode 100644 index 0000000000..7afc21c693 --- /dev/null +++ b/examples/process_restart_basic.cf @@ -0,0 +1,43 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { "process_restart" }; +} + +######################################################### + +bundle agent process_restart +{ + processes: + + "/usr/bin/daemon" + restart_class => "launch"; + + commands: + + launch:: + + "/usr/bin/daemon"; + +} diff --git a/examples/process_signalling.cf b/examples/process_signalling.cf new file mode 100644 index 0000000000..7e02662254 --- /dev/null +++ b/examples/process_signalling.cf @@ -0,0 +1,69 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test process restart +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + processes: + + "cfservd" + + process_count => up("cfservd"); + + cfservd_out_of_control:: + + "cfservd" + + signals => { "stop" , "term" }, + restart_class => "start_cfserv"; + + + commands: + + start_cfserv:: + + "/usr/local/bin/cfservd"; + +} + +######################################################## + +body process_count up(s) + +{ + match_range => "1,10"; # or irange("1","10"); + out_of_range_define => { "$(s)_out_of_control" }; +} diff --git a/examples/processes_define_class_based_on_process_runtime.cf b/examples/processes_define_class_based_on_process_runtime.cf new file mode 100644 index 0000000000..8fac6ece08 --- /dev/null +++ b/examples/processes_define_class_based_on_process_runtime.cf @@ -0,0 +1,63 @@ +######################################################## +# +# Simple process example +# +# Define a class if a process has been running for more +# than one day +# +# For 3.6- run with cf-agent -KIf ./processes_define_class_based_on_process_runtime.cf -b main +# For 3.7+ run with cf-agent -KIf ./processes_define_class_based_on_process_runtime.cf +# - main is the default bundle executed if no bundlesequence is defined in +# 3.7+ +######################################################## + +bundle agent main + +{ + processes: + + "init" + process_count => any_count("booted_over_1_day_ago"), + process_select => days_older_than(1), + comment => "Define a class indicating we found an init process running + for more than 1 day."; + + reports: + + booted_over_1_day_ago:: + + "This system was booted over 1 days ago since there is an init process + that is older than 1 day."; + + !booted_over_1_day_ago:: + "This system has been rebooted recently as the init process has been + running for less than a day"; +} + +######################################################## + +######################################################## +# NOTE: These bundles were copied from the stdlib in +# order to make this a standalone policy that requires +# no external files. Because the standard library is +# constantly evolving their definition may change +######################################################## + +body process_count any_count(cl) +# @brief Define class `cl` if one or more process is running +# @param cl Name of the class to be defined +{ + match_range => "0,0"; + out_of_range_define => { "$(cl)" }; +} + +######################################################## + +body process_select days_older_than(d) +# @brief Select all processes that are older than `d` days +# @param d Days that processes need to be old to be selected +{ + stime_range => irange(ago(0,0,"$(d)",0,0,0),now); + process_result => "!stime"; +} + diff --git a/examples/product.cf b/examples/product.cf new file mode 100644 index 0000000000..d8ce18d8fc --- /dev/null +++ b/examples/product.cf @@ -0,0 +1,47 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + + "series" rlist => { "1.1", "2.2", "3.3", "5.5", "7.7" }; + + "prod" real => product("series"); + "sum" real => sum("series"); + + reports: + "Product result: $(prod) > $(sum)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Product result: 338.207100 > 19.800000 +#@ ``` +#+end_src diff --git a/examples/promises.cf b/examples/promises.cf new file mode 100644 index 0000000000..bfa7b1bae7 --- /dev/null +++ b/examples/promises.cf @@ -0,0 +1,69 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# The starting point for every configuration +# +####################################################### + +body common control + +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; + bundlesequence => { "main", "cfengine2" }; +} + +####################################################### + +bundle agent main + +{ + # do something + vars: + "sys_files" slist => { + "/etc/passwd", + "/etc/services" + }; + files: + "$(sys_files)" perms => mo("0644", "root"), + changes => detect_content; + "/etc/shadow" perms => mo("0600", "root"), + changes => detect_content; + "/usr" changes => detect_content, + depth_search => recurse("inf"); + "/tmp" delete => tidy, + file_select => days_old("2"), + depth_search => recurse("inf"); +} + +####################################################### + +bundle agent cfengine2 + +{ + commands: + + "/var/cfengine/bin/cfagent"; + + +} diff --git a/examples/quoting.cf b/examples/quoting.cf new file mode 100644 index 0000000000..e28dff0f4a --- /dev/null +++ b/examples/quoting.cf @@ -0,0 +1,34 @@ +bundle agent main +# @brief Example showing various ways of quoting in CFEngine. +{ + vars: + 'single' string => 'single quotes'; + `backtick` string => `backtick quotes`; + "double" string => "double"; + 'single_escape' string => 'You can \'escape\' single quotes'; + "double_escape" string => "You can \"escape\" double quotes"; + `backtick_escape` string => `Note: You can't escape backtick quotes`; + + reports: + "$(single)"; + `$(backtick)`; + `$(double)`; + '$(single_escape)'; + "$(double_escape)"; + `$(backtick_escape)`; +} + +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: single quotes +#@ R: backtick quotes +#@ R: double +#@ R: You can 'escape' single quotes +#@ R: You can "escape" double quotes +#@ R: Note: You can't escape backtick quotes +#@ ``` +#+end_src + + + diff --git a/examples/randomint.cf b/examples/randomint.cf new file mode 100644 index 0000000000..c33cf4c064 --- /dev/null +++ b/examples/randomint.cf @@ -0,0 +1,55 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + "low" string => "4"; + "high" string => "60"; + + "random" int => randomint($(low), $(high)); + classes: + "isabove" expression => isgreaterthan($(random), 3); + + reports: + isabove:: + "The generated random number was above 3"; + + show_random:: + "Randomly generated '$(random)'"; +} +#+end_src +############################################################################### +#+begin_src show_random_example_output +#@ ``` +#@ R: The generated random number was above 3 +#@ R: Randomly generated '9' +#@ R: Randomly generated '52' +#@ R: Randomly generated '26' +#@ ``` +#+end_src +#+begin_src example_output +#@ ``` +#@ R: The generated random number was above 3 +#@ ``` +#+end_src diff --git a/examples/read_module_protocol.cf b/examples/read_module_protocol.cf new file mode 100644 index 0000000000..bffd155757 --- /dev/null +++ b/examples/read_module_protocol.cf @@ -0,0 +1,44 @@ +bundle agent cache_maintenance +# Creates a module protocol cache, refreshes it if minute is 30-35 +{ + vars: + "file" + string => "$(this.promise_dirname)/cached_module"; + + classes: + "cache_refresh" + if => not(fileexists("$(file)")); + Min30_35:: + "cache_refresh"; + + files: + cache_refresh:: + "$(file)" + create => "true", + edit_template_string => "=my_variable=$(sys.date)", + template_data => "{}", + template_method => "inline_mustache"; +} + +bundle agent demo +# Demonstrates read_module_protocol function, prints a variable from it +{ + classes: + "cache_was_read" + if => read_module_protocol("$(cache_maintenance.file)"); + + reports: + cache_was_read:: + "Module cache was read!"; + "cached_module.my_variable = $(cached_module.my_variable)"; +} + + +bundle agent __main__ +{ + methods: + "cache_maintenance" + handle => "cache_maintenance_done"; + "demo" + depends_on => { "cache_maintenance_done" }; +} diff --git a/examples/readcsv.cf b/examples/readcsv.cf new file mode 100644 index 0000000000..e0fa7199ab --- /dev/null +++ b/examples/readcsv.cf @@ -0,0 +1,50 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo -n 1,2,3 > /tmp/csv +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent main +{ + vars: + + # note that the CSV file has to have ^M (DOS) EOL terminators + # thus the prep step uses `echo -n` and just one line, so it will work on Unix + "csv" data => readcsv("/tmp/csv"); + "csv_str" string => format("%S", csv); + + reports: + + "From /tmp/csv, got data $(csv_str)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: From /tmp/csv, got data [["1","2","3"]] +#@ ``` +#+end_src diff --git a/examples/readdata.cf b/examples/readdata.cf new file mode 100644 index 0000000000..38bc103820 --- /dev/null +++ b/examples/readdata.cf @@ -0,0 +1,64 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo -n 1,2,3 > /tmp/file.csv +#@ echo -n '{ "x": 200 }' > /tmp/file.json +#@ echo '- a' > /tmp/file.yaml +#@ echo '- b' >> /tmp/file.yaml +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent main +{ + vars: + + "csv" data => readdata("/tmp/file.csv", "auto"); # or file type "CSV" + "json" data => readdata("/tmp/file.json", "auto"); # or file type "JSON" + + "csv_str" string => format("%S", csv); + "json_str" string => format("%S", json); + + feature_yaml:: # we can only test YAML data if libyaml is compiled in + "yaml" data => readdata("/tmp/file.yaml", "auto"); # or file type "YAML" + "yaml_str" string => format("%S", yaml); + reports: + + "From /tmp/file.csv, got data $(csv_str)"; + "From /tmp/file.json, got data $(json_str)"; + feature_yaml:: + "From /tmp/file.yaml, we would get data $(yaml_str)"; + !feature_yaml:: # show the output anyway + 'From /tmp/file.yaml, we would get data ["a","b"]'; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: From /tmp/file.csv, got data [["1","2","3"]] +#@ R: From /tmp/file.json, got data {"x":200} +#@ R: From /tmp/file.yaml, we would get data ["a","b"] +#@ ``` +#+end_src diff --git a/examples/readenvfile.cf b/examples/readenvfile.cf new file mode 100644 index 0000000000..efaf2f4651 --- /dev/null +++ b/examples/readenvfile.cf @@ -0,0 +1,89 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo 'PRETTY_NAME="Ubuntu 14.04.5 LTS"' > /tmp/os-release +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +# Definitions (from standard library): +body edit_defaults empty +{ + empty_file_before_editing => "true"; + edit_backup => "false"; +} + +bundle edit_line insert_lines(lines) +{ + insert_lines: + "$(lines)"; +} + +body printfile cat(file) +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} +# empty, insert_lines() and cat() are part of the standard library +# To import the standard library, remove them and uncomment this line: +# body file control { inputs => { "$(sys.libdir)/stdlib.cf" }; } + +bundle agent main +{ + classes: + "file_found" expression => fileexists("/tmp/os-release"); + + # Use readenvfile() to load /tmp/os-release, then convert to json: + vars: + file_found:: + "envdata" + data => readenvfile("/tmp/os-release"); + "jsonstring" + string => storejson(envdata); + + # Print input(os-release) and output(json) files: + reports: + file_found:: + "/tmp/os-release :" + printfile => cat("/tmp/os-release"); + "/tmp/os-release converted to json:"; + "$(jsonstring)"; + "(The data for this system is available in sys.os_release)"; + !file_found:: + "/tmp/os-release doesn't exist, run this command:"; + "echo 'PRETTY_NAME=\"Ubuntu 14.04.5 LTS\"' > /tmp/os-release"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /tmp/os-release : +#@ R: PRETTY_NAME="Ubuntu 14.04.5 LTS" +#@ R: /tmp/os-release converted to json: +#@ R: { +#@ "PRETTY_NAME": "Ubuntu 14.04.5 LTS" +#@ } +#@ R: (The data for this system is available in sys.os_release) +#@ ``` +#+end_src diff --git a/examples/readfile.cf b/examples/readfile.cf new file mode 100644 index 0000000000..ca3a040e28 --- /dev/null +++ b/examples/readfile.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo alpha > /tmp/cfe_hostlist +#@ echo beta >> /tmp/cfe_hostlist +#@ echo gamma >> /tmp/cfe_hostlist +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "xxx" + string => readfile( "/tmp/cfe_hostlist" , "5" ); + reports: + "first 5 characters of /tmp/cfe_hostlist: $(xxx)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: first 5 characters of /tmp/cfe_hostlist: alpha +#@ ``` +#+end_src diff --git a/examples/readintarray.cf b/examples/readintarray.cf new file mode 100644 index 0000000000..857063bda3 --- /dev/null +++ b/examples/readintarray.cf @@ -0,0 +1,86 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo 1: 5:7:21:13 > /tmp/readintarray.txt +#@ echo 2:19:8:14:14 >> /tmp/readintarray.txt +#@ echo 3:45:1:78:22 >> /tmp/readintarray.txt +#@ echo 4:64:2:98:99 >> /tmp/readintarray.txt +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent main + +{ + vars: + + "lines" int => readintarray("array_name", + "/tmp/readintarray.txt", + "#[^\n]*", + ":", + 10, + 4000); + + reports: + "array_name contains $(lines) keys$(const.n)$(with)" + with => string_mustache("{{%-top-}}", "array_name"); + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: array_name contains 4 keys +#@ { +#@ "1": { +#@ "0": "1", +#@ "1": "5", +#@ "2": "7", +#@ "3": "21", +#@ "4": "13" +#@ }, +#@ "2": { +#@ "0": "2", +#@ "1": "19", +#@ "2": "8", +#@ "3": "14", +#@ "4": "14" +#@ }, +#@ "3": { +#@ "0": "3", +#@ "1": "45", +#@ "2": "1", +#@ "3": "78", +#@ "4": "22" +#@ }, +#@ "4": { +#@ "0": "4", +#@ "1": "64", +#@ "2": "2", +#@ "3": "98", +#@ "4": "99" +#@ } +#@ } +#@ ``` +#+end_src diff --git a/examples/readintlist.cf b/examples/readintlist.cf new file mode 100644 index 0000000000..5e38cc9eba --- /dev/null +++ b/examples/readintlist.cf @@ -0,0 +1,34 @@ +#+begin_src prep +#@ ``` +#@ printf "one\ntwo\nthree\n" > /tmp/list.txt +#@ printf "1\n2\n3\n" >> /tmp/list.txt +#@ printf "1.0\n2.0\n3.0" >> /tmp/list.txt +#@ ``` +#+end_src +#+begin_src cfengine3 +bundle agent example_readintlist +# @brief Example showing function return types +{ + vars: + "my_list_of_integers" + ilist => readintlist( "/tmp/list.txt", # File to read + "^(\D+)|(\d+[^\n]+)", # Ignore any lines that are not integers + "\n", # Split on newlines + inf, # Maximum number of entries + inf); # Maximum number of bytes to read + + reports: + "my_list_of_integers includes '$(my_list_of_integers)'"; +} +bundle agent __main__ +{ + methods: "example_readintlist"; +} +#+end_src +#+begin_src example_output +#@ ``` +#@ R: my_list_of_integers includes '1' +#@ R: my_list_of_integers includes '2' +#@ R: my_list_of_integers includes '3' +#@ ``` +#+end_src diff --git a/examples/readintrealstringlist.cf b/examples/readintrealstringlist.cf new file mode 100644 index 0000000000..d3456be414 --- /dev/null +++ b/examples/readintrealstringlist.cf @@ -0,0 +1,78 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo 1 > /tmp/cfe_list_ints +#@ echo # Comment >> /tmp/cfe_list_ints +#@ echo 2 >> /tmp/cfe_list_ints +#@ echo # Another Comment >> /tmp/cfe_list_ints +#@ echo 3 >> /tmp/cfe_list_ints +#@ echo 1.1 > /tmp/cfe_list_reals +#@ echo # Comment >> /tmp/cfe_list_reals +#@ echo 2.2 >> /tmp/cfe_list_reals +#@ echo # Another Comment >> /tmp/cfe_list_reals +#@ echo 3 >> /tmp/cfe_list_reals +#@ echo alpha > /tmp/cfe_list_strings +#@ echo # Comment >> /tmp/cfe_list_strings +#@ echo beta >> /tmp/cfe_list_strings +#@ echo # Another Comment >> /tmp/cfe_list_strings +#@ echo gamma >> /tmp/cfe_list_strings +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "integers" ilist => readintlist("/tmp/cfe_list_ints","#[^\n]*","[\n]",10,400); + "strings" slist => readstringlist("/tmp/cfe_list_strings", "#[^\n]*", "\s", 10, 400); + "reals" rlist => readreallist("/tmp/cfe_list_reals","#[^\n]*","[\n]",10,400); + + reports: + + "integers in /tmp/cfe_list_ints: $(integers)"; + "strings in /tmp/cfe_list_strings: $(strings)"; + "reals in /tmp/cfe_list_reals: $(reals)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: integers in /tmp/cfe_list_ints: 1 +#@ R: integers in /tmp/cfe_list_ints: 2 +#@ R: integers in /tmp/cfe_list_ints: 3 +#@ R: strings in /tmp/cfe_list_strings: alpha +#@ R: strings in /tmp/cfe_list_strings: beta +#@ R: strings in /tmp/cfe_list_strings: gamma +#@ R: reals in /tmp/cfe_list_reals: 1.1 +#@ R: reals in /tmp/cfe_list_reals: 2.2 +#@ R: reals in /tmp/cfe_list_reals: 3 +#@ ``` +#+end_src diff --git a/examples/readlist.cf b/examples/readlist.cf new file mode 100644 index 0000000000..6cb581bd1c --- /dev/null +++ b/examples/readlist.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "mylist" ilist => { readintlist("/tmp/listofint","#.*","[\n]",10,400) }; + + reports: + + "List entry: $(mylist)"; + +} + diff --git a/examples/readrealarray.cf b/examples/readrealarray.cf new file mode 100644 index 0000000000..0191f294b6 --- /dev/null +++ b/examples/readrealarray.cf @@ -0,0 +1,85 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo "1: 5.0:7:21:13" > /tmp/readrealarray.txt +#@ echo "2:19:8.1:14:14" >> /tmp/readrealarray.txt +#@ echo "3:45:1:78.2:22" >> /tmp/readrealarray.txt +#@ echo "4:64:2:98:99.3" >> /tmp/readrealarray.txt +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent main +{ + vars: + + "lines" int => readrealarray("array_name", + "/tmp/readrealarray.txt", + "#[^\n]*", + ":", + 10, + 4000); + + reports: + "array_name contains $(lines) keys$(const.n)$(with)" + with => string_mustache("{{%-top-}}", "array_name"); + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: array_name contains 4 keys +#@ { +#@ "1": { +#@ "0": "1", +#@ "1": " 5.0", +#@ "2": "7", +#@ "3": "21", +#@ "4": "13" +#@ }, +#@ "2": { +#@ "0": "2", +#@ "1": "19", +#@ "2": "8.1", +#@ "3": "14", +#@ "4": "14" +#@ }, +#@ "3": { +#@ "0": "3", +#@ "1": "45", +#@ "2": "1", +#@ "3": "78.2", +#@ "4": "22" +#@ }, +#@ "4": { +#@ "0": "4", +#@ "1": "64", +#@ "2": "2", +#@ "3": "98", +#@ "4": "99.3" +#@ } +#@ } +#@ ``` +#+end_src diff --git a/examples/readreallist.cf b/examples/readreallist.cf new file mode 100644 index 0000000000..1171026c02 --- /dev/null +++ b/examples/readreallist.cf @@ -0,0 +1,37 @@ +#+begin_src prep +#@ ``` +#@ printf "one\ntwo\nthree\n" > /tmp/list.txt +#@ printf "1\n2\n3\n" >> /tmp/list.txt +#@ printf "1.0\n2.0\n3.0" >> /tmp/list.txt +#@ ``` +#+end_src +#+begin_src cfengine3 +bundle agent example_readreallist +# @brief Example showing function return types +{ + vars: + "my_list_of_reals" + rlist => readreallist( "/tmp/list.txt", # File to read + "^(\D+)", # Ignore any non-digits + "\n", # Split on newlines + inf, # Maximum number of entries + inf ); # Maximum number of bytes to read + + reports: + "my_list_of_reals includes '$(my_list_of_reals)'"; +} +bundle agent __main__ +{ + methods: "example_readreallist"; +} +#+end_src +#+begin_src example_output +#@ ``` +#@ R: my_list_of_reals includes '1' +#@ R: my_list_of_reals includes '2' +#@ R: my_list_of_reals includes '3' +#@ R: my_list_of_reals includes '1.0' +#@ R: my_list_of_reals includes '2.0' +#@ R: my_list_of_reals includes '3.0' +#@ ``` +#+end_src diff --git a/examples/readstringarray.cf b/examples/readstringarray.cf new file mode 100644 index 0000000000..36ab7745c8 --- /dev/null +++ b/examples/readstringarray.cf @@ -0,0 +1,104 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo "at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash" > /tmp/readstringarray.txt +#@ echo "avahi:x:103:105:User for Avahi:/var/run/avahi-daemon:/bin/false # Disallow login" >> /tmp/readstringarray.txt +#@ echo "beagleindex:x:104:106:User for Beagle indexing:/var/cache/beagle:/bin/bash" >> /tmp/readstringarray.txt +#@ echo "bin:x:1:1:bin:/bin:/bin/bash" >> /tmp/readstringarray.txt +#@ echo "# Daemon has the default shell" >> /tmp/readstringarray.txt +#@ echo "daemon:x:2:2:Daemon:/sbin:" >> /tmp/readstringarray.txt +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +bundle agent main +{ + vars: + + "lines" int => readstringarray("array_name", + "/tmp/readstringarray.txt", + "#[^\n]*", + ":", + 10, + 4000); + + reports: + "array_name contains $(lines) keys$(const.n)$(with)" + with => string_mustache("{{%-top-}}", "array_name"); + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: array_name contains 5 keys +#@ { +#@ "at": { +#@ "0": "at", +#@ "1": "x", +#@ "2": "25", +#@ "3": "25", +#@ "4": "Batch jobs daemon", +#@ "5": "/var/spool/atjobs", +#@ "6": "/bin/bash" +#@ }, +#@ "avahi": { +#@ "0": "avahi", +#@ "1": "x", +#@ "2": "103", +#@ "3": "105", +#@ "4": "User for Avahi", +#@ "5": "/var/run/avahi-daemon", +#@ "6": "/bin/false " +#@ }, +#@ "beagleindex": { +#@ "0": "beagleindex", +#@ "1": "x", +#@ "2": "104", +#@ "3": "106", +#@ "4": "User for Beagle indexing", +#@ "5": "/var/cache/beagle", +#@ "6": "/bin/bash" +#@ }, +#@ "bin": { +#@ "0": "bin", +#@ "1": "x", +#@ "2": "1", +#@ "3": "1", +#@ "4": "bin", +#@ "5": "/bin", +#@ "6": "/bin/bash" +#@ }, +#@ "daemon": { +#@ "0": "daemon", +#@ "1": "x", +#@ "2": "2", +#@ "3": "2", +#@ "4": "Daemon", +#@ "5": "/sbin", +#@ "6": "" +#@ } +#@ } +#@ ``` +#+end_src diff --git a/examples/readstringlist.cf b/examples/readstringlist.cf new file mode 100644 index 0000000000..8a4c2fefb9 --- /dev/null +++ b/examples/readstringlist.cf @@ -0,0 +1,44 @@ +#+begin_src prep +#@ ``` +#@ printf "one\ntwo\nthree\n" > /tmp/list.txt +#@ printf " # commented line\n" >> /tmp/list.txt +#@ printf "1\n2\n3\n" >> /tmp/list.txt +#@ printf "# another commented line\n" >> /tmp/list.txt +#@ printf "Not a commented # line\n" >> /tmp/list.txt +#@ printf "1.0\n2.0\n3.0" >> /tmp/list.txt +#@ ``` +#+end_src +#+begin_src cfengine3 +bundle agent example_readstringlist +# @brief Example showing readstringlist() +{ + vars: + "my_list_of_strings" + slist => readstringlist( "/tmp/list.txt", # File to read + "^\s*#[^\n]*", # Exclude hash comment lines lines + "\n", # Split on newlines + inf, # Maximum number of entries + inf); # Maximum number of bytes to read + + reports: + "my_list_of_strings includes '$(my_list_of_strings)'"; +} +bundle agent __main__ +{ + methods: "example_readstringlist"; +} +#+end_src +#+begin_src example_output +#@ ``` +#@ R: my_list_of_strings includes 'one' +#@ R: my_list_of_strings includes 'two' +#@ R: my_list_of_strings includes 'three' +#@ R: my_list_of_strings includes '1' +#@ R: my_list_of_strings includes '2' +#@ R: my_list_of_strings includes '3' +#@ R: my_list_of_strings includes 'Not a commented # line' +#@ R: my_list_of_strings includes '1.0' +#@ R: my_list_of_strings includes '2.0' +#@ R: my_list_of_strings includes '3.0' +#@ ``` +#+end_src diff --git a/examples/readtcp.cf b/examples/readtcp.cf new file mode 100644 index 0000000000..c0da53d756 --- /dev/null +++ b/examples/readtcp.cf @@ -0,0 +1,49 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "my80" string => readtcp("myserver.com","80","GET /index.html HTTP/1.1$(const.r)$(const.n)Host: myserver.com$(const.r)$(const.n)$(const.r)$(const.n)",20); + + classes: + + "server_ok" expression => regcmp("[^\n]*200 OK.*\n.*","$(my80)"); + + reports: + + server_ok:: + + "Server is alive"; + + !server_ok:: + + "Server is not responding - got $(my80)"; +} +#+end_src diff --git a/examples/reference_values_inside_data.cf b/examples/reference_values_inside_data.cf new file mode 100755 index 0000000000..9ea7507b55 --- /dev/null +++ b/examples/reference_values_inside_data.cf @@ -0,0 +1,34 @@ +#!/var/cfengine/bin/cf-agent -f- +#+begin_src cfengine3 +bundle agent example_reference_values_inside_data +{ + vars: + + "data" data => '{ + "Key1": "Value1", + "Key2": "Value2", + "Key3": [ + "Value3", + "Value4" + ] +}'; + + reports: + "Key1 contains '$(data[Key1])'"; + "Key2 contains '$(data[Key2])'"; + "Key3 iterates and contains '$(data[Key3])'"; +} +bundle agent __main__ +{ + methods: "example_reference_values_inside_data"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Key1 contains 'Value1' +#@ R: Key2 contains 'Value2' +#@ R: Key3 iterates and contains 'Value3' +#@ R: Key3 iterates and contains 'Value4' +#@ ``` +#+end_src diff --git a/examples/reg_multiline.cf b/examples/reg_multiline.cf new file mode 100644 index 0000000000..3a3bd571f3 --- /dev/null +++ b/examples/reg_multiline.cf @@ -0,0 +1,30 @@ +############################################# +# Matching multiple lines in regexp (PCRE) +############################################## + +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "data" string => "Something boring is + written + up here. This is the interesting + part: KWYDK (know what you don't know)"; + + classes: + + # note the (?s) at the beginning of the pattern - this ensures newlines are matched by . + + "matched" expression => regextract("(?s).*: (.*)", "$(data)", "interesting"); + + reports: + matched:: + "This is interesting: \"$(interesting[1])\""; + + !matched:: + "Nothing was interesting!"; +} diff --git a/examples/regarray.cf b/examples/regarray.cf new file mode 100644 index 0000000000..d1e4953b06 --- /dev/null +++ b/examples/regarray.cf @@ -0,0 +1,58 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "myarray[0]" string => "bla1"; + "myarray[1]" string => "bla2"; + "myarray[3]" string => "bla"; + + classes: + + "ok" expression => regarray("myarray","b.*2"); + + reports: + + ok:: + + "Found in list"; + + !ok:: + + "Not found in list"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Found in list +#@ ``` +#+end_src diff --git a/examples/regcmp.cf b/examples/regcmp.cf new file mode 100644 index 0000000000..87988028ba --- /dev/null +++ b/examples/regcmp.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { subtest("mark") }; +} + +bundle agent subtest(user) +{ + classes: + + "invalid" not => regcmp("[a-z]{4}","$(user)"); + + reports: + + !invalid:: + + "User name $(user) is valid at exactly 4 letters"; + + invalid:: + + "User name $(user) is invalid"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: User name mark is valid at exactly 4 letters +#@ ``` +#+end_src diff --git a/examples/regex_replace.cf b/examples/regex_replace.cf new file mode 100644 index 0000000000..48fb79b6fc --- /dev/null +++ b/examples/regex_replace.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + # global regex replace A with B + "AB" string => regex_replace("This has AAA rating", "A", "B", "g"); + # global regex replace [Aa] with B (case insensitive) + "AaB" string => regex_replace("This has AAA rating", "A", "B", "gi"); + # global replace every three characters with [cap=thecharacters] using $1 + "cap123" string => regex_replace("abcdefghijklmn", "(...)", "[cap=$1]", "g"); + # multiple captures using \1 \2 (just like $1 $2 but can only go up to \9) + "path_breakdown" string => regex_replace("/a/b/c/example.txt", "(.+)/(.+)", "dirname = \1 file basename = \2", ""); + + reports: + # in order, the above... + "AB replacement = '$(AB)'"; + "AaB replacement = '$(AaB)'"; + "cap123 replacement = '$(cap123)'"; + "path_breakdown replacement = '$(path_breakdown)'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: AB replacement = 'This has BBB rating' +#@ R: AaB replacement = 'This hBs BBB rBting' +#@ R: cap123 replacement = '[cap=abc][cap=def][cap=ghi][cap=jkl]mn' +#@ R: path_breakdown replacement = 'dirname = /a/b/c file basename = example.txt' +#@ ``` +#+end_src diff --git a/examples/regex_win.cf b/examples/regex_win.cf new file mode 100644 index 0000000000..e68f8c79f8 --- /dev/null +++ b/examples/regex_win.cf @@ -0,0 +1,26 @@ +###################################################################### +# Using path regular expressions on Windows (always forward slash) +###################################################################### + +body common control +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example +{ + files: + "c:/test/.*.xml" + edit_line => add_information(); +} + +######################################################## + +bundle edit_line add_information() +{ + insert_lines: + "a line" + comment => "adding line"; +} diff --git a/examples/regextract.cf b/examples/regextract.cf new file mode 100644 index 0000000000..ada8232f5a --- /dev/null +++ b/examples/regextract.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + # Extract regex backreferences and put them in an array + + "ok" expression => regextract( + "xx ([^\s]+) ([^\s]+).* xx", + "xx one two three four xx", + "myarray" + ); + reports: + + ok:: + + "ok - \"$(myarray[0])\" = xx + \"$(myarray[1])\" + \"$(myarray[2])\" + .. + xx"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: ok - "xx one two three four xx" = xx + "one" + "two" + .. + xx +#@ ``` +#+end_src diff --git a/examples/registry.cf b/examples/registry.cf new file mode 100644 index 0000000000..ebda7ba8a3 --- /dev/null +++ b/examples/registry.cf @@ -0,0 +1,74 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + + +body common control +{ + + bundlesequence => { "databases" }; +} + + +bundle agent databases + +{ + databases: + + windows:: + + # Regsitry has (value,data) pairs in "keys" which are directories + + # "HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS" + + # database_operation => "create", + # database_type => "ms_registry"; + + # "HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS\Cfengine" + + # database_operation => "create", + # database_rows => { "value1,REG_SZ,new value 1", "value2,REG_SZ,new val 2"} , + # database_type => "ms_registry"; + + + "HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS\Cfengine" + + database_operation => "delete", + database_columns => { "value1", "value2" } , + database_type => "ms_registry"; + + + # "HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS\Cfengine" + + # database_operation => "cache", # cache,restore + + # registry_exclude => { ".*Windows.*CurrentVersion.*", ".*Touchpad.*", ".*Capabilities.FileAssociations.*", ".*Rfc1766.*" , ".*Synaptics.SynTP.*", ".*SupportedDevices.*8086", ".*Microsoft.*ErrorThresholds" }, + + # database_type => "ms_registry"; + + "HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS" + + database_operation => "restore", + database_type => "ms_registry"; + +} diff --git a/examples/registry_cache.cf b/examples/registry_cache.cf new file mode 100644 index 0000000000..729341d0cc --- /dev/null +++ b/examples/registry_cache.cf @@ -0,0 +1,53 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => { + # "registry_cache" + # "registry_restore" + }; +} + +######################################### + +bundle agent registry_cache +{ + databases: + windows:: + + "HKEY_LOCAL_MACHINE\SOFTWARE\Adobe" + database_operation => "cache", + database_type => "ms_registry", + comment => "Save correct registry settings for Adobe products"; +} + +######################################### + +bundle agent registry_restore +{ + databases: + windows:: + + "HKEY_LOCAL_MACHINE\SOFTWARE\Adobe" + database_operation => "restore", + database_type => "ms_registry", + comment => "Make sure Adobe products have correct registry settings"; +} diff --git a/examples/registryvalue.cf b/examples/registryvalue.cf new file mode 100644 index 0000000000..685600459b --- /dev/null +++ b/examples/registryvalue.cf @@ -0,0 +1,47 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "reg" }; +} + +bundle agent reg +{ + vars: + windows:: + "value" string => registryvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS\CFEngine","value3"); + !windows:: + "value" string => "Sorry, no registry data is available"; + + reports: + "Value extracted: $(value)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Value extracted: Sorry, no registry data is available +#@ ``` +#+end_src diff --git a/examples/regline.cf b/examples/regline.cf new file mode 100644 index 0000000000..2c91c067f8 --- /dev/null +++ b/examples/regline.cf @@ -0,0 +1,66 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# Function regline(regex,file) +###################################################################### + +#+begin_src cfengine3 +bundle agent main +# @brief An example showing how to use regline to see if a pattern exists within +# a file. +{ + vars: + + linux:: + + "file" string => "/proc/sys/net/ipv4/ip_forward"; + "reg_enabled" string => "^1$"; + "reg_disabled" string => "^0$"; + + classes: + + linux:: + + "ipv4_forwarding_enabled" -> { "SecOps" } + expression => regline( $(reg_enabled) , $(file) ), + comment => "We want to know if ip forwarding is enabled because it is a + potential security issue."; + + "ipv4_forwarding_disabled" -> { "SecOps" } + expression => regline( $(reg_disabled) , $(file) ); + + reports: + + ipv4_forwarding_enabled:: + "I found that IPv4 forwarding is enabled!"; + + ipv4_forwarding_disabled:: + "I found that IPv4 forwarding is disabled."; +} +#+end_src + +#+begin_src random_example_output +#@ ``` +#@ R: I found that IPv4 forwarding is disabled. +#@ ``` +#+end_src diff --git a/examples/reglist.cf b/examples/reglist.cf new file mode 100644 index 0000000000..5090c6dcf7 --- /dev/null +++ b/examples/reglist.cf @@ -0,0 +1,54 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "nameservers" slist => { + "128.39.89.10", + "128.39.74.16", + "192.168.1.103", + "10.132.51.66" + }; + classes: + + "am_name_server" expression => reglist(@(nameservers), "127\.0\.0\.1"); + reports: + am_name_server:: + "127.0.0.1 is currently set as a nameserver"; + !am_name_server:: + "127.0.0.1 is NOT currently set as a nameserver"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: 127.0.0.1 is NOT currently set as a nameserver +#@ ``` +#+end_src diff --git a/examples/remake_outputs.pl b/examples/remake_outputs.pl new file mode 100755 index 0000000000..421bea6270 --- /dev/null +++ b/examples/remake_outputs.pl @@ -0,0 +1,241 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Data::Dumper; +use Getopt::Long; +use File::Basename; +use Sys::Hostname; + +$|=1; # autoflush + +my %options = ( + check => 0, + verbose => 0, + veryverbose => 0, + help => 0, + cfagent => "../cf-agent/cf-agent", + workdir => "/tmp", + ); + +die ("Unknown options") unless GetOptions(\%options, + "help|h!", + "check|c!", + "cfagent=s", + "workdir=s", + "verbose|v!", + "veryverbose!", + ); + +if ($options{help}) +{ + print <; + close $fh; + my $copy = $data; + if ($data =~ m/#\+begin_src cfengine3\n(.+?)\n#\+end_src/s) + { + my $example = $1; + + my $prep; + if ($data =~ m/#\+begin_src prep\n(.+?)\n#\+end_src/s) + { + $prep = [split "\n", $1]; + } + + $data =~ s/(#\+begin_src example_output( no_check)?\n)(.*?)(#\+end_src)/$1 . rewrite_output($file, $prep, $example, $3) . $4/es; + if (!defined($2) && $data ne $copy) + { + print "$file: output differs from original..."; + if ($options{check}) + { + $rc = 1; + print "\n"; + next; + } + + open my $fh, '>', $file or warn "Could not open $file: $!"; + print $fh $data; + close $fh; + print "new output written!\n"; + } + } + else + { + warn "No example to run was found in $file, skipping"; + } +} + +exit $rc; + +sub rewrite_output +{ + my $file = shift @_; + my $prep = shift @_; + my $example = shift @_; + my $old_output = shift @_; + my $new_output = run_example($file, $prep, $example); + + if (equal_outputs($old_output, $new_output, $file)) + { + return $old_output; + } + + if (defined $new_output && length $new_output > 0) + { + $new_output =~ s/^/#@ /mg; + $new_output = "#@ ```\n$new_output#@ ```\n"; + } + + return $new_output; +} + +sub equal_outputs +{ + # strip out date, e.g. '2013-12-16T20:48:24+0200' + my $x = shift @_; + my $y = shift @_; + my $file = shift @_; + + my ($tempfile, $base) = get_tempfile($file); + + $x =~ s/^#@ ```\s+//mg; + $y =~ s/^#@ ```\s+//mg; + + $x =~ s/^(#@ )//mg; + $x =~ s/^[-0-9T:+]+\s+//mg; + $y =~ s/^(#@ )//mg; + $y =~ s/^[-0-9T:+]+\s+//mg; + + $x =~ s/.*RANDOM.*//mg; + $y =~ s/.*RANDOM.*//mg; + + # strip leading blanks, for example from " error:" + $x =~ s/^ *//mg; + $y =~ s/^ *//mg; + + if ($x ne $y) + { + open my $fha, '>', "$tempfile.a" or die "Could not write to diff output $tempfile.a: $!"; + print $fha $x; + close $fha; + + open my $fhb, '>', "$tempfile.b" or die "Could not write to diff output $tempfile.b: $!"; + print $fhb $y; + close $fhb; + + system("diff -u $tempfile.a $tempfile.b") if $options{verbose}; + return 0; + } + + return 1; +} + +sub get_tempfile +{ + my $file = shift @_; + + my $base = basename($file); + my $tempfile = "$options{workdir}/$base"; + mkdir $options{workdir} unless -e $options{workdir}; + + return ($tempfile, $base); +} + +sub run_example +{ + my $file = shift @_; + my $prep = shift @_ || []; + my $example = shift @_; + + my ($tempfile, $base) = get_tempfile($file); + open my $fh, '>', $tempfile or die "Could not write to $tempfile: $!"; + print $fh $example; + close $fh; + chmod 0600, $tempfile; + + foreach (@$prep) + { + s/^#@ //; + # skip Markdown markup like ``` + next unless m/^\w/; + s/FILE/$tempfile/g; + s/\$HOSTNAME/hostname()/ge; + print "processing $file: Running prep '$_'" + if $options{verbose}; + system($_); + } + + $ENV{EXAMPLE} = $base; + $ENV{CFENGINE_COLOR} = 0; + my $cmd = "$options{cfagent} -D_cfe_output_testing -Kf $tempfile 2>&1"; + open my $ofh, '-|', $cmd; + my $output = join '', <$ofh>; + close $ofh; + + print "Test file: $file\nCommand: $cmd\n\nNEW OUTPUT: [[[$output]]]\n\n\n" + if $options{verbose}; + + return $output; +} diff --git a/examples/remoteclasses.cf b/examples/remoteclasses.cf new file mode 100644 index 0000000000..7e2b2f9280 --- /dev/null +++ b/examples/remoteclasses.cf @@ -0,0 +1,175 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +######################################################## +# +# Remote classes from server connection to cfServer +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-d2] +# cf-agent -f runtest_2.cf +# +# Notice that the same file configures all parts of cfengine + +######################################################## + +body common control + +{ + bundlesequence => { job_chain("Hr16|Hr17") }; + + version => "1.2.3"; +} + +######################################################## + +bundle common g +{ + vars: + + # Signals are in scope of promiser and promisee + + "signal" string => "pack_a_name"; + +} + +######################################################## + +bundle agent job_chain(time) + +{ + vars: + + "client" string => "localhost"; + "server" string => "localhost"; + "margin" string => "5"; # mins deadtime + + classes: + + "client_primed" expression => classmatch(canonify("$(client)")), + if => "$(time)"; + + "server_primed" expression => classmatch(canonify("$(server)")), + if => "$(time)"; + + client_primed:: + + "succeeded" expression => remoteclassesmatching("$(g.signal)","$(server)","yes","myprefix"); + + # + # Now the job itself + # + + methods: + + client_primed:: + + "downstream" usebundle => do_job("Starting local follow-up job"), + action => if_elapsed("$(margin)"), + if => "myprefix_$(g.signal)"; + + server_primed:: + + "upstream" usebundle => do_job("Starting remote job"), + action => if_elapsed("$(margin)"), + classes => signal_repaired("$(g.signal)"); + + reports: + + !succeeded:: + + "Server communication failed" + + if => "$(time)"; + + + "Job completed on the server..." + + if => "$(g.signal)"; + +} + +######################################################### + +bundle agent do_job(job) +{ + commands: + + # do whatever... + + "/bin/echo $(job)"; +} + + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowusers => { "mark" }; +} + +######################################################### + +bundle server access_rules() + +{ + vars: + + "localvar" string => "literal string"; + + access: + + "$(g.signal)" + + handle => "test_class_signal", + resource_type => "context", + admit => { "127.0.0.1" }; +} + + +######################################################### +# Standard library +######################################################### + +body action if_elapsed(x) +{ + ifelapsed => "$(x)"; +} + +######################################################### + +body classes signal_repaired(x) +{ + promise_repaired => { "$(x)" }; + persist_time => "10"; +} diff --git a/examples/remoteclasses2.cf b/examples/remoteclasses2.cf new file mode 100644 index 0000000000..65b51bfa37 --- /dev/null +++ b/examples/remoteclasses2.cf @@ -0,0 +1,64 @@ +# + +body common control +{ + bundlesequence => { "overture" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +####################################################### + +bundle agent overture +{ + classes: + "extended_context" + expression => remoteclassesmatching(".*did.*","127.0.0.1","yes","got"); + + files: + + "/etc/passwd" + create => "true", + classes => set_outcome_classes; + + + reports: + + got_did_task_one:: + "task 1 complete"; + + extended_context.got_did_task_two:: + "task 2 complete"; + + extended_context.got_did_task_three:: + "task 3 complete"; + +} + + +body classes set_outcome_classes +{ + promise_kept => { "did_task_one","did_task_two", "did_task_three" }; + promise_repaired => { "did_task_one","did_task_two", "did_task_three" }; + #cancel_kept => { "did_task_one" }; + persist_time => "10"; +} + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1",}; + allowallconnects => { "127.0.0.1" , "::1", }; + trustkeysfrom => { "127.0.0.1" , "::1",}; +} + + + +bundle server access_rules() +{ + access: + + "did.*" + resource_type => "context", + admit => { "127.0.0.1" }; + +} diff --git a/examples/remotescalar.cf b/examples/remotescalar.cf new file mode 100644 index 0000000000..3acdf1ac51 --- /dev/null +++ b/examples/remotescalar.cf @@ -0,0 +1,89 @@ + +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################################## +# +# Testing comms for remote orchestration +# +######################################################################## + +body common control +{ + bundlesequence => { "overture" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1",}; + allowallconnects => { "127.0.0.1" , "::1", }; + trustkeysfrom => { "127.0.0.1" , "::1",}; +} + +####################################################### + +bundle agent overture +{ + vars: + + "remote" string => remotescalar("test_scalar","127.0.0.1","yes"); + + "know" string => hubknowledge("test_scalar"); + + "count_getty" string => hubknowledge("count_getty"); + + processes: + + # Use the enumerated library body to count hosts running getty + + "getty" + + comment => "Count this host if a job is matched", + classes => enumerate("count_getty"); + + reports: + "GOT remote scalar $(remote)"; + "GOT knowedge scalar $(know)"; + "GOT persistent scalar $(xyz)"; + +} + +####################################################### + +bundle server access_rules() +{ + access: + + "value of my test_scalar, can expand variables here - $(sys.host)" + handle => "test_scalar", + comment => "Grant access to contents of test_scalar VAR", + resource_type => "literal", + admit => { "127.0.0.1" }; + + "XYZ" + resource_type => "variable", + handle => "XYZ", + admit => { "127.0.0.1" }; + +} diff --git a/examples/remove_deadlinks.cf b/examples/remove_deadlinks.cf new file mode 100644 index 0000000000..abc1d8aac6 --- /dev/null +++ b/examples/remove_deadlinks.cf @@ -0,0 +1,74 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Test dead link removal +# +####################################################### + +body common control + +{ + bundlesequence => { "example" }; +} + + +############################################ + +bundle agent example + +{ + files: + + "/home/mark/tmp/test_to" -> "someone" + + depth_search => recurse("inf"), + perms => modestuff, + action => tell_me; + +} + +############################################ + +body depth_search recurse(d) + +{ + rmdeadlinks => "true"; + depth => "$(d)"; +} + +############################################ + +body perms modestuff + +{ + mode => "o-w"; +} + +############################################ + +body action tell_me + +{ + report_level => "inform"; +} diff --git a/examples/rename.cf b/examples/rename.cf new file mode 100644 index 0000000000..2d0e4ad644 --- /dev/null +++ b/examples/rename.cf @@ -0,0 +1,42 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => {"test"}; +} + +bundle agent example +{ + files: + + "/tmp/foo" + + rename => moveit; + +} + +body rename moveit +{ + newname => "/tmp/foo_moved"; +} diff --git a/examples/repairedcommand.cf b/examples/repairedcommand.cf new file mode 100644 index 0000000000..cdaedd009f --- /dev/null +++ b/examples/repairedcommand.cf @@ -0,0 +1,24 @@ +body common control +{ + bundlesequence => { "cmdtest" }; +} + + +bundle agent cmdtest +{ + commands: + "/bin/false" + classes => example; + + reports: + wasrepaired:: + "The command-promise got repaired!"; +} + + + +body classes example +{ + repaired_returncodes => { "0", "1" }; + promise_repaired => { "wasrepaired" }; +} diff --git a/examples/report_custom.cf b/examples/report_custom.cf new file mode 100644 index 0000000000..0dfd335476 --- /dev/null +++ b/examples/report_custom.cf @@ -0,0 +1,70 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + + +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + + "software" slist => { "gpg", "zip", "rsync" }; + + classes: + + "noreport" expression => fileexists("/tmp/report.html"); + "have_$(software)" expression => fileexists("/usr/bin/$(software)"); + + reports: + + no_report:: + + " + + Name of this host is: $(sys.host)
    + Type of this host is: $(sys.os)
    + " + + report_to_file => "/tmp/report.html"; + + # + + " + Host has software $(software)
    + " + + if => "have_$(software)", + report_to_file => "/tmp/report.html"; + + # + + " + + " + report_to_file => "/tmp/report.html"; + +} diff --git a/examples/report_state.cf b/examples/report_state.cf new file mode 100644 index 0000000000..c0afd959ce --- /dev/null +++ b/examples/report_state.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "report" }; +} + +########################################################### + +bundle agent report + +{ + reports: + "/etc/passwd except $(const.n)" + + printfile => pr("/etc/passwd","5"), + + showstate => { "otherprocs", "rootprocs" }; + +} + +###################################################################### + +body printfile pr(file,lines) + +{ + file_to_print => "$(file)"; + number_of_lines => "$(lines)"; +} diff --git a/examples/reports.cf b/examples/reports.cf new file mode 100644 index 0000000000..b2f7590c50 --- /dev/null +++ b/examples/reports.cf @@ -0,0 +1,51 @@ +# Example policy showing features of reports type promises +bundle agent main +{ + reports: + "It's recommended that you always guard reports" + comment => "Remember by default output from cf-agent when run + from cf-execd will be emailed"; + + DEBUG|DEBUG_main:: + "Run with --define DEBUG or --define DEBUG_main to display this report"; + + methods: + "Actuate bundle that reports with a return value" + usebundle => bundle_with_return_value, + useresult => "return_array", + comment => "Reports can be used to return data into a parent bundle. + This is useful in some re-usable bundle patterns."; + + reports: + "I got '$(return_array[key])' returned from bundle_with_return_value"; + + "Reports can be redirected and appended to files" + report_to_file => "$(sys.workdir)/report_output.txt", + comment => "It's important to note that this will suppress the report + from stdout."; + + "Report content of a file:$(const.n)$(const.n)------------------------" + printfile => cat( $(this.promise_filename) ); +} + +bundle agent bundle_with_return_value +{ + reports: + "value from bundle_with_return_value" + bundle_return_value_index => "key"; +} + +body printfile cat(file) +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +@if minimum_version(3.8) +body printfile head(file) +{ + inherit_from => "cat"; + # GNU head defaults to 10 + number_of_lines => "10"; +} +@endif diff --git a/examples/reporttofile.cf b/examples/reporttofile.cf new file mode 100644 index 0000000000..5c617ab3a4 --- /dev/null +++ b/examples/reporttofile.cf @@ -0,0 +1,36 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "example" }; +} + +# + +bundle agent example +{ + reports: + "$(sys.date),This is a report" + report_to_file => "/tmp/test_log"; +} diff --git a/examples/resolveconf.cf b/examples/resolveconf.cf new file mode 100644 index 0000000000..11368c5065 --- /dev/null +++ b/examples/resolveconf.cf @@ -0,0 +1,116 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Resolve conf +# +####################################################### + +bundle common g # globals +{ + vars: + + "searchlist" slist => { + "search iu.hio.no", + "search cfengine.com" + }; + + "nameservers" slist => { + "128.39.89.10", + "128.39.74.16", + "192.168.1.103" + }; + classes: + + "am_name_server" expression => reglist("@(nameservers)","$(sys.ipv4[eth1])"); +} + +####################################################### + +body common control + +{ + any:: + + bundlesequence => { + "g", + resolver(@(g.searchlist),@(g.nameservers)) + }; + + domain => "iu.hio.no"; +} + +####################################################### + +bundle agent resolver(s,n) + +{ + files: + + # When passing parameters down, we have to refer to + # a source context + + "$(sys.resolv)" # test on "/tmp/resolv.conf" # + + create => "true", + edit_line => doresolv("@(this.s)","@(this.n)"), + edit_defaults => reconstruct; + # or edit_defaults => modify +} + +####################################################### +# For the library +####################################################### + +bundle edit_line doresolv(s,n) + +{ + vars: + + "line" slist => { @(s), @(n) }; + + insert_lines: + + "$(line)"; + +} + +####################################################### + +body edit_defaults reconstruct +{ + empty_file_before_editing => "true"; + edit_backup => "false"; + max_file_size => "100000"; +} + +####################################################### + +body edit_defaults modify +{ + empty_file_before_editing => "false"; + edit_backup => "false"; + max_file_size => "100000"; +} + + diff --git a/examples/returnszero.cf b/examples/returnszero.cf new file mode 100644 index 0000000000..2fc409c66b --- /dev/null +++ b/examples/returnszero.cf @@ -0,0 +1,50 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "my_result" expression => returnszero("/usr/local/bin/mycommand","noshell"); + + reports: + + !my_result:: + + "Command failed"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ 2014-08-18T14:13:28+0100 error: Proposed executable file '/usr/local/bin/mycommand' doesn't exist +#@ 2014-08-18T14:13:28+0100 error: returnszero '/usr/local/bin/mycommand' is assumed to be executable but isn't +#@ R: Command failed +#@ ``` +#+end_src diff --git a/examples/reverse.cf b/examples/reverse.cf new file mode 100644 index 0000000000..019c9bd09f --- /dev/null +++ b/examples/reverse.cf @@ -0,0 +1,64 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "one", "two", "three", + }; + + "reversed" slist => reverse("test"); + + reports: + "Original list is $(test)"; + "The reversed list is $(reversed)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Original list is 1 +#@ R: Original list is 2 +#@ R: Original list is 3 +#@ R: Original list is one +#@ R: Original list is two +#@ R: Original list is three +#@ R: Original list is long string +#@ R: The reversed list is three +#@ R: The reversed list is two +#@ R: The reversed list is one +#@ R: The reversed list is long string +#@ R: The reversed list is 3 +#@ R: The reversed list is 2 +#@ R: The reversed list is 1 +#@ ``` +#+end_src diff --git a/examples/root_passwd.cf b/examples/root_passwd.cf new file mode 100644 index 0000000000..5c963a05cb --- /dev/null +++ b/examples/root_passwd.cf @@ -0,0 +1,136 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +###################################################################### +# +# Root password distribution +# +###################################################################### + +body common control + +{ + version => "1.2.3"; + bundlesequence => { "SetRootPassword" }; +} + +######################################################## + +bundle common g +{ + vars: + + "secret_keys_dir" string => "/tmp"; + +} + +######################################################## + +bundle agent SetRootPassword + +{ + files: + + "/var/cfengine/ppkeys/rootpw.txt" + + copy_from => scp("$(fqhost)-root.txt","master_host.example.org"); + + # or $(pw_class)-root.txt + + # Test this on a copy + + "/tmp/shadow" + + edit_line => SetPasswd("root"); + +} + +######################################################## + +bundle edit_line SetPasswd(user) +{ + vars: + + # Assume this file contains a single string of the form :passwdhash: + # with : delimiters to avoid end of line/file problems + + "gotpw" int => readstringarray("pw","$(sys.workdir)/ppkeys/root-pw.txt","#[^\n]*",":","3","200"); + + field_edits: + + "$(user).*" + + # Set field of the file to parameter + # File has format root:HASH: or user:HASH: + + edit_field => col(":","2","$(pw[root][1])","set"); +} + +######################################################## + +bundle server passwords +{ + vars: + + # Read a file of format + # + # classname: host1,host2,host4,IP-address,regex.*,etc + # + + "pw_classes" int => readstringarray("acl","$(g.secret_keys_dir)/classes.txt","#[^\n]*",":","100","4000"); + "each_pw_class" slist => getindices("acl"); + + access: + + "/secret/keys/$(each_pw_class)-root.txt" + + admit => splitstring("$(acl[$(each_pw_class)][1])" , ":" , "100"), + ifencrypted => "true"; + +} + +######################################## +# Bodies +######################################## + +body edit_field col(split,col,newval,method) + +{ + field_separator => "$(split)"; + select_field => "$(col)"; + value_separator => ","; + field_value => "$(newval)"; + field_operation => "$(method)"; + extend_fields => "true"; + allow_blank_fields => "true"; +} + +######################################## + +body copy_from scp(from,server) + +{ + source => "$(from)"; + compare => "digest"; + encrypt => "true"; + verify => "true"; +} diff --git a/examples/rxdirs.cf b/examples/rxdirs.cf new file mode 100644 index 0000000000..81eeb9acdc --- /dev/null +++ b/examples/rxdirs.cf @@ -0,0 +1,67 @@ +############################################################################### +#+begin_src cfengine3 +bundle agent __main__ +# @brief Illustrating the behavior of rxdirs in perms bodies. +{ + vars: + "example_dirs" slist => { + "/tmp/rxdirs_example/rxdirs=default(false)-r", + "/tmp/rxdirs_example/rxdirs=default(false)-rx", + "/tmp/rxdirs_example/rxdirs=true-r", + "/tmp/rxdirs_example/rxdirs=true-rx", + }; + + files: + "$(example_dirs)/." + create => "true"; + + "/tmp/rxdirs_example/rxdirs=default(false)-r" + perms => example:m( 600 ); + + "/tmp/rxdirs_example/rxdirs=default(false)-rx" + perms => example:m( 700 ); + + "/tmp/rxdirs_example/rxdirs=true-r" + perms => example:m_rxdirs_on( 600 ); + + "/tmp/rxdirs_example/rxdirs=true-rx" + perms => example:m_rxdirs_on( 700 ); + + reports: + "$(example_dirs) modeoct='$(with)'" + with => filestat( $(example_dirs), modeoct ); +} + +body file control{ namespace => "example"; } + +body perms m(mode) +# @brief Set the file mode +# @param mode The new mode +{ + mode => "$(mode)"; +} +body perms m_rxdirs_on(mode) +# @brief Set the file mode and set +x on directory when +r is desired +# @param mode The new mode +{ + inherit_from => m( $(mode) ); + rxdirs => "true"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ warning: Using the default value 'false' for attribute rxdirs (promiser: /tmp/rxdirs_example/rxdirs=default(false)-r), please set it explicitly +#@ warning: Using the default value 'false' for attribute rxdirs (promiser: /tmp/rxdirs_example/rxdirs=default(false)-r), please set it explicitly +#@ warning: Using the default value 'false' for attribute rxdirs (promiser: /tmp/rxdirs_example/rxdirs=default(false)-rx), please set it explicitly +#@ warning: Using the default value 'false' for attribute rxdirs (promiser: /tmp/rxdirs_example/rxdirs=default(false)-rx), please set it explicitly +#@ R: /tmp/rxdirs_example/rxdirs=default(false)-r modeoct='40600' +#@ R: /tmp/rxdirs_example/rxdirs=default(false)-rx modeoct='40600' +#@ R: /tmp/rxdirs_example/rxdirs=true-r modeoct='40700' +#@ R: /tmp/rxdirs_example/rxdirs=true-rx modeoct='40700' +#@ warning: Using the default value 'false' for attribute rxdirs (promiser: /tmp/rxdirs_example/rxdirs=default(false)-r), please set it explicitly +#@ warning: Using the default value 'false' for attribute rxdirs (promiser: /tmp/rxdirs_example/rxdirs=default(false)-rx), please set it explicitly +#@ warning: Using the default value 'false' for attribute rxdirs (promiser: /tmp/rxdirs_example/rxdirs=default(false)-r), please set it explicitly +#@ warning: Using the default value 'false' for attribute rxdirs (promiser: /tmp/rxdirs_example/rxdirs=default(false)-rx), please set it explicitly +#@ ``` +#+end_example diff --git a/examples/select_class.cf b/examples/select_class.cf new file mode 100644 index 0000000000..9eb150637c --- /dev/null +++ b/examples/select_class.cf @@ -0,0 +1,53 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "g" }; + +} + +################################# + +bundle common g +{ + classes: + + "selection" select_class => { "one", "two" }; + + reports: + + one:: + + "One was selected"; + + two:: + + "Two was selected"; + + selection:: + + "A selection was made"; + +} + diff --git a/examples/select_mode.cf b/examples/select_mode.cf new file mode 100644 index 0000000000..d7e0313b46 --- /dev/null +++ b/examples/select_mode.cf @@ -0,0 +1,66 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Searching for permissions +# +####################################################### + +body common control + +{ + bundlesequence => { "example" }; +} + +############################################ + +bundle agent example + +{ + files: + + "/home/mark/tmp/test_from" + + file_select => by_modes, + transformer => "/bin/echo DETECTED $(this.promiser)", + depth_search => recurse("inf"); + +} + +############################################ + +body file_select by_modes + +{ + search_mode => { "711" , "666" }; + file_result => "mode"; +} + +############################################ + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + diff --git a/examples/select_region.cf b/examples/select_region.cf new file mode 100644 index 0000000000..217c0be843 --- /dev/null +++ b/examples/select_region.cf @@ -0,0 +1,94 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src prep +#@ ``` +#@ echo "MATCHING before region" > /tmp/example_select_region.txt +#@ echo "BEGIN" >> /tmp/example_select_region.txt +#@ echo "MATCHING inside region" >> /tmp/example_select_region.txt +#@ echo "END" >> /tmp/example_select_region.txt +#@ echo "MATCHING after region" >> /tmp/example_select_region.txt +#@ ``` +#+end_src + +#+begin_src cfengine3 +bundle agent main +# @brief Demonstrate how edit_line can operate within a region of a file +{ + + vars: + "file" string => "/tmp/example_select_region.txt"; + files: + + "$(file)" + comment => "We want to delete any lines that begin with the string + MATCHING inside the region described by regular expressions + matching the beginning and end of the region.", + create => "true", + edit_line => delete_inside_region("^MATCHING.*", + "^BEGIN.*", + "^END.*"); + + reports: + "$(file)" printfile => cat( $(this.promiser) ); +} + +######################################################## + +bundle edit_line delete_inside_region(line_reg, begin_reg, end_reg) +# @brief Delete lines matching `line_reg` when found within the expected region starting with `begin_reg` and `end_reg` +{ + delete_lines: + + "$(line_reg)" + select_region => between( $(begin_reg), $(end_reg) ); + +} + +body select_region between(start, end) +# @brief Select a region exclusively between regular expressions `start` and `end`. +{ + select_start => "$(start)"; + select_end => "$(end)"; +@if minimum_version(3.10) + select_end_match_eof => "false"; +@endif +} + +body printfile cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: /tmp/example_select_region.txt +#@ R: MATCHING before region +#@ R: BEGIN +#@ R: END +#@ R: MATCHING after region +#@ ``` +#+end_src diff --git a/examples/select_size.cf b/examples/select_size.cf new file mode 100644 index 0000000000..19da8064b2 --- /dev/null +++ b/examples/select_size.cf @@ -0,0 +1,57 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "example" }; +} + +# + +bundle agent example +{ + files: + + # Show files in range + + "/home/mark/tmp" + + file_select => name_and_sizes(".*cf","100","300"), + depth_search => recurse("1"), + transformer => "/bin/ls -l $(this.promiser)"; + +} + +# + +body file_select name_and_sizes(n,x,y) +{ + leaf_name => { "$(n)" }; + search_size => irange("$(x)", "$(y)"); + file_result => "leaf_name&size"; +} + +body depth_search recurse(x) +{ + depth => "$(x)"; +} diff --git a/examples/selectservers.cf b/examples/selectservers.cf new file mode 100644 index 0000000000..0afd360160 --- /dev/null +++ b/examples/selectservers.cf @@ -0,0 +1,70 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +# +# Testing some variable/class definitions - note scope +# +# Use browser -f promise_output_agent.html to view +# + +body common control + +{ + bundlesequence => { "test" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "hosts" slist => { "slogans.iu.hio.no", "eternity.iu.hio.no", "nexus.iu.hio.no" }; + + # selectservers(hostlist,port,sendstr,regex_on_reply,maxbytesread_reply,array_name + + "up_servers" int => selectservers("@(hosts)","80","","","100","alive_servers"); + + classes: + + "someone_alive" expression => isgreaterthan("$(up_servers)","0"); + + "i_am_a_server" expression => regarray("up_servers","$(host)|$(fqhost)"); + + reports: + + someone_alive:: + + "Number of active servers $(up_servers)" action => always; + + "First server $(alive_servers[0]) fails over to $(alive_servers[1])"; + + +} + +############################################################# + +body action always +{ + ifelapsed => "0"; +} diff --git a/examples/server_callback.cf b/examples/server_callback.cf new file mode 100644 index 0000000000..3383fce61e --- /dev/null +++ b/examples/server_callback.cf @@ -0,0 +1,92 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test copy from server connection to cfServer +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-v] +# cf-agent -f runtest_2.cf +# +# Notice that the same file configures all parts of cfengine + + +######################################################## + +body common control +{ + bundlesequence => { "example" }; + version => "1.2.3"; +} + +######################################################## + +bundle agent example +{ + vars: +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + call_collect_interval => "5"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + # On the policy hub + am_policy_hub:: + + "collect_calls" + comment => "Enable call-collect report collection for the specific client", + resource_type => "query", + admit => { "1.2.3.4" }; + + # On the isolated clients in the field + any:: + + "delta" + comment => "Grant access to cfengine hub to collect report deltas", + resource_type => "query", + admit => { "$(sys.policy_hub)" }; + "full" + comment => "Grant access to cfengine hub to collect full report dump", + resource_type => "query", + admit => { "$(sys.policy_hub)" }; +} diff --git a/examples/server_copy_localhost.cf b/examples/server_copy_localhost.cf new file mode 100644 index 0000000000..8c78bf92c5 --- /dev/null +++ b/examples/server_copy_localhost.cf @@ -0,0 +1,139 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test copy from server connection to cfServer +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-v] +# cf-agent -f runtest_2.cf +# +# Notice that the same file configures all parts of cfengine + + +######################################################## + +body common control +{ + bundlesequence => { "example" }; + version => "1.2.3"; + #fips_mode => "true"; +} + +######################################################## + +bundle agent example +{ + files: + + "/tmp/testcopy" + comment => "test copy promise", + copy_from => mycopy("/tmp/test-src","127.0.0.1"), + perms => system, + depth_search => recurse("inf"), + classes => satisfied("copy_ok"); + + "/tmp/testcopy/single_file" + + comment => "test copy promise", + copy_from => mycopy("/tmp/test-src/README","127.0.0.1"), + perms => system; + + reports: + + copy_ok:: + + "Files were copied.."; +} + +######################################################### + +body perms system + +{ + mode => "0644"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + +######################################################### + +body copy_from mycopy(from,server) + +{ + source => "$(from)"; + servers => { "$(server)" }; + compare => "digest"; + encrypt => "true"; + verify => "true"; + copy_backup => "true"; #/false/timestamp + purge => "false"; + type_check => "true"; + force_ipv4 => "true"; + trustkey => "true"; +} + +######################################################### + +body classes satisfied(x) +{ + promise_repaired => { "$(x)" }; + persist_time => "0"; +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + # allowusers +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "/tmp/test-src" + + admit => { "127.0.0.1" }; +} + + diff --git a/examples/server_copy_purge.cf b/examples/server_copy_purge.cf new file mode 100644 index 0000000000..09a0056784 --- /dev/null +++ b/examples/server_copy_purge.cf @@ -0,0 +1,130 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test copy from server connection to cfServer +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-d2] +# cf-agent -f runtest_2.cf +# +# Notice that the same file configures all parts of cfengine + +######################################################## + +body common control + +{ + bundlesequence => { "example" }; + version => "1.2.3"; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp/test_to" + + comment => "test copy promise", + copy_from => mycopy("/home/mark/tmp/test_from","127.0.0.1"), + perms => system, + depth_search => recurse("inf"), + classes => satisfied("copy_ok"); + + reports: + + copy_ok:: + + "Files were copied.."; +} + +######################################################### + +body perms system + +{ + mode => "0644"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + +######################################################### + +body copy_from mycopy(from,server) + +{ + source => "$(from)"; + servers => { "$(server)" }; + compare => "digest"; + verify => "true"; + copy_backup => "true"; #/false/timestamp + purge => "true"; + type_check => "true"; + force_ipv4 => "true"; + trustkey => "true"; +} + +######################################################### + +body classes satisfied(x) +{ + promise_repaired => { "$(x)" }; + persist_time => "0"; +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "/home/mark/tmp" + + admit => { "127.0.0.1" }; +} diff --git a/examples/server_copy_remote.cf b/examples/server_copy_remote.cf new file mode 100644 index 0000000000..fad85208a2 --- /dev/null +++ b/examples/server_copy_remote.cf @@ -0,0 +1,131 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +######################################################## +# +# Simple test copy from server connection to cfServer +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-v] (on remote-host.example.org) +# cf-agent -f runtest_2.cf (on host 1.2.3.4) +# +# Notice that the same file configures all parts of cfengine +# even though different parts are read by different hosts and agents + +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/tmp/testcopy" + + perms => system, + copy_from => mycopy("/src/document","remote-host.example.org"), + depth_search => recurse("inf"); + +} + +######################################################## + +body perms system + +{ + mode => "0444"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + +######################################################### + +body copy_from mycopy(from,server) + +{ + source => "$(from)"; + #portnumber => "6789"; + servers => { "$(server)" , "failover1" }; + copy_backup => "true"; #/false/timestamp + stealth => "true"; #/on/false/off + preserve => "true"; + linkcopy_patterns => { ".*fish.*" }; + copylink_patterns => { "non-local.*"}; + compare => "mtime"; # ctime/mtime/checksum/sum/byte/binary/any + link_type => "absolute"; # /symbolic/relative/hard etc + type_check => "true"; + force_update => "false"; + force_ipv4 => "false"; + copy_size => irange("0","50000"); + trustkey => "true"; + encrypt => "true"; + verify => "true"; + purge => "false"; + findertype => "MacOSX"; +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "1.2.3.4" , "::1" }; + allowallconnects => { "1.2.3.4" , "::1" }; + trustkeysfrom => { "1.2.3.4" , "::1" }; + # allowusers +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + # Grant access to the remote client to access the source docs + + "/src" admit => { "1.2.3.4" }; +} + + + diff --git a/examples/server_flatcopy_localhost.cf b/examples/server_flatcopy_localhost.cf new file mode 100644 index 0000000000..5abd95241a --- /dev/null +++ b/examples/server_flatcopy_localhost.cf @@ -0,0 +1,138 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test copy from server connection to cfServer +# +######################################################## + +# +# run this as follows: +# +# cf-serverd -f runtest_1.cf [-d2] +# cf-agent -f runtest_2.cf +# +# Notice that the same file configures all parts of cfengine + +######################################################## + +body common control + +{ + bundlesequence => { "example" }; + version => "1.2.3"; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp/testflatcopy" + + comment => "test copy promise", + copy_from => mycopy("/home/mark/LapTop/words","127.0.0.1"), + perms => system, + depth_search => recurse("inf"), + classes => satisfied("copy_ok"); + + + "/home/mark/tmp/testcopy/single_file" + + comment => "test copy promise", + copy_from => mycopy("/home/mark/LapTop/Cfengine3/trunk/README","127.0.0.1"), + perms => system; + + reports: + + copy_ok:: + + "Files were copied.."; +} + +######################################################### + +body perms system + +{ + mode => "0644"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} + +######################################################### + +body copy_from mycopy(from,server) + +{ + source => "$(from)"; + servers => { "$(server)" }; + compare => "digest"; + verify => "true"; + copy_backup => "true"; #/false/timestamp + purge => "false"; + type_check => "true"; + force_ipv4 => "true"; + trustkey => "true"; + collapse_destination_dir => "true"; +} + +######################################################### + +body classes satisfied(x) +{ + promise_repaired => { "$(x)" }; + persist_time => "0"; +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "/home/mark/LapTop" + + admit => { "127.0.0.1" }; +} diff --git a/examples/service_catalogue.cf b/examples/service_catalogue.cf new file mode 100644 index 0000000000..b60e12eb8c --- /dev/null +++ b/examples/service_catalogue.cf @@ -0,0 +1,59 @@ +body common control +{ + bundlesequence => { "service_catalogue" }; +} + + +bundle agent service_catalogue +{ + services: + "syslog" service_policy => "start"; + "www" service_policy => "stop"; +} + + + +bundle agent standard_services(service, state) +{ + vars: + + debian:: + + "startcommand[www]" string => "/etc/init.d/apache2 start"; + "stopcommand[www]" string => "/etc/init.d/apache2 stop"; + "processname[www]" string => "apache2"; + + "startcommand[syslog]" string => "/etc/init.d/rsyslog start"; + "stopcommand[syslog]" string => "/etc/init.d/rsyslog stop"; + "processname[syslog]" string => "rsyslogd"; + + + classes: + + "start" expression => strcmp("start","$(state)"); + "stop" expression => strcmp("stop","$(state)"); + + processes: + + start:: + + ".*$(processname[$(service)]).*" + + comment => "Verify that the service appears in the process table", + restart_class => "restart_$(service)"; + + stop:: + + ".*$(processname[$(service)]).*" + + comment => "Verify that the service does not appear in the process table", + process_stop => "$(stopcommand[$(service)])", + signals => { "term", "kill"}; + + commands: + + "$(startcommand[$(service)])" + + comment => "Execute command to restart the $(service) service", + if => "restart_$(service)"; +} diff --git a/examples/service_catalogue_separate.cf b/examples/service_catalogue_separate.cf new file mode 100644 index 0000000000..258c378447 --- /dev/null +++ b/examples/service_catalogue_separate.cf @@ -0,0 +1,33 @@ +body common control +{ + bundlesequence => { "service_catalogue_separate" }; +} + + +bundle agent service_catalogue_separate +{ + services: + "foo" service_policy => "start", + service_method => service_bundle_separate; + + "bar" service_policy => "stop", + service_method => service_bundle_separate; +} + +body service_method service_bundle_separate +{ + service_bundle => $(this.promiser)("$(this.service_policy)"); +} + + +bundle agent foo(service_policy) +{ + reports: + "we need to ensure $(service_policy) of foo"; +} + +bundle agent bar(service_policy) +{ + reports: + "we need to ensure $(service_policy) of bar"; +} diff --git a/examples/service_disable.cf b/examples/service_disable.cf new file mode 100644 index 0000000000..629fdae511 --- /dev/null +++ b/examples/service_disable.cf @@ -0,0 +1,44 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control + +{ + bundlesequence => { "winservice" }; +} + +########################################################### + +bundle agent winservice + +{ + vars: + + "bad_services" slist => { "Alerter", "ClipSrv" }; + + services: + + windows:: + + "$(bad_services)" + + service_policy => "disable", + comment => "Disable services that create security issues"; +} diff --git a/examples/service_start.cf b/examples/service_start.cf new file mode 100644 index 0000000000..a7544832ff --- /dev/null +++ b/examples/service_start.cf @@ -0,0 +1,26 @@ +body common control +{ + bundlesequence => { "example" }; +} + +############################################# + +bundle agent example +{ + services: + + "Themes" + service_policy => "start", + service_dependencies => { "Alerter" }, + service_method => exmethod; +} + +############################################# + +body service_method exmethod +{ + service_type => "windows"; + service_args => "-f \"the file with spaces.cf\" --some-args"; + service_autostart_policy => "boot_time"; + service_dependence_chain => "start_parent_services"; +} diff --git a/examples/services.cf b/examples/services.cf new file mode 100644 index 0000000000..edf58503f6 --- /dev/null +++ b/examples/services.cf @@ -0,0 +1,56 @@ +body file control +{ + inputs => { "$(sys.libdir)/services.cf", "$(sys.libdir)/commands.cf" }; +} + +bundle agent main +# @brief Example showing services promises to manage standard operating system +# services +{ + vars: + + linux:: + "enable[ssh]" + string => ifelse( "debian|ubuntu", "ssh", "sshd"), + comment => "The name of the ssh service varies on different platforms. + Here we set the name of the service based on existing + classes and defaulting to `sshd`"; + + "disable[apache]" + string => ifelse( "debian|ubuntu", "apache2", "httpd" ), + comment => "The name of the apache web service varies on different + platforms. Here we set the name of the service based on + existing classes and defaulting to `httpd`"; + + "enable[cron]" + string => ifelse( "debian|ubuntu", "cron", "crond" ), + comment => "The name of the cron service varies on different + platforms. Here we set the name of the service based on + existing classes and defaulting to `crond`"; + + "disable[cups]" + string => "cups", + comment => "Printing services are not needed on most hosts."; + + "enabled" slist => getvalues( enable ); + "disabled" slist => getvalues( disable ); + + services: + + linux:: + + "$(enabled)" -> { "SysOps" } + service_policy => "start", + comment => "These services should be running because x, y or z."; + + "$(disabled)" -> { "SysOps" } + service_policy => "stop", + comment => "These services should not be running because x, y or z."; + + systemd:: + + "sysstat" + service_policy => "stop", + comment => "Standard service handling for sysstat only works with + systemd. Other inits need cron entries managed."; +} diff --git a/examples/services_concept.cf b/examples/services_concept.cf new file mode 100644 index 0000000000..9955b4fe06 --- /dev/null +++ b/examples/services_concept.cf @@ -0,0 +1,126 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +##################################################################### +# +# Concept of standard services promises +# +##################################################################### + +body common control +{ + bundlesequence => { "example" }; +} + +# + +bundle agent example +{ + vars: + + "mail" slist => { "milter", "spamassassin", "postfix" }; + + + services: + + "www" service_policy => "start", + service_method => service_test; + + "$(mail)" service_policy => "stop", + service_method => service_test; + +} + +#################################################################### + +body service_method service_test +{ + service_bundle => non_standard_services("$(this.promiser)","$(this.service_policy)"); +} + +#################################################################### + +bundle agent standard_services(service,state) +{ + # DATA, + + vars: + + suse|redhat:: + + "startcommand[www]" string => "/etc/init.d/apache2 start"; + "stopcommand[www]" string => "/etc/init.d/apache2 stop"; + + debian|ubuntu:: + + "startcommand[www]" string => "/etc/init.d/httpd start"; + "stopcommand[www]" string => "/etc/init.d/httpd stop"; + + linux:: + + "startcommand[postfix]" string => "/etc/init.d/postfix start"; + "stopcommand[postfix]" string => "/etc/init.d/postfix stop"; + + + # METHODS + + classes: + + "start" expression => strcmp("start","$(state)"); + "stop" expression => strcmp("stop","$(state)"); + + processes: + + start:: + + ".*$(service).*" + + comment => "Verify that the service appears in the process table", + restart_class => "restart_$(service)"; + + stop:: + + ".*$(service).*" + + comment => "Verify that the service does not appear in the process", + process_stop => "$(stopcommand[$(service)])", + signals => { "term", "kill"}; + + commands: + + "$(startcommand[$(service)])" + + comment => "Execute command to restart the $(service) service", + if => "restart_$(service)"; + +} + +###################################################################### + +bundle agent non_standard_services(service,state) +{ + reports: + + !done:: + + "Test service promise for \"$(service)\" -> $(state)"; +} diff --git a/examples/services_default_service_bundle.cf b/examples/services_default_service_bundle.cf new file mode 100644 index 0000000000..7182d20f8d --- /dev/null +++ b/examples/services_default_service_bundle.cf @@ -0,0 +1,29 @@ +bundle agent main +# @brief Example showing how service_bundle is defaulted. +{ + services: + "my-custom-service" + service_method => my_custom_service, + service_policy => "stop"; +} + +body service_method my_custom_service +{ + service_type => "generic"; +} + +bundle agent service_my_custom_service(service, state) +{ + reports: + "$(service) should have state $(state)"; +} + +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: my-custom-service should have state stop +#@ ``` +#+end_src + + + diff --git a/examples/services_win.cf b/examples/services_win.cf new file mode 100644 index 0000000000..561e676979 --- /dev/null +++ b/examples/services_win.cf @@ -0,0 +1,55 @@ +######################################################################### +# +# win_services.cf - Windows Service Management +# +######################################################################### + +body file control +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent win_services +{ + vars: + + # NOTE: Use "Service Name" (not "Display Name"); + # Administrative Tools -> Services -> Double Click on one to see its name + + "bad_services" slist => { + "RemoteRegistry" + }; + + + Windows_Server_2003_R2:: + + "autostart_services" slist => { + "Alerter", + "W32Time" # Windows Time + }; + + Windows_Server_2008:: + + "autostart_services" slist => { + "MpsSvc", # Windows Firewall + "W32Time" # Windows Time + }; + + + services: + + "$(bad_services)" + service_policy => "disable", + service_method => force_deps, + comment => "Disable services that create security issues"; + + + Windows_Server_2003_R2|Windows_Server_2008:: + + "$(autostart_services)" + service_policy => "start", + service_method => bootstart, + comment => "Make sure important services are running and set to start at boot time"; + +} + diff --git a/examples/setuidlog.cf b/examples/setuidlog.cf new file mode 100644 index 0000000000..5f8c06873a --- /dev/null +++ b/examples/setuidlog.cf @@ -0,0 +1,61 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp" -> "me" + + changes => tripwire, + depth_search => recurse("1"); + +} + + +######################################################### + +body changes tripwire + +{ + hash => "md5"; + report_changes => "content"; + update_hashes => "true"; +} + +######################################################### + +body depth_search recurse(d) + +{ + depth => "$(d)"; +} diff --git a/examples/setvar.cf b/examples/setvar.cf new file mode 100644 index 0000000000..84554960a1 --- /dev/null +++ b/examples/setvar.cf @@ -0,0 +1,56 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "setvars" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent setvars +{ + vars: + + # want to set these values by the names of their array keys + + "rhs[lhs1]" string => " Mary had a little pig"; + "rhs[lhs2]" string => "Whose Fleece was white as snow"; + "rhs[lhs3]" string => "And everywhere that Mary went"; + + "rhs[net/ipv4/tcp_syncookies]" string => "1"; + "rhs[net/ipv4/icmp_echo_ignore_broadcasts]" string => "1"; + "rhs[net/ipv4/ip_forward]" string => "0"; + + # oops, now change pig -> lamb + + files: + + + "/tmp/system" + + comment => "Create a file of variable assignments and manage this file", + create => "true", + edit_line => set_variable_values("setvars.rhs"); + +} + diff --git a/examples/shuffle.cf b/examples/shuffle.cf new file mode 100644 index 0000000000..d8a96f91d7 --- /dev/null +++ b/examples/shuffle.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "mylist" slist => { "b", "c", "a" }; + "seeds" slist => { "xx", "yy", "zz" }; + + "shuffled_$(seeds)" slist => shuffle(mylist, $(seeds)); + + "joined_$(seeds)" string => join(",", "shuffled_$(seeds)"); + + reports: + "shuffled RANDOMLY by $(seeds) = '$(joined_$(seeds))'"; +} +#+end_src +############################################################################### +# Not checked because shuffle is random. +#+begin_src example_output +#@ ``` +#@ R: shuffled RANDOMLY by xx = 'b,a,c' +#@ R: shuffled RANDOMLY by yy = 'a,c,b' +#@ R: shuffled RANDOMLY by zz = 'c,b,a' +#@ ``` +#+end_src diff --git a/examples/simple_ssh_key_distribution.cf b/examples/simple_ssh_key_distribution.cf new file mode 100644 index 0000000000..0ee199c906 --- /dev/null +++ b/examples/simple_ssh_key_distribution.cf @@ -0,0 +1,86 @@ +body common control +{ + bundlesequence => { "autorun_ssh_key_distribution" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle common ssh_key_info +{ + meta: + "description" + string => "This bundle defines common ssh key information, like which + directory and server keys should be sourced from."; + + vars: + "key_server" string => "$(sys.policy_hub)"; + + # We set the path to the repo in a common bundle so that we can reference + # the same path when defining access rules and when copying files. + # This directory is expected to contain one file for each users authorized + # keys, named for the username. For example: /srv/ssh_authorized_keys/kelly + "repo_path" string => "/srv/ssh_authorized_keys"; +} + +bundle agent autorun_ssh_key_distribution +{ + meta: + # Here we simply tag the bundle for use with the `services_autorun` + # feature. + "tags" slist => { "autorun" }; + + vars: + "users" slist => { "bob", "frank", "kelly" }; + + methods: + "Distribute SSH Keys" + usebundle => ssh_key_distribution( $(users) ), + if => userexists( $(users) ), + comment => "It's important that we make sure each of these users + ssh_authorized_keys file has the correct content and + permissions so that they can successfully log in, if + the user exists on the executing agents host."; +} + +bundle agent ssh_key_distribution(users) +{ + meta: + "description" + string => "Ensure that specified users are able to log in using their ssh + keys"; + vars: + # We get the users UID so that we can set permission appropriately + "uid[$(users)]" int => getuid( $(users) ); + + files: + "/home/$(users)/.ssh/." + create => "true", + perms => mo( 700, "$(uid[$(users)])"), + comment => "It is important to set the proper restrictive permissions and + ownership so that the ssh authorized_keys feature works + correctly."; + + "/home/$(users)/.ssh/authorized_keys" + perms => mo( 600, "$(uid[$(users)])" ), + copy_from => remote_dcp( "$(ssh_key_info.repo_path)/$(users)", + $(ssh_key_info.key_server) ), + comment => "We centrally manage and users authorized keys. We source each + users complete authorized_keys file from the central server."; +} + + +bundle server ssh_key_access_rules +{ + meta: + "description" + string => "This bundle handles sharing the directory where ssh keys + are distributed from."; + + access: + # Only hosts with class `policy_server` should share the path to ssh + # authorized_keys + policy_server:: + "$(ssh_key_info.repo_path)" + admit => { @(def.acl) }, + comment => "We share the ssh authorized keys with all authorized + hosts."; +} diff --git a/examples/software_dist.cf b/examples/software_dist.cf new file mode 100644 index 0000000000..3c059ffde3 --- /dev/null +++ b/examples/software_dist.cf @@ -0,0 +1,175 @@ +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################################### +# +# software_local.cf - Application Deployment From Directory Repository +# +# NOTE: Windows needs to support WMI queries about installed msi files +# in order for Cfengine to detect them. On Windows 2003, +# go to Control Panel -> Add/Remove Programs -> +# Windows Components -> Mgmnt and Monitoring Tools and check +# WMI Windows Installer Provider. +# +# NOTE: Naming conventions are important when updating packages. +# By default, Cfengine expects "name-version-arch.msi" +# on Windows, where name is lowercase, and arch is +# i686 or x86_64. No spaces should be included in the filename. +# The Caption and Version fields inside the msi package +# are important. They must correspond to the file name as +# follows: name = lowercase(spacetodash(Caption)), +# version = Version. For any msi-file, use InstEd +# (www.instedit.com) to check/modify the +# Caption and Version fields +# (Tables->Property->ProductName/ProductVersion). +# +# For example, ProductName "CFEngine Nova" with ProductVersion +# "1.1.2" for 32-bit Windows will correspond to the filename +# "cfengine-nova-1.1.2-i686.msi". +# +######################################################################### + +body common control +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; + bundlesequence => { "check_software" }; +} + +bundle agent check_software +{ + vars: + + # software to install if not installed + "include_software" slist => { + "7-zip-4.50-$(sys.arch).msi" + }; + + # this software gets updated if it is installed + "autoupdate_software" slist => { + "7-zip" + }; + + # software to uninstall if it is installed + "exclude_software" slist => { + "7-zip-4.65-$(sys.arch).msi" + }; + + methods: + # TODO: Fix the following bundles, msi_implicit is giving errors: + # "any" usebundle => add_software( "@(check_software.include_software)", "$(sys.policy_hub)" ); + # "any" usebundle => update_software( "@(check_software.autoupdate_software)", "$(sys.policy_hub)" ); + # "any" usebundle => remove_software( "@(check_software.exclude_software)", "$(sys.policy_hub)" ); +} + +######################################################################### + +bundle agent add_software(pkg_name, srv) +{ + vars: + # dir to install from locally - can also check multiple directories + "local_software_dir" string => "C:\Program Files\Cfengine\software\add"; + + files: + + "$(local_software_dir)" + copy_from => remote_cp("/var/cfengine/master_software_updates/$(sys.flavour)_$(sys.arch)/add", "$(srv)"), + depth_search => recurse("1"), + classes => if_repaired("got_newpkg"), + comment => "Copy software from remote repository"; + + + packages: + + # When to check if the package is installed ? + got_newpkg|any:: + "$(pkg_name)" + package_policy => "add", + package_method => msi_implicit( "$(local_software_dir)" ), + classes => if_else("add_success", "add_fail" ), + comment => "Install new software, if not already present"; + + reports: + add_fail:: + "Failed to install one or more packages"; +} + +######################################################################### + +bundle agent update_software(sw_names, srv) +{ + vars: + # dir to install from locally - can also check multiple directories + "local_software_dir" string => "C:\Program Files\Cfengine\software\update"; + + files: + + "$(local_software_dir)" + copy_from => remote_cp("/var/cfengine/master_software_updates/$(sys.flavour)_$(sys.arch)/update", "$(srv)"), + depth_search => recurse("1"), + classes => if_repaired("got_newpkg"), + comment => "Copy software updates from remote repository"; + + + packages: + + # When to check if the package is updated ? + got_newpkg|any:: + "$(sw_names)" + package_policy => "update", + package_select => ">=", # picks the newest update available + package_architectures => { "$(sys.arch)" }, # install 32 or 64 bit package ? + package_version => "1.0", # at least version 1.0 + package_method => msi_explicit( "$(local_software_dir)" ), + classes => if_else("update_success", "update_fail"); + + + reports: + update_fail:: + "Failed to update one or more packages"; +} + +######################################################################### + +bundle agent remove_software(pkg_name, srv) +{ + vars: + # dir to install from locally - can also check multiple directories + "local_software_dir" string => "C:\Program Files\Cfengine\software\remove"; + + files: + + "$(local_software_dir)" + copy_from => remote_cp("/var/cfengine/master_software_updates/$(sys.flavour)_$(sys.arch)/remove", "$(srv)"), + depth_search => recurse("1"), + classes => if_repaired("got_newpkg"), + comment => "Copy removable software from remote repository"; + + packages: + got_newpkg:: + "$(pkg_name)" + package_policy => "delete", + package_method => msi_implicit( "$(local_software_dir)" ), + classes => if_else("remove_success", "remove_fail" ), + comment => "Remove software, if present"; + + reports: + remove_fail:: + "Failed to remove one or more packages"; +} diff --git a/examples/software_update_version_yum.cf b/examples/software_update_version_yum.cf new file mode 100644 index 0000000000..3df34b742e --- /dev/null +++ b/examples/software_update_version_yum.cf @@ -0,0 +1,55 @@ +# +# Schedule software update for yum-based distributions (e.g. RedHat, CentOS) +# Will only update to the given package_version assumed to be found in the yum repository. +# If installed version is the same as package_version or newer, no action is taken. +# + +body common control +{ + bundlesequence => { "system_software" }; +} + + +bundle agent system_software +{ + classes: + "update_hosts" expression => "host1|host2"; + "update_schedule" expression => "Day27.Hr02.Min00_05"; + + packages: + update_hosts.update_schedule:: + "bash" + comment => "Make sure bash package is updated to right version", + handle => "package_bash_update", + package_version => "3.2-32.el5", + package_architectures => { "x86_64" }, + package_policy => "addupdate", + package_select => ">=", + package_method => yum_version; + +} + + +body package_method yum_version +{ + package_changes => "bulk"; + package_list_command => "/bin/rpm -qa --qf '%{name} %{version}-%{release} %{arch}\n'"; + package_patch_list_command => "/usr/bin/yum check-update"; + + package_list_name_regex => "^(\S+?)\s\S+?\s\S+$"; + package_list_version_regex => "^\S+?\s(\S+?)\s\S+$"; + package_list_arch_regex => "^\S+?\s\S+?\s(\S+)$"; + + package_installed_regex => ".*"; + package_name_convention => "$(name)-$(version).$(arch)"; + + package_patch_installed_regex => "^\s.*"; + package_patch_name_regex => "([^.]+).*"; + package_patch_version_regex => "[^\s]\s+([^\s]+).*"; + package_patch_arch_regex => "[^.]+\.([^\s]+).*"; + + package_add_command => "/usr/bin/yum -y install"; + package_update_command => "/usr/bin/yum -y update"; + package_delete_command => "/bin/rpm -e --nodeps --allmatches"; + package_verify_command => "/bin/rpm -V"; +} diff --git a/examples/some.cf b/examples/some.cf new file mode 100644 index 0000000000..bbcfa210d6 --- /dev/null +++ b/examples/some.cf @@ -0,0 +1,124 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + classes: + + # This is an easy way to check if a list is empty, better than + # expression => strcmp(length(x), "0") + + # Note that if you use length() or none() or every() they will + # go through all the elements!!! some() returns as soon as any + # element matches. + "empty_x" not => some(".*", x); + "empty_y" not => some(".*", y); + + "some11" expression => some("long string", test1); + "some12" expression => some("none", test1); + "some21" expression => some("long string", test2); + "some22" expression => some("none", test2); + + vars: + "x" slist => { "a", "b" }; + "y" slist => { }; + + "test1" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; + + + "test2" data => parsejson('[1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three",]'); + + reports: + empty_x:: + "x has no elements"; + empty_y:: + "y has no elements"; + + any:: + "The test1 list is $(test1)"; + some11:: + "some() test1 1 passed"; + !some11:: + "some() test1 1 failed"; + some12:: + "some() test1 2 failed"; + !some12:: + "some() test1 2 passed"; + + "The test2 list is $(test2)"; + some21:: + "some() test2 1 passed"; + !some21:: + "some() test2 1 failed"; + some22:: + "some() test2 2 failed"; + !some22:: + "some() test2 2 passed"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: y has no elements +#@ R: The test1 list is 1 +#@ R: The test1 list is 2 +#@ R: The test1 list is 3 +#@ R: The test1 list is one +#@ R: The test1 list is two +#@ R: The test1 list is three +#@ R: The test1 list is long string +#@ R: The test1 list is four +#@ R: The test1 list is fix +#@ R: The test1 list is six +#@ R: some() test1 1 passed +#@ R: some() test1 2 passed +#@ R: The test2 list is 1 +#@ R: The test2 list is 2 +#@ R: The test2 list is 3 +#@ R: The test2 list is one +#@ R: The test2 list is two +#@ R: The test2 list is three +#@ R: The test2 list is long string +#@ R: The test2 list is four +#@ R: The test2 list is fix +#@ R: The test2 list is six +#@ R: some() test2 1 passed +#@ R: some() test2 2 passed +#@ ``` +#+end_src diff --git a/examples/sort.cf b/examples/sort.cf new file mode 100644 index 0000000000..4ed79f00f9 --- /dev/null +++ b/examples/sort.cf @@ -0,0 +1,100 @@ +#+begin_src cfengine3 +body common control +{ + bundlesequence => { test }; +} + +bundle agent test +{ + vars: + "a" slist => { "b", "c", "a" }; + "b" slist => { "100", "9", "10", "8.23" }; + "c" slist => { }; + "d" slist => { "", "a", "", "b" }; + "e" slist => { "a", "1", "b" }; + + "ips" slist => { "100.200.100.0", "1.2.3.4", "9.7.5.1", "9", "9.7", "9.7.5", "", "-1", "where are the IP addresses?" }; + "ipv6" slist => { "FE80:0000:0000:0000:0202:B3FF:FE1E:8329", + "FE80::0202:B3FF:FE1E:8329", + "::1", + # the following should all be parsed as the same address and sorted together + "2001:db8:0:0:1:0:0:1", + "2001:0db8:0:0:1:0:0:1", + "2001:db8::1:0:0:1", + "2001:db8::0:1:0:0:1", + "2001:0db8::1:0:0:1", + "2001:db8:0:0:1::1", + "2001:db8:0000:0:1::1", + "2001:DB8:0:0:1::1", # note uppercase IPv6 addresses are invalid + # examples from https://www.ripe.net/lir-services/new-lir/ipv6_reference_card.pdf + "8000:63bf:3fff:fdd2", + "::ffff:192.0.2.47", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "2001:0000:4136:e378:", + "8000:63bf:3fff:fdd2", + "2001:0002:6c::430", + "2001:10:240:ab::a", + "2002:cb0a:3cdd:1::1", + "2001:db8:8:4::2", + "ff01:0:0:0:0:0:0:2", + "-1", "where are the IP addresses?" }; + + "macs" slist => { "00:14:BF:F7:23:1D", "0:14:BF:F7:23:1D", ":14:BF:F7:23:1D", "00:014:BF:0F7:23:01D", + "00:14:BF:F7:23:1D", "0:14:BF:F7:23:1D", ":14:BF:F7:23:1D", "00:014:BF:0F7:23:01D", + "01:14:BF:F7:23:1D", "1:14:BF:F7:23:1D", + "01:14:BF:F7:23:2D", "1:14:BF:F7:23:2D", + "-1", "where are the MAC addresses?" }; + + "ja" string => join(",", "a"); + "jb" string => join(",", "b"); + "jc" string => join(",", "c"); + "jd" string => join(",", "d"); + "je" string => join(",", "e"); + + "jips" string => join(",", "ips"); + "jipv6" string => join(",", "ipv6"); + "jmacs" string => join(",", "macs"); + + "sa" slist => sort("a", "lex"); + "sb" slist => sort("b", "lex"); + "sc" slist => sort("c", "lex"); + "sd" slist => sort("d", "lex"); + "se" slist => sort("e", "lex"); + + "sb_int" slist => sort("b", "int"); + "sb_real" slist => sort("b", "real"); + + "sips" slist => sort("ips", "ip"); + "sipv6" slist => sort("ipv6", "ip"); + "smacs" slist => sort("macs", "mac"); + + + "jsa" string => join(",", "sa"); + "jsb" string => join(",", "sb"); + "jsc" string => join(",", "sc"); + "jsd" string => join(",", "sd"); + "jse" string => join(",", "se"); + + "jsb_int" string => join(",", "sb_int"); + "jsb_real" string => join(",", "sb_real"); + + "jsips" string => join(",", "sips"); + "jsipv6" string => join(",", "sipv6"); + "jsmacs" string => join(",", "smacs"); + + reports: + "sorted lexicographically '$(ja)' => '$(jsa)'"; + "sorted lexicographically '$(jb)' => '$(jsb)'"; + "sorted lexicographically '$(jc)' => '$(jsc)'"; + "sorted lexicographically '$(jd)' => '$(jsd)'"; + "sorted lexicographically '$(je)' => '$(jse)'"; + + "sorted integers '$(jb)' => '$(jsb_int)'"; + "sorted reals '$(jb)' => '$(jsb_real)'"; + + "sorted IPs '$(jips)' => '$(jsips)'"; + "sorted IPv6s '$(jipv6)' => '$(jsipv6)'"; + "sorted MACs '$(jmacs)' => '$(jsmacs)'"; +} +#+end_src diff --git a/examples/splitstring.cf b/examples/splitstring.cf new file mode 100644 index 0000000000..c32f4ea088 --- /dev/null +++ b/examples/splitstring.cf @@ -0,0 +1,55 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + + "split1" slist => splitstring("one:two:three",":","10"); + "split2" slist => splitstring("one:two:three",":","1"); + "split3" slist => splitstring("alpha:xyz:beta","xyz","10"); + + reports: + + "split1: $(split1)"; # will list "one", "two", and "three" + "split2: $(split2)"; # will list "one", "two:three" will be thrown away. + "split3: $(split3)"; # will list "alpha:" and ":beta" + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: split1: one +#@ R: split1: two +#@ R: split1: three +#@ R: split2: one +#@ R: split3: alpha: +#@ R: split3: :beta +#@ ``` +#+end_src diff --git a/examples/sql_table_structure.cf b/examples/sql_table_structure.cf new file mode 100644 index 0000000000..d1c3816daf --- /dev/null +++ b/examples/sql_table_structure.cf @@ -0,0 +1,47 @@ +# +# Database promises are introduced in CFEngine Community edition 3.3.0 +# + +body common control +{ + bundlesequence => { "databases" }; +} + + +bundle agent databases + +{ + databases: + + "cfengine_db/users" + + database_operation => "create", + database_type => "sql", + database_columns => { + "username,varchar,50", + "password,varchar,80", + "email,varchar,20", + }, + database_server => local_mysql("root", ""); +} + + +body database_server local_mysql(username, password) +{ + db_server_owner => "$(username)"; + db_server_password => "$(password)"; + db_server_host => "localhost"; + db_server_type => "mysql"; + db_server_connection_db => "mysql"; +} + + +body database_server local_postgresql(username, password) +{ + db_server_owner => "$(username)"; + db_server_password => "$(password)"; + db_server_host => "localhost"; + db_server_type => "postgres"; + db_server_connection_db => "postgres"; +} + diff --git a/examples/storage-cifs.cf b/examples/storage-cifs.cf new file mode 100644 index 0000000000..3568f69c15 --- /dev/null +++ b/examples/storage-cifs.cf @@ -0,0 +1,30 @@ +bundle agent main +# @brief Example illustrating CIFS/Samba storage type promise mount +{ + vars: + redhat|centos:: + "cifs" data => '{ "server": "192.168.42.251", "path": "/Audio" }'; + + packages: + redhat|centos:: + "cifs-utils" policy => "present"; + "samba-client" policy => "present"; + + files: + redhat|centos:: + "/mnt/CIFS/." create => "true"; + + storage: + redhat|centos:: + "/mnt/CIFS" + mount => cifs_guest( $(cifs[server]) , $(cifs[path]) ); +} + +body mount cifs_guest(server,source) +{ + mount_type => "cifs"; + mount_source => "$(source)"; + mount_server => "$(server)"; + mount_options => { "guest" }; + edit_fstab => "false"; +} diff --git a/examples/storage.cf b/examples/storage.cf new file mode 100644 index 0000000000..7cecbe531d --- /dev/null +++ b/examples/storage.cf @@ -0,0 +1,57 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# +# cfengine 3 +# +# cf-agent -f ./cftest.cf -K +# + +body common control + +{ + bundlesequence => { "storage" }; +} + +# + +bundle agent storage + +{ + storage: + + "/usr" volume => mycheck("11G"); + "/" volume => mycheck("60%"); + +} + +###################################################################### + +body volume mycheck(free) # reusable template + +{ + check_foreign => "false"; + freespace => "$(free)"; + sensible_size => "10000"; + sensible_count => "2"; +} diff --git a/examples/storejson.cf b/examples/storejson.cf new file mode 100644 index 0000000000..851f47d6d0 --- /dev/null +++ b/examples/storejson.cf @@ -0,0 +1,59 @@ +#+begin_src cfengine3 +bundle common globals +{ + vars: + "example_data" data => '{ "msg": "Hello from $(this.bundle)" }'; +} +bundle agent example_storejson +# @brief Example showing storejson +{ + vars: + "example_data" data => '{ "msg": "Hello from $(this.bundle)" }'; + + # Using storejson with data from remote bundle + + # "json_string_zero" -> { "CFEngine 3.16.0"} + # string => storejson( globals.example_data ) + # comment => "Unquoted with . (dot) present will cause the parser to error"; + + "json_string_one" string => storejson( @(globals.example_data) ); + "json_string_two" string => storejson( "globals.example_data" ); + + # Using storejson with data from this bundle + "json_string_three" string => storejson( @(example_storejson.example_data) ); + "json_string_four" string => storejson( "example_storejson.example_data"); + "json_string_five" string => storejson( example_data ); + "json_string_six" string => storejson( "$(this.bundle).example_data"); + "json_string_seven" string => storejson( @(example_data) ); + + reports: + "json_string_one and json_string_two are identical:$(const.n)$(json_string_one)" + if => strcmp( $(json_string_one), $(json_string_two) ); + + "json_string_{one,two,three,four,five,six,seven} are identical:$(const.n)$(json_string_three)" + if => and( + strcmp( $(json_string_three), $(json_string_four) ), + strcmp( $(json_string_four), $(json_string_five) ), + strcmp( $(json_string_five), $(json_string_six) ), + strcmp( $(json_string_six), $(json_string_seven) ) + ); +} + +bundle agent __main__ +{ + methods: "example_storejson"; +} +############################################################################### +#+end_src +#+begin_src example_output +#@ ``` +#@ R: json_string_one and json_string_two are identical: +#@ { +#@ "msg": "Hello from globals" +#@ } +#@ R: json_string_{one,two,three,four,five,six,seven} are identical: +#@ { +#@ "msg": "Hello from example_storejson" +#@ } +#@ ``` +#+end_src diff --git a/examples/strcmp.cf b/examples/strcmp.cf new file mode 100644 index 0000000000..016dafed4c --- /dev/null +++ b/examples/strcmp.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "same" expression => strcmp("test","test"); + + reports: + + same:: + + "Strings are equal"; + + !same:: + + "Strings are not equal"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Strings are equal +#@ ``` +#+end_src diff --git a/examples/strftime.cf b/examples/strftime.cf new file mode 100644 index 0000000000..ead82f8d45 --- /dev/null +++ b/examples/strftime.cf @@ -0,0 +1,170 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +#@ The templates used here are from the documentation of the standard strftime +#@ implementation in the [glibc manual](http://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#Formatting-Calendar-Time). +body agent control +{ + environment => { "LC_ALL=en_US.UTF-8", "TZ=GMT" }; +} +bundle agent main +{ + vars: + "time" int => "1234567890"; + + "template[%a]" string => "The abbreviated weekday name according to the current locale."; + "template[%A]" string => "The full weekday name according to the current locale."; + "template[%b]" string => "The abbreviated month name according to the current locale."; + "template[%B]" string => "The full month name according to the current locale."; + "template[%c]" string => "The preferred calendar time representation for the current locale."; + "template[%C]" string => "The century of the year. This is equivalent to the greatest integer not greater than the year divided by 100."; + "template[%d]" string => "The day of the month as a decimal number (range 01 through 31)."; + "template[%D]" string => "The date using the format %m/$d/%y."; + "template[%e]" string => "The day of the month like with %d, but padded with blank (range 1 through 31)."; + "template[%F]" string => "The date using the format %Y-%m-%d. This is the form specified in the ISO 8601 standard and is the preferred form for all uses."; + "template[%g]" string => "The year corresponding to the ISO week number, but without the century (range 00 through 99). This has the same format and value as %y, except that if the ISO week number (see %V) belongs to the previous or next year, that year is used instead."; + "template[%G]" string => "The year corresponding to the ISO week number. This has the same format and value as %Y, except that if the ISO week number (see %V) belongs to the previous or next year, that year is used instead."; + "template[%h]" string => "The abbreviated month name according to the current locale. The action is the same as for %b."; + "template[%H]" string => "The hour as a decimal number, using a 24-hour clock (range 00 through 23)."; + "template[%I]" string => "The hour as a decimal number, using a 12-hour clock (range 01 through 12)."; + "template[%j]" string => "The day of the year as a decimal number (range 001 through 366)."; + "template[%k]" string => "The hour as a decimal number, using a 24-hour clock like %H, but padded with blank (range 0 through 23)."; + "template[%l]" string => "The hour as a decimal number, using a 12-hour clock like %I, but padded with blank (range 1 through 12)."; + "template[%m]" string => "The month as a decimal number (range 01 through 12)."; + "template[%M]" string => "The minute as a decimal number (range 00 through 59)."; + "template[%n]" string => "A single \n (newline) character."; + "template[%p]" string => "Either AM or PM, according to the given time value; or the corresponding strings for the current locale. Noon is treated as PM and midnight as AM. In most locales AM/PM format is not supported, in such cases %p yields an empty string."; + "template[%P]" string => "Either am or pm, according to the given time value; or the corresponding strings for the current locale, printed in lowercase characters. Noon is treated as pm and midnight as am. In most locales AM/PM format is not supported, in such cases %P yields an empty string."; + "template[%r]" string => "The complete calendar time using the AM/PM format of the current locale."; + "template[%R]" string => "The hour and minute in decimal numbers using the format %H:%M."; + "template[%S]" string => "The seconds as a decimal number (range 00 through 60)."; + "template[%t]" string => "A single \t (tabulator) character."; + "template[%T]" string => "The time of day using decimal numbers using the format %H:%M:%S."; + "template[%u]" string => "The day of the week as a decimal number (range 1 through 7), Monday being 1."; + "template[%U]" string => "The week number of the current year as a decimal number (range 00 through 53), starting with the first Sunday as the first day of the first week. Days preceding the first Sunday in the year are considered to be in week 00."; + "template[%V]" string => "The *ISO 8601:1988* week number as a decimal number (range 01 through 53). ISO weeks start with Monday and end with Sunday. Week 01 of a year is the first week which has the majority of its days in that year; this is equivalent to the week containing the year's first Thursday, and it is also equivalent to the week containing January 4. Week 01 of a year can contain days from the previous year. The week before week 01 of a year is the last week (52 or 53) of the previous year even if it contains days from the new year."; + "template[%w]" string => "The day of the week as a decimal number (range 0 through 6), Sunday being 0."; + "template[%w]" string => "The day of the week as a decimal number (range 0 through 6), Sunday being 0."; + "template[%x]" string => "The preferred date representation for the current locale."; + "template[%X]" string => "The preferred time of day representation for the current locale."; + "template[%y]" string => "The year without a century as a decimal number (range 00 through 99). This is equivalent to the year modulo 100."; + "template[%Y]" string => "The year as a decimal number, using the Gregorian calendar. Years before the year 1 are numbered 0, -1, and so on."; + "template[%z]" string => "*RFC 822*/*ISO 8601:1988* style numeric time zone (e.g., -0600 or +0100), or nothing if no time zone is determinable."; + "template[%Z]" string => "The time zone abbreviation (empty if the time zone can't be determined)."; + "template[%%]" string => "A literal % character."; + + # Since %s is ever changing resulting in unstable output, causing a test failure in CI + # it's not used unless show_seconds_since_epoch is defined + "template[%s]" string => "The number of seconds since the epoch, i.e., since 1970-01-01 00:00:00 UTC. Leap seconds are not counted unless leap second support is available.", if => "show_seconds_since_epoch"; + + "_i" slist => sort(getindices(template), lex); + + reports: + "$(_i) :: $(template[$(_i)])$(const.n)$(const.t)For example: '$(with)'" + with => strftime(gmtime, $(_i), $(time)); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: %% :: A literal % character. +#@ For example: '%' +#@ R: %A :: The full weekday name according to the current locale. +#@ For example: 'Friday' +#@ R: %B :: The full month name according to the current locale. +#@ For example: 'February' +#@ R: %C :: The century of the year. This is equivalent to the greatest integer not greater than the year divided by 100. +#@ For example: '20' +#@ R: %D :: The date using the format %m/$d/%y. +#@ For example: '02/13/09' +#@ R: %F :: The date using the format %Y-%m-%d. This is the form specified in the ISO 8601 standard and is the preferred form for all uses. +#@ For example: '2009-02-13' +#@ R: %G :: The year corresponding to the ISO week number. This has the same format and value as %Y, except that if the ISO week number (see %V) belongs to the previous or next year, that year is used instead. +#@ For example: '2009' +#@ R: %H :: The hour as a decimal number, using a 24-hour clock (range 00 through 23). +#@ For example: '23' +#@ R: %I :: The hour as a decimal number, using a 12-hour clock (range 01 through 12). +#@ For example: '11' +#@ R: %M :: The minute as a decimal number (range 00 through 59). +#@ For example: '31' +#@ R: %P :: Either am or pm, according to the given time value; or the corresponding strings for the current locale, printed in lowercase characters. Noon is treated as pm and midnight as am. In most locales AM/PM format is not supported, in such cases %P yields an empty string. +#@ For example: 'pm' +#@ R: %R :: The hour and minute in decimal numbers using the format %H:%M. +#@ For example: '23:31' +#@ R: %S :: The seconds as a decimal number (range 00 through 60). +#@ For example: '30' +#@ R: %T :: The time of day using decimal numbers using the format %H:%M:%S. +#@ For example: '23:31:30' +#@ R: %U :: The week number of the current year as a decimal number (range 00 through 53), starting with the first Sunday as the first day of the first week. Days preceding the first Sunday in the year are considered to be in week 00. +#@ For example: '06' +#@ R: %V :: The *ISO 8601:1988* week number as a decimal number (range 01 through 53). ISO weeks start with Monday and end with Sunday. Week 01 of a year is the first week which has the majority of its days in that year; this is equivalent to the week containing the year's first Thursday, and it is also equivalent to the week containing January 4. Week 01 of a year can contain days from the previous year. The week before week 01 of a year is the last week (52 or 53) of the previous year even if it contains days from the new year. +#@ For example: '07' +#@ R: %X :: The preferred time of day representation for the current locale. +#@ For example: '23:31:30' +#@ R: %Y :: The year as a decimal number, using the Gregorian calendar. Years before the year 1 are numbered 0, -1, and so on. +#@ For example: '2009' +#@ R: %Z :: The time zone abbreviation (empty if the time zone can't be determined). +#@ For example: 'GMT' +#@ R: %a :: The abbreviated weekday name according to the current locale. +#@ For example: 'Fri' +#@ R: %b :: The abbreviated month name according to the current locale. +#@ For example: 'Feb' +#@ R: %c :: The preferred calendar time representation for the current locale. +#@ For example: 'Fri Feb 13 23:31:30 2009' +#@ R: %d :: The day of the month as a decimal number (range 01 through 31). +#@ For example: '13' +#@ R: %e :: The day of the month like with %d, but padded with blank (range 1 through 31). +#@ For example: '13' +#@ R: %g :: The year corresponding to the ISO week number, but without the century (range 00 through 99). This has the same format and value as %y, except that if the ISO week number (see %V) belongs to the previous or next year, that year is used instead. +#@ For example: '09' +#@ R: %h :: The abbreviated month name according to the current locale. The action is the same as for %b. +#@ For example: 'Feb' +#@ R: %j :: The day of the year as a decimal number (range 001 through 366). +#@ For example: '044' +#@ R: %k :: The hour as a decimal number, using a 24-hour clock like %H, but padded with blank (range 0 through 23). +#@ For example: '23' +#@ R: %l :: The hour as a decimal number, using a 12-hour clock like %I, but padded with blank (range 1 through 12). +#@ For example: '11' +#@ R: %m :: The month as a decimal number (range 01 through 12). +#@ For example: '02' +#@ R: %n :: A single \n (newline) character. +#@ For example: ' +#@ ' +#@ R: %p :: Either AM or PM, according to the given time value; or the corresponding strings for the current locale. Noon is treated as PM and midnight as AM. In most locales AM/PM format is not supported, in such cases %p yields an empty string. +#@ For example: 'PM' +#@ R: %r :: The complete calendar time using the AM/PM format of the current locale. +#@ For example: '11:31:30 PM' +#@ R: %t :: A single \t (tabulator) character. +#@ For example: ' ' +#@ R: %u :: The day of the week as a decimal number (range 1 through 7), Monday being 1. +#@ For example: '5' +#@ R: %w :: The day of the week as a decimal number (range 0 through 6), Sunday being 0. +#@ For example: '5' +#@ R: %x :: The preferred date representation for the current locale. +#@ For example: '02/13/09' +#@ R: %y :: The year without a century as a decimal number (range 00 through 99). This is equivalent to the year modulo 100. +#@ For example: '09' +#@ R: %z :: *RFC 822*/*ISO 8601:1988* style numeric time zone (e.g., -0600 or +0100), or nothing if no time zone is determinable. +#@ For example: '+0000' +#@ ``` +#+end_src diff --git a/examples/string.cf b/examples/string.cf new file mode 100644 index 0000000000..9953b0d2f9 --- /dev/null +++ b/examples/string.cf @@ -0,0 +1,24 @@ +#+begin_src cfengine3 +bundle agent main +{ + classes: + "classA"; + "classB"; + + vars: + "some_string" string => "cba"; + "class_expressions" slist => {"classA.classB", + string(and("classA", strcmp("$(some_string)", "abc"))) + }; + + reports: + "$(class_expressions)"; +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: classA.classB +#@ R: !any +#@ ``` +#+end_src diff --git a/examples/string_downcase.cf b/examples/string_downcase.cf new file mode 100644 index 0000000000..d9d0eb2a7d --- /dev/null +++ b/examples/string_downcase.cf @@ -0,0 +1,42 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "downcase" string => string_downcase("ABC"); # will contain "abc" + reports: + "downcased ABC = $(downcase)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: downcased ABC = abc +#@ ``` +#+end_src diff --git a/examples/string_head.cf b/examples/string_head.cf new file mode 100644 index 0000000000..da7d51febe --- /dev/null +++ b/examples/string_head.cf @@ -0,0 +1,43 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "start" string => string_head("abc", "1"); # will contain "a" + reports: + "start of abc = $(start)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: start of abc = a +#@ ``` +#+end_src diff --git a/examples/string_length.cf b/examples/string_length.cf new file mode 100644 index 0000000000..daebc9bc8a --- /dev/null +++ b/examples/string_length.cf @@ -0,0 +1,42 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "length" int => string_length("abc"); # will contain "3" + reports: + "length of string abc = $(length)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: length of string abc = 3 +#@ ``` +#+end_src diff --git a/examples/string_mustache.cf b/examples/string_mustache.cf new file mode 100644 index 0000000000..4e646df464 --- /dev/null +++ b/examples/string_mustache.cf @@ -0,0 +1,70 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "config", "example" }; +} + +bundle agent config +{ + vars: + "deserts" data => parsejson('{ "deserts": { + "Africa": "Sahara", + "Asia": "Gobi" +} }'); +} + + +bundle agent example +{ + vars: + # {{@}} is the current key during an iteration in 3.7 with Mustache + "with_data_container" string => string_mustache("from container: deserts = {{%deserts}} +from container: {{#deserts}}The desert {{.}} is in {{@}}. {{/deserts}}", "config.deserts"); + + # you can dump an entire data structure with {{%myvar}} in 3.7 with Mustache + "with_system_state" string => string_mustache("from datastate(): deserts = {{%vars.config.deserts.deserts}} +from datastate(): {{#vars.config.deserts.deserts}}The desert {{.}} is in {{@}}. {{/vars.config.deserts.deserts}}"); # will use datastate() + + reports: + "With an explicit data container: $(with_data_container)"; + + "With the system datastate(): $(with_system_state)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: With an explicit data container: from container: deserts = { +#@ "Africa": "Sahara", +#@ "Asia": "Gobi" +#@ } +#@ from container: The desert Sahara is in Africa. The desert Gobi is in Asia. +#@ R: With the system datastate(): from datastate(): deserts = { +#@ "Africa": "Sahara", +#@ "Asia": "Gobi" +#@ } +#@ from datastate(): The desert Sahara is in Africa. The desert Gobi is in Asia. +#@ ``` +#+end_src diff --git a/examples/string_replace.cf b/examples/string_replace.cf new file mode 100644 index 0000000000..1e4e84940b --- /dev/null +++ b/examples/string_replace.cf @@ -0,0 +1,52 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + # replace one occurence + "replace_once" string => string_replace("This is a string", "string", "thing"); + # replace several occurences + "replace_several" string => string_replace("This is a string", "i", "o"); + # replace nothing + "replace_none" string => string_replace("This is a string", "boat", "no"); + # replace ambiguous order + "replace_ambiguous" string => string_replace("aaaaa", "aaa", "b"); + + reports: + # in order, the above... + "replace_once = '$(replace_once)'"; + "replace_several = '$(replace_several)'"; + "replace_none = '$(replace_none)'"; + "replace_ambiguous = '$(replace_ambiguous)'"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: replace_once = 'This is a thing' +#@ R: replace_several = 'Thos os a strong' +#@ R: replace_none = 'This is a string' +#@ R: replace_ambiguous = 'baa' +#@ ``` +#+end_src diff --git a/examples/string_reverse.cf b/examples/string_reverse.cf new file mode 100644 index 0000000000..402030d2ca --- /dev/null +++ b/examples/string_reverse.cf @@ -0,0 +1,43 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "reversed" + string => string_reverse("abc"); # will contain "cba" + reports: + "reversed abs = $(reversed)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: reversed abs = cba +#@ ``` +#+end_src diff --git a/examples/string_split.cf b/examples/string_split.cf new file mode 100644 index 0000000000..7e1cf4a4bc --- /dev/null +++ b/examples/string_split.cf @@ -0,0 +1,55 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + + "split1" slist => string_split("one:two:three", ":", "10"); + "split2" slist => string_split("one:two:three", ":", "1"); + "split3" slist => string_split("alpha:xyz:beta", "xyz", "10"); + + reports: + + "split1: $(split1)"; # will list "one", "two", and "three" + "split2: $(split2)"; # will list "one:two:three" + "split3: $(split3)"; # will list "alpha:" and ":beta" + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: split1: one +#@ R: split1: two +#@ R: split1: three +#@ R: split2: one:two:three +#@ R: split3: alpha: +#@ R: split3: :beta +#@ ``` +#+end_src diff --git a/examples/string_tail.cf b/examples/string_tail.cf new file mode 100644 index 0000000000..441af12280 --- /dev/null +++ b/examples/string_tail.cf @@ -0,0 +1,43 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "end" string => string_tail("abc", "1"); # will contain "c" + reports: + "end of abc = $(end)"; + +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: end of abc = c +#@ ``` +#+end_src diff --git a/examples/string_trim.cf b/examples/string_trim.cf new file mode 100644 index 0000000000..466c3a0125 --- /dev/null +++ b/examples/string_trim.cf @@ -0,0 +1,28 @@ +#+begin_src cfengine3 +bundle agent example_trim +# @brief Example showing string_trim +{ + vars: + "my_string_one" string => string_trim( " Trim spaces please "); + "my_string_two" string => string_trim( " + Trim newlines also please + + "); + + reports: + "my_string_one: '$(my_string_one)'"; + "my_string_two: '$(my_string_two)'"; +} + +bundle agent __main__ +{ + methods: "example_trim"; +} +############################################################################### +#+end_src +#+begin_src example_output +#@ ``` +#@ R: my_string_one: 'Trim spaces please' +#@ R: my_string_two: 'Trim newlines also please' +#@ ``` +#+end_src diff --git a/examples/string_upcase.cf b/examples/string_upcase.cf new file mode 100644 index 0000000000..85edc87a88 --- /dev/null +++ b/examples/string_upcase.cf @@ -0,0 +1,42 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "upcase" string => string_upcase("abc"); # will contain "ABC" + reports: + "upcased abc: $(upcase)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: upcased abc: ABC +#@ ``` +#+end_src diff --git a/examples/stringarray.cf b/examples/stringarray.cf new file mode 100644 index 0000000000..147d9b22be --- /dev/null +++ b/examples/stringarray.cf @@ -0,0 +1,47 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "dim_array" + + int => readstringarray("array_name","/etc/passwd","#[^\n]*",":",10,4000); + + "idx" slist => getindices("array_name"); + + reports: + + "Index $(idx): [1]=$(array_name[$(idx)][1]),[2]=$(array_name[$(idx)][2])...[7]=$(array_name[$(idx)][6])"; +} + diff --git a/examples/style_PascaleCase.cf b/examples/style_PascaleCase.cf new file mode 100644 index 0000000000..739bc5ca1f --- /dev/null +++ b/examples/style_PascaleCase.cf @@ -0,0 +1,31 @@ +bundle agent __main__ +{ + methods: + "Ssh"; +} +bundle agent Ssh +{ + vars: + "ServiceName" string => "ssh"; + "ConfigFile" string => "/etc/ssh/sshd_config"; + "Conf[Port]" string => "22"; + + files: + "$(ConfigFile)" + edit_line => default:set_line_based("$(this.bundle).Conf", + " ", + "\s+", + ".*", + "\s*#\s*"), + classes => default:results( "bundle", "$(ConfigFile)"); + + services: + _etc_ssh_sshd_config_repaired:: + "$(ServiceName)" + service_policy => "restart", + classes => default:results( "bundle", "$(ServiceName)_restart"); + + reports: + ssh_restart_repaired._etc_ssh_sshd_config_repaired:: + "We restarted ssh because the config file was repaired"; +} diff --git a/examples/style_camelCase.cf b/examples/style_camelCase.cf new file mode 100644 index 0000000000..87fb29d4d7 --- /dev/null +++ b/examples/style_camelCase.cf @@ -0,0 +1,31 @@ +bundle agent __main__ +{ + methods: + "Ssh"; +} +bundle agent ssh +{ + vars: + "serviceName" string => "ssh"; + "configFile" string => "/etc/ssh/sshd_config"; + "conf[Port]" string => "22"; + + files: + "$(configFile)" + edit_line => default:set_line_based("$(this.bundle).conf", + " ", + "\s+", + ".*", + "\s*#\s*"), + classes => default:results( "bundle", "$(configFile)"); + + services: + _etc_ssh_sshd_config_repaired:: + "$(serviceName)" + service_policy => "restart", + classes => default:results( "bundle", "$(serviceName)_restart"); + + reports: + ssh_restart_repaired._etc_ssh_sshd_config_repaired:: + "We restarted ssh because the config file was repaired"; +} diff --git a/examples/style_hungarian.cf b/examples/style_hungarian.cf new file mode 100644 index 0000000000..49d1ddf76d --- /dev/null +++ b/examples/style_hungarian.cf @@ -0,0 +1,52 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + vars: + "s_one" string => "one"; + "ITwo" int => "2"; + "rThree" real => "3.0"; + + "lMyList" slist => { "$(s_one)", "$(ITwo)", "$(rThree)" }; + + methods: + "Iteration inside (bundle called once)" + usebundle => dollar_vs_at( @(lMyList) ); + + "Iteration outside (bundle called length(lMyList) times)" + usebundle => dollar_vs_at( $(lMyList) ); +} + +bundle agent dollar_vs_at( myParam ) +{ + vars: + "myParamType" string => type( myParam ); + + classes: + "myParamType_slist" expression => strcmp( $(myParamType), "slist" ); + "myParamType_string" expression => strcmp( $(myParamType), "string" ); + + reports: + "Bundle promised by '$(with)'" + with => nth( reverse( callstack_promisers() ), 0 ); + + myParamType_slist:: + "myParam is of type '$(myParamType)' with value $(with)" + with => join( ", ", @(myParam) ); + + myParamType_string:: + "myParam is of type '$(myParamType)' with value $(myParam)"; + +} +#+end_src cfengine3 + +#+begin_src example_output +#@ ``` +#@ R: Bundle promised by 'Iteration inside (bundle called once)' +#@ R: myParam is of type 'slist' with value one, 2, 3.000000 +#@ R: Bundle promised by 'Iteration outside (bundle called length(lMyList) times)' +#@ R: myParam is of type 'string' with value one +#@ R: myParam is of type 'string' with value 2 +#@ R: myParam is of type 'string' with value 3.000000 +#@ ``` +#+end_src + diff --git a/examples/style_snake_case.cf b/examples/style_snake_case.cf new file mode 100644 index 0000000000..3d83833bdd --- /dev/null +++ b/examples/style_snake_case.cf @@ -0,0 +1,31 @@ +bundle agent __main__ +{ + methods: + "ssh"; +} +bundle agent ssh +{ + vars: + "service_name" string => "ssh"; + "config_file" string => "/etc/ssh/sshd_config"; + "conf[Port]" string => "22"; + + files: + "$(config_file)" + edit_line => default:set_line_based("$(this.bundle).conf", + " ", + "\s+", + ".*", + "\s*#\s*"), + classes => default:results( "bundle", "$(config_file)"); + + services: + _etc_ssh_sshd_config_repaired:: + "$(service_name)" + service_policy => "restart", + classes => default:results( "bundle", "$(service_name)_restart"); + + reports: + ssh_restart_repaired._etc_ssh_sshd_config_repaired:: + "We restarted ssh because the config file was repaired"; +} diff --git a/examples/sublist.cf b/examples/sublist.cf new file mode 100644 index 0000000000..d2104f8309 --- /dev/null +++ b/examples/sublist.cf @@ -0,0 +1,109 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + }; + + "test_head9999" slist => sublist("test", "head", 9999); + "test_head1" slist => sublist("test", "head", 1); + "test_head0" slist => sublist("test", "head", 0); + + "test_tail9999" slist => sublist("test", "tail", 9999); + "test_tail10" slist => sublist("test", "tail", 10); + "test_tail2" slist => sublist("test", "tail", 2); + "test_tail1" slist => sublist("test", "tail", 1); + "test_tail0" slist => sublist("test", "tail", 0); + + reports: + "The test list is $(test)"; + "This line should not appear: $(test_head0)"; + "The head(1) of the test list is $(test_head1)"; + "The head(9999) of the test list is $(test_head9999)"; + "This line should not appear: $(test_tail0)"; + "The tail(1) of the test list is $(test_tail1)"; + "The tail(10) of the test list is $(test_tail10)"; + "The tail(2) of the test list is $(test_tail2)"; + "The tail(9999) of the test list is $(test_tail9999)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The test list is 1 +#@ R: The test list is 2 +#@ R: The test list is 3 +#@ R: The test list is one +#@ R: The test list is two +#@ R: The test list is three +#@ R: The test list is long string +#@ R: The test list is four +#@ R: The test list is fix +#@ R: The test list is six +#@ R: The head(1) of the test list is 1 +#@ R: The head(9999) of the test list is 1 +#@ R: The head(9999) of the test list is 2 +#@ R: The head(9999) of the test list is 3 +#@ R: The head(9999) of the test list is one +#@ R: The head(9999) of the test list is two +#@ R: The head(9999) of the test list is three +#@ R: The head(9999) of the test list is long string +#@ R: The head(9999) of the test list is four +#@ R: The head(9999) of the test list is fix +#@ R: The head(9999) of the test list is six +#@ R: The tail(1) of the test list is six +#@ R: The tail(10) of the test list is 1 +#@ R: The tail(10) of the test list is 2 +#@ R: The tail(10) of the test list is 3 +#@ R: The tail(10) of the test list is one +#@ R: The tail(10) of the test list is two +#@ R: The tail(10) of the test list is three +#@ R: The tail(10) of the test list is long string +#@ R: The tail(10) of the test list is four +#@ R: The tail(10) of the test list is fix +#@ R: The tail(10) of the test list is six +#@ R: The tail(2) of the test list is fix +#@ R: The tail(2) of the test list is six +#@ R: The tail(9999) of the test list is 1 +#@ R: The tail(9999) of the test list is 2 +#@ R: The tail(9999) of the test list is 3 +#@ R: The tail(9999) of the test list is one +#@ R: The tail(9999) of the test list is two +#@ R: The tail(9999) of the test list is three +#@ R: The tail(9999) of the test list is long string +#@ R: The tail(9999) of the test list is four +#@ R: The tail(9999) of the test list is fix +#@ R: The tail(9999) of the test list is six +#@ ``` +#+end_src diff --git a/examples/sum.cf b/examples/sum.cf new file mode 100644 index 0000000000..5aa0fe157a --- /dev/null +++ b/examples/sum.cf @@ -0,0 +1,46 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "adds_to_six" ilist => { "1", "2", "3" }; + "six" real => sum("adds_to_six"); + "adds_to_zero" rlist => { "1.0", "2", "-3e0" }; + "zero" real => sum("adds_to_zero"); + + reports: + "six is $(six), zero is $(zero)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: six is 6.000000, zero is 0.000000 +#@ ``` +#+end_src diff --git a/examples/switchcase.cf b/examples/switchcase.cf new file mode 100644 index 0000000000..7865a8448c --- /dev/null +++ b/examples/switchcase.cf @@ -0,0 +1,73 @@ + +body common control +{ + bundlesequence => { "test1", "test2" }; +} + +################################################## + +bundle agent test1 +{ + classes: + + "default" expression => "any"; + + reports: + + linux:: + + "This is a linux box" + classes => exclusive; + + solaris:: + + + "This is a solaris box" + classes => exclusive; + + default:: + + "This is something not worth mentioning specifically" + classes => reset_default; +} + +################################################## + +bundle agent test2 +{ + classes: + + "default" expression => "any"; + + reports: + + linux:: + + "This is another linux box" + classes => exclusive; + + solaris:: + + "This is another solaris box" + classes => exclusive; + + default:: + + "This is something else not worth mentioning specifically" + classes => reset_default; + +} + +########################################################## + +body classes exclusive +{ + cancel_kept => { "default" }; + cancel_notkept => { "default" }; + cancel_repaired => { "default" }; +} + +body classes reset_default +{ + promise_kept => { "default" }; +} diff --git a/examples/symlink.cf b/examples/symlink.cf new file mode 100644 index 0000000000..f89e51f1ca --- /dev/null +++ b/examples/symlink.cf @@ -0,0 +1,41 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +######################################################## + +body file control +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent main +{ + files: + + # We use move_obstructions because we want the symlink to replace a + # regular file if necessary. + "/etc/apache2/sites-enabled/www.cfengine.com" -> { "webmaster@cfengine.com" } + link_from => ln_s( "/etc/apache2/sites-available/www.cfengine.com" ), + move_obstructions => "true", + comment => "We always want our website to be enabled."; +} + +######################################################### diff --git a/examples/symlink_children.cf b/examples/symlink_children.cf new file mode 100644 index 0000000000..338fa1e4fd --- /dev/null +++ b/examples/symlink_children.cf @@ -0,0 +1,44 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +######################################################## + +#+begin_src cfengine3 +body file control +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent main +{ + files: + + # This will make symlinks to each file in /var/cfengine/bin + # for example: + # '/usr/local/bin/cf-agent' -> '/var/cfengine/bin/cf-agent' + # '/usr/local/bin/cf-serverd' -> '/var/cfengine/bin/cf-serverd' + "/usr/local/bin" + link_from => linkchildren("/var/cfengine/bin"), + comment => "We like for cfengine binaries to be available inside of the + common $PATH"; +} +#+end_src +######################################################## diff --git a/examples/sys_interfaces_ip_addresses_ipv4.cf b/examples/sys_interfaces_ip_addresses_ipv4.cf new file mode 100644 index 0000000000..39c93e1c91 --- /dev/null +++ b/examples/sys_interfaces_ip_addresses_ipv4.cf @@ -0,0 +1,62 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent example_sys_interfaces +# @brief Illustrate iterating over interfaces and addresses +{ + reports: + "Address: $(sys.ip_addresses)"; + "Interface: $(sys.interfaces)"; + "Address of '$(sys.interfaces)' is '$(sys.ipv4[$(sys.interfaces)])'"; +} +bundle agent __main__ +{ + methods: + "example_sys_interfaces"; +} +#+end_src +############################################################################### +#+begin_src static_example_output +#@ ``` +#@ R: Address: 127.0.0.1 +#@ R: Address: 192.168.42.189 +#@ R: Address: 192.168.122.1 +#@ R: Address: 172.17.0.1 +#@ R: Address: 192.168.33.1 +#@ R: Address: 172.27.224.211 +#@ R: Address: 192.168.69.1 +#@ R: Interface: wlan0 +#@ R: Interface: virbr0 +#@ R: Interface: docker0 +#@ R: Interface: vboxnet3 +#@ R: Interface: tun0 +#@ R: Interface: vboxnet13 +#@ R: Address of 'wlan0' is '192.168.42.189' +#@ R: Address of 'virbr0' is '192.168.122.1' +#@ R: Address of 'docker0' is '172.17.0.1' +#@ R: Address of 'vboxnet3' is '192.168.33.1' +#@ R: Address of 'tun0' is '172.27.224.211' +#@ R: Address of 'vboxnet13' is '192.168.69.1' +#@ ``` +#+end_src + diff --git a/examples/syslog.cf b/examples/syslog.cf new file mode 100644 index 0000000000..574cf6c91f --- /dev/null +++ b/examples/syslog.cf @@ -0,0 +1,47 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +body common control +{ + bundlesequence => { "one" }; +} + + + +bundle agent one +{ + files: + + "/tmp/xyz" + + create => "true", + action => log; + +} + +body action log +{ + log_level => "inform"; +} + + diff --git a/examples/syslog2.cf b/examples/syslog2.cf new file mode 100644 index 0000000000..0cf62746fd --- /dev/null +++ b/examples/syslog2.cf @@ -0,0 +1,34 @@ +# +# With Nova, log directly to a central server -- careful of scalability (UDP) +# + +body common control +{ + bundlesequence => { "example" }; + syslog_host => "loghost.example.org"; +} + +# + +bundle agent example +{ + vars: + + "software" slist => { "/root/xyz", "/tmp/xyz" }; + + files: + + "$(software)" + + create => "true", + action => logme("$(software)"); + +} + +# + +body action logme(x) +{ + log_repaired => "udp_syslog"; + log_string => "cfengine repaired promise $(this.handle) - $(x)"; +} diff --git a/examples/template.cf b/examples/template.cf new file mode 100644 index 0000000000..bf7b45e775 --- /dev/null +++ b/examples/template.cf @@ -0,0 +1,65 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Simple test editfile - template expansion +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/home/mark/tmp/file_based_on_template" + + create => "true", + edit_line => ExpandMeFrom("/tmp/source_template"); + + +} + +######################################################## + +bundle edit_line ExpandMeFrom(template) +{ + vars: + + "myvar" string => "[sub string]"; + + insert_lines: + + "$(template)" + + insert_type => "file", + expand_scalars => "true"; +} + diff --git a/examples/template2.cf b/examples/template2.cf new file mode 100644 index 0000000000..8e506caeb3 --- /dev/null +++ b/examples/template2.cf @@ -0,0 +1,47 @@ + +body common control +{ + bundlesequence => { "example" }; + + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent example +{ + methods: + + "any" usebundle => get_template("/tmp/sudoers","400"); + "any" usebundle => get_template("/tmp/hosts","644"); + +} + +############################################################# + +bundle agent get_template(final_destination,mode) +{ + vars: + + # This needs to ne preconfigured to your site + + "masterfiles" string => "/home/mark/tmp"; + "this_template" string => lastnode("$(final_destination)","/"); + + files: + + "$(final_destination).staging" + + comment => "Get template and expand variables for this host", + perms => mo("400","root"), + copy_from => remote_cp("$(masterfiles)/templates/$(this_template)","$(policy_server)"), + action => if_elapsed("60"); + + "$(final_destination)" + + comment => "Expand the template", + create => "true", + edit_line => expand_template("$(final_destination).staging"), + edit_defaults => empty, + perms => mo("$(mode)","root"), + action => if_elapsed("60"); + +} diff --git a/examples/template_method-inline_mustache.cf b/examples/template_method-inline_mustache.cf new file mode 100644 index 0000000000..b78550f6c1 --- /dev/null +++ b/examples/template_method-inline_mustache.cf @@ -0,0 +1,72 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of Cfengine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +############################################################################### +#+begin_src cfengine3 +bundle agent example_using_template_method_inline_mustache +{ + vars: + + # Here we construct a data container that will be passed to the mustache + # templating engine + + "d" + data => '{ "host": "docs.cfengine.com" }'; + + # Here we specify a string that will be used as an inline mustache template + "mustache_template_string" + string => "Welcome to host '{{{host}}}'"; + + files: + # Here we render the file using the data container and inline template specification + + "/tmp/example.txt" + create => "true", + template_method => "inline_mustache", + edit_template_string => "$(mustache_template_string)", + template_data => @(d); + + reports: + "/tmp/example.txt" + printfile => cat( $(this.promiser) ); +} + +# Copied from stdlib, lib/reports.cf +body printfile cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} +bundle agent __main__ +{ + methods: "example_using_template_method_inline_mustache"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /tmp/example.txt +#@ R: Welcome to host 'docs.cfengine.com' +#@ ``` +#+end_src diff --git a/examples/test_environment.cf b/examples/test_environment.cf new file mode 100644 index 0000000000..54f2be9e9a --- /dev/null +++ b/examples/test_environment.cf @@ -0,0 +1,103 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Guest environments +# +####################################################### + +body common control + +{ + bundlesequence => { "my_vm_cloud" }; +} + +####################################################### + +bundle agent my_vm_cloud + +{ + guest_environments: + + scope||any:: # These should probably be in class "any" to ensure uniqueness + + "test2" + + environment_resources => my_environment, + environment_interface => vnet("eth0,192.168.1.100/24"), + environment_type => "test", + environment_state => "create", + environment_host => "atlas"; + + + "test2" + + environment_resources => my_environment, + environment_interface => vnet("eth0,192.168.1.101/24"), + environment_type => "test", + environment_state => "delete", + environment_host => "atlas"; + + + "test4" + + environment_resources => my_environment, + environment_interface => vnet("eth0,192.168.1.102/24"), + environment_type => "test", + environment_state => "create", + environment_host => "atlas"; + + "network1" + environment_type => "test_net", + environment_state => "create", + environment_host => "atlas"; + + + # default environment_state => "create" on host, and "suspended elsewhere" +} + +####################################################### + +body environment_resources my_environment +{ + env_cpus => "2"; + env_memory => "512"; # in KB + env_disk => "1024"; # in MB +} + +####################################################### + +body environment_interface vnet(primary) +{ + env_name => "$(this.promiser)"; + env_addresses => { "$(primary)" }; + + host1:: + + env_network => "default_vnet1"; + + host2:: + + env_network => "default_vnet2"; + +} diff --git a/examples/tidy_all_files.cf b/examples/tidy_all_files.cf new file mode 100644 index 0000000000..413b8e38d7 --- /dev/null +++ b/examples/tidy_all_files.cf @@ -0,0 +1,87 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Deleting files, like cf2 tidy age=0 r=inf +# +####################################################### + +body common control + +{ + any:: + + bundlesequence => { "example" }; +} + +############################################ + +bundle agent example + +{ + files: + + "/home/mark/tmp/test_to" + + delete => tidyfiles, + file_select => zero_age, + depth_search => recurse("inf"); + + # Now delete the parent. + + "/home/mark/tmp/testcopy" + delete => tidyfiles; +} + +######################################################### + +body depth_search recurse(d) + +{ + #include_basedir => "true"; + depth => "$(d)"; +} + +######################################################### + +body delete tidyfiles + +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +######################################################### + +body file_select zero_age + +# +# we can build old "include", "exclude", and "ignore" +# from these as standard patterns - these bodies can +# form a library of standard patterns +# + +{ + mtime => irange(ago(1,0,0,0,0,0),now); + file_result => "mtime"; +} diff --git a/examples/topic_map.ltm b/examples/topic_map.ltm new file mode 100644 index 0000000000..a15334f2d3 --- /dev/null +++ b/examples/topic_map.ltm @@ -0,0 +1,67 @@ +/*********************/ +/* Class types */ +/*********************/ + +[WWW: Services = "World Wide Web service (WWW)"] + +[looks_up_addresses_with = "looks up addresses with" + = "serves addresses to" / Programs] + + +[is_implemented_by = "is implemented by" + = "implements" / Programs] + +[named: Programs = "A name service process (named)"] +[httpd: Programs = "A web service process (httpd)"] +[desktop: Computers = "Common name for a computer for end users (desktop)"] +[server: Computers = "Common name for a computer in a datacentre without separate screen and keyboard (server)"] +[Computers = "Generic boxes (Computers)"] + +[run = "run" + = "are run on" / unknown_association_counterpart] +[Processes = "Programs running on a computer (Processes)"] + +/*********************/ +/* Association types */ +/*********************/ + +looks_up_addresses_with( WWW : Services, named : Programs) +is_implemented_by( WWW : Services, httpd : Programs) +run( Computers : any, Services : any) + +/*********************/ +/* Occurrences */ +/*********************/ + + + /* occurrences of WWW */ + +{WWW,Explanation, [[World Wide Web service]]} + + /* occurrences of named */ + +{named,Explanation, [[A name service process]]} + + /* occurrences of httpd */ + +{httpd,website, "http://www.apache.org"} + + /* occurrences of httpd */ + +{httpd,Explanation, [[A web service process]]} + + /* occurrences of desktop */ + +{desktop,Explanation, [[Common name for a computer for end users]]} + + /* occurrences of server */ + +{server,Explanation, [[Common name for a computer in a datacentre without separate screen and keyboard]]} + + /* occurrences of Computers */ + +{Computers,Explanation, [[Generic boxes]]} + + /* occurrences of Processes */ + +{Processes,Explanation, [[Programs running on a computer]]} diff --git a/examples/translatepath.cf b/examples/translatepath.cf new file mode 100644 index 0000000000..8271a24f93 --- /dev/null +++ b/examples/translatepath.cf @@ -0,0 +1,48 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "inputs_dir" string => translatepath("/a/b/c/inputs"); + + reports: + + windows:: + "The path has backslashes: $(inputs_dir)"; + + !windows:: + "The path has slashes: $(inputs_dir)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The path has slashes: /a/b/c/inputs +#@ ``` +#+end_src diff --git a/examples/type.cf b/examples/type.cf new file mode 100644 index 0000000000..07ab7a2032 --- /dev/null +++ b/examples/type.cf @@ -0,0 +1,45 @@ +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "foo" + data => '{ "bar": true, "baz": [1, 2, 3] }'; + + "_foo" string => type("foo", "true"); + "_foobar" string => type("foo[bar]", "false"); + "_foobaz" string => type("foo[baz]", "true"); + + "a" string => "hello"; + "b" int => "123"; + "c" rlist => { "1.1", "2.2", "3.3" }; + + "_a" string => type("a"); + "_b" string => type("b", "true"); + "_c" string => type("c", "false"); + + reports: + "'foo' is of type '$(_foo)' (detail)"; + "'foo[bar]' is of type '$(_foobar)' (no detail)"; + "'foo[baz]' is of type '$(_foobaz)' (detail)"; + + "'a' is of type '$(_a)' (no detail)"; + "'b' is of type '$(_b)' (detail)"; + "'c' is of type '$(_c)' (no detail)"; +} +#+end_src +############################################################################# +#+begin_src example_output +#@ ``` +#@ R: 'foo' is of type 'data object' (detail) +#@ R: 'foo[bar]' is of type 'data' (no detail) +#@ R: 'foo[baz]' is of type 'data array' (detail) +#@ R: 'a' is of type 'string' (no detail) +#@ R: 'b' is of type 'policy int' (detail) +#@ R: 'c' is of type 'rlist' (no detail) +#@ ``` +#+end_src diff --git a/examples/unique.cf b/examples/unique.cf new file mode 100644 index 0000000000..028f4b7673 --- /dev/null +++ b/examples/unique.cf @@ -0,0 +1,55 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; + + "test_str" string => join(",", "test"); + "test_unique" slist => unique("test"); + "unique_str" string => join(",", "test_unique"); + + reports: + "The test list is $(test_str)"; + "The unique elements of the test list: $(unique_str)"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: The test list is 1,2,3,one,two,three,long string,four,fix,six,one,two,three +#@ R: The unique elements of the test list: 1,2,3,one,two,three,long string,four,fix,six +#@ ``` +#+end_src diff --git a/examples/unpack_method_calls.cf b/examples/unpack_method_calls.cf new file mode 100644 index 0000000000..7f0a8dae58 --- /dev/null +++ b/examples/unpack_method_calls.cf @@ -0,0 +1,39 @@ +#+begin_src cfengine3 +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + "todo" slist => { "call_1,a,b", "call_2,x,y", "call_2,p,q" }; + + methods: + "call" usebundle => unpack($(todo)); +} + +bundle agent unpack(list) +{ + vars: + "split" slist => splitstring($(list), ",", "100"); + "method" string => nth("split", "0"); + "param1" string => nth("split", "1"); + "param2" string => nth("split", "2"); + + methods: + "relay" usebundle => $(method)($(param1), $(param2)); +} + +bundle agent call_1(p1, p2) +{ + reports: + "$(this.bundle): called with parameters $(p1) and $(p2)"; +} + +bundle agent call_2(p1, p2) +{ + reports: + "$(this.bundle): called with parameters $(p1) and $(p2)"; +} +#+end_src diff --git a/examples/update.cf b/examples/update.cf new file mode 100644 index 0000000000..0c1a93656d --- /dev/null +++ b/examples/update.cf @@ -0,0 +1,142 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#cop update,example +#cop What should a failsafe and update file contain?,example + +# Minimum failsafe + +body common control +{ + bundlesequence => { "update" }; +} + +bundle agent update +{ + vars: + + "master_location" string => "$(sys.workdir)/masterfiles"; + + "policy_server" string => readfile("$(sys.workdir)/policy_server.dat",40), + comment => "IP address to locate your policy host."; + + classes: + + "policy_host" or => { + classmatch(canonify("ipv4_$(policy_server)")), + classmatch(canonify("$(policy_server)")) + }, + + comment => "Define the ip identity of the policy source host"; + + "have_ppkeys" expression => fileexists("$(sys.workdir)/ppkeys/localhost.pub"); + + "nofile" expression => fileexists("$(sys.workdir)/policy_server.dat"); + + commands: + + !have_ppkeys:: + + "/var/cfengine/bin/cf-key"; + + files: + + "/var/cfengine/inputs" + + handle => "update_policy", + perms => u_p("600"), + copy_from => u_scp("$(master_location)"), + depth_search => u_recurse("inf"), + action => immediate; + + processes: + + any:: + + "cf-execd" restart_class => "start_exec"; + + policy_host:: + + "cf-serverd" restart_class => "start_server"; + + commands: + + start_exec:: + "$(sys.workdir)/bin/cf-execd" + action => logme("executor"); + + start_server:: + "$(sys.workdir)/bin/cf-serverd" + action => logme("server"); + + reports: + + bootstrap_mode.policy_host:: + + "I am the policy host - i.e. with ipv4 address $(policy_server)"; + +} + +############################################ + +body action logme(x) +{ + log_repaired => "stdout"; + log_string => " -> Started the $(x) (success)"; +} + +############################################ + +body perms u_p(p) + +{ + mode => "$(p)"; +} + +############################################# + +body copy_from u_scp(from) + +{ + source => "$(from)"; + compare => "digest"; + trustkey => "true"; + + !policy_host:: + + servers => { "$(policy_server)" }; +} + +######################################################### + +body action immediate +{ + ifelapsed => "1"; +} + +############################################ + +body depth_search u_recurse(d) + +{ + depth => "$(d)"; +} diff --git a/examples/user_edit.cf b/examples/user_edit.cf new file mode 100644 index 0000000000..1a19d20ee9 --- /dev/null +++ b/examples/user_edit.cf @@ -0,0 +1,69 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# + +body common control +{ + bundlesequence => { "users" }; +} + +body agent control +{ + abortbundleclasses => { "ldap_fail" }; +} + +################################################################################ + +bundle agent users +{ + vars: + + "users" slist => { "mark", "nakarin", "jonhenrik" }; + + files: + + "/tmp/passwd2" + + create => "true", + edit_line => fix_passwd("$(users)"); + + reports: + + !ldap_fail:: + + "Editing user $(users)"; +} + +################################################################################################### + +bundle edit_line fix_passwd(x) +{ + classes: + + "ldap_fail" not => ldaparray("dat","ldap://eternity.iu.hio.no","dc=cfengine,dc=com","(uid=$(x))","subtree","none"); + + insert_lines: + + "Hello $(x),$(dat[uid]),$(dat[gecos])"; +} diff --git a/examples/user_edit_method.cf b/examples/user_edit_method.cf new file mode 100644 index 0000000000..fc1a354925 --- /dev/null +++ b/examples/user_edit_method.cf @@ -0,0 +1,81 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + +# + +body common control +{ + bundlesequence => { "users" }; +} + +body agent control +{ + abortbundleclasses => { "ldap_fail" }; +} + +#################################################################################### + +bundle agent users +{ + vars: + + "users" slist => { "mark", "nakarin", "three" }; + + methods: + + "all" usebundle => fix_user("$(users)"); + +} + +################################################################################### + +bundle agent fix_user(x) + +{ + classes: + + "ldap_fail" not => ldaparray("dat","ldap://eternity.iu.hio.no","dc=cfengine,dc=com","(uid=$(x))","subtree","none"); + + files: + + "/tmp/passwd" + + create => "true", + edit_line => fix_passwd("$(x)"); + + reports: + + !ldap_fail:: + + "Editing user $(x)"; +} + +#################################################################################### + +bundle edit_line fix_passwd(x) + +{ + insert_lines: + + "Hello $(x),$(fix_user.dat[uid]),$(fix_user.dat[gecos])"; +} diff --git a/examples/userexists.cf b/examples/userexists.cf new file mode 100644 index 0000000000..979a8f4f97 --- /dev/null +++ b/examples/userexists.cf @@ -0,0 +1,51 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + + "ok" expression => userexists("root"); + + reports: + + ok:: + + "Root exists"; + + !ok:: + + "Root does not exist"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Root exists +#@ ``` +#+end_src diff --git a/examples/users_type.cf b/examples/users_type.cf new file mode 100644 index 0000000000..aebf87cfa2 --- /dev/null +++ b/examples/users_type.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +### Users main BEGIN ### +bundle agent main +{ + vars: + "users" slist => { "jack", "john" }; + "skel" string => "/etc/skel"; + + users: + !windows:: + "$(users)" + policy => "present", + home_dir => "/home/$(users)", + home_bundle => home_skel($(users), $(skel)); +} +### Users main END ### + +### Home Bundle BEGIN ### +bundle agent home_skel(user, skel) +# @brief Initialize a user's home directory with the contents of skel +# @param user The user's home directory to create +# @param skel The directory to seed the user's home with +{ + files: + "/home/$(user)/." + create => "true", + copy_from => seed_cp($(skel)), + depth_search => recurse("inf"); +} +### Home Bundle END ### + +### Locked User BEGIN ### +bundle agent service_accounts +{ + vars: + "users" slist => { "apache", "libuuid" }; + + users: + !windows:: + "$(users)" + policy => "locked"; +} +### Locked User END ### diff --git a/examples/validdata.cf b/examples/validdata.cf new file mode 100644 index 0000000000..bd0255292f --- /dev/null +++ b/examples/validdata.cf @@ -0,0 +1,19 @@ +#+begin_src cfengine3 +bundle agent main +{ + vars: + "json_string" string => '{"test": [1, 2, 3]}'; + + reports: + "This JSON string is valid!" + if => validdata("$(json_string)", "JSON"); + "This JSON string is not valid." + unless => validdata("$(json_string)", "JSON"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This JSON string is valid! +#@ ``` +#+end_src diff --git a/examples/validjson.cf b/examples/validjson.cf new file mode 100644 index 0000000000..32345e9ebc --- /dev/null +++ b/examples/validjson.cf @@ -0,0 +1,19 @@ +#+begin_src cfengine3 +bundle agent main +{ + vars: + "json_string" string => '{"test": [1, 2, 3]}'; + + reports: + "This JSON string is valid!" + if => validjson("$(json_string)"); + "This JSON string is not valid." + unless => validjson("$(json_string)"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This JSON string is valid! +#@ ``` +#+end_src diff --git a/examples/varclass.cf b/examples/varclass.cf new file mode 100644 index 0000000000..8eacead6ad --- /dev/null +++ b/examples/varclass.cf @@ -0,0 +1,48 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + commands: + + "/bin/echo This is linux" + + if => "linux"; + + + "/bin/echo This is solaris" + + if => "solaris"; + +} + diff --git a/examples/varexpansion.cf b/examples/varexpansion.cf new file mode 100644 index 0000000000..6f30ee2818 --- /dev/null +++ b/examples/varexpansion.cf @@ -0,0 +1,54 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + + + +body common control + +{ + bundlesequence => { "example" }; +} + +########################################################### + +bundle agent example + +{ + vars: + + "scalar1" string => "SCALAR 1"; + "list1" slist => { "LIST1_1", "LIST1_2" } ; + + "array[1]" string => "ARRAY 1"; + "array[2]" string => "ARRAY 2"; + + "i" slist => getindices("array"); + + reports: + "Scalar $(scalar1)"; + + "LIst $(list1)"; + + "Array $(array[$(i)])"; + +} + diff --git a/examples/variablesmatching.cf b/examples/variablesmatching.cf new file mode 100644 index 0000000000..647b598a9e --- /dev/null +++ b/examples/variablesmatching.cf @@ -0,0 +1,49 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + "all" slist => variablesmatching(".*"); + "v" slist => variablesmatching("default:sys.cf_version.*"); + "v_sorted" slist => sort(v, lex); + reports: + "Variables matching 'default:sys.cf_version.*' = $(v_sorted)"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Variables matching 'default:sys.cf_version.*' = default:sys.cf_version +#@ R: Variables matching 'default:sys.cf_version.*' = default:sys.cf_version_major +#@ R: Variables matching 'default:sys.cf_version.*' = default:sys.cf_version_minor +#@ R: Variables matching 'default:sys.cf_version.*' = default:sys.cf_version_patch +#@ R: Variables matching 'default:sys.cf_version.*' = default:sys.cf_version_release +#@ ``` +#+end_src diff --git a/examples/variablesmatching_as_data.cf b/examples/variablesmatching_as_data.cf new file mode 100644 index 0000000000..19bc026792 --- /dev/null +++ b/examples/variablesmatching_as_data.cf @@ -0,0 +1,45 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + "all" data => variablesmatching_as_data(".*"); # this is huge + "v" data => variablesmatching_as_data("default:sys.cf_version_major.*"); + "v_dump" string => format("%S", v); + reports: + "Variables matching 'default:sys.cf_version.*' = $(v_dump)"; +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: Variables matching 'default:sys.cf_version.*' = {"default:sys.cf_version_major":"3"} +#@ ``` +#+end_src diff --git a/examples/varnet.cf b/examples/varnet.cf new file mode 100644 index 0000000000..d104643ff1 --- /dev/null +++ b/examples/varnet.cf @@ -0,0 +1,14 @@ + +body common control +{ + bundlesequence => { "example" }; +} + + + +bundle agent example +{ + reports: + "My default interface and ip4 address is $(sys.interface) and $(sys.ipv4)"; + "Can also extract $(sys.ipv4[Local_Area_Connection_3]), $(sys.ipv4_2[Wireless_Network_Connection])"; +} diff --git a/examples/vars.cf b/examples/vars.cf new file mode 100644 index 0000000000..92b0b6c719 --- /dev/null +++ b/examples/vars.cf @@ -0,0 +1,39 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +body common control +{ + bundlesequence => {"yes"}; +} + + +bundle agent yes +{ + vars: + + "var" string => "/one/two/last1", + comment => "This is a comment"; + + reports: + + "Test = $(var)"; +} diff --git a/examples/version_compare.cf b/examples/version_compare.cf new file mode 100644 index 0000000000..e9859f67e1 --- /dev/null +++ b/examples/version_compare.cf @@ -0,0 +1,16 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + reports: + "This will be skipped because the version comparison is false" + if => version_compare("3.21.0", "<", "3.20.99"); + "This will be printed because the version comparison is true" + if => version_compare("3.24.1", ">=", "3.24.1"); +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: This will be printed because the version comparison is true +#@ ``` +#+end_src diff --git a/examples/warnifline.cf b/examples/warnifline.cf new file mode 100644 index 0000000000..f86e560ce3 --- /dev/null +++ b/examples/warnifline.cf @@ -0,0 +1,63 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +######################################################## +# +# Warn if line matched +# +######################################################## + +body common control + +{ + bundlesequence => { "example" }; +} + +######################################################## + +bundle agent example + +{ + files: + + "/var/cfengine/inputs/.*" + + edit_line => DeleteLinesMatching(".*cfenvd.*"), + action => WarnOnly; +} + +######################################################## + +bundle edit_line DeleteLinesMatching(regex) +{ + delete_lines: + + "$(regex)" action => WarnOnly; + +} + +######################################################## + +body action WarnOnly +{ + action_policy => "warn"; +} diff --git a/examples/webserver.cf b/examples/webserver.cf new file mode 100644 index 0000000000..d6458a5bc6 --- /dev/null +++ b/examples/webserver.cf @@ -0,0 +1,121 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +####################################################### +# +# Apache 2 reconfig - modelled on SuSE +# +####################################################### + +body common control + +{ + any:: + + bundlesequence => { + apache + }; +} + +####################################################### + +bundle agent apache + +{ + files: + + "/home/mark/tmp/apache2" # "/etc/sysconfig/apache2" + + edit_line => fixapache; +} + +####################################################### +# For the library +####################################################### + +bundle edit_line fixapache + +{ # Values have the form NAME = "quoted space separated list" + + vars: + + "add_modules" slist => { + "dav", + "dav_fs", + "ssl", + "php5", + "dav_svn", + "xyz", + "superduper" + }; + + "del_modules" slist => { + "php3", + "jk", + "userdir", + "imagemap", + "alias" + }; + + insert_lines: + + "APACHE_CONF_INCLUDE_FILES=\"/site/masterfiles/local-http.conf\""; + + field_edits: + + ##################################################################### + # APACHE_MODULES="authz_host actions alias auth_basic dav dav_fs imagemap ssl php5 dav_svn authz_default jk" + ##################################################################### + + "APACHE_MODULES=.*" + + # Insert module "columns" between the quoted RHS + # using space separators + + edit_field => quotedvar("$(add_modules)","append"); + + "APACHE_MODULES=.*" + + # Delte module "columns" between the quoted RHS + # using space separators + + edit_field => quotedvar("$(del_modules)","delete"); + + # if this line already exists, edit it +} + +######################################## +# Bodies +######################################## + +body edit_field quotedvar(newval,method) + +{ + field_separator => "\""; + select_field => "2"; + value_separator => " "; + field_value => "$(newval)"; + field_operation => "$(method)"; + extend_fields => "false"; + allow_blank_fields => "true"; +} + diff --git a/examples/win_dns_client.cf b/examples/win_dns_client.cf new file mode 100644 index 0000000000..eb4d066952 --- /dev/null +++ b/examples/win_dns_client.cf @@ -0,0 +1,27 @@ +# Ensures correct DNS settings for Windows DNS clients +# Tested on: Windows Server 2003 32-bit, Windows Server 2008 R2 64-bit + + +body common control +{ + bundlesequence => { "windows_dns" }; +} + +bundle agent windows_dns +{ + vars: + "dns_servers" string => "192.168.1.3 192.168.1.4"; + + "keys" string => execresult("$(sys.winsysdir)\reg.exe query HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces", "noshell"); + "key_list" slist => splitstring("$(keys)", "\r\n", 50); + + + databases: + "$(key_list)" + comment => "Ensure DNS client configuration is correct", + handle => canonify("windows_dns_$(dns_servers)"), + database_operation => "create", + database_rows => { "NameServer,REG_SZ,$(dns_servers)" }, + database_type => "ms_registry", + if => not( strcmp("$(key_list)", "" )); +} diff --git a/examples/win_emergency.cf b/examples/win_emergency.cf new file mode 100644 index 0000000000..e4279ef885 --- /dev/null +++ b/examples/win_emergency.cf @@ -0,0 +1,73 @@ +######################################################################### +# +# win_emergency.cf - Emergency Policy To Close Potential Security Holes +# +# NOTE: The class "emergency" may be set automatically by Cfengine +# based on some criteria, or it may be explicitly set by a remote +# execution of cf-agent through cf-runagent (if this is allowed +# by the server control policy). +# +######################################################################### + +body common control +{ + bundlesequence => { "win_emergency" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent win_emergency +{ + vars: + "disable_services" slist => { + "RemoteRegistry" # Windows Remote Management + }; + + "secure_files" slist => { + "C:\Secret", + "$(sys.workdir)\secret.txt" + }; + + "close_ports" slist => { + "6510", + "9300" + }; + + + commands: + + emergency:: + + "\"$(sys.winsysdir)\netsh.exe\"" + args => "firewall add portopening ALL $(close_ports) \"Port $(close_ports)\" DISABLE", + comment => "Close firewall ports on emergency"; + + + databases: + + emergency:: + + "HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS\Cfengine" + database_operation => "create", + database_rows => { "emergency,REG_SZ,This is an emergency!" } , + database_type => "ms_registry", + comment => "Create emergency policy registry settings"; + + + files: + + emergency:: + + "$(secure_files)" + acl => strict, + comment => "Secure important file access on emergency"; + + + services: + + emergency:: + + "$(disable_services)" + service_policy => "disable", + comment => "Disable security-relevant services on emergency"; + +} diff --git a/examples/win_registry.cf b/examples/win_registry.cf new file mode 100644 index 0000000000..0b66b91e9e --- /dev/null +++ b/examples/win_registry.cf @@ -0,0 +1,94 @@ +######################################################################### +# +# win_registry.cf - Windows Registry Management +# +######################################################################### + +bundle agent win_registry +{ + vars: + + "cache_keys" slist => { + "HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS" + }; + + "reg_create" slist => + { + "value1,REG_SZ,this is the first value...", + "value2,REG_SZ,...and this is the second!" + }; + + "reg_delete" slist => + { + "value1" + }; + + + methods: + + # 1) Cfengine can cache and restore any part of the registry + + # "any" usebundle => registry_cache("@(win_registry.cache_keys)"); + # "any" usebundle => registry_restore("@(win_registry.cache_keys)"); + + # 2) Registry settings may also be explicitly defined/deleted in policy + + # "any" usebundle => registry_define("HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS\Cfengine", "@(win_registry.reg_create)"); + # "any" usebundle => registry_delete("HKEY_LOCAL_MACHINE\SOFTWARE\Northern.tech AS\Cfengine", "@(win_registry.reg_delete)"); +} + +######################################################################### + +bundle agent registry_cache(keys) +{ + databases: + windows:: + + "$(keys)" + database_operation => "cache", + database_type => "ms_registry", + comment => "Save correct registry settings"; +} + +######################################################################### + +bundle agent registry_restore(keys) +{ + databases: + windows:: + + "$(keys)" + database_operation => "restore", + database_type => "ms_registry", + comment => "Make sure correct registry settings are set, according to cached version"; +} + +######################################################################### + +bundle agent registry_define(key, contents) +{ + databases: + windows:: + + "$(key)" + + database_operation => "create", + database_rows => { "$(contents)" } , + database_type => "ms_registry", + comment => "Explicitly define important registry settings"; +} + +######################################################################### + +bundle agent registry_delete(key, values) +{ + databases: + windows:: + + "$(key)" + + database_operation => "delete", + database_columns => { "@(values)" } , + database_type => "ms_registry", + comment => "Remove unwanted registry values"; +} diff --git a/examples/win_schedule.cf b/examples/win_schedule.cf new file mode 100644 index 0000000000..29d46b340f --- /dev/null +++ b/examples/win_schedule.cf @@ -0,0 +1,65 @@ +######################################################################### +# +# scheduling.cf - Command Execution Scheduling +# +# NOTE: Commands can be executed based on any class expression. +# Examples include execution of other commands (sequencing), +# time, OS, hostname or the outcome of another promise. +# If a shell wrapper is not needed, removing it will increase +# security and performance. +# +######################################################################### + +body common control +{ + bundlesequence => { "system_scheduling" }; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +bundle agent system_scheduling +{ + vars: + + # Command definitions are given in cmd[index]. + # The dependencies of the commands are dep_cmd[index], as boolean class expressions. + # Comments are given as comment_cmd[index] + + "cmd[1]" string => "$(sys.cf_key)"; + "dep_cmd[1]" string => "cmd2_success"; + "comment_cmd[1]" string => "Check that a Cfengine key is generated if command 2 has run (dummy)"; + + + windows:: + "cmd[2]" string => "\"$(sys.winsysdir)\shutdown.exe\" /r /c \"Cfengine automatic restart\" /t 120"; + "dep_cmd[2]" string => "Monday.Hr05.Min00_05"; + "comment_cmd[2]" string => "Restart windows every week"; + + "cmd[3]" string => "$(sys.winsysdir)\cscript.exe \"$(sys.workdir)\script1.vbs\" //Nologo > \"$(sys.workdir)\script1_out.txt\""; + "dep_cmd[3]" string => "Hr05.Min05_10"; + "comment_cmd[3]" string => "Run a script and save its output for later reference (needs to be run in shell)"; + + + !windows:: + + "cmd[2]" string => "/bin/echo Hello World!"; + "dep_cmd[2]" string => "Hr12.Min00_05"; + "comment_cmd[2]" string => "Print a message (dummy)"; + + + any:: + + "cmd_index" slist => getindices("cmd"); + + + commands: + + # Runs a command if its dependencies are satisfied + + "$(cmd[$(cmd_index)])" + classes => if_repaired("cmd$(cmd_index)_success"), + if => "$(dep_cmd[$(cmd_index)])", + contain => in_shell, + comment => "$(comment_cmd[$(cmd_index)])"; + +} + diff --git a/examples/with.cf b/examples/with.cf new file mode 100644 index 0000000000..9a9ce649d9 --- /dev/null +++ b/examples/with.cf @@ -0,0 +1,69 @@ +# Copyright 2021 Northern.tech AS + +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +# To the extent this program is licensed as part of the Enterprise +# versions of Cfengine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +#+begin_src cfengine3 +bundle agent main +{ + vars: + "todo" slist => { "a 1", "b 2", "c 3" }; + # Here, `with` is the canonified version of $(todo), letting us avoid an + # intermediate canonification array. + "$(with)" string => "$(todo)", with => canonify($(todo)); + + "complex" data => ' +{ + "x": 200, + "y": [ 1, 2, null, true, false ] +} +'; + + reports: + "For iterable '$(todo)' we created variable '$(with)' and its value is '$(todo)'" + with => canonify($(todo)); + + "We can print a data container compactly without creating a temporary variable: $(with)" + with => format("%S", complex); + + "We can print a data container fully without creating a temporary variable: $(with)" + with => storejson(complex); +} + +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: For iterable 'a 1' we created variable 'a_1' and its value is 'a 1' +#@ R: For iterable 'b 2' we created variable 'b_2' and its value is 'b 2' +#@ R: For iterable 'c 3' we created variable 'c_3' and its value is 'c 3' +#@ R: We can print a data container compactly without creating a temporary variable: {"x":200,"y":[1,2,null,true,false]} +#@ R: We can print a data container fully without creating a temporary variable: { +#@ "x": 200, +#@ "y": [ +#@ 1, +#@ 2, +#@ null, +#@ true, +#@ false +#@ ] +#@ } +#@ ``` +#+end_src diff --git a/examples/zenoss.cf b/examples/zenoss.cf new file mode 100644 index 0000000000..45736fc610 --- /dev/null +++ b/examples/zenoss.cf @@ -0,0 +1,88 @@ + +###################################################################### +# +# Zenoss integration template example +# +###################################################################### + +body common control +{ + bundlesequence => { "zenoss_host", "zenoss_client" }; + syslog_host => "zenoss_syslog.example.org"; + syslog_port => "514"; + inputs => { "$(sys.libdir)/stdlib.cf" }; +} + +######################################################## + +bundle agent zenoss_host +{ + vars: + + "cf_server_hosts" string => "cfengine_policy.example.org"; + "cf_doc_root" string => "/srv/www/html"; + "zCfengineComplianceFile" string => "/home/zenoss/compliance.zen"; + + files: + + # Assume Cfengine is running on the zenoss server to collect data + + zenoss_syslog_example_org:: + + "$(zCfengineComplianceFile)" + + comment => "Collect data from the Cfengine policy server", + perms => mo("644","zenoss"), + copy_from => secure_cp("$(cf_doc_root)/reports/summary.z","$(cf_server_host)"); + +} + +######################################################## + +bundle agent zenoss_client +{ + processes: + + # On clients, we just pass any messages to the zenoss server + # for logging... + + "bad_process" + + comment => "Make sure that process X is not running, tell zenoss if it was", + signals => { "term", "kill" }, + action => tell_zenoss_repaired("bad_process was killed"); + + files: + + "/etc/passwd" + + comment => "Check passwd security, tell zenoss if permissions were wrong", + perms => mog("644","root","root"), + action => tell_zenoss_repaired("passwd file had incorrect permissions"); + + commands: + + "/my/important/script -xyz" + + comment => "Run my mission critical batch process", + action => tell_zenoss_failed("myscript failed to execute or returned error"); + +} + +# +# Library stuff +# + +body action tell_zenoss_repaired(x) +{ + log_repaired => "udp_syslog"; + log_string => "zenoss_cfengine_integration $(x) promise repaired"; + ifelapsed => "10"; # Every 10 mins +} + +body action tell_zenoss_failed(x) +{ + log_failed => "udp_syslog"; + log_string => "zenoss_cfengine_integration $(x) persistent problem"; + ifelapsed => "10"; # Every 10 mins +} diff --git a/ext/Makefile.am b/ext/Makefile.am new file mode 100644 index 0000000000..0fba6d3611 --- /dev/null +++ b/ext/Makefile.am @@ -0,0 +1,26 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +bin_PROGRAMS = rpmvercmp + +LDADD = ../libntech/libcompat/libcompat.la diff --git a/ext/rpmvercmp.c b/ext/rpmvercmp.c new file mode 100644 index 0000000000..0e199504f3 --- /dev/null +++ b/ext/rpmvercmp.c @@ -0,0 +1,252 @@ +/* + +rpmvercmp.c contains code from RPM project, licensed as the following: + +RPM and it's source code are covered under two separate licenses. + +The entire code base may be distributed under the terms of the GNU General +Public License (GPL), which appears immediately below. Alternatively, +all of the source code in the lib subdirectory of the RPM source code +distribution as well as any code derived from that code may instead be +distributed under the GNU Library General Public License (LGPL), at the +choice of the distributor. The complete text of the LGPL appears +at the bottom of this file. + +This alternatively is allowed to enable applications to be linked against +the RPM library (commonly called librpm) without forcing such applications +to be distributed under the GPL. + +Any questions regarding the licensing of RPM should be addressed to +marc@redhat.com and ewt@redhat.com. +*/ + +#include +#include +#include +#include +#include + +static inline int rstreq(const char *s1, const char *s2) +{ + return (strcmp(s1, s2) == 0); +} + +static inline int rislower(int c) { + return (c >= 'a' && c <= 'z'); +} + +static inline int risupper(int c) { + return (c >= 'A' && c <= 'Z'); +} + +static inline int risalpha(int c) { + return (rislower(c) || risupper(c)); +} + +static inline int risdigit(int c) { + return (c >= '0' && c <= '9'); +} + +static inline int risalnum(int c) { + return (risalpha(c) || risdigit(c)); +} + +static int rpmvercmp(const char * a, const char * b) +{ + /* easy comparison to see if versions are identical */ + if (rstreq(a, b)) return 0; + + char oldch1, oldch2; + char abuf[strlen(a)+1], bbuf[strlen(b)+1]; + char *str1 = abuf, *str2 = bbuf; + char * one, * two; + int rc; + int isnum; + + strcpy(str1, a); + strcpy(str2, b); + + one = str1; + two = str2; + + /* loop through each version segment of str1 and str2 and compare them */ + while (*one || *two) { + while (*one && !risalnum(*one) && *one != '~') one++; + while (*two && !risalnum(*two) && *two != '~') two++; + + /* handle the tilde separator, it sorts before everything else */ + if (*one == '~' || *two == '~') { + if (*one != '~') return 1; + if (*two != '~') return -1; + one++; + two++; + continue; + } + + /* If we ran to the end of either, we are finished with the loop */ + if (!(*one && *two)) break; + + str1 = one; + str2 = two; + + /* grab first completely alpha or completely numeric segment */ + /* leave one and two pointing to the start of the alpha or numeric */ + /* segment and walk str1 and str2 to end of segment */ + if (risdigit(*str1)) { + while (*str1 && risdigit(*str1)) str1++; + while (*str2 && risdigit(*str2)) str2++; + isnum = 1; + } else { + while (*str1 && risalpha(*str1)) str1++; + while (*str2 && risalpha(*str2)) str2++; + isnum = 0; + } + + /* save character at the end of the alpha or numeric segment */ + /* so that they can be restored after the comparison */ + oldch1 = *str1; + *str1 = '\0'; + oldch2 = *str2; + *str2 = '\0'; + + /* this cannot happen, as we previously tested to make sure that */ + /* the first string has a non-null segment */ + if (one == str1) return -1; /* arbitrary */ + + /* take care of the case where the two version segments are */ + /* different types: one numeric, the other alpha (i.e. empty) */ + /* numeric segments are always newer than alpha segments */ + /* XXX See patch #60884 (and details) from bugzilla #50977. */ + if (two == str2) return (isnum ? 1 : -1); + + if (isnum) { + size_t onelen, twolen; + /* this used to be done by converting the digit segments */ + /* to ints using atoi() - it's changed because long */ + /* digit segments can overflow an int - this should fix that. */ + + /* throw away any leading zeros - it's a number, right? */ + while (*one == '0') one++; + while (*two == '0') two++; + + /* whichever number has more digits wins */ + onelen = strlen(one); + twolen = strlen(two); + if (onelen > twolen) return 1; + if (twolen > onelen) return -1; + } + + /* strcmp will return which one is greater - even if the two */ + /* segments are alpha or if they are numeric. don't return */ + /* if they are equal because there might be more segments to */ + /* compare */ + rc = strcmp(one, two); + if (rc) return (rc < 1 ? -1 : 1); + + /* restore character that was replaced by null above */ + *str1 = oldch1; + one = str1; + *str2 = oldch2; + two = str2; + } + + /* this catches the case where all numeric and alpha segments have */ + /* compared identically but the segment sepparating characters were */ + /* different */ + if ((!*one) && (!*two)) return 0; + + /* whichever version still has characters left over wins */ + if (!*one) return -1; else return 1; +} + +typedef struct +{ + char *epoch; + char *version; + char *release; +} EVR; + +static void parseEVR(char * evr, EVR *evr_parsed) +{ + char *s, *se; + + s = evr; + while (*s && risdigit(*s)) s++; /* s points to epoch terminator */ + se = strrchr(s, '-'); /* se points to version terminator */ + + if (*s == ':') { + evr_parsed->epoch = evr; + *s++ = '\0'; + evr_parsed->version = s; + if (*(evr_parsed->epoch) == '\0') evr_parsed->epoch = "0"; + } else { + evr_parsed->epoch = NULL; /* XXX disable epoch compare if missing */ + evr_parsed->version = evr; + } + if (se) { + *se++ = '\0'; + evr_parsed->release = se; + } else { + evr_parsed->release = NULL; + } +} + +static int rpmVersionCompare(EVR *first, EVR *second) +{ + uint32_t epochOne = first->epoch ? atoi(first->epoch) : 0; + uint32_t epochTwo = second->epoch ? atoi(second->epoch) : 0; + + int rc; + + if (epochOne < epochTwo) + return -1; + else if (epochOne > epochTwo) + return 1; + + rc = rpmvercmp(first->version, second->version); + if (rc) + return rc; + + return rpmvercmp(first->release ? first->release : "", + second->release ? second->release : ""); +} + +static void usage(void) +{ + fprintf(stderr, "Usage: rpmvercmp lt \n"); + fprintf(stderr, " rpmvercmp eq \n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Returns 0 if requested comparison holds, 1 otherwise\n"); +} + +int main(int argc, char **argv) +{ + if (argc != 4) + { + usage(); + exit(255); + } + + if (!rstreq(argv[2], "lt") && !rstreq(argv[2], "eq")) + { + usage(); + exit(255); + } + + EVR first, second; + + parseEVR(argv[1], &first); + parseEVR(argv[3], &second); + + int rc = rpmVersionCompare(&first, &second); + + if (rstreq(argv[2], "lt")) + { + exit(rc == -1 ? EXIT_SUCCESS : EXIT_FAILURE); + } + else + { + exit(rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + } +} + diff --git a/lgtm.yml b/lgtm.yml new file mode 100644 index 0000000000..bd600be640 --- /dev/null +++ b/lgtm.yml @@ -0,0 +1,14 @@ +path_classifiers: + library: + - "ext/rpmvercmp.c" + +extraction: + cpp: + after_prepare: + - git fetch --all --tags + python: + python_setup: + version: 3 + +queries: + - include: "*" diff --git a/libcfecompat/Makefile.am b/libcfecompat/Makefile.am new file mode 100644 index 0000000000..d078f845cc --- /dev/null +++ b/libcfecompat/Makefile.am @@ -0,0 +1,49 @@ +# +# Copyright 2024 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +noinst_LTLIBRARIES = libcfecompat.la + +AM_CPPFLAGS = -I$(top_srcdir)/libntech/libutils # platform.h + +libcfecompat_la_LIBADD = $(LTLIBOBJS) + +libcfecompat_la_SOURCES = \ + unused.c + +EXTRA_DIST = \ + cfecompat.h \ + compat_getopt.h + +if NO_SYSTEM_GETOPT_H +BUILT_SOURCES = getopt.h + +getopt.h: + cp $(srcdir)/compat_getopt.h $(srcdir)/getopt.h + +libcfecompat_la_SOURCES += \ + getopt.c \ + getopt1.c + +nodist_libcfecompat_la_SOURCES = getopt.h +endif diff --git a/libcfecompat/cfecompat.h b/libcfecompat/cfecompat.h new file mode 100644 index 0000000000..5f09cc653a --- /dev/null +++ b/libcfecompat/cfecompat.h @@ -0,0 +1,29 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#if !HAVE_DECL_GETLOADAVG +int getloadavg (double loadavg[], int nelem); +#endif diff --git a/libcfecompat/compat_getopt.h b/libcfecompat/compat_getopt.h new file mode 100644 index 0000000000..e66acc971b --- /dev/null +++ b/libcfecompat/compat_getopt.h @@ -0,0 +1,135 @@ +/* Declarations for getopt. + Copyright (C) 1989, 90, 91, 92, 93, 94 Free Software Foundation, Inc. + +This file is part of the GNU C Library. Its master source is NOT part of +the C library, however. The master source lives in /gd/gnu/lib. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +#ifndef _GETOPT_H +#define _GETOPT_H 1 +#define __GETOPT_H__ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +#if defined (__STDC__) && __STDC__ + const char *name; +#else + char *name; +#endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +#if defined (__STDC__) && __STDC__ +#if defined (__GNU_LIBRARY__) || defined (__APPLE__) +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C and MacOS libraries. */ +extern int getopt (int argc, char *const *argv, const char *shortopts); +#else /* not __GNU_LIBRARY__ or __APPLE__ */ +extern int getopt (); +#endif /* __GNU_LIBRARY__ || __APPLE__ */ +extern int getopt_long (int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); +extern int getopt_long_only (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); +#else /* not __STDC__ */ +extern int getopt (); +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +#endif /* __STDC__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* _GETOPT_H */ + diff --git a/libcfecompat/getloadavg.c b/libcfecompat/getloadavg.c new file mode 100644 index 0000000000..53a28fc9fc --- /dev/null +++ b/libcfecompat/getloadavg.c @@ -0,0 +1,1025 @@ +/* Get the system load averages. + Copyright (C) 1985, 86, 87, 88, 89, 91, 92, 93, 1994, 1995, 1997 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +/* Compile-time symbols that this file uses: + + HAVE_PSTAT_GETDYNAMIC Define this if your system has the + pstat_getdynamic function. I think it + is unique to HPUX9. The best way to get the + definition is through the AC_FUNC_GETLOADAVG + macro that comes with autoconf 2.13 or newer. + If that isn't an option, then just put + AC_CHECK_FUNCS(pstat_getdynamic) in your + configure.in file. + FIXUP_KERNEL_SYMBOL_ADDR() Adjust address in returned struct nlist. + KERNEL_FILE Pathname of the kernel to nlist. + LDAV_CVT() Scale the load average from the kernel. + Returns a double. + LDAV_SYMBOL Name of kernel symbol giving load average. + LOAD_AVE_TYPE Type of the load average array in the kernel. + Must be defined unless one of + apollo, DGUX, NeXT, or UMAX is defined; + or we have libkstat; + otherwise, no load average is available. + NLIST_STRUCT Include nlist.h, not a.out.h, and + the nlist n_name element is a pointer, + not an array. + NLIST_NAME_UNION struct nlist has an n_un member, not n_name. + LINUX_LDAV_FILE [__linux__]: File containing load averages. + + Specific system predefines this file uses, aside from setting + default values if not emacs: + + apollo + BSD Real BSD, not just BSD-like. + convex + DGUX + eunice UNIX emulator under VMS. + hpux + __MSDOS__ No-op for MSDOS. + NeXT + sgi + sequent Sequent Dynix 3.x.x (BSD) + _SEQUENT_ Sequent DYNIX/ptx 1.x.x (SYSV) + sony_news NEWS-OS (works at least for 4.1C) + UMAX + UMAX4_3 + VMS + WINDOWS32 No-op for Windows95/NT. + __linux__ Linux: assumes /proc filesystem mounted. + Support from Michael K. Johnson. + __NetBSD__ NetBSD: assumes /kern filesystem mounted. + + In addition, to avoid nesting many #ifdefs, we internally set + LDAV_DONE to indicate that the load average has been computed. + + We also #define LDAV_PRIVILEGED if a program will require + special installation to be able to call getloadavg. */ + +/* This should always be first. */ + +# include + +#ifdef HAVE_NLIST_H /* What autoconf defines. */ +# undef NLIST_STRUCT +# define NLIST_STRUCT +#endif + +#include + +/* Both the Emacs and non-Emacs sections want this. Some + configuration files' definitions for the LOAD_AVE_CVT macro (like + sparc.h's) use macros like FSCALE, defined here. */ +#if defined (unix) || defined (__unix) +# include +#endif + + +/* Exclude all the code except the test program at the end + if the system has its own `getloadavg' function. + + The declaration of `errno' is needed by the test program + as well as the function itself, so it comes first. */ + +#include + +#ifndef errno +extern int errno; +#endif + +#ifndef HAVE_GETLOADAVG + + +/* The existing Emacs configuration files define a macro called + LOAD_AVE_CVT, which accepts a value of type LOAD_AVE_TYPE, and + returns the load average multiplied by 100. What we actually want + is a macro called LDAV_CVT, which returns the load average as an + unmultiplied double. + + For backwards compatibility, we'll define LDAV_CVT in terms of + LOAD_AVE_CVT, but future machine config files should just define + LDAV_CVT directly. */ + +# if !defined(LDAV_CVT) && defined(LOAD_AVE_CVT) +# define LDAV_CVT(n) (LOAD_AVE_CVT (n) / 100.0) +# endif + +# if !defined (BSD) && defined (ultrix) +/* Ultrix behaves like BSD on Vaxen. */ +# define BSD +# endif + +# ifdef NeXT +/* NeXT in the 2.{0,1,2} releases defines BSD in , which + conflicts with the definition understood in this file, that this + really is BSD. */ +# undef BSD + +/* NeXT defines FSCALE in . However, we take FSCALE being + defined to mean that the nlist method should be used, which is not true. */ +# undef FSCALE +# endif + +/* Same issues as for NeXT apply to the HURD-based GNU system. */ +# ifdef __GNU__ +# undef BSD +# undef FSCALE +# endif /* __GNU__ */ + +/* Set values that are different from the defaults, which are + set a little farther down with #ifndef. */ + + +/* Some shorthands. */ + +# if defined (HPUX) && !defined (hpux) +# define hpux +# endif + +# if defined (__hpux) && !defined (hpux) +# define hpux +# endif + +# if defined (__sun) && !defined (sun) +# define sun +# endif + +# if defined(hp300) && !defined(hpux) +# define MORE_BSD +# endif + +# if defined(ultrix) && defined(mips) +# define decstation +# endif + +# if defined (__SVR4) && !defined (SVR4) +# define SVR4 +# endif + +# if (defined(sun) && defined(SVR4)) || defined (SOLARIS2) +# define SUNOS_5 +# endif + +# if defined (__osf__) && (defined (__alpha) || defined (__alpha__)) +# define OSF_ALPHA +# include +# include +# include +# include +# endif + +# if defined (__osf__) && (defined (mips) || defined (__mips__)) +# define OSF_MIPS +# include +# endif + +/* UTek's /bin/cc on the 4300 has no architecture specific cpp define by + default, but _MACH_IND_SYS_TYPES is defined in . Combine + that with a couple of other things and we'll have a unique match. */ +# if !defined (tek4300) && defined (unix) && defined (m68k) && defined (mc68000) && defined (mc68020) && defined (_MACH_IND_SYS_TYPES) +# define tek4300 /* Define by emacs, but not by other users. */ +# endif + + +/* VAX C can't handle multi-line #ifs, or lines longer than 256 chars. */ +# ifndef LOAD_AVE_TYPE + +# ifdef MORE_BSD +# define LOAD_AVE_TYPE long +# endif + +# ifdef sun +# define LOAD_AVE_TYPE long +# endif + +# ifdef decstation +# define LOAD_AVE_TYPE long +# endif + +# ifdef _SEQUENT_ +# define LOAD_AVE_TYPE long +# endif + +# ifdef sgi +# define LOAD_AVE_TYPE long +# endif + +# ifdef SVR4 +# define LOAD_AVE_TYPE long +# endif + +# ifdef sony_news +# define LOAD_AVE_TYPE long +# endif + +# ifdef sequent +# define LOAD_AVE_TYPE long +# endif + +# ifdef OSF_ALPHA +# define LOAD_AVE_TYPE long +# endif + +# if defined (ardent) && defined (titan) +# define LOAD_AVE_TYPE long +# endif + +# ifdef tek4300 +# define LOAD_AVE_TYPE long +# endif + +# if defined(alliant) && defined(i860) /* Alliant FX/2800 */ +# define LOAD_AVE_TYPE long +# endif + +# ifdef _AIX +# define LOAD_AVE_TYPE long +# endif + +# ifdef convex +# define LOAD_AVE_TYPE double +# ifndef LDAV_CVT +# define LDAV_CVT(n) (n) +# endif +# endif + +# endif /* No LOAD_AVE_TYPE. */ + +# ifdef OSF_ALPHA +/* defines an incorrect value for FSCALE on Alpha OSF/1, + according to ghazi@noc.rutgers.edu. */ +# undef FSCALE +# define FSCALE 1024.0 +# endif + +# if defined(alliant) && defined(i860) /* Alliant FX/2800 */ +/* defines an incorrect value for FSCALE on an + Alliant FX/2800 Concentrix 2.2, according to ghazi@noc.rutgers.edu. */ +# undef FSCALE +# define FSCALE 100.0 +# endif + + +# ifndef FSCALE + +/* SunOS and some others define FSCALE in sys/param.h. */ + +# ifdef MORE_BSD +# define FSCALE 2048.0 +# endif + +# if defined(MIPS) || defined(SVR4) || defined(decstation) +# define FSCALE 256 +# endif + +# if defined (sgi) || defined (sequent) +/* Sometimes both MIPS and sgi are defined, so FSCALE was just defined + above under #ifdef MIPS. But we want the sgi value. */ +# undef FSCALE +# define FSCALE 1000.0 +# endif + +# if defined (ardent) && defined (titan) +# define FSCALE 65536.0 +# endif + +# ifdef tek4300 +# define FSCALE 100.0 +# endif + +# ifdef _AIX +# define FSCALE 65536.0 +# endif + +# endif /* Not FSCALE. */ + +# if !defined (LDAV_CVT) && defined (FSCALE) +# define LDAV_CVT(n) (((double) (n)) / FSCALE) +# endif + + +# if defined(sgi) || (defined(mips) && !defined(BSD)) +# define FIXUP_KERNEL_SYMBOL_ADDR(nl) ((nl)[0].n_value &= ~(1 << 31)) +# endif + + +# if !defined (KERNEL_FILE) && defined (sequent) +# define KERNEL_FILE "/dynix" +# endif + +# if !defined (KERNEL_FILE) && defined (hpux) +# define KERNEL_FILE "/hp-ux" +# endif + +# if !defined(KERNEL_FILE) && (defined(_SEQUENT_) || defined(MIPS) || defined(SVR4) || defined(ISC) || defined (sgi) || (defined (ardent) && defined (titan))) +# define KERNEL_FILE "/unix" +# endif + + +# if !defined (LDAV_SYMBOL) && defined (alliant) +# define LDAV_SYMBOL "_Loadavg" +# endif + +# if !defined(LDAV_SYMBOL) && ((defined(hpux) && !defined(hp9000s300)) || defined(_SEQUENT_) || defined(SVR4) || defined(ISC) || defined(sgi) || (defined (ardent) && defined (titan)) || defined (_AIX)) +# define LDAV_SYMBOL "avenrun" +# endif + +# ifdef HAVE_UNISTD_H +# include +# endif + +# include + +/* LOAD_AVE_TYPE should only get defined if we're going to use the + nlist method. */ +# if !defined(LOAD_AVE_TYPE) && (defined(BSD) || defined(LDAV_CVT) || defined(KERNEL_FILE) || defined(LDAV_SYMBOL)) +# define LOAD_AVE_TYPE double +# endif + +# ifdef LOAD_AVE_TYPE + +# ifndef VMS +# ifndef __linux__ +# ifndef NLIST_STRUCT +# include +# else /* NLIST_STRUCT */ +# include +# endif /* NLIST_STRUCT */ + +# ifdef SUNOS_5 +# include +//# include +# include +# endif + +# if defined (hpux) && defined (HAVE_PSTAT_GETDYNAMIC) +# include +# endif + +# ifndef KERNEL_FILE +# define KERNEL_FILE "/vmunix" +# endif /* KERNEL_FILE */ + +# ifndef LDAV_SYMBOL +# define LDAV_SYMBOL "_avenrun" +# endif /* LDAV_SYMBOL */ +# endif /* __linux__ */ + +# else /* VMS */ + +# ifndef eunice +# include +# include +# else /* eunice */ +# include +# endif /* eunice */ +# endif /* VMS */ + +# ifndef LDAV_CVT +# define LDAV_CVT(n) ((double) (n)) +# endif /* !LDAV_CVT */ + +# endif /* LOAD_AVE_TYPE */ + +# if defined(__GNU__) && !defined (NeXT) +/* Note that NeXT Openstep defines __GNU__ even though it should not. */ +/* GNU system acts much like NeXT, for load average purposes, + but not exactly. */ +# define NeXT +# define host_self mach_host_self +# endif + +# ifdef NeXT +# ifdef HAVE_MACH_MACH_H +# include +# else +# include +# endif +# endif /* NeXT */ + +# ifdef sgi +# include +# endif /* sgi */ + +# ifdef UMAX +# include +# include +# include +# include +# include + +# ifdef UMAX_43 +# include +# include +# include +# include +# include +# else /* Not UMAX_43. */ +# include +# include +# include +# include +# include +# include +# endif /* Not UMAX_43. */ +# endif /* UMAX */ + +# ifdef DGUX +# include +# endif + +# if defined(HAVE_FCNTL_H) || defined(_POSIX_VERSION) +# include +# else +# include +# endif + + +/* Avoid static vars inside a function since in HPUX they dump as pure. */ + +# ifdef NeXT +static processor_set_t default_set; +static int getloadavg_initialized; +# endif /* NeXT */ + +# ifdef UMAX +static unsigned int cpus = 0; +static unsigned int samples; +# endif /* UMAX */ + +# ifdef DGUX +static struct dg_sys_info_load_info load_info; /* what-a-mouthful! */ +# endif /* DGUX */ + +#if !defined(HAVE_LIBKSTAT) && defined(LOAD_AVE_TYPE) +/* File descriptor open to /dev/kmem or VMS load ave driver. */ +static int channel; +/* Nonzero iff channel is valid. */ +static int getloadavg_initialized; +/* Offset in kmem to seek to read load average, or 0 means invalid. */ +static long offset; + +#if !defined(VMS) && !defined(sgi) && !defined(__linux__) +static struct nlist nl[2]; +#endif /* Not VMS or sgi */ + +#ifdef SUNOS_5 +static kvm_t *kd; +#endif /* SUNOS_5 */ + +#endif /* LOAD_AVE_TYPE && !HAVE_LIBKSTAT */ + +/* Put the 1 minute, 5 minute and 15 minute load averages + into the first NELEM elements of LOADAVG. + Return the number written (never more than 3, but may be less than NELEM), + or -1 if an error occurred. */ + +int +getloadavg (double loadavg[], int nelem); + +int +getloadavg (loadavg, nelem) + double loadavg[]; + int nelem; +{ + int elem = 0; /* Return value. */ + +# ifdef NO_GET_LOAD_AVG +# define LDAV_DONE + /* Set errno to zero to indicate that there was no particular error; + this function just can't work at all on this system. */ + errno = 0; + elem = -1; +# endif + +# if !defined (LDAV_DONE) && defined (HAVE_LIBKSTAT) +/* Use libkstat because we don't have to be root. */ +# define LDAV_DONE + kstat_ctl_t *kc; + kstat_t *ksp; + kstat_named_t *kn; + + kc = kstat_open (); + if (kc == 0) + return -1; + ksp = kstat_lookup (kc, "unix", 0, "system_misc"); + if (ksp == 0 ) + return -1; + if (kstat_read (kc, ksp, 0) == -1) + return -1; + + + kn = kstat_data_lookup (ksp, "avenrun_1min"); + if (kn == 0) + { + /* Return -1 if no load average information is available. */ + nelem = 0; + elem = -1; + } + + if (nelem >= 1) + loadavg[elem++] = (double) kn->value.ul/FSCALE; + + if (nelem >= 2) + { + kn = kstat_data_lookup (ksp, "avenrun_5min"); + if (kn != 0) + { + loadavg[elem++] = (double) kn->value.ul/FSCALE; + + if (nelem >= 3) + { + kn = kstat_data_lookup (ksp, "avenrun_15min"); + if (kn != 0) + loadavg[elem++] = (double) kn->value.ul/FSCALE; + } + } + } + + kstat_close (kc); +# endif /* HAVE_LIBKSTAT */ + +# if !defined (LDAV_DONE) && defined (hpux) && defined (HAVE_PSTAT_GETDYNAMIC) +/* Use pstat_getdynamic() because we don't have to be root. */ +# define LDAV_DONE +# undef LOAD_AVE_TYPE + + struct pst_dynamic dyn_info; + if (pstat_getdynamic (&dyn_info, sizeof (dyn_info), 0, 0) < 0) + return -1; + if (nelem > 0) + loadavg[elem++] = dyn_info.psd_avg_1_min; + if (nelem > 1) + loadavg[elem++] = dyn_info.psd_avg_5_min; + if (nelem > 2) + loadavg[elem++] = dyn_info.psd_avg_15_min; + +# endif /* hpux && HAVE_PSTAT_GETDYNAMIC */ + +# if !defined (LDAV_DONE) && defined (__linux__) +# define LDAV_DONE +# undef LOAD_AVE_TYPE + +# ifndef LINUX_LDAV_FILE +# define LINUX_LDAV_FILE "/proc/loadavg" +# endif + + char ldavgbuf[40]; + double load_ave[3]; + int fd, count; + + fd = open (LINUX_LDAV_FILE, O_RDONLY); + if (fd == -1) + return -1; + count = read (fd, ldavgbuf, 40); + (void) close (fd); + if (count <= 0) + return -1; + + count = sscanf (ldavgbuf, "%lf %lf %lf", + &load_ave[0], &load_ave[1], &load_ave[2]); + if (count < 1) + return -1; + + for (elem = 0; elem < nelem && elem < count; elem++) + loadavg[elem] = load_ave[elem]; + + return elem; + +# endif /* __linux__ */ + +# if !defined (LDAV_DONE) && defined (__NetBSD__) +# define LDAV_DONE +# undef LOAD_AVE_TYPE + +# ifndef NETBSD_LDAV_FILE +# define NETBSD_LDAV_FILE "/kern/loadavg" +# endif + + unsigned long int load_ave[3], scale; + int count; + FILE *fp; + + fp = fopen (NETBSD_LDAV_FILE, "r"); + if (fp == NULL) + return -1; + count = fscanf (fp, "%lu %lu %lu %lu\n", + &load_ave[0], &load_ave[1], &load_ave[2], + &scale); + (void) fclose (fp); + if (count != 4) + return -1; + + for (elem = 0; elem < nelem; elem++) + loadavg[elem] = (double) load_ave[elem] / (double) scale; + + return elem; + +# endif /* __NetBSD__ */ + +# if !defined (LDAV_DONE) && defined (NeXT) +# define LDAV_DONE + /* The NeXT code was adapted from iscreen 3.2. */ + + host_t host; + struct processor_set_basic_info info; + unsigned info_count; + + /* We only know how to get the 1-minute average for this system, + so even if the caller asks for more than 1, we only return 1. */ + + if (!getloadavg_initialized) + { + if (processor_set_default (host_self (), &default_set) == KERN_SUCCESS) + getloadavg_initialized = 1; + } + + if (getloadavg_initialized) + { + info_count = PROCESSOR_SET_BASIC_INFO_COUNT; + if (processor_set_info (default_set, PROCESSOR_SET_BASIC_INFO, &host, + (processor_set_info_t) &info, &info_count) + != KERN_SUCCESS) + getloadavg_initialized = 0; + else + { + if (nelem > 0) + loadavg[elem++] = (double) info.load_average / LOAD_SCALE; + } + } + + if (!getloadavg_initialized) + return -1; +# endif /* NeXT */ + +# if !defined (LDAV_DONE) && defined (UMAX) +# define LDAV_DONE +/* UMAX 4.2, which runs on the Encore Multimax multiprocessor, does not + have a /dev/kmem. Information about the workings of the running kernel + can be gathered with inq_stats system calls. + We only know how to get the 1-minute average for this system. */ + + struct proc_summary proc_sum_data; + struct stat_descr proc_info; + double load; + register unsigned int i, j; + + if (cpus == 0) + { + register unsigned int c, i; + struct cpu_config conf; + struct stat_descr desc; + + desc.sd_next = 0; + desc.sd_subsys = SUBSYS_CPU; + desc.sd_type = CPUTYPE_CONFIG; + desc.sd_addr = (char *) &conf; + desc.sd_size = sizeof conf; + + if (inq_stats (1, &desc)) + return -1; + + c = 0; + for (i = 0; i < conf.config_maxclass; ++i) + { + struct class_stats stats; + memset(&stats, 0, sizeof stats); + + desc.sd_type = CPUTYPE_CLASS; + desc.sd_objid = i; + desc.sd_addr = (char *) &stats; + desc.sd_size = sizeof stats; + + if (inq_stats (1, &desc)) + return -1; + + c += stats.class_numcpus; + } + cpus = c; + samples = cpus < 2 ? 3 : (2 * cpus / 3); + } + + proc_info.sd_next = 0; + proc_info.sd_subsys = SUBSYS_PROC; + proc_info.sd_type = PROCTYPE_SUMMARY; + proc_info.sd_addr = (char *) &proc_sum_data; + proc_info.sd_size = sizeof (struct proc_summary); + proc_info.sd_sizeused = 0; + + if (inq_stats (1, &proc_info) != 0) + return -1; + + load = proc_sum_data.ps_nrunnable; + j = 0; + for (i = samples - 1; i > 0; --i) + { + load += proc_sum_data.ps_nrun[j]; + if (j++ == PS_NRUNSIZE) + j = 0; + } + + if (nelem > 0) + loadavg[elem++] = load / samples / cpus; +# endif /* UMAX */ + +# if !defined (LDAV_DONE) && defined (DGUX) +# define LDAV_DONE + /* This call can return -1 for an error, but with good args + it's not supposed to fail. The first argument is for no + apparent reason of type `long int *'. */ + dg_sys_info ((long int *) &load_info, + DG_SYS_INFO_LOAD_INFO_TYPE, + DG_SYS_INFO_LOAD_VERSION_0); + + if (nelem > 0) + loadavg[elem++] = load_info.one_minute; + if (nelem > 1) + loadavg[elem++] = load_info.five_minute; + if (nelem > 2) + loadavg[elem++] = load_info.fifteen_minute; +# endif /* DGUX */ + +# if !defined (LDAV_DONE) && defined (apollo) +# define LDAV_DONE +/* Apollo code from lisch@mentorg.com (Ray Lischner). + + This system call is not documented. The load average is obtained as + three long integers, for the load average over the past minute, + five minutes, and fifteen minutes. Each value is a scaled integer, + with 16 bits of integer part and 16 bits of fraction part. + + I'm not sure which operating system first supported this system call, + but I know that SR10.2 supports it. */ + + extern void proc1_$get_loadav (); + unsigned long load_ave[3]; + + proc1_$get_loadav (load_ave); + + if (nelem > 0) + loadavg[elem++] = load_ave[0] / 65536.0; + if (nelem > 1) + loadavg[elem++] = load_ave[1] / 65536.0; + if (nelem > 2) + loadavg[elem++] = load_ave[2] / 65536.0; +# endif /* apollo */ + +# if !defined (LDAV_DONE) && defined (OSF_MIPS) +# define LDAV_DONE + + struct tbl_loadavg load_ave; + table (TBL_LOADAVG, 0, &load_ave, 1, sizeof (load_ave)); + loadavg[elem++] + = (load_ave.tl_lscale == 0 + ? load_ave.tl_avenrun.d[0] + : (load_ave.tl_avenrun.l[0] / (double) load_ave.tl_lscale)); +# endif /* OSF_MIPS */ + +# if !defined (LDAV_DONE) && (defined (__MSDOS__) || defined (WINDOWS32)) +# define LDAV_DONE + + /* A faithful emulation is going to have to be saved for a rainy day. */ + for ( ; elem < nelem; elem++) + { + loadavg[elem] = 0.0; + } +# endif /* __MSDOS__ || WINDOWS32 */ + +# if !defined (LDAV_DONE) && defined (OSF_ALPHA) +# define LDAV_DONE + + struct tbl_loadavg load_ave; + table (TBL_LOADAVG, 0, &load_ave, 1, sizeof (load_ave)); + for (elem = 0; elem < nelem; elem++) + loadavg[elem] + = (load_ave.tl_lscale == 0 + ? load_ave.tl_avenrun.d[elem] + : (load_ave.tl_avenrun.l[elem] / (double) load_ave.tl_lscale)); +# endif /* OSF_ALPHA */ + +# if !defined (LDAV_DONE) && defined (VMS) + /* VMS specific code -- read from the Load Ave driver. */ + + LOAD_AVE_TYPE load_ave[3]; + static int getloadavg_initialized = 0; +# ifdef eunice + struct + { + int dsc$w_length; + char *dsc$a_pointer; + } descriptor; +# endif + + /* Ensure that there is a channel open to the load ave device. */ + if (!getloadavg_initialized) + { + /* Attempt to open the channel. */ +# ifdef eunice + descriptor.dsc$w_length = 18; + descriptor.dsc$a_pointer = "$$VMS_LOAD_AVERAGE"; +# else + $DESCRIPTOR (descriptor, "LAV0:"); +# endif + if (sys$assign (&descriptor, &channel, 0, 0) & 1) + getloadavg_initialized = 1; + } + + /* Read the load average vector. */ + if (getloadavg_initialized + && !(sys$qiow (0, channel, IO$_READVBLK, 0, 0, 0, + load_ave, 12, 0, 0, 0, 0) & 1)) + { + sys$dassgn (channel); + getloadavg_initialized = 0; + } + + if (!getloadavg_initialized) + return -1; +# endif /* VMS */ + +# if !defined (LDAV_DONE) && defined(LOAD_AVE_TYPE) && !defined(VMS) + + /* UNIX-specific code -- read the average from /dev/kmem. */ + +# define LDAV_PRIVILEGED /* This code requires special installation. */ + + LOAD_AVE_TYPE load_ave[3]; + + /* Get the address of LDAV_SYMBOL. */ + if (offset == 0) + { +# ifndef sgi +# ifndef NLIST_STRUCT + strcpy (nl[0].n_name, LDAV_SYMBOL); + strcpy (nl[1].n_name, ""); +# else /* NLIST_STRUCT */ +# ifdef NLIST_NAME_UNION + nl[0].n_un.n_name = LDAV_SYMBOL; + nl[1].n_un.n_name = 0; +# else /* not NLIST_NAME_UNION */ + nl[0].n_name = LDAV_SYMBOL; + nl[1].n_name = 0; +# endif /* not NLIST_NAME_UNION */ +# endif /* NLIST_STRUCT */ + +# ifndef SUNOS_5 + if ( +# if !(defined (_AIX) && !defined (ps2)) + nlist (KERNEL_FILE, nl) +# else /* _AIX */ + knlist (nl, 1, sizeof (nl[0])) +# endif + >= 0) + /* Omit "&& nl[0].n_type != 0 " -- it breaks on Sun386i. */ + { +# ifdef FIXUP_KERNEL_SYMBOL_ADDR + FIXUP_KERNEL_SYMBOL_ADDR (nl); +# endif + offset = nl[0].n_value; + } +# endif /* !SUNOS_5 */ +# else /* sgi */ + int ldav_off; + + ldav_off = sysmp (MP_KERNADDR, MPKA_AVENRUN); + if (ldav_off != -1) + offset = (long) ldav_off & 0x7fffffff; +# endif /* sgi */ + } + + /* Make sure we have /dev/kmem open. */ + if (!getloadavg_initialized) + { +# ifndef SUNOS_5 + channel = open ("/dev/kmem", 0); + if (channel >= 0) + { + /* Set the channel to close on exec, so it does not + litter any child's descriptor table. */ +# ifdef F_SETFD +# ifndef FD_CLOEXEC +# define FD_CLOEXEC 1 +# endif + (void) fcntl (channel, F_SETFD, FD_CLOEXEC); +# endif + getloadavg_initialized = 1; + } +# else /* SUNOS_5 */ + /* We pass 0 for the kernel, corefile, and swapfile names + to use the currently running kernel. */ + kd = kvm_open (0, 0, 0, O_RDONLY, 0); + if (kd != 0) + { + /* nlist the currently running kernel. */ + kvm_nlist (kd, nl); + offset = nl[0].n_value; + getloadavg_initialized = 1; + } +# endif /* SUNOS_5 */ + } + + /* If we can, get the load average values. */ + if (offset && getloadavg_initialized) + { + /* Try to read the load. */ +# ifndef SUNOS_5 + if (lseek (channel, offset, 0) == -1L + || read (channel, (char *) load_ave, sizeof (load_ave)) + != sizeof (load_ave)) + { + close (channel); + getloadavg_initialized = 0; + } +# else /* SUNOS_5 */ + if (kvm_read (kd, offset, (char *) load_ave, sizeof (load_ave)) + != sizeof (load_ave)) + { + kvm_close (kd); + getloadavg_initialized = 0; + } +# endif /* SUNOS_5 */ + } + + if (offset == 0 || !getloadavg_initialized) + return -1; +# endif /* LOAD_AVE_TYPE and not VMS */ + +# if !defined (LDAV_DONE) && defined (LOAD_AVE_TYPE) /* Including VMS. */ + if (nelem > 0) + loadavg[elem++] = LDAV_CVT (load_ave[0]); + if (nelem > 1) + loadavg[elem++] = LDAV_CVT (load_ave[1]); + if (nelem > 2) + loadavg[elem++] = LDAV_CVT (load_ave[2]); + +# define LDAV_DONE +# endif /* !LDAV_DONE && LOAD_AVE_TYPE */ + +# ifdef LDAV_DONE + return elem; +# else + /* Set errno to zero to indicate that there was no particular error; + this function just can't work at all on this system. */ + errno = 0; + return -1; +# endif +} + +#endif /* ! HAVE_GETLOADAVG */ + +#ifdef TEST +int +main (argc, argv) + int argc; + char **argv; +{ + int naptime = 0; + + if (argc > 1) + naptime = atoi (argv[1]); + + while (1) + { + double avg[3]; + int loads; + + errno = 0; /* Don't be misled if it doesn't set errno. */ + loads = getloadavg (avg, 3); + if (loads == -1) + { + perror ("Error getting load average"); + exit (1); + } + if (loads > 0) + printf ("1-minute: %f ", avg[0]); + if (loads > 1) + printf ("5-minute: %f ", avg[1]); + if (loads > 2) + printf ("15-minute: %f ", avg[2]); + if (loads > 0) + putchar ('\n'); + + if (naptime == 0) + break; + sleep (naptime); + } + + exit (0); +} +#endif /* TEST */ diff --git a/libcfecompat/getopt.c b/libcfecompat/getopt.c new file mode 100644 index 0000000000..1c90a4f65b --- /dev/null +++ b/libcfecompat/getopt.c @@ -0,0 +1,716 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu + before changing it! + + Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94 + Free Software Foundation, Inc. + +This file is part of the GNU C Library. Its master source is NOT part of +the C library, however. The master source lives in /gd/gnu/lib. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in . + Ditto for AIX 3.2 and . */ +#ifndef _NO_PROTO +#define _NO_PROTO +#endif + + +#if !defined (__STDC__) || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +#include +#endif /* GNU C library. */ + +char *getenv(const char *name); + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = NULL; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* XXX 1003.2 says this must be 1 before any call. */ +int optind = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return EOF with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#include +#define my_index strchr + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange ( + char **argv +) +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char * +_getopt_initialize ( + const char *optstring +) +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind = 1; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns `EOF'. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal ( + int argc, + char *const *argv, + const char *optstring, + const struct option *longopts, + int *longind, + int long_only +) +{ + optarg = NULL; + + if (optind == 0) + optstring = _getopt_initialize (optstring); + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc + && (argv[optind][0] != '-' || argv[optind][1] == '\0')) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return EOF; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if ((argv[optind][0] != '-' || argv[optind][1] == '\0')) + { + if (ordering == REQUIRE_ORDER) + return EOF; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if (nameend - nextchar == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, "%s: option `%s' is ambiguous\n", + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + "%s: option `--%s' doesn't allow an argument\n", + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + "%s: option `%c%s' doesn't allow an argument\n", + argv[0], argv[optind - 1][0], pfound->name); + } + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, "%s: option `%s' requires an argument\n", + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, "%s: unrecognized option `--%s'\n", + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, "%s: unrecognized option `%c%s'\n", + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: illegal option -- %c\n", argv[0], c); + else + fprintf (stderr, "%s: invalid option -- %c\n", argv[0], c); + } + optopt = c; + return '?'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: option requires an argument -- %c\n", + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt ( + int argc, + char *const *argv, + const char *optstring +) +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == EOF) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit(EXIT_SUCCESS); +} + +#endif /* TEST */ diff --git a/libcfecompat/getopt1.c b/libcfecompat/getopt1.c new file mode 100644 index 0000000000..f709434d02 --- /dev/null +++ b/libcfecompat/getopt1.c @@ -0,0 +1,184 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987, 88, 89, 90, 91, 92, 1993, 1994 + Free Software Foundation, Inc. + +This file is part of the GNU C Library. Its master source is NOT part of +the C library, however. The master source lives in /gd/gnu/lib. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + + +#include + +#if !defined (__STDC__) || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#else +char *getenv (); +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long ( + int argc, + char *const *argv, + const char *options, + const struct option *long_options, + int *opt_index +) +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only ( + int argc, + char *const *argv, + const char *options, + const struct option *long_options, + int *opt_index +) +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +#include + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == EOF) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit(EXIT_SUCCESS); +} + +#endif /* TEST */ + diff --git a/libcfecompat/unused.c b/libcfecompat/unused.c new file mode 100644 index 0000000000..082c870c20 --- /dev/null +++ b/libcfecompat/unused.c @@ -0,0 +1,27 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* We need at least something to always be part of libcfecompat and this + * functions is just good enough for it. */ +void __unused_cfecompat_function() {}; diff --git a/libcfnet/Makefile.am b/libcfnet/Makefile.am new file mode 100644 index 0000000000..3e5cff8619 --- /dev/null +++ b/libcfnet/Makefile.am @@ -0,0 +1,49 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +noinst_LTLIBRARIES = libcfnet.la + +AM_CPPFLAGS = -I$(top_srcdir)/libntech/libutils \ + -I$(top_srcdir)/libpromises \ + $(PCRE2_CPPFLAGS) \ + $(SYSTEMD_SOCKET_CPPFLAGS) \ + $(OPENSSL_CPPFLAGS) + +libcfnet_la_SOURCES = \ + addr_lib.c addr_lib.h \ + client_protocol.c client_protocol.h cfnet.h\ + client_code.c client_code.h \ + classic.c classic.h \ + communication.c communication.h \ + connection_info.c connection_info.h \ + conn_cache.c conn_cache.h \ + key.c key.h \ + misc.c \ + net.c net.h \ + policy_server.c policy_server.h \ + protocol.c protocol.h \ + protocol_version.c protocol_version.h \ + server_code.c server_code.h \ + stat_cache.c stat_cache.h \ + tls_client.c tls_client.h \ + tls_generic.c tls_generic.h diff --git a/libcfnet/README.md b/libcfnet/README.md new file mode 100644 index 0000000000..568b920ef1 --- /dev/null +++ b/libcfnet/README.md @@ -0,0 +1,61 @@ +# libcfnet - CFEngine Network protocol + +Generally, details about the protocol are explained in comments / code. +However, some explanations don't naturally fit in one part / file of the codebase. +So, they are provided here. + + +## Network protocol versioning + + +### Protocol versions + +Names of protocol versions: + +1. `"classic"` - Legacy, pre-TLS, protocol. Not enabled or allowed by default. +2. `"tls"` - TLS Protocol using OpenSSL. Encrypted and 2-way authentication. +3. `"cookie"` - TLS Protocol with cookie command for duplicate host detection. + +Wanted protocol version can be specified from policy: + +``` +body copy_from protocol_latest +{ + protocol_version => "latest"; +} +``` + +Additionally, numbers can also be used: + +``` +body copy_from protocol_three +{ + protocol_version => "3"; +} +``` + + +### Version negotiation + +Client side (`cf-agent`, `cf-hub`, `cf-runagent`, `cf-net`) uses `ServerConnection()` function to connect to a server. +Server side (`cf-serverd`, `cf-testd`) uses `ServerTLSPeek()` to check if the connection is TLS or not, distinguishing version 1 and 2. +Protocol version 1 is not allowed by default, but can be allowed using `allowlegacyconnects`. +Version negotiation then happens inside `ServerIdentificationDialog()` (server side) and `ServerConnection()` (client side). +Client requests a wanted version, by sending a version string, for example: + +``` +CFE_v3 +``` + +The version requested is usually the latest supported, unless specified in policy (`body copy_from`). +Then, the server responds with the highest supported version (but not higher than the requested version). +For a 3.12 server, this would be: + +``` +CFE_v2 +``` + +Both server and client will then set `conn_info->protocol` to `2`, and use protocol version 2. +There is currently no way to require a specific version number (only allow / disallow version 1). +This is because version 2 and 3 are practically identical. +Downgrade from version 3 to 2 happens seamlessly, but crucially, it doesn't downgrade to version 1 inside the TLS code. diff --git a/libcfnet/addr_lib.c b/libcfnet/addr_lib.c new file mode 100644 index 0000000000..81e457fdec --- /dev/null +++ b/libcfnet/addr_lib.c @@ -0,0 +1,648 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#include +#include + +#define CF_ADDRSIZE 128 + + +/* Match two IP strings - with : or . in hex or decimal + s1 is the test string, and s2 is the reference e.g. + FuzzySetMatch("128.39.74.10/23","128.39.75.56") == 0 + + Returns 0 on match. */ + +/* TODO rename to AddrSubnetMatch() */ +int FuzzySetMatch(const char *s1, const char *s2) +{ + short isCIDR = false, isrange = false, isv6 = false, isv4 = false; + char address[CF_ADDRSIZE]; + + if (strcmp(s1, s2) == 0) + { + return 0; + } + + if (strstr(s1, "/") != 0) + { + isCIDR = true; + } + + if (strstr(s1, "-") != 0) + { + isrange = true; + } + + if (strstr(s1, ".") != 0) + { + isv4 = true; + } + + if (strstr(s1, ":") != 0) + { + isv6 = true; + } + + if (strstr(s2, ".") != 0) + { + isv4 = true; + } + + if (strstr(s2, ":") != 0) + { + isv6 = true; + } + + if (isv4 && isv6) + { + /* This is just wrong */ + return -1; + } + + if (isCIDR && isrange) + { + Log(LOG_LEVEL_ERR, "Cannot mix CIDR notation with xxx-yyy range notation '%s'", s1); + return -1; + } + + if (!(isv6 || isv4)) + { + Log(LOG_LEVEL_ERR, "Not a valid address range - or not a fully qualified name '%s'", s1); + return -1; + } + + if (!(isrange || isCIDR)) + { + if (strlen(s2) > strlen(s1)) + { + if (*(s2 + strlen(s1)) != '.') + { + return -1; // Because xxx.1 should not match xxx.12 in the same octet + } + } + + return strncmp(s1, s2, strlen(s1)); /* do partial string match */ + } + + if (isv4) + { + if (isCIDR) + { + struct sockaddr_in addr1, addr2; + unsigned long mask; + + address[0] = '\0'; + int ret = sscanf(s1, "%16[^/]/%lu", address, &mask); + if (ret != 2 || mask > 32) + { + Log(LOG_LEVEL_ERR, "Invalid IPv4 CIDR: %s", s1); + return -1; + } + else if (mask == 0) + { + return 0; /* /0 CIDR matches everything */ + } + + inet_pton(AF_INET, address, &addr1.sin_addr); + inet_pton(AF_INET, s2, &addr2.sin_addr); + + unsigned long a1 = htonl(addr1.sin_addr.s_addr); + unsigned long a2 = htonl(addr2.sin_addr.s_addr); + + unsigned long shift = 32 - mask; + assert(shift < 32); /* Undefined behaviour if 32 */ + + a1 = a1 >> shift; + a2 = a2 >> shift; + + if (a1 == a2) + { + return 0; + } + else + { + return -1; + } + } + else + { + long i, from = -1, to = -1, cmp = -1; + char buffer1[64], buffer2[64]; + + const char *sp1 = s1; + const char *sp2 = s2; + + for (i = 0; i < 4; i++) + { + buffer1[0] = '\0'; + sscanf(sp1, "%63[^.]", buffer1); + buffer1[63] = '\0'; + + if (strlen(buffer1) == 0) + { + break; + } + + sp1 += strlen(buffer1) + 1; + + sscanf(sp2, "%63[^.]", buffer2); + buffer2[63] = '\0'; + + sp2 += strlen(buffer2) + 1; + + if (strstr(buffer1, "-")) + { + sscanf(buffer1, "%ld-%ld", &from, &to); + sscanf(buffer2, "%ld", &cmp); + + if ((from < 0) || (to < 0)) + { + Log(LOG_LEVEL_DEBUG, "Couldn't read range"); + return -1; + } + + if ((from > cmp) || (cmp > to)) + { + Log(LOG_LEVEL_DEBUG, "Out of range %ld > %ld > %ld, range '%s'", from, cmp, to, buffer2); + return -1; + } + } + else + { + sscanf(buffer1, "%ld", &from); + sscanf(buffer2, "%ld", &cmp); + + if (from != cmp) + { + Log(LOG_LEVEL_DEBUG, "Unequal"); + return -1; + } + } + + Log(LOG_LEVEL_DEBUG, "Matched octet '%s' with '%s'", buffer1, buffer2); + } + + Log(LOG_LEVEL_DEBUG, "Matched IP range"); + return 0; + } + } + + if (isv6) + { + int i; + + if (isCIDR) + { + int blocks; + struct sockaddr_in6 addr1 = {0}; + struct sockaddr_in6 addr2 = {0}; + unsigned long mask; + + address[0] = '\0'; + int ret = sscanf(s1, "%40[^/]/%lu", address, &mask); + if (ret != 2 || mask > 128) + { + Log(LOG_LEVEL_ERR, "Invalid IPv6 CIDR: %s", s1); + return -1; + } + blocks = mask / 8; + + if (mask % 8 != 0) + { + Log(LOG_LEVEL_ERR, "Cannot handle ipv6 masks which are not 8 bit multiples (fix me)"); + return -1; + } + + addr1.sin6_family = AF_INET6; + inet_pton(AF_INET6, address, &addr1.sin6_addr); + addr2.sin6_family = AF_INET6; + inet_pton(AF_INET6, s2, &addr2.sin6_addr); + + for (i = 0; i < blocks; i++) /* blocks < 16 */ + { + if (addr1.sin6_addr.s6_addr[i] != addr2.sin6_addr.s6_addr[i]) + { + return -1; + } + } + return 0; + } + else + { + long i, from = -1, to = -1, cmp = -1; + char buffer1[64], buffer2[64]; + + const char *sp1 = s1; + const char *sp2 = s2; + + for (i = 0; i < 8; i++) + { + sscanf(sp1, "%63[^:]", buffer1); + buffer1[63] = '\0'; + + sp1 += strlen(buffer1) + 1; + + sscanf(sp2, "%63[^:]", buffer2); + buffer2[63] = '\0'; + + sp2 += strlen(buffer2) + 1; + + if (strstr(buffer1, "-")) + { + sscanf(buffer1, "%lx-%lx", &from, &to); + sscanf(buffer2, "%lx", &cmp); + + if (from < 0 || to < 0) + { + return -1; + } + + if ((from >= cmp) || (cmp > to)) + { + Log(LOG_LEVEL_DEBUG, "%lx < %lx < %lx", from, cmp, to); + return -1; + } + } + else + { + sscanf(buffer1, "%ld", &from); + sscanf(buffer2, "%ld", &cmp); + + if (from != cmp) + { + return -1; + } + } + } + + return 0; + } + } + + return -1; +} + +bool FuzzyHostParse(const char *arg2) +{ + long start = -1, end = -1; + int n; + + n = sscanf(arg2, "%ld-%ld", &start, &end); + + if (n != 2) + { + Log(LOG_LEVEL_ERR, + "HostRange syntax error: second arg should have X-Y format where X and Y are decimal numbers"); + return false; + } + + return true; +} + +int FuzzyHostMatch(const char *arg0, const char *arg1, const char *refhost) +{ + char *sp, refbase[1024]; + long cmp = -1, start = -1, end = -1; + char buf1[CF_BUFSIZE], buf2[CF_BUFSIZE]; + + strlcpy(refbase, refhost, sizeof(refbase)); + sp = refbase + strlen(refbase) - 1; + + while (isdigit((int) *sp)) + { + sp--; + } + + sp++; + sscanf(sp, "%ld", &cmp); + *sp = '\0'; + + if (cmp < 0) + { + return 1; + } + + if (strlen(refbase) == 0) + { + return 1; + } + + sscanf(arg1, "%ld-%ld", &start, &end); + + if ((cmp < start) || (cmp > end)) + { + return 1; + } + + strlcpy(buf1, refbase, CF_BUFSIZE); + strlcpy(buf2, arg0, CF_BUFSIZE); + + ToLowerStrInplace(buf1); + ToLowerStrInplace(buf2); + + if (strcmp(buf1, buf2) != 0) + { + return 1; + } + + return 0; +} + +bool FuzzyMatchParse(const char *s) +{ + short isCIDR = false, isrange = false, isv6 = false, isv4 = false, isADDR = false; + char address[CF_ADDRSIZE]; + int mask, count = 0; + + for (const char *sp = s; *sp != '\0'; sp++) /* Is this an address or hostname */ + { + if (!isxdigit((int) *sp)) + { + isADDR = false; + break; + } + + if (*sp == ':') /* Catches any ipv6 address */ + { + isADDR = true; + break; + } + + if (isdigit((int) *sp)) /* catch non-ipv4 address - no more than 3 digits */ + { + count++; + if (count > 3) + { + isADDR = false; + break; + } + } + else + { + count = 0; + } + } + + if (!isADDR) + { + return true; + } + + if (strstr(s, "/") != 0) + { + isCIDR = true; + } + + if (strstr(s, "-") != 0) + { + isrange = true; + } + + if (strstr(s, ".") != 0) + { + isv4 = true; + } + + if (strstr(s, ":") != 0) + { + isv6 = true; + } + + if (isv4 && isv6) + { + Log(LOG_LEVEL_ERR, "Mixture of IPv6 and IPv4 addresses"); + return false; + } + + if (isCIDR && isrange) + { + Log(LOG_LEVEL_ERR, "Cannot mix CIDR notation with xx-yy range notation"); + return false; + } + + if (isv4 && isCIDR) + { + if (strlen(s) > 4 + 3 * 4 + 1 + 2) /* xxx.yyy.zzz.mmm/cc */ + { + Log(LOG_LEVEL_ERR, "IPv4 address looks too long"); + return false; + } + + address[0] = '\0'; + mask = 0; + sscanf(s, "%16[^/]/%d", address, &mask); + + if (mask < 8) + { + Log(LOG_LEVEL_ERR, "Mask value %d in '%s' is less than 8", mask, s); + return false; + } + + if (mask > 30) + { + Log(LOG_LEVEL_ERR, "Mask value %d in '%s' is silly (> 30)", mask, s); + return false; + } + } + + if (isv4 && isrange) + { + long i, from = -1, to = -1; + char buffer1[64]; + + const char *sp1 = s; + + for (i = 0; i < 4; i++) + { + buffer1[0] = '\0'; + sscanf(sp1, "%63[^.]", buffer1); + sp1 += strlen(buffer1) + 1; + + if (strstr(buffer1, "-")) + { + sscanf(buffer1, "%ld-%ld", &from, &to); + + if ((from < 0) || (to < 0)) + { + Log(LOG_LEVEL_ERR, "Error in IP range - looks like address, or bad hostname"); + return false; + } + + if (to < from) + { + Log(LOG_LEVEL_ERR, "Bad IP range"); + return false; + } + + } + } + } + + if (isv6 && isCIDR) + { + char address[CF_ADDRSIZE]; + int mask; + + if (strlen(s) < 20) + { + Log(LOG_LEVEL_ERR, "IPv6 address looks too short"); + return false; + } + + if (strlen(s) > 42) + { + Log(LOG_LEVEL_ERR, "IPv6 address looks too long"); + return false; + } + + address[0] = '\0'; + mask = 0; + sscanf(s, "%40[^/]/%d", address, &mask); + + if (mask % 8 != 0) + { + Log(LOG_LEVEL_ERR, "Cannot handle ipv6 masks which are not 8 bit multiples (fix me)"); + return false; + } + + if (mask > 15) + { + Log(LOG_LEVEL_ERR, "IPv6 CIDR mask is too large"); + return false; + } + } + + return true; +} + + +/** + * Simple check to avoid writing to illegal memory addresses. + * NOT a proper test for valid IP. + */ +static AddressType AddressTypeCheckValidity(char *s, AddressType address_type) +{ + if(NULL_OR_EMPTY(s)) + { + return ADDRESS_TYPE_OTHER; + } + if(strlen(s) >= CF_MAX_IP_LEN) + { + return ADDRESS_TYPE_OTHER; + } + return address_type; +} + +/** + * Parses "hostname:port" or "[hostname]:port", where hostname may also be + * IPv4 or IPv6 address string. + * + * @param hostname will point to the hostname, or NULL if no or empty hostname + * @param port will point to the port, or NULL if no or empty port + * @WARNING modifies #s to '\0' terminate hostname if followed by port. + */ +AddressType ParseHostPort(char *s, char **hostname, char **port) +{ + s = TrimWhitespace(s); + if ( NULL_OR_EMPTY(s) ) + { + *hostname = NULL; + *port = NULL; + return ADDRESS_TYPE_OTHER; + } + + AddressType address_type = ADDRESS_TYPE_OTHER; + char *h, *p; // hostname, port temporaries + + h = s; + p = NULL; + + char *first_colon = strchr(s, ':'); + char *first_dot = strchr(s, '.'); + + if (s[0] == '[') // [host or ip]:port + { + h = s + 1; + p = strchr(h, ']'); + if (p != NULL) + { + if (first_colon != NULL && first_colon < p) + { + address_type = ADDRESS_TYPE_IPV6; + } + else if (isdigit(h[0])) + { + address_type = ADDRESS_TYPE_IPV4; + } // (else it's other by default) + + *p = '\0'; // '\0' terminate host name + if (p[1] == ':') // move port* forward + { + p += 2; + } + } + } + else if (first_colon == NULL) // localhost, 192.168.0.1 + { + if (isdigit(h[0])) + { + address_type = ADDRESS_TYPE_IPV4; + } + } + else if (first_dot == NULL || first_colon < first_dot) + { + // If only one colon: (cfengine.com:222 or localhost:) + if (strchr(first_colon + 1, ':') == NULL) + { + *first_colon = '\0'; + p = first_colon + 1; + } + else // Multiple colons: + { + address_type = ADDRESS_TYPE_IPV6; + } + } + else // (first_dot < first_colon) : IPv4 or hostname + { + p = strchr(h, ':'); + if (p != NULL) + { + *p = '\0'; // '\0'-terminate hostname + p++; + } + if (isdigit(h[0])) + { + address_type = ADDRESS_TYPE_IPV4; + } + } + + *hostname = (h[0] != '\0') ? h : NULL; + *port = (p != NULL && p[0] != '\0') ? p : NULL; + + return AddressTypeCheckValidity(*hostname, address_type); +} diff --git a/libcfnet/addr_lib.h b/libcfnet/addr_lib.h new file mode 100644 index 0000000000..aba84c91ba --- /dev/null +++ b/libcfnet/addr_lib.h @@ -0,0 +1,44 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ADDR_LIB_H +#define CFENGINE_ADDR_LIB_H + +#include + +bool IsLoopbackAddress(const char *address); +int FuzzySetMatch(const char *s1, const char *s2); +bool FuzzyHostParse(const char *arg2); +int FuzzyHostMatch(const char *arg0, const char *arg1, const char *basename); +bool FuzzyMatchParse(const char *item); + +typedef enum { + ADDRESS_TYPE_OTHER, // Hostname or invalid + ADDRESS_TYPE_IPV4, + ADDRESS_TYPE_IPV6 +} AddressType; + +AddressType ParseHostPort(char *s, char **hostname, char **port); + +#endif diff --git a/libcfnet/cfnet.h b/libcfnet/cfnet.h new file mode 100644 index 0000000000..051afe354e --- /dev/null +++ b/libcfnet/cfnet.h @@ -0,0 +1,130 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#ifndef CFENGINE_CFNET_H +#define CFENGINE_CFNET_H + + +#include +#include /* CF_BUFSIZE, CF_SMALLBUF */ +#include // ProtocolVersion + +/* Only set with DetermineCfenginePort() and from cf-serverd */ +extern char CFENGINE_PORT_STR[16]; /* GLOBAL_P GLOBAL_E */ +extern int CFENGINE_PORT; /* GLOBAL_P GLOBAL_E */ + + +#define CF_MAX_IP_LEN 64 /* max IPv4/IPv6 address length */ +#define CF_MAX_PORT_LEN 6 +#define CF_MAX_HOST_LEN 256 +// Since both have 1 extra for null byte, we don't need to add 1 for ':' +#define CF_MAX_SERVER_LEN (CF_MAX_HOST_LEN + CF_MAX_PORT_LEN) + +#define CF_DONE 't' +#define CF_MORE 'm' +#define SOCKET_INVALID -1 +#define MAXIP4CHARLEN 16 +#define CF_RSA_PROTO_OFFSET 24 +#define CF_PROTO_OFFSET 16 +#define CF_INBAND_OFFSET 8 +#define CF_MSGSIZE (CF_BUFSIZE - CF_INBAND_OFFSET) + +typedef struct +{ + ProtocolVersion protocol_version : 3; + bool cache_connection : 1; + bool force_ipv4 : 1; + bool trust_server : 1; + bool off_the_record : 1; +} ConnectionFlags; + +static inline bool ConnectionFlagsEqual(const ConnectionFlags *f1, + const ConnectionFlags *f2) +{ + if (f1->protocol_version == f2->protocol_version && + f1->cache_connection == f2->cache_connection && + f1->force_ipv4 == f2->force_ipv4 && + f1->trust_server == f2->trust_server && + f1->off_the_record == f2->off_the_record) + { + return true; + } + else + { + return false; + } +} + + +#include "connection_info.h" /* needs ProtocolVersion */ + + +/* + * TLS support + */ +#define DEFAULT_TLS_TIMEOUT_SECONDS 5 +#define DEFAULT_TLS_TIMEOUT_USECONDS 0 +#define SET_DEFAULT_TLS_TIMEOUT(x) \ + x.tv_sec = DEFAULT_TLS_TIMEOUT_SECONDS; \ + x.tv_usec = DEFAULT_TLS_TIMEOUT_USECONDS +#define DEFAULT_TLS_TRIES 5 + +struct Stat_; /* defined in stat_cache.h, typedef'ed to "Stat" */ + +typedef struct +{ + ConnectionInfo *conn_info; + int authenticated; + char username[CF_SMALLBUF]; + /* Unused for now... */ + /* char localip[CF_MAX_IP_LEN]; */ + char remoteip[CF_MAX_IP_LEN]; + unsigned char *session_key; + char encryption_type; + short error; + struct Stat_ *cache; /* cache for remote STATs */ + + /* The following consistutes the ID of a server host, mostly taken from + * the copy_from connection attributes. */ + ConnectionFlags flags; + char *this_server; + char *this_port; +} AgentConnection; + + + +/* misc.c */ + +void EnforceBwLimit(int tosend); +int cf_closesocket(int sd); + + +/* client_protocol.c */ +void SetSkipIdentify(bool enabled); + +/* net.c */ +void SetBindInterface(const char *ip); + +#endif diff --git a/libcfnet/classic.c b/libcfnet/classic.c new file mode 100644 index 0000000000..815cf31b52 --- /dev/null +++ b/libcfnet/classic.c @@ -0,0 +1,168 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include + +#include +#include + +static bool LastRecvTimedOut(void) +{ +#ifndef __MINGW32__ + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + { + return true; + } +#else + int lasterror = GetLastError(); + + if (lasterror == EAGAIN || lasterror == WSAEWOULDBLOCK) + { + return true; + } +#endif + + return false; +} + +/** + * @brief Receive up to #toget bytes, plus a '\0', into buffer from sd. + * @param sd Socket descriptor + * @param buffer Buffer into which to read data + * @param toget Number of bytes to read; a '\0' shall be written after + * the data; buffer must have space for that. + * + * @note Writes up to buffer[toget] (toget + 1 bytes total) + * @return number of bytes actually received, must be equal to #toget + * -1 in case of timeout or error - socket is unusable + * 0 connection was closed (not an error, that's how clients + * normally terminate) + * CF_BUFSIZE - 1 || toget <= 0) + { + Log(LOG_LEVEL_ERR, "Bad software request to receive %d bytes", toget); + return -1; + } + + /* Repeat recv() until we get "toget" bytes. */ + for (already = 0; already < toget; already += got) + { + got = recv(sd, buffer + already, toget - already, 0); + + if (got == -1) + { + if (errno != EINTR) /* recv() again in case of signal */ + { + if (LastRecvTimedOut()) + { + Log(LOG_LEVEL_ERR, "Receive timeout" + " (received=%dB, expecting=%dB) (recv: %s)", + already, toget, GetErrorStr()); + Log(LOG_LEVEL_VERBOSE, + "Consider increasing body agent control" + " \"default_timeout\" setting"); + + /* Shutdown() TCP connection despite of EAGAIN error, in + * order to avoid receiving this delayed response later on + * (Redmine #6027). */ + shutdown(sd, SHUT_RDWR); + } + else + { + Log(LOG_LEVEL_ERR, "Couldn't receive (recv: %s)", + GetErrorStr()); + } + return -1; + } + } + else if (got == 0) + { + Log(LOG_LEVEL_VERBOSE, "Peer has closed the connection"); + buffer[already] = '\0'; + return 0; + } + } + assert(already == toget); + + buffer[already] = '\0'; + return already; +} + +/*************************************************************************/ + +/** + * @brief Send #tosend bytes from buffer to sd. + * @param sd Socket descriptor + * @param buffer Buffer from which to read data + * @param tosend Number of bytes to write. + * + * @return number of bytes actually sent, must be equal to #tosend + * -1 in case of timeout or error - socket is unusable + * -1 also in case of early proper connection close + * 0 NEVER + * + +int RecvSocketStream(int sd, char *buffer, int toget); +int SendSocketStream(int sd, const char buffer[CF_BUFSIZE], int tosend); + +#endif // CLASSIC_H diff --git a/libcfnet/client_code.c b/libcfnet/client_code.c new file mode 100644 index 0000000000..f31463c385 --- /dev/null +++ b/libcfnet/client_code.c @@ -0,0 +1,922 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include /* struct ConnectionInfo */ +#include +#include +#include +#include /* RecvSocketStream */ +#include /* SendTransaction,ReceiveTransaction */ +#include /* ERR_get_error */ +#include /* ProtocolIsUndefined() */ +#include /* TLSTry */ +#include /* TLSVerifyPeer */ +#include +#include +#include /* AllocateDirentForFilename */ +#include +#include /* CryptoInitialize,SavePublicKey,EncryptString */ +#include +#include /* HashFile */ +#include /* ThreadLock */ +#include /* FullWrite,safe_open */ +#include /* MemSpan,MemSpanInverse */ +#include /* ProgrammingError */ +#include /* PRINTSIZE */ +#include /* LastSaw */ + + +#define CFENGINE_SERVICE "cfengine" + + +/** + * Initialize client's network library. + */ +bool cfnet_init(const char *tls_min_version, const char *ciphers) +{ + CryptoInitialize(); + return TLSClientInitialize(tls_min_version, ciphers); +} + +void cfnet_shut() +{ + TLSDeInitialize(); + CryptoDeInitialize(); +} + +bool cfnet_IsInitialized() +{ + return TLSClientIsInitialized(); +} + +#define MAX_PORT_NUMBER 65535 /* 2^16 - 1 */ + +/* These should only be modified by the two functions below! */ +int CFENGINE_PORT = 5308; +char CFENGINE_PORT_STR[16] = "5308"; + +void DetermineCfenginePort() +{ + nt_static_assert(sizeof(CFENGINE_PORT_STR) >= PRINTSIZE(CFENGINE_PORT)); + /* ... but we leave more space, as service names can be longer */ + + errno = 0; + struct servent *server = getservbyname(CFENGINE_SERVICE, "tcp"); + if (server != NULL) + { + CFENGINE_PORT = ntohs(server->s_port); + snprintf(CFENGINE_PORT_STR, sizeof(CFENGINE_PORT_STR), + "%d", CFENGINE_PORT); + } + else if (errno == 0) + { + Log(LOG_LEVEL_VERBOSE, + "No registered cfengine service, using default"); + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Unable to query services database, using default. (getservbyname: %s)", + GetErrorStr()); + } + Log(LOG_LEVEL_VERBOSE, "Default port for cfengine is %d", CFENGINE_PORT); +} + +bool SetCfenginePort(const char *port_str) +{ + /* parse and store the new value (use the string representation of the + * parsed value because it may potentially be better/nicer/more + * canonical) */ + long int port; + int ret = StringToLong(port_str, &port); + if (ret != 0) + { + LogStringToLongError(port_str, "CFENGINE_PORT", ret); + return false; + } + if (port > MAX_PORT_NUMBER) + { + Log(LOG_LEVEL_ERR, "Invalid port number given, must be <= %d", MAX_PORT_NUMBER); + return false; + } + + CFENGINE_PORT = port; + Log(LOG_LEVEL_VERBOSE, "Setting default port number to %d", + CFENGINE_PORT); + xsnprintf(CFENGINE_PORT_STR, sizeof(CFENGINE_PORT_STR), + "%d", CFENGINE_PORT); + return true; +} + +/** + * @return 1 success, 0 auth/ID error, -1 other error + */ +int TLSConnect(ConnectionInfo *conn_info, bool trust_server, const Rlist *restrict_keys, + const char *ipaddr, const char *username) +{ + int ret; + + ret = TLSTry(conn_info); + if (ret == -1) + { + return -1; + } + + /* TODO username is local, fix. */ + ret = TLSVerifyPeer(conn_info, ipaddr, username); + + if (ret == -1) /* error */ + { + return -1; + } + + const char *key_hash = KeyPrintableHash(conn_info->remote_key); + + // If restrict_key is defined, check if the key is there + if (restrict_keys != NULL) + { + if (RlistContainsString(restrict_keys, key_hash)) + { + Log(LOG_LEVEL_VERBOSE, "Server key in allowed list: %s", key_hash); + } + else + { + Log(LOG_LEVEL_ERR, + "Server key not in allowed keys, server presented: %s", key_hash); + return -1; + } + } + + if (ret == 1) + { + Log(LOG_LEVEL_VERBOSE, + "Server is TRUSTED, received key '%s' MATCHES stored one.", + key_hash); + } + else /* ret == 0 */ + { + if (trust_server) /* We're most probably bootstrapping. */ + { + Log(LOG_LEVEL_NOTICE, "Trusting new key: %s", key_hash); + SavePublicKey(username, KeyPrintableHash(conn_info->remote_key), + KeyRSA(conn_info->remote_key)); + } + else + { + Log(LOG_LEVEL_ERR, + "TRUST FAILED, server presented untrusted key: %s", key_hash); + return -1; + } + } + + /* TLS CONNECTION IS ESTABLISHED, negotiate protocol version and send + * identification data. */ + ret = TLSClientIdentificationDialog(conn_info, username); + + return ret; +} + +/** + * @NOTE if #flags.protocol_version is CF_PROTOCOL_UNDEFINED, then latest + * protocol is used by default. + */ +AgentConnection *ServerConnection(const char *server, const char *port, const Rlist *restrict_keys, + unsigned int connect_timeout, + ConnectionFlags flags, int *err) +{ + AgentConnection *conn = NULL; + int ret; + *err = 0; + + conn = NewAgentConn(server, port, flags); + +#if !defined(__MINGW32__) && !defined(__ANDROID__) + signal(SIGPIPE, SIG_IGN); + + sigset_t signal_mask; + sigemptyset(&signal_mask); + sigaddset(&signal_mask, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); + + /* FIXME: username is local */ + GetCurrentUserName(conn->username, sizeof(conn->username)); +#else + /* Always say "root" if windows or termux. */ + strlcpy(conn->username, "root", sizeof(conn->username)); +#endif + + if (port == NULL || *port == '\0') + { + port = CFENGINE_PORT_STR; + } + + char txtaddr[CF_MAX_IP_LEN] = ""; + conn->conn_info->sd = SocketConnect(server, port, connect_timeout, + flags.force_ipv4, + txtaddr, sizeof(txtaddr)); + if (conn->conn_info->sd == -1) + { + Log(LOG_LEVEL_INFO, "No server is responding on port: %s", + port); + DisconnectServer(conn); + *err = -1; + return NULL; + } + + nt_static_assert(sizeof(conn->remoteip) >= sizeof(txtaddr)); + strcpy(conn->remoteip, txtaddr); + + ProtocolVersion protocol_version = flags.protocol_version; + if (ProtocolIsUndefined(protocol_version)) + { + protocol_version = CF_PROTOCOL_LATEST; + } + + if (ProtocolIsTLS(protocol_version)) + { + /* Set the version to request during protocol negotiation. After + * TLSConnect() it will have the version we finally ended up with. */ + conn->conn_info->protocol = protocol_version; + + ret = TLSConnect(conn->conn_info, flags.trust_server, restrict_keys, + conn->remoteip, conn->username); + + if (ret == -1) /* Error */ + { + DisconnectServer(conn); + *err = -1; + return NULL; + } + else if (ret == 0) /* Auth/ID error */ + { + DisconnectServer(conn); + errno = EPERM; + *err = -2; + return NULL; + } + assert(ret == 1); + + conn->conn_info->status = CONNECTIONINFO_STATUS_ESTABLISHED; + if (!flags.off_the_record) + { + LastSaw1(conn->remoteip, KeyPrintableHash(conn->conn_info->remote_key), + LAST_SEEN_ROLE_CONNECT); + } + } + else if (ProtocolIsClassic(protocol_version)) + { + conn->conn_info->protocol = CF_PROTOCOL_CLASSIC; + conn->encryption_type = CfEnterpriseOptions(); + + if (!IdentifyAgent(conn->conn_info)) + { + Log(LOG_LEVEL_ERR, "Id-authentication for '%s' failed", VFQNAME); + errno = EPERM; + DisconnectServer(conn); + *err = -2; // auth err + return NULL; + } + + if (!AuthenticateAgent(conn, flags.trust_server)) + { + Log(LOG_LEVEL_ERR, "Authentication dialogue with '%s' failed", server); + errno = EPERM; + DisconnectServer(conn); + *err = -2; // auth err + return NULL; + } + conn->conn_info->status = CONNECTIONINFO_STATUS_ESTABLISHED; + } + else + { + ProgrammingError("ServerConnection: ProtocolVersion %d!", + protocol_version); + } + + conn->authenticated = true; + return conn; +} + +/*********************************************************************/ + +void DisconnectServer(AgentConnection *conn) +{ + /* Socket needs to be closed even after SSL_shutdown. */ + if (conn->conn_info->sd >= 0) /* Not INVALID or OFFLINE */ + { + if (conn->conn_info->protocol >= CF_PROTOCOL_TLS && + conn->conn_info->ssl != NULL) + { + SSL_shutdown(conn->conn_info->ssl); + } + + cf_closesocket(conn->conn_info->sd); + conn->conn_info->sd = SOCKET_INVALID; + Log(LOG_LEVEL_VERBOSE, "Connection to %s is closed", conn->remoteip); + } + DeleteAgentConn(conn); +} + +/*********************************************************************/ + +/* Returning NULL (an empty list) does not mean empty directory but ERROR, + * since every directory has to contain at least . and .. */ +Item *RemoteDirList(const char *dirname, bool encrypt, AgentConnection *conn) +{ + char sendbuffer[CF_BUFSIZE]; + char recvbuffer[CF_BUFSIZE]; + char in[CF_BUFSIZE]; + char out[CF_BUFSIZE]; + int cipherlen = 0, tosend; + + if (strlen(dirname) > CF_BUFSIZE - 20) + { + Log(LOG_LEVEL_ERR, "Directory name too long"); + return NULL; + } + + /* We encrypt only for CLASSIC protocol. The TLS protocol is always over + * encrypted layer, so it does not support encrypted (S*) commands. */ + encrypt = encrypt && conn->conn_info->protocol == CF_PROTOCOL_CLASSIC; + + if (encrypt) + { + if (conn->session_key == NULL) + { + Log(LOG_LEVEL_ERR, "Cannot do encrypted copy without keys (use cf-key)"); + return NULL; + } + + snprintf(in, CF_BUFSIZE, "OPENDIR %s", dirname); + cipherlen = EncryptString(out, sizeof(out), in, strlen(in) + 1, conn->encryption_type, conn->session_key); + + tosend = cipherlen + CF_PROTO_OFFSET; + + if (tosend < 0) + { + ProgrammingError("RemoteDirList: tosend (%d) < 0", tosend); + } + else if ((unsigned long) tosend > sizeof(sendbuffer)) + { + ProgrammingError("RemoteDirList: tosend (%d) > sendbuffer (%zd)", + tosend, sizeof(sendbuffer)); + } + + snprintf(sendbuffer, CF_BUFSIZE - 1, "SOPENDIR %d", cipherlen); + memcpy(sendbuffer + CF_PROTO_OFFSET, out, cipherlen); + } + else + { + snprintf(sendbuffer, CF_BUFSIZE, "OPENDIR %s", dirname); + tosend = strlen(sendbuffer); + } + + if (SendTransaction(conn->conn_info, sendbuffer, tosend, CF_DONE) == -1) + { + return NULL; + } + + Item *start = NULL, *end = NULL; /* NULL is empty list */ + while (true) + { + /* TODO check the CF_MORE flag, no need for CFD_TERMINATOR. */ + int nbytes = ReceiveTransaction(conn->conn_info, recvbuffer, NULL); + + /* If recv error or socket closed before receiving CFD_TERMINATOR. */ + if (nbytes == -1) + { + /* TODO mark connection in the cache as closed. */ + goto err; + } + + if (encrypt) + { + memcpy(in, recvbuffer, nbytes); + DecryptString(recvbuffer, sizeof(recvbuffer), in, nbytes, + conn->encryption_type, conn->session_key); + } + + if (recvbuffer[0] == '\0') + { + Log(LOG_LEVEL_ERR, + "Empty%s server packet when listing directory '%s'!", + (start == NULL) ? " first" : "", + dirname); + goto err; + } + + if (FailedProtoReply(recvbuffer)) + { + Log(LOG_LEVEL_INFO, "Network access to '%s:%s' denied", conn->this_server, dirname); + goto err; + } + + if (BadProtoReply(recvbuffer)) + { + Log(LOG_LEVEL_INFO, "%s", recvbuffer + strlen("BAD: ")); + goto err; + } + + /* Double '\0' means end of packet. */ + for (char *sp = recvbuffer; *sp != '\0'; sp += strlen(sp) + 1) + { + if (strcmp(sp, CFD_TERMINATOR) == 0) /* end of all packets */ + { + return start; + } + + Item *ip = xcalloc(1, sizeof(Item)); + ip->name = (char *) AllocateDirentForFilename(sp); + + if (start == NULL) /* First element */ + { + start = ip; + end = ip; + } + else + { + end->next = ip; + end = ip; + } + } + } + + return start; + + err: /* free list */ + for (Item *ip = start; ip != NULL; ip = start) + { + start = ip->next; + free(ip->name); + free(ip); + } + + return NULL; +} + +/*********************************************************************/ + +bool CompareHashNet(const char *file1, const char *file2, bool encrypt, AgentConnection *conn) +{ + unsigned char d[EVP_MAX_MD_SIZE + 1]; + char *sp; + char sendbuffer[CF_BUFSIZE] = {0}; + char recvbuffer[CF_BUFSIZE] = {0}; + int i, tosend, cipherlen; + + HashFile(file2, d, CF_DEFAULT_DIGEST, false); + + memset(recvbuffer, 0, CF_BUFSIZE); + + /* We encrypt only for CLASSIC protocol. The TLS protocol is always over + * encrypted layer, so it does not support encrypted (S*) commands. */ + encrypt = encrypt && conn->conn_info->protocol == CF_PROTOCOL_CLASSIC; + + if (encrypt) + { + char in[CF_BUFSIZE] = {0}; + char out[CF_BUFSIZE] = {0}; + snprintf(in, CF_BUFSIZE, "MD5 %s", file1); + + sp = in + strlen(in) + CF_SMALL_OFFSET; + + for (i = 0; i < CF_DEFAULT_DIGEST_LEN; i++) + { + *sp++ = d[i]; + } + + cipherlen = + EncryptString(out, sizeof(out), in, + strlen(in) + CF_SMALL_OFFSET + CF_DEFAULT_DIGEST_LEN, + conn->encryption_type, conn->session_key); + + tosend = cipherlen + CF_PROTO_OFFSET; + + if (tosend < 0) + { + ProgrammingError("CompareHashNet: tosend (%d) < 0", tosend); + } + else if ((unsigned long) tosend > sizeof(sendbuffer)) + { + ProgrammingError("CompareHashNet: tosend (%d) > sendbuffer (%zd)", + tosend, sizeof(sendbuffer)); + } + + snprintf(sendbuffer, CF_BUFSIZE, "SMD5 %d", cipherlen); + memcpy(sendbuffer + CF_PROTO_OFFSET, out, cipherlen); + } + else + { + snprintf(sendbuffer, CF_BUFSIZE, "MD5 %s", file1); + sp = sendbuffer + strlen(sendbuffer) + CF_SMALL_OFFSET; + + for (i = 0; i < CF_DEFAULT_DIGEST_LEN; i++) + { + *sp++ = d[i]; + } + + tosend = strlen(sendbuffer) + CF_SMALL_OFFSET + CF_DEFAULT_DIGEST_LEN; + } + + if (SendTransaction(conn->conn_info, sendbuffer, tosend, CF_DONE) == -1) + { + Log(LOG_LEVEL_ERR, "Failed send. (SendTransaction: %s)", GetErrorStr()); + Log(LOG_LEVEL_VERBOSE, "Networking error, assuming different checksum"); + return true; + } + + if (ReceiveTransaction(conn->conn_info, recvbuffer, NULL) == -1) + { + /* TODO mark connection in the cache as closed. */ + Log(LOG_LEVEL_ERR, "Failed receive. (ReceiveTransaction: %s)", GetErrorStr()); + Log(LOG_LEVEL_VERBOSE, "No answer from host, assuming different checksum"); + return true; + } + + if (strcmp(CFD_TRUE, recvbuffer) == 0) + { + return true; /* mismatch */ + } + else + { + return false; + } + +/* Not reached */ +} + +/*********************************************************************/ + + +static bool EncryptCopyRegularFileNet(const char *source, const char *dest, off_t size, AgentConnection *conn) +{ + int blocksize = 2048, n_read = 0, plainlen, more = true, finlen; + int tosend, cipherlen = 0; + char *buf, in[CF_BUFSIZE], out[CF_BUFSIZE], workbuf[CF_BUFSIZE], cfchangedstr[265]; + unsigned char iv[32] = + { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; + + snprintf(cfchangedstr, 255, "%s%s", CF_CHANGEDSTR1, CF_CHANGEDSTR2); + + if ((strlen(dest) > CF_BUFSIZE - 20)) + { + Log(LOG_LEVEL_ERR, "Filename too long"); + return false; + } + + unlink(dest); /* To avoid link attacks */ + + int dd = safe_open_create_perms(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY, CF_PERMS_DEFAULT); + if (dd == -1) + { + Log(LOG_LEVEL_ERR, + "Copy from server '%s' to destination '%s' failed (open: %s)", + conn->this_server, dest, GetErrorStr()); + unlink(dest); + return false; + } + + if (size == 0) + { + // No sense in copying an empty file + close(dd); + return true; + } + + workbuf[0] = '\0'; + + snprintf(in, CF_BUFSIZE - CF_PROTO_OFFSET, "GET dummykey %s", source); + cipherlen = EncryptString(out, sizeof(out), in, strlen(in) + 1, conn->encryption_type, conn->session_key); + + tosend = cipherlen + CF_PROTO_OFFSET; + + if (tosend < 0) + { + ProgrammingError("EncryptCopyRegularFileNet: tosend (%d) < 0", tosend); + } + else if ((unsigned long) tosend > sizeof(workbuf)) + { + ProgrammingError("EncryptCopyRegularFileNet: tosend (%d) > workbuf (%zd)", + tosend, sizeof(workbuf)); + } + + snprintf(workbuf, CF_BUFSIZE, "SGET %4d %4d", cipherlen, blocksize); + memcpy(workbuf + CF_PROTO_OFFSET, out, cipherlen); + +/* Send proposition C0 - query */ + + if (SendTransaction(conn->conn_info, workbuf, tosend, CF_DONE) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't send data. (SendTransaction: %s)", GetErrorStr()); + close(dd); + return false; + } + + EVP_CIPHER_CTX *crypto_ctx = EVP_CIPHER_CTX_new(); + if (crypto_ctx == NULL) + { + Log(LOG_LEVEL_ERR, "Failed to allocate cipher: %s", + TLSErrorString(ERR_get_error())); + close(dd); + return false; + } + + buf = xmalloc(CF_BUFSIZE + sizeof(int)); + + bool last_write_made_hole = false; + size_t n_wrote_total = 0; + + while (more) + { + if ((cipherlen = ReceiveTransaction(conn->conn_info, buf, &more)) == -1) + { + close(dd); + free(buf); + EVP_CIPHER_CTX_free(crypto_ctx); + return false; + } + + + /* If the first thing we get is an error message, break. */ + + if (n_wrote_total == 0 && + strncmp(buf + CF_INBAND_OFFSET, CF_FAILEDSTR, strlen(CF_FAILEDSTR)) == 0) + { + Log(LOG_LEVEL_INFO, "Network access to '%s:%s' denied", conn->this_server, source); + close(dd); + free(buf); + EVP_CIPHER_CTX_free(crypto_ctx); + return false; + } + + if (strncmp(buf + CF_INBAND_OFFSET, cfchangedstr, strlen(cfchangedstr)) == 0) + { + Log(LOG_LEVEL_INFO, "Source '%s:%s' changed while copying", conn->this_server, source); + close(dd); + free(buf); + EVP_CIPHER_CTX_free(crypto_ctx); + return false; + } + + EVP_DecryptInit_ex(crypto_ctx, CfengineCipher(CfEnterpriseOptions()), NULL, conn->session_key, iv); + + if (!EVP_DecryptUpdate(crypto_ctx, (unsigned char *) workbuf, &plainlen, (unsigned char *) buf, cipherlen)) + { + close(dd); + free(buf); + EVP_CIPHER_CTX_free(crypto_ctx); + return false; + } + + if (!EVP_DecryptFinal_ex(crypto_ctx, (unsigned char *) workbuf + plainlen, &finlen)) + { + close(dd); + free(buf); + EVP_CIPHER_CTX_free(crypto_ctx); + return false; + } + + n_read = plainlen + finlen; + + bool w_ok = FileSparseWrite(dd, workbuf, n_read, + &last_write_made_hole); + if (!w_ok) + { + Log(LOG_LEVEL_ERR, + "Local disk write failed copying '%s:%s' to '%s'", + conn->this_server, source, dest); + free(buf); + unlink(dest); + close(dd); + conn->error = true; + EVP_CIPHER_CTX_free(crypto_ctx); + return false; + } + + n_wrote_total += n_read; + } + + const bool do_sync = false; + + bool ret = FileSparseClose(dd, dest, do_sync, + n_wrote_total, last_write_made_hole); + if (!ret) + { + unlink(dest); + free(buf); + EVP_CIPHER_CTX_free(crypto_ctx); + return false; + } + + free(buf); + EVP_CIPHER_CTX_free(crypto_ctx); + return true; +} + +static void FlushFileStream(int sd, int toget) +{ + int i; + char buffer[2]; + + Log(LOG_LEVEL_VERBOSE, "Flushing rest of file...%d bytes", toget); + + for (i = 0; i < toget; i++) + { + recv(sd, buffer, 1, 0); /* flush to end of current file */ + } +} + +/* TODO finalise socket or TLS session in all cases that this function fails + * and the transaction protocol is out of sync. */ +bool CopyRegularFileNet(const char *source, const char *dest, off_t size, + bool encrypt, AgentConnection *conn) +{ + char *buf, workbuf[CF_BUFSIZE], cfchangedstr[265]; + const int buf_size = 2048; + + /* We encrypt only for CLASSIC protocol. The TLS protocol is always over + * encrypted layer, so it does not support encrypted (S*) commands. */ + encrypt = encrypt && conn->conn_info->protocol == CF_PROTOCOL_CLASSIC; + + if (encrypt) + { + return EncryptCopyRegularFileNet(source, dest, size, conn); + } + + snprintf(cfchangedstr, 255, "%s%s", CF_CHANGEDSTR1, CF_CHANGEDSTR2); + + if ((strlen(dest) > CF_BUFSIZE - 20)) + { + Log(LOG_LEVEL_ERR, "Filename too long"); + return false; + } + + unlink(dest); /* To avoid link attacks */ + + int dd = safe_open_create_perms(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY, CF_PERMS_DEFAULT); + if (dd == -1) + { + Log(LOG_LEVEL_ERR, + "Copy from server '%s' to destination '%s' failed (open: %s)", + conn->this_server, dest, GetErrorStr()); + unlink(dest); + return false; + } + + workbuf[0] = '\0'; + int tosend = snprintf(workbuf, CF_BUFSIZE, "GET %d %s", buf_size, source); + if (tosend <= 0 || tosend >= CF_BUFSIZE) + { + Log(LOG_LEVEL_ERR, "Failed to compose GET command for file %s", + source); + close(dd); + return false; + } + + /* Send proposition C0 */ + + if (SendTransaction(conn->conn_info, workbuf, tosend, CF_DONE) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't send GET command"); + close(dd); + return false; + } + + buf = xmalloc(CF_BUFSIZE + sizeof(int)); /* Note CF_BUFSIZE not buf_size !! */ + + Log(LOG_LEVEL_VERBOSE, "Copying remote file '%s:%s', expecting %jd bytes", + conn->this_server, source, (intmax_t)size); + + int n_wrote_total = 0; + bool last_write_made_hole = false; + while (n_wrote_total < size) + { + int toget = MIN(size - n_wrote_total, buf_size); + + assert(toget > 0); + + /* Stage C1 - receive */ + int n_read; + + const ProtocolVersion version = conn->conn_info->protocol; + + if (ProtocolIsClassic(version)) + { + n_read = RecvSocketStream(conn->conn_info->sd, buf, toget); + } + else if (ProtocolIsTLS(version)) + { + n_read = TLSRecv(conn->conn_info->ssl, buf, toget); + } + else + { + UnexpectedError("CopyRegularFileNet: ProtocolVersion %d!", + conn->conn_info->protocol); + n_read = -1; + } + + /* TODO what if 0 < n_read < toget? Might happen with TLS. */ + + if (n_read <= 0) + { + /* This may happen on race conditions, where the file has shrunk + * since we asked for its size in SYNCH ... STAT source */ + + Log(LOG_LEVEL_ERR, + "Error in client-server stream, has %s:%s shrunk? (code %d)", + conn->this_server, source, n_read); + + close(dd); + free(buf); + return false; + } + + /* If the first thing we get is an error message, break. */ + + if (n_wrote_total == 0 + && strncmp(buf, CF_FAILEDSTR, strlen(CF_FAILEDSTR)) == 0) + { + Log(LOG_LEVEL_INFO, "Network access to '%s:%s' denied", + conn->this_server, source); + close(dd); + free(buf); + return false; + } + + if (strncmp(buf, cfchangedstr, strlen(cfchangedstr)) == 0) + { + Log(LOG_LEVEL_INFO, "Source '%s:%s' changed while copying", + conn->this_server, source); + close(dd); + free(buf); + return false; + } + + /* Check for mismatch between encryption here and on server. */ + + int value = -1; + sscanf(buf, "t %d", &value); + + if ((value > 0) && (strncmp(buf + CF_INBAND_OFFSET, "BAD: ", 5) == 0)) + { + Log(LOG_LEVEL_INFO, "Network access to cleartext '%s:%s' denied", + conn->this_server, source); + close(dd); + free(buf); + return false; + } + + bool w_ok = FileSparseWrite(dd, buf, n_read, + &last_write_made_hole); + if (!w_ok) + { + Log(LOG_LEVEL_ERR, + "Local disk write failed copying '%s:%s' to '%s'", + conn->this_server, source, dest); + free(buf); + unlink(dest); + close(dd); + FlushFileStream(conn->conn_info->sd, size - n_wrote_total - n_read); + conn->error = true; + return false; + } + + n_wrote_total += n_read; + } + + const bool do_sync = false; + + bool ret = FileSparseClose(dd, dest, do_sync, + n_wrote_total, last_write_made_hole); + if (!ret) + { + unlink(dest); + free(buf); + FlushFileStream(conn->conn_info->sd, size - n_wrote_total); + return false; + } + + free(buf); + return true; +} diff --git a/libcfnet/client_code.h b/libcfnet/client_code.h new file mode 100644 index 0000000000..d926541721 --- /dev/null +++ b/libcfnet/client_code.h @@ -0,0 +1,56 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CLIENT_CODE_H +#define CFENGINE_CLIENT_CODE_H + + +#include +#include + +#include + + +bool cfnet_init(const char *tls_min_version, const char *ciphers); +void cfnet_shut(void); +bool cfnet_IsInitialized(void); +void DetermineCfenginePort(void); +bool SetCfenginePort(const char *port_str); + +/** + @param err Set to 0 on success, -1 no server response, -2 authentication failure. + */ +AgentConnection *ServerConnection(const char *server, const char *port, const Rlist *restrict_keys, + unsigned int connect_timeout, + ConnectionFlags flags, int *err); +void DisconnectServer(AgentConnection *conn); + +bool CompareHashNet(const char *file1, const char *file2, bool encrypt, AgentConnection *conn); +bool CopyRegularFileNet(const char *source, const char *dest, off_t size, + bool encrypt, AgentConnection *conn); +Item *RemoteDirList(const char *dirname, bool encrypt, AgentConnection *conn); + +int TLSConnectCallCollect(ConnectionInfo *conn_info, const char *username); + +#endif diff --git a/libcfnet/client_protocol.c b/libcfnet/client_protocol.c new file mode 100644 index 0000000000..110c91cd1b --- /dev/null +++ b/libcfnet/client_protocol.c @@ -0,0 +1,561 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include /* BN_* */ +#include /* ERR_get_error */ +#include + +#include +#include + +/* libutils */ +#include /* Log */ + +/* TODO remove all includes from libpromises. */ +extern char VIPADDRESS[CF_MAX_IP_LEN]; +extern char VDOMAIN[]; +extern char VFQNAME[]; +#include /* GetCurrentUsername */ +#include /* LastSaw */ +#include /* PublicKeyFile */ +#include /* HashString,HashesMatch,HashPubKey*/ +#include +#include +#include /* TLSErrorString */ + + +/*********************************************************************/ + +static bool SKIPIDENTIFY = false; /* GLOBAL_P */ + +/*********************************************************************/ + +void SetSkipIdentify(bool enabled) +{ + SKIPIDENTIFY = enabled; +} + +/*********************************************************************/ + +bool IdentifyAgent(ConnectionInfo *conn_info) +{ + assert(conn_info != NULL); + + char uname[CF_MAXVARSIZE], sendbuff[CF_BUFSIZE]; + char dnsname[CF_MAXVARSIZE], localip[CF_MAX_IP_LEN]; + int ret; + + if ((!SKIPIDENTIFY) && (strcmp(VDOMAIN, CF_START_DOMAIN) == 0)) + { + Log(LOG_LEVEL_ERR, "Undefined domain name"); + return false; + } + + if (!SKIPIDENTIFY) + { + /* First we need to find out the IP address and DNS name of the socket + we are sending from. This is not necessarily the same as VFQNAME if + the machine has a different uname from its IP name (!) This can + happen on poorly set up machines or on hosts with multiple + interfaces, with different names on each interface ... */ + struct sockaddr_storage myaddr = {0}; + socklen_t myaddr_len = sizeof(myaddr); + + if (getsockname(conn_info->sd, (struct sockaddr *) &myaddr, &myaddr_len) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't get socket address. (getsockname: %s)", GetErrorStr()); + return false; + } + + /* No lookup, just convert the bound address to string. */ + ret = getnameinfo((struct sockaddr *) &myaddr, myaddr_len, + localip, sizeof(localip), + NULL, 0, NI_NUMERICHOST); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, + "During agent identification. (getnameinfo: %s)", + gai_strerror(ret)); + return false; + } + + /* dnsname: Reverse lookup of the bound IP address. */ + ret = getnameinfo((struct sockaddr *) &myaddr, myaddr_len, + dnsname, sizeof(dnsname), NULL, 0, 0); + if (ret != 0) + { + /* getnameinfo doesn't fail on resolution failure, it just prints + * the IP, so here something else is wrong. */ + Log(LOG_LEVEL_ERR, + "During agent identification for '%s'. (getnameinfo: %s)", + localip, gai_strerror(ret)); + return false; + } + + /* Append a hostname if getnameinfo() does not return FQDN. Might only + * happen if the host has no domainname, in which case VDOMAIN might + * also be empty! In addition, missing PTR will give numerical result, + * so use it either it's IPv4 or IPv6. Finally don't append the + * VDOMAIN if "localhost" is resolved name, it means we're connecting + * via loopback. */ + if ((strlen(VDOMAIN) > 0) && + !IsIPV6Address(dnsname) && (strchr(dnsname, '.') == NULL) && + strcmp(dnsname, "localhost") != 0) + { + strcat(dnsname, "."); + strlcat(dnsname, VDOMAIN, sizeof(dnsname)); + } + + /* Seems to be a bug in some resolvers that adds garbage, when it just + * returns the input. */ + if (strncmp(dnsname, localip, strlen(localip)) == 0 + && dnsname[strlen(localip)] != '\0') + { + dnsname[strlen(localip)] = '\0'; + Log(LOG_LEVEL_WARNING, + "getnameinfo() seems to append garbage to unresolvable IPs, bug mitigated by CFEngine but please report your platform!"); + } + } + else + { + nt_static_assert(sizeof(localip) >= sizeof(VIPADDRESS)); + strcpy(localip, VIPADDRESS); + + Log(LOG_LEVEL_VERBOSE, + "skipidentify was promised, so we are trusting and simply announcing the identity as '%s' for this host", + strlen(VFQNAME) > 0 ? VFQNAME : "skipident"); + if (strlen(VFQNAME) > 0) + { + strcpy(dnsname, VFQNAME); + } + else + { + strcpy(dnsname, "skipident"); + } + } + +/* client always identifies as root on windows */ +#ifdef __MINGW32__ + snprintf(uname, sizeof(uname), "%s", "root"); +#else + GetCurrentUserName(uname, sizeof(uname)); +#endif + + snprintf(sendbuff, sizeof(sendbuff), "CAUTH %s %s %s %d", + localip, dnsname, uname, 0); + + if (SendTransaction(conn_info, sendbuff, 0, CF_DONE) == -1) + { + Log(LOG_LEVEL_ERR, + "During identify agent, could not send auth response. (SendTransaction: %s)", GetErrorStr()); + return false; + } + + return true; +} + +/*********************************************************************/ + +static bool SetSessionKey(AgentConnection *conn) +{ + BIGNUM *bp; + int session_size = CfSessionKeySize(conn->encryption_type); + + bp = BN_new(); + + if (bp == NULL) + { + Log(LOG_LEVEL_ERR, "Could not allocate session key"); + return false; + } + + // session_size is in bytes + if (!BN_rand(bp, session_size * 8, -1, 0)) + { + Log(LOG_LEVEL_ERR, "Can't generate cryptographic key"); + BN_clear_free(bp); + return false; + } + + conn->session_key = xmalloc(BN_num_bytes(bp)); + BN_bn2bin(bp, conn->session_key); + + BN_clear_free(bp); + return true; +} + +bool AuthenticateAgent(AgentConnection *conn, bool trust_key) +{ + char *decrypted_cchall; + unsigned char sendbuffer[CF_EXPANDSIZE]; + unsigned char *out; + unsigned char in[CF_BUFSIZE]; + BIGNUM *nonce_challenge, *bn = NULL; + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + int encrypted_len, nonce_len = 0, len, session_size; + bool need_to_implicitly_trust_server; + char enterprise_field = 'c'; + + if (PRIVKEY == NULL || PUBKEY == NULL) + { + Log(LOG_LEVEL_ERR, "No public/private key pair is loaded," + " please create one using cf-key"); + return false; + } + + enterprise_field = CfEnterpriseOptions(); + session_size = CfSessionKeySize(enterprise_field); + +/* Generate a random challenge to authenticate the server */ + + nonce_challenge = BN_new(); + if (nonce_challenge == NULL) + { + Log(LOG_LEVEL_ERR, "Cannot allocate BIGNUM structure for server challenge"); + return false; + } + + BN_rand(nonce_challenge, CF_NONCELEN, 0, 0); + nonce_len = BN_bn2mpi(nonce_challenge, in); + + if (FIPS_MODE) + { + HashString((const char *) in, nonce_len, digest, CF_DEFAULT_DIGEST); + } + else + { + HashString((const char *) in, nonce_len, digest, HASH_METHOD_MD5); + } + +/* We assume that the server bound to the remote socket is the official one i.e. = root's */ + + /* Ask the server to send us the public key if we don't have it. */ + RSA *server_pubkey = HavePublicKeyByIP(conn->username, conn->remoteip); + if (server_pubkey) + { + need_to_implicitly_trust_server = false; + encrypted_len = RSA_size(server_pubkey); + } + else + { + need_to_implicitly_trust_server = true; + encrypted_len = nonce_len; + } + +// Server pubkey is what we want to has as a unique ID + + snprintf((char *) sendbuffer, sizeof(sendbuffer), "SAUTH %c %d %d %c", + need_to_implicitly_trust_server ? 'n': 'y', + encrypted_len, nonce_len, enterprise_field); + + out = xmalloc(encrypted_len); + + if (server_pubkey != NULL) + { + if (RSA_public_encrypt(nonce_len, in, out, server_pubkey, RSA_PKCS1_PADDING) <= 0) + { + Log(LOG_LEVEL_ERR, + "Public encryption failed. (RSA_public_encrypt: %s)", + CryptoLastErrorString()); + free(out); + RSA_free(server_pubkey); + return false; + } + + memcpy(sendbuffer + CF_RSA_PROTO_OFFSET, out, encrypted_len); + } + else + { + memcpy(sendbuffer + CF_RSA_PROTO_OFFSET, in, nonce_len); + } + +/* proposition C1 - Send challenge / nonce */ + + SendTransaction(conn->conn_info, (const char *) sendbuffer, CF_RSA_PROTO_OFFSET + encrypted_len, CF_DONE); + + BN_free(bn); + BN_free(nonce_challenge); + free(out); + +/*Send the public key - we don't know if server has it */ +/* proposition C2 */ + + const BIGNUM *pubkey_n, *pubkey_e; + RSA_get0_key(PUBKEY, &pubkey_n, &pubkey_e, NULL); + + memset(sendbuffer, 0, CF_EXPANDSIZE); + len = BN_bn2mpi(pubkey_n, sendbuffer); + SendTransaction(conn->conn_info, (const char *) sendbuffer, len, CF_DONE); /* No need to encrypt the public key ... */ + +/* proposition C3 */ + memset(sendbuffer, 0, CF_EXPANDSIZE); + len = BN_bn2mpi(pubkey_e, sendbuffer); + SendTransaction(conn->conn_info, (const char *) sendbuffer, len, CF_DONE); + +/* check reply about public key - server can break conn_info here */ + +/* proposition S1 */ + memset(in, 0, CF_BUFSIZE); + + if (ReceiveTransaction(conn->conn_info, (char *) in, NULL) == -1) + { + Log(LOG_LEVEL_ERR, "Protocol transaction broken off (1). (ReceiveTransaction: %s)", GetErrorStr()); + RSA_free(server_pubkey); + return false; + } + + if (BadProtoReply((const char *) in)) + { + Log(LOG_LEVEL_ERR, "Bad protocol reply: %s", in); + RSA_free(server_pubkey); + return false; + } + +/* Get challenge response - should be CF_DEFAULT_DIGEST of challenge */ + +/* proposition S2 */ + memset(in, 0, CF_BUFSIZE); + + if (ReceiveTransaction(conn->conn_info, (char *) in, NULL) == -1) + { + Log(LOG_LEVEL_ERR, "Protocol transaction broken off (2). (ReceiveTransaction: %s)", GetErrorStr()); + RSA_free(server_pubkey); + return false; + } + + /* Check if challenge reply was correct */ + if ((HashesMatch(digest, in, CF_DEFAULT_DIGEST)) || + (HashesMatch(digest, in, HASH_METHOD_MD5))) // Legacy + { + if (need_to_implicitly_trust_server == false) + { + /* The IP was found in lastseen. */ + Log(LOG_LEVEL_VERBOSE, + ".....................[.h.a.i.l.]................................."); + Log(LOG_LEVEL_VERBOSE, + "Strong authentication of server '%s' connection confirmed", + conn->this_server); + } + else /* IP was not found in lastseen */ + { + if (trust_key) + { + Log(LOG_LEVEL_VERBOSE, + "Trusting server identity, promise to accept key from '%s' = '%s'", + conn->this_server, conn->remoteip); + } + else + { + Log(LOG_LEVEL_ERR, + "Not authorized to trust public key of server '%s' (trustkey = false)", + conn->this_server); + RSA_free(server_pubkey); + return false; + } + } + } + else + { + Log(LOG_LEVEL_ERR, "Challenge response from server '%s/%s' was incorrect", conn->this_server, + conn->remoteip); + RSA_free(server_pubkey); + return false; + } + +/* Receive counter challenge from server */ + +/* proposition S3 */ + memset(in, 0, CF_BUFSIZE); + encrypted_len = ReceiveTransaction(conn->conn_info, (char *) in, NULL); + + if (encrypted_len == -1) + { + Log(LOG_LEVEL_ERR, "Protocol transaction sent illegal cipher length"); + RSA_free(server_pubkey); + return false; + } + + decrypted_cchall = xmalloc(encrypted_len); + + if (RSA_private_decrypt(encrypted_len, in, (unsigned char *) decrypted_cchall, PRIVKEY, RSA_PKCS1_PADDING) <= 0) + { + Log(LOG_LEVEL_ERR, + "Private decrypt failed, abandoning. (RSA_private_decrypt: %s)", + CryptoLastErrorString()); + RSA_free(server_pubkey); + return false; + } + +/* proposition C4 */ + if (FIPS_MODE) + { + HashString(decrypted_cchall, nonce_len, digest, CF_DEFAULT_DIGEST); + } + else + { + HashString(decrypted_cchall, nonce_len, digest, HASH_METHOD_MD5); + } + + if (FIPS_MODE) + { + SendTransaction(conn->conn_info, (const char *) digest, CF_DEFAULT_DIGEST_LEN, CF_DONE); + } + else + { + SendTransaction(conn->conn_info, (const char *) digest, CF_MD5_LEN, CF_DONE); + } + + free(decrypted_cchall); + +/* If we don't have the server's public key, it will be sent */ + + if (server_pubkey == NULL) + { + RSA *newkey = RSA_new(); + + Log(LOG_LEVEL_VERBOSE, "Collecting public key from server!"); + + /* proposition S4 - conditional */ + if ((len = ReceiveTransaction(conn->conn_info, (char *) in, NULL)) == -1) + { + Log(LOG_LEVEL_ERR, "Protocol error in RSA authentation from IP '%s'", conn->this_server); + return false; + } + + BIGNUM *newkey_n, *newkey_e; + if ((newkey_n = BN_mpi2bn(in, len, NULL)) == NULL) + { + Log(LOG_LEVEL_ERR, + "Private key decrypt failed. (BN_mpi2bn: %s)", + CryptoLastErrorString()); + RSA_free(newkey); + return false; + } + + /* proposition S5 - conditional */ + + if ((len = ReceiveTransaction(conn->conn_info, (char *) in, NULL)) == -1) + { + Log(LOG_LEVEL_INFO, "Protocol error in RSA authentation from IP '%s'", + conn->this_server); + BN_clear_free(newkey_n); + RSA_free(newkey); + return false; + } + + if ((newkey_e = BN_mpi2bn(in, len, NULL)) == NULL) + { + Log(LOG_LEVEL_ERR, + "Public key decrypt failed. (BN_mpi2bn: %s)", + CryptoLastErrorString()); + BN_clear_free(newkey_n); + RSA_free(newkey); + return false; + } + + if (RSA_set0_key(newkey, newkey_n, newkey_e, NULL) != 1) + { + Log(LOG_LEVEL_ERR, "Failed to set RSA key: %s", + TLSErrorString(ERR_get_error())); + BN_clear_free(newkey_e); + BN_clear_free(newkey_n); + RSA_free(newkey); + return false; + } + + server_pubkey = RSAPublicKey_dup(newkey); + RSA_free(newkey); + } + assert(server_pubkey != NULL); + +/* proposition C5 */ + + if (!SetSessionKey(conn)) + { + Log(LOG_LEVEL_ERR, "Unable to set session key"); + return false; + } + + if (conn->session_key == NULL) + { + Log(LOG_LEVEL_ERR, "A random session key could not be established"); + RSA_free(server_pubkey); + return false; + } + + encrypted_len = RSA_size(server_pubkey); + + out = xmalloc(encrypted_len); + + if (RSA_public_encrypt(session_size, conn->session_key, out, server_pubkey, RSA_PKCS1_PADDING) <= 0) + { + Log(LOG_LEVEL_ERR, + "Public encryption failed. (RSA_public_encrypt: %s)", + CryptoLastErrorString()); + free(out); + RSA_free(server_pubkey); + return false; + } + + SendTransaction(conn->conn_info, (const char *) out, encrypted_len, CF_DONE); + + Key *key = KeyNew(server_pubkey, CF_DEFAULT_DIGEST); + conn->conn_info->remote_key = key; + + Log(LOG_LEVEL_VERBOSE, "Public key identity of host '%s' is: %s", + conn->remoteip, KeyPrintableHash(conn->conn_info->remote_key)); + + SavePublicKey(conn->username, KeyPrintableHash(conn->conn_info->remote_key), server_pubkey); + + unsigned int length = 0; + LastSaw(conn->remoteip, KeyBinaryHash(conn->conn_info->remote_key, &length), LAST_SEEN_ROLE_CONNECT); + + free(out); + + return true; +} + + +/*********************************************************************/ + +bool BadProtoReply(const char *const buf) +{ + return (strncmp(buf, "BAD: ", 5) == 0); +} + +/*********************************************************************/ + +bool OKProtoReply(const char *const buf) +{ + return (strncmp(buf, "OK:", 3) == 0); +} + +/*********************************************************************/ + +bool FailedProtoReply(const char *const buf) +{ + return (strncmp(buf, CF_FAILEDSTR, strlen(CF_FAILEDSTR)) == 0); +} diff --git a/libcfnet/client_protocol.h b/libcfnet/client_protocol.h new file mode 100644 index 0000000000..f300f448e6 --- /dev/null +++ b/libcfnet/client_protocol.h @@ -0,0 +1,38 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CLIENT_PROTOCOL_H +#define CFENGINE_CLIENT_PROTOCOL_H + +#include + + +bool IdentifyAgent(ConnectionInfo *connection); +bool AuthenticateAgent(AgentConnection *conn, bool trust_key); +bool BadProtoReply(const char *buf); +bool OKProtoReply(const char *buf); +bool FailedProtoReply(const char *buf); + + +#endif diff --git a/libcfnet/communication.c b/libcfnet/communication.c new file mode 100644 index 0000000000..d89570d5d0 --- /dev/null +++ b/libcfnet/communication.c @@ -0,0 +1,251 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include +#include + +#include +#include /* Stat */ +#include /* xmalloc,... */ +#include /* Log */ +#include /* ProgrammingError */ +#include /* Buffer */ +#include /* IPAddress */ + + +AgentConnection *NewAgentConn(const char *server, const char *port, + ConnectionFlags flags) +{ + AgentConnection *conn = xcalloc(1, sizeof(AgentConnection)); + conn->conn_info = ConnectionInfoNew(); + conn->this_server = xstrdup(server); + conn->this_port = (port == NULL) ? NULL : xstrdup(port); + conn->flags = flags; + conn->encryption_type = 'c'; + conn->authenticated = false; + return conn; +} + +void DeleteAgentConn(AgentConnection *conn) +{ + Stat *sp = conn->cache; + + while (sp != NULL) + { + Stat *previous = sp; + sp = sp->next; + DestroyStatCache(previous); + } + + ConnectionInfoDestroy(&conn->conn_info); + free(conn->this_server); + free(conn->this_port); + free(conn->session_key); + *conn = (AgentConnection) {0}; + free(conn); +} + +bool IsIPV6Address(char *name) +{ + if (!name) + { + return false; + } + Buffer *buffer = BufferNewFrom(name, strlen(name)); + if (!buffer) + { + return false; + } + IPAddress *ip_address = NULL; + bool is_ip = false; + is_ip = IPAddressIsIPAddress(buffer, &ip_address); + if (!is_ip) + { + BufferDestroy(buffer); + return false; + } + if (IPAddressType(ip_address) != IP_ADDRESS_TYPE_IPV6) + { + BufferDestroy(buffer); + IPAddressDestroy(&ip_address); + return false; + } + BufferDestroy(buffer); + IPAddressDestroy(&ip_address); + return true; +} + +/*******************************************************************/ + +bool IsIPV4Address(char *name) +{ + if (!name) + { + return false; + } + Buffer *buffer = BufferNewFrom(name, strlen(name)); + if (!buffer) + { + return false; + } + IPAddress *ip_address = NULL; + bool is_ip = false; + is_ip = IPAddressIsIPAddress(buffer, &ip_address); + if (!is_ip) + { + BufferDestroy(buffer); + return false; + } + if (IPAddressType(ip_address) != IP_ADDRESS_TYPE_IPV4) + { + BufferDestroy(buffer); + IPAddressDestroy(&ip_address); + return false; + } + BufferDestroy(buffer); + IPAddressDestroy(&ip_address); + return true; +} + +/*****************************************************************************/ + +/** + * @brief DNS lookup of hostname, store the address as string into dst of size + * dst_size. + * @return -1 in case of unresolvable hostname or other error. + */ +int Hostname2IPString(char *dst, const char *hostname, size_t dst_size) +{ + int ret; + struct addrinfo *response = NULL, *ap; + struct addrinfo query = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM + }; + + if (dst_size < CF_MAX_IP_LEN) + { + ProgrammingError("Hostname2IPString got %zu, needs at least" + " %d length buffer for IPv6 portability!", + dst_size, CF_MAX_IP_LEN); + } + + ret = getaddrinfo(hostname, NULL, &query, &response); + if (ret != 0) + { + Log(LOG_LEVEL_INFO, + "Unable to lookup hostname '%s' or cfengine service. (getaddrinfo: %s)", + hostname, gai_strerror(ret)); + if (response != NULL) + { + freeaddrinfo(response); + } + return -1; + } + + for (ap = response; ap != NULL; ap = ap->ai_next) + { + /* No lookup, just convert numeric IP to string. */ + int ret2 = getnameinfo(ap->ai_addr, ap->ai_addrlen, + dst, dst_size, NULL, 0, NI_NUMERICHOST); + if (ret2 == 0) + { + freeaddrinfo(response); + return 0; /* Success */ + } + } + + assert(response != NULL); /* getaddrinfo() was successful */ + freeaddrinfo(response); + + Log(LOG_LEVEL_ERR, + "Hostname2IPString: ERROR even though getaddrinfo returned success!"); + return -1; +} + +/*****************************************************************************/ + +/** + * @brief Reverse DNS lookup of ipaddr, store the address as string into dst + * of size dst_size. + * @return -1 in case of unresolvable IP address or other error. + */ +int IPString2Hostname(char *dst, const char *ipaddr, size_t dst_size) +{ + int ret; + struct addrinfo *response = NULL; + + /* First convert ipaddr string to struct sockaddr, with no DNS query. */ + struct addrinfo query = { + .ai_flags = AI_NUMERICHOST + }; + + ret = getaddrinfo(ipaddr, NULL, &query, &response); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, + "Unable to convert IP address '%s'. (getaddrinfo: %s)", + ipaddr, gai_strerror(ret)); + if (response != NULL) + { + freeaddrinfo(response); + } + return -1; + } + + /* response should only have one reply, so no need to iterate over the + * response struct addrinfo. */ + + /* Reverse DNS lookup. NI_NAMEREQD forces an error if not resolvable. */ + ret = getnameinfo(response->ai_addr, response->ai_addrlen, + dst, dst_size, NULL, 0, NI_NAMEREQD); + if (ret != 0) + { + Log(LOG_LEVEL_INFO, + "Couldn't reverse resolve '%s'. (getaddrinfo: %s)", + ipaddr, gai_strerror(ret)); + freeaddrinfo(response); + return -1; + } + + assert(response != NULL); /* getaddrinfo() was successful */ + freeaddrinfo(response); + return 0; /* Success */ +} + +/*****************************************************************************/ + +unsigned short SocketFamily(int sd) +{ + struct sockaddr_storage ss = {0}; + socklen_t len = sizeof(ss); + + if (getsockname(sd, (struct sockaddr *) &ss, &len) == -1) + { + Log(LOG_LEVEL_ERR, "Could not get socket family. (getsockname: %s)", GetErrorStr()); + } + + return ss.ss_family; +} diff --git a/libcfnet/communication.h b/libcfnet/communication.h new file mode 100644 index 0000000000..2837a08b11 --- /dev/null +++ b/libcfnet/communication.h @@ -0,0 +1,47 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_COMMUNICATION_H +#define CFENGINE_COMMUNICATION_H + +#include + +/** + @brief Allocates a new AgentConnection (stores a connection from Agent + to Server). + */ +AgentConnection *NewAgentConn(const char *server, const char *port, + ConnectionFlags flags); +/** + @brief Destroys an AgentConnection. + @param ap AgentConnection structure. + */ +void DeleteAgentConn(AgentConnection *ap); +bool IsIPV6Address(char *name); +bool IsIPV4Address(char *name); +int Hostname2IPString(char *dst, const char *hostname, size_t dst_size); +int IPString2Hostname(char *dst, const char *ipaddr, size_t dst_size); +unsigned short SocketFamily(int sd); + +#endif diff --git a/libcfnet/conn_cache.c b/libcfnet/conn_cache.c new file mode 100644 index 0000000000..60b4526ee2 --- /dev/null +++ b/libcfnet/conn_cache.c @@ -0,0 +1,242 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include +#include + +#include /* AgentConnection */ +#include /* DisconnectServer */ +#include /* Seq */ +#include /* ThreadLock */ +#include /* Hostname2IPString */ +#include /* CF_ASSERT */ +#include /* StringEqual */ + + +/** + Global cache for connections to servers, currently only used in cf-agent. + + @note THREAD-SAFETY: yes this connection cache *is* thread-safe, but is + extremely slow if used intensely from multiple threads. It needs to + be redesigned from scratch for that, not a priority for + single-threaded cf-agent! +*/ + + +typedef struct +{ + AgentConnection *conn; + enum ConnCacheStatus status; /* TODO unify with conn->conn_info->status */ +} ConnCache_entry; + + +static pthread_mutex_t cft_conncache = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; + +static Seq *conn_cache = NULL; + + +void ConnCache_Init() +{ + ThreadLock(&cft_conncache); + + assert(conn_cache == NULL); + conn_cache = SeqNew(100, free); + + ThreadUnlock(&cft_conncache); +} + +void ConnCache_Destroy() +{ + ThreadLock(&cft_conncache); + + for (size_t i = 0; i < SeqLength(conn_cache); i++) + { + ConnCache_entry *svp = SeqAt(conn_cache, i); + + CF_ASSERT(svp != NULL, + "Destroy: NULL ConnCache_entry!"); + CF_ASSERT(svp->conn != NULL, + "Destroy: NULL connection in ConnCache_entry!"); + + DisconnectServer(svp->conn); + } + + SeqDestroy(conn_cache); + conn_cache = NULL; + + ThreadUnlock(&cft_conncache); +} + +static bool ConnCacheEntryMatchesConnection(ConnCache_entry *entry, + const char *server, + const char *port, + ConnectionFlags flags) +{ + return ConnectionFlagsEqual(&flags, &entry->conn->flags) && + StringEqual(port, entry->conn->this_port) && + StringEqual(server, entry->conn->this_server); +} + +AgentConnection *ConnCache_FindIdleMarkBusy(const char *server, + const char *port, + ConnectionFlags flags) +{ + ThreadLock(&cft_conncache); + + AgentConnection *ret_conn = NULL; + for (size_t i = 0; i < SeqLength(conn_cache); i++) + { + ConnCache_entry *svp = SeqAt(conn_cache, i); + + CF_ASSERT(svp != NULL, + "FindIdle: NULL ConnCache_entry!"); + CF_ASSERT(svp->conn != NULL, + "FindIdle: NULL connection in ConnCache_entry!"); + + + if (svp->status == CONNCACHE_STATUS_BUSY) + { + Log(LOG_LEVEL_DEBUG, + "FindIdle: connection %p seems to be busy.", + svp->conn); + } + else if (svp->status == CONNCACHE_STATUS_OFFLINE) + { + Log(LOG_LEVEL_DEBUG, + "FindIdle: connection %p is marked as offline.", + svp->conn); + } + else if (svp->status == CONNCACHE_STATUS_BROKEN) + { + Log(LOG_LEVEL_DEBUG, + "FindIdle: connection %p is marked as broken.", + svp->conn); + } + else if (ConnCacheEntryMatchesConnection(svp, server, port, flags)) + { + if (svp->conn->conn_info->sd >= 0) + { + assert(svp->status == CONNCACHE_STATUS_IDLE); + + // Check connection state before returning it + int error = 0; + socklen_t len = sizeof(error); + if (getsockopt(svp->conn->conn_info->sd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) + { + Log(LOG_LEVEL_DEBUG, "FindIdle: found connection to '%s' but could not get socket status, skipping.", + server); + svp->status = CONNCACHE_STATUS_BROKEN; + continue; + } + if (error != 0) + { + Log(LOG_LEVEL_DEBUG, "FindIdle: found connection to '%s' but connection is broken, skipping.", + server); + svp->status = CONNCACHE_STATUS_BROKEN; + continue; + } + + Log(LOG_LEVEL_VERBOSE, "FindIdle:" + " found connection to '%s' already open and ready.", + server); + + svp->status = CONNCACHE_STATUS_BUSY; + ret_conn = svp->conn; + break; + } + else + { + Log(LOG_LEVEL_VERBOSE, "FindIdle:" + " connection to '%s' has invalid socket descriptor %d!", + server, svp->conn->conn_info->sd); + svp->status = CONNCACHE_STATUS_BROKEN; + } + } + } + + ThreadUnlock(&cft_conncache); + + if (ret_conn == NULL) + { + Log(LOG_LEVEL_VERBOSE, "FindIdle:" + " no existing connection to '%s' is established.", server); + } + + return ret_conn; +} + +void ConnCache_MarkNotBusy(AgentConnection *conn) +{ + Log(LOG_LEVEL_DEBUG, "Searching for specific busy connection to: %s", + conn->this_server); + + ThreadLock(&cft_conncache); + + bool found = false; + for (size_t i = 0; i < SeqLength(conn_cache); i++) + { + ConnCache_entry *svp = SeqAt(conn_cache, i); + + CF_ASSERT(svp != NULL, + "MarkNotBusy: NULL ConnCache_entry!"); + CF_ASSERT(svp->conn != NULL, + "MarkNotBusy: NULL connection in ConnCache_entry!"); + + if (svp->conn == conn) + { + /* There might be many connections to the same server, some busy + * some not. But here we're searching by the address of the + * AgentConnection object. There can be only one. */ + CF_ASSERT(svp->status == CONNCACHE_STATUS_BUSY, + "MarkNotBusy: status is not busy, it is %d!", + svp->status); + + svp->status = CONNCACHE_STATUS_IDLE; + found = true; + break; + } + } + + ThreadUnlock(&cft_conncache); + + if (!found) + { + ProgrammingError("MarkNotBusy: No busy connection found!"); + } + + Log(LOG_LEVEL_DEBUG, "Busy connection just became free"); +} + +/* First time we open a connection, so store it. */ +void ConnCache_Add(AgentConnection *conn, enum ConnCacheStatus status) +{ + ConnCache_entry *svp = xmalloc(sizeof(*svp)); + svp->status = status; + svp->conn = conn; + + ThreadLock(&cft_conncache); + SeqAppend(conn_cache, svp); + ThreadUnlock(&cft_conncache); +} diff --git a/libcfnet/conn_cache.h b/libcfnet/conn_cache.h new file mode 100644 index 0000000000..01666d1ea5 --- /dev/null +++ b/libcfnet/conn_cache.h @@ -0,0 +1,51 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#ifndef CFENGINE_CONN_CACHE_H +#define CFENGINE_CONN_CACHE_H + + +#include /* AgentConnection */ + + +enum ConnCacheStatus +{ + CONNCACHE_STATUS_IDLE = 0, + CONNCACHE_STATUS_BUSY, + CONNCACHE_STATUS_OFFLINE, + CONNCACHE_STATUS_BROKEN, +}; + + +void ConnCache_Init(void); +void ConnCache_Destroy(void); + +AgentConnection *ConnCache_FindIdleMarkBusy(const char *server, + const char *port, + ConnectionFlags flags); +void ConnCache_MarkNotBusy(AgentConnection *conn); +void ConnCache_Add(AgentConnection *conn, enum ConnCacheStatus status); +void ConnCache_IsBusy(AgentConnection *conn); + + +#endif diff --git a/libcfnet/connection_info.c b/libcfnet/connection_info.c new file mode 100644 index 0000000000..e2df1ac8ba --- /dev/null +++ b/libcfnet/connection_info.c @@ -0,0 +1,169 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include + + +ConnectionInfo *ConnectionInfoNew(void) +{ + struct ConnectionInfo *info = xcalloc(1, sizeof(struct ConnectionInfo)); + info->sd = SOCKET_INVALID; + + return info; +} + +void ConnectionInfoDestroy(ConnectionInfo **info) +{ + if (!info || !*info) + { + return; + } + /* Destroy everything */ + if ((*info)->ssl) + { + SSL_free((*info)->ssl); + } + KeyDestroy(&(*info)->remote_key); + free(*info); + *info = NULL; +} + +ProtocolVersion ConnectionInfoProtocolVersion(const ConnectionInfo *info) +{ + assert(info != NULL); + + return info ? info->protocol : CF_PROTOCOL_UNDEFINED; +} + +void ConnectionInfoSetProtocolVersion(ConnectionInfo *info, ProtocolVersion version) +{ + assert(info != NULL); + + if (info == NULL) + { + return; + } + switch (version) + { + case CF_PROTOCOL_UNDEFINED: + case CF_PROTOCOL_CLASSIC: + case CF_PROTOCOL_TLS: + info->protocol = version; + break; + default: + break; + } +} + +int ConnectionInfoSocket(const ConnectionInfo *info) +{ + assert(info != NULL); + + return info ? info->sd : -1; +} + +void ConnectionInfoSetSocket(ConnectionInfo *info, int s) +{ + assert(info != NULL); + + if (info == NULL) + { + return; + } + info->sd = s; +} + +SSL *ConnectionInfoSSL(const ConnectionInfo *info) +{ + assert(info != NULL); + + return info ? info->ssl : NULL; +} + +void ConnectionInfoSetSSL(ConnectionInfo *info, SSL *ssl) +{ + assert(info != NULL); + + if (info == NULL) + { + return; + } + info->ssl = ssl; +} + +const Key *ConnectionInfoKey(const ConnectionInfo *info) +{ + assert(info != NULL); + + const Key *key = info ? info->remote_key : NULL; + return key; +} + +void ConnectionInfoSetKey(ConnectionInfo *info, Key *key) +{ + assert(info != NULL); + + if (info == NULL) + { + return; + } + /* The key can be assigned only once on a session */ + if (info->remote_key) + { + return; + } + if (!key) + { + return; + } + info->remote_key = key; +} + +const unsigned char *ConnectionInfoBinaryKeyHash(ConnectionInfo *info, unsigned int *length) +{ + assert(info != NULL); + + if (info == NULL) + { + return NULL; + } + Key *connection_key = info->remote_key; + unsigned int real_length = 0; + const unsigned char *binary = KeyBinaryHash(connection_key, &real_length); + if (length) + { + *length = real_length; + } + return binary; +} + +const char *ConnectionInfoPrintableKeyHash(ConnectionInfo *info) +{ + assert(info != NULL); + + return info ? KeyPrintableHash(info->remote_key) : NULL; +} diff --git a/libcfnet/connection_info.h b/libcfnet/connection_info.h new file mode 100644 index 0000000000..f2e5cd562a --- /dev/null +++ b/libcfnet/connection_info.h @@ -0,0 +1,161 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CONNECTION_INFO_H +#define CONNECTION_INFO_H + + +#include + +#include + +#include + + +/** + @brief ConnectionInfo Structure and support routines + + ConnectionInfo is used to abstract the underlying type of connection from our protocol implementation. + It can hold both a normal socket connection and a TLS stream. + */ + + +/** + * @brief Status of the connection, for the connection cache and for + * propagating errors up in function callers. + */ +typedef enum +{ + CONNECTIONINFO_STATUS_NOT_ESTABLISHED, + CONNECTIONINFO_STATUS_ESTABLISHED, + /* used to propagate connection errors up in function calls */ + CONNECTIONINFO_STATUS_BROKEN + /* TODO ESTABLISHED==IDLE, BUSY, OFFLINE */ +} ConnectionStatus; + +struct ConnectionInfo { + ProtocolVersion protocol; + ConnectionStatus status; + int sd; /* Socket descriptor */ + SSL *ssl; /* OpenSSL struct for TLS connections */ + Key *remote_key; + socklen_t ss_len; + struct sockaddr_storage ss; + bool is_call_collect; /* Maybe replace with a bitfield later ... */ +}; + +typedef struct ConnectionInfo ConnectionInfo; + + +/** + @brief Creates a new ConnectionInfo structure. + @return A initialized ConnectionInfo structure, needs to be populated. + */ +ConnectionInfo *ConnectionInfoNew(void); + +/** + @brief Destroys a ConnectionInfo structure. + @param info Pointer to the ConectionInfo structure to be destroyed. + */ +void ConnectionInfoDestroy(ConnectionInfo **info); + +/** + @brief Protocol Version + @param info ConnectionInfo structure + @return Returns the protocol version or CF_PROTOCOL_UNDEFINED in case of error. + */ +ProtocolVersion ConnectionInfoProtocolVersion(const ConnectionInfo *info); + +/** + @brief Sets the protocol version + + Notice that if an invalid protocol version is passed, the value will not be changed. + @param info ConnectionInfo structure. + @param version New protocol version + */ +void ConnectionInfoSetProtocolVersion(ConnectionInfo *info, ProtocolVersion version); + +/** + @brief Connection socket + + For practical reasons there is no difference between an invalid socket and an error on this routine. + @param info ConnectionInfo structure. + @return Returns the connection socket or -1 in case of error. + */ +int ConnectionInfoSocket(const ConnectionInfo *info); + +/** + @brief Sets the connection socket. + @param info ConnectionInfo structure. + @param s New connection socket. + */ +void ConnectionInfoSetSocket(ConnectionInfo *info, int s); + +/** + @brief SSL structure. + @param info ConnectionInfo structure. + @return The SSL structure attached to this connection or NULL in case of error. + */ +SSL *ConnectionInfoSSL(const ConnectionInfo *info); + +/** + @brief Sets the SSL structure. + @param info ConnectionInfo structure. + @param ssl SSL structure to attached to this connection. + */ +void ConnectionInfoSetSSL(ConnectionInfo *info, SSL *ssl); + +/** + @brief RSA key + @param info ConnectionInfo structure. + @return Returns the RSA key or NULL in case of error. + */ +const Key *ConnectionInfoKey(const ConnectionInfo *info); + +/** + @brief Sets the key for the connection structure. + + This triggers a calculation of two other fields. + @param info ConnectionInfo structure. + @param key RSA key. + */ +void ConnectionInfoSetKey(ConnectionInfo *info, Key *key); + +/** + @brief A constant pointer to the binary hash of the key + @param info ConnectionInfo structure + @param length Length of the hash + @return Returns a constant pointer to the binary hash and if length is not NULL the size is stored there. + */ +const unsigned char *ConnectionInfoBinaryKeyHash(ConnectionInfo *info, unsigned int *length); + +/** + @brief A constant pointer to the binary hash of the key + @param info ConnectionInfo structure + @return Returns a printable representation of the hash. The string is '\0' terminated or NULL in case of failure. + */ +const char *ConnectionInfoPrintableKeyHash(ConnectionInfo *info); + + +#endif // CONNECTION_INFO_H diff --git a/libcfnet/key.c b/libcfnet/key.c new file mode 100644 index 0000000000..0197e7685c --- /dev/null +++ b/libcfnet/key.c @@ -0,0 +1,118 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +// platform.h / config.h / bool.h has bool type +// which is needed before including key.h +// TODO: Fix this, including key.h shouldn't require bool type defined +#include +#include +#include + +struct Key { + RSA *key; + Hash *hash; +}; + +Key *KeyNew(RSA *rsa, HashMethod method) +{ + if (!rsa) + { + return NULL; + } + Key *key = xmalloc (sizeof(Key)); + key->key = rsa; + /* Hash the key */ + key->hash = HashNewFromKey(rsa, method); + if (key->hash == NULL) + { + free (key); + return NULL; + } + return key; +} + +void KeyDestroy(Key **key) +{ + if (!key || !*key) + { + return; + } + if ((*key)->key) + { + RSA_free((*key)->key); + } + HashDestroy(&(*key)->hash); + free (*key); + *key = NULL; +} + +RSA *KeyRSA(const Key *key) +{ + return key ? key->key : NULL; +} + +const unsigned char *KeyBinaryHash(const Key *key, unsigned int *length) +{ + if (!key || !length) + { + return NULL; + } + return HashData(key->hash, length); +} + +const char *KeyPrintableHash(const Key *key) +{ + return key ? HashPrintable(key->hash) : NULL; +} + +HashMethod KeyHashMethod(const Key *key) +{ + return key ? HashType(key->hash) : HASH_METHOD_NONE; +} + +int KeySetHashMethod(Key *key, HashMethod method) +{ + if (!key) + { + return -1; + } + /* We calculate the new hash before changing the value, in case there is an error. */ + Hash *hash = NULL; + hash = HashNewFromKey(key->key, method); + if (hash == NULL) + { + return -1; + } + if (key->hash) + { + HashDestroy(&key->hash); + } + key->hash = hash; + return 0; +} + +const Hash *KeyData(Key *key) +{ + return key ? key->hash : NULL; +} diff --git a/libcfnet/key.h b/libcfnet/key.h new file mode 100644 index 0000000000..b981883aec --- /dev/null +++ b/libcfnet/key.h @@ -0,0 +1,92 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef KEY_H +#define KEY_H + +#include + +#include + + +/** + @brief Structure to simplify the key management. + + */ +typedef struct Key Key; + +/** + @brief Creates a new Key structure. + @param key RSA structure + @param hash Hash method to use when hashing the key. + @return A fully initialized Key structure or NULL in case of error. + */ +Key *KeyNew(RSA *rsa, HashMethod method); +/** + @brief Destroys a structure of type Key. + @param key Structure to be destroyed. + */ +void KeyDestroy(Key **key); +/** + @brief Constant pointer to the key data. + @param key Key + @return A pointer to the RSA structure. + */ +RSA *KeyRSA(const Key *key); +/** + @brief Binary hash of the key + @param key Key structure + @param length Length of the binary hash + @return A pointer to the binary hash or NULL in case of error. + */ +const unsigned char *KeyBinaryHash(const Key *key, unsigned int *length); +/** + @brief Printable hash of the key. + @param key + @return A pointer to the printable hash of the key. + */ +const char *KeyPrintableHash(const Key *key); +/** + @brief Method use to hash the key. + @param key Structure + @return Method used to hash the key. + */ +HashMethod KeyHashMethod(const Key *key); +/** + @brief Changes the method used to hash the key. + + This method triggers a rehashing of the key. This can be an expensive operation. + @param key Structure + @param hash New hashing mechanism. + @return 0 if successful, -1 in case of error. + */ +int KeySetHashMethod(Key *key, HashMethod method); +/** + @brief Internal Hash data + @param key Structure + @return A pointer to the Hash structure or NULL in case of error. + */ +const Hash *KeyData(Key *key); + +#endif // KEY_H diff --git a/libcfnet/misc.c b/libcfnet/misc.c new file mode 100644 index 0000000000..0cddb05b45 --- /dev/null +++ b/libcfnet/misc.c @@ -0,0 +1,54 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include /* GetErrorStr */ + + +int cf_closesocket(int sd) +{ + int res; + +#ifdef __MINGW32__ + res = closesocket(sd); + if (res == SOCKET_ERROR) + { + Log(LOG_LEVEL_VERBOSE, + "Failed to close socket (closesocket: %s)", + GetErrorStrFromCode(WSAGetLastError())); + } +#else + res = close(sd); + if (res == -1) + { + Log(LOG_LEVEL_VERBOSE, + "Failed to close socket (close: %s)", + GetErrorStr()); + } +#endif + + return res; +} diff --git a/libcfnet/net.c b/libcfnet/net.c new file mode 100644 index 0000000000..9de7663f64 --- /dev/null +++ b/libcfnet/net.c @@ -0,0 +1,711 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include /* CF_BUFSIZE */ +#include +#include +#include +#include +#include +#include +#include +#include + + +/* TODO remove libpromises dependency. */ +extern char BINDINTERFACE[CF_MAXVARSIZE]; /* cf3globals.c, cf3.extern.h */ + +void SetBindInterface(const char *ip) +{ + strlcpy(BINDINTERFACE, ip, sizeof(BINDINTERFACE)); + Log(LOG_LEVEL_VERBOSE, "Setting bindtointerface to '%s'", BINDINTERFACE); +} + + +/** + * @param len is the number of bytes to send, or 0 if buffer is a + * '\0'-terminated string so strlen(buffer) can be used. + * @return -1 in case of error or connection closed + * (also currently returns 0 for success but don't count on it) + * @NOTE #buffer can't be of zero length, our protocol + * does not allow empty transactions! + * @NOTE (len <= CF_BUFSIZE - CF_INBAND_OFFSET) + * + * @TODO Currently only transactions up to CF_BUFSIZE-CF_INBAND_OFFSET are + * allowed to be sent. This function should be changed to allow up to + * CF_BUFSIZE-1 (since '\0' is not sent, but the receiver needs space to + * append it). So transaction length will be at most 4095! + */ +int SendTransaction(ConnectionInfo *conn_info, + const char *buffer, int len, char status) +{ + assert(status == CF_MORE || status == CF_DONE); + + char work[CF_BUFSIZE] = { 0 }; + int ret; + + if (len == 0) + { + len = strlen(buffer); + } + + /* Not allowed to send zero-payload packets */ + assert(len > 0); + + if (len > CF_BUFSIZE - CF_INBAND_OFFSET) + { + Log(LOG_LEVEL_ERR, "SendTransaction: len (%d) > %d - %d", + len, CF_BUFSIZE, CF_INBAND_OFFSET); + return -1; + } + + snprintf(work, CF_INBAND_OFFSET, "%c %d", status, len); + + memcpy(work + CF_INBAND_OFFSET, buffer, len); + + Log(LOG_LEVEL_DEBUG, "SendTransaction header: %s", work); + LogRaw(LOG_LEVEL_DEBUG, "SendTransaction data: ", + work + CF_INBAND_OFFSET, len); + + switch (ProtocolClassicOrTLS(conn_info->protocol)) + { + + case CF_PROTOCOL_CLASSIC: + ret = SendSocketStream(conn_info->sd, work, + len + CF_INBAND_OFFSET); + break; + + case CF_PROTOCOL_TLS: + ret = TLSSend(conn_info->ssl, work, len + CF_INBAND_OFFSET); + if (ret <= 0) + { + ret = -1; + } + break; + + default: + UnexpectedError("SendTransaction: ProtocolVersion %d!", + conn_info->protocol); + ret = -1; + } + + if (ret == -1) + { + /* We are experiencing problems with sending data to server. + * This might lead to packages being not delivered in correct + * order and unexpected issues like directories being replaced + * with files. + * In order to make sure that file transfer is reliable we have to + * close connection to avoid broken packages being received. */ + conn_info->status = CONNECTIONINFO_STATUS_BROKEN; + return -1; + } + else + { + /* SSL_MODE_AUTO_RETRY guarantees no partial writes. */ + assert(ret == len + CF_INBAND_OFFSET); + + return 0; + } +} + +/*************************************************************************/ + +/** + * Receive a transaction packet of at most CF_BUFSIZE-1 bytes, and + * NULL-terminate it. + * + * @param #buffer must be of size at least CF_BUFSIZE. + * + * @return -1 in case of closed socket, other error or timeout. + * The connection MAY NOT BE FINALISED! + * >0 the number of bytes read, transaction was successfully received. + * + * @TODO shutdown() the connection in all cases were this function returns -1, + * in order to protect against future garbage reads. + */ +int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more) +{ + char proto[CF_INBAND_OFFSET + 1] = { 0 }; + int ret; + + /* Get control channel. */ + switch (ProtocolClassicOrTLS(conn_info->protocol)) + { + case CF_PROTOCOL_CLASSIC: + ret = RecvSocketStream(conn_info->sd, proto, CF_INBAND_OFFSET); + break; + case CF_PROTOCOL_TLS: + ret = TLSRecv(conn_info->ssl, proto, CF_INBAND_OFFSET); + break; + default: + UnexpectedError("ReceiveTransaction: ProtocolVersion %d!", + conn_info->protocol); + ret = -1; + } + + /* If error occurred or recv() timeout or if connection was gracefully + * closed. Connection has been finalised. */ + if (ret <= 0) + { + /* We are experiencing problems with receiving data from server. + * This might lead to packages being not delivered in correct + * order and unexpected issues like directories being replaced + * with files. + * In order to make sure that file transfer is reliable we have to + * close connection to avoid broken packages being received. */ + conn_info->status = CONNECTIONINFO_STATUS_BROKEN; + return -1; + } + else if (ret != CF_INBAND_OFFSET) + { + /* If we received less bytes than expected. Might happen + * with TLSRecv(). */ + Log(LOG_LEVEL_ERR, + "ReceiveTransaction: bogus short header (%d bytes: '%s')", + ret, proto); + conn_info->status = CONNECTIONINFO_STATUS_BROKEN; + return -1; + } + + LogRaw(LOG_LEVEL_DEBUG, "ReceiveTransaction header: ", proto, ret); + + char status = 'x'; + int len = 0; + + ret = sscanf(proto, "%c %d", &status, &len); + if (ret != 2) + { + Log(LOG_LEVEL_ERR, + "ReceiveTransaction: bogus header: %s", proto); + conn_info->status = CONNECTIONINFO_STATUS_BROKEN; + return -1; + } + + if (status != CF_MORE && status != CF_DONE) + { + Log(LOG_LEVEL_ERR, + "ReceiveTransaction: bogus header (more='%c')", status); + conn_info->status = CONNECTIONINFO_STATUS_BROKEN; + return -1; + } + if (len > CF_BUFSIZE - CF_INBAND_OFFSET) + { + Log(LOG_LEVEL_ERR, + "ReceiveTransaction: packet too long (len=%d)", len); + conn_info->status = CONNECTIONINFO_STATUS_BROKEN; + return -1; + } + else if (len <= 0) + { + /* Zero-length packets are disallowed, because + * ReceiveTransaction() == 0 currently means connection closed. */ + Log(LOG_LEVEL_ERR, + "ReceiveTransaction: packet too short (len=%d)", len); + conn_info->status = CONNECTIONINFO_STATUS_BROKEN; + return -1; + } + + if (more != NULL) + { + switch (status) + { + case CF_MORE: + *more = true; + break; + case CF_DONE: + *more = false; + break; + default: + ProgrammingError("Unreachable, " + "bogus headers have already been checked!"); + } + } + + /* Get data. */ + switch (ProtocolClassicOrTLS(conn_info->protocol)) + { + case CF_PROTOCOL_CLASSIC: + ret = RecvSocketStream(conn_info->sd, buffer, len); + break; + case CF_PROTOCOL_TLS: + ret = TLSRecv(conn_info->ssl, buffer, len); + break; + default: + UnexpectedError("ReceiveTransaction: ProtocolVersion %d!", + conn_info->protocol); + ret = -1; + } + + /* Connection gracefully closed (ret==0) or connection error (ret==-1) or + * just partial receive of bytestream.*/ + if (ret != len) + { + /* + * Should never happen except with TLS, given that we are using + * SSL_MODE_AUTO_RETRY and that transaction payload < CF_BUFSIZE < TLS + * record size, it can currently only happen if the other side does + * TLSSend(wrong_number) for the transaction. + * + * TODO IMPORTANT terminate TLS session in that case. + */ + Log(LOG_LEVEL_ERR, + "Partial transaction read %d != %d bytes!", + ret, len); + conn_info->status = CONNECTIONINFO_STATUS_BROKEN; + return -1; + } + + LogRaw(LOG_LEVEL_DEBUG, "ReceiveTransaction data: ", buffer, ret); + + return ret; +} + +/* BWlimit global variables + + Throttling happens for all network interfaces, all traffic being sent for + any connection of this process (cf-agent or cf-serverd). + We need a lock, to avoid concurrent writes to "bwlimit_next". + Then, "bwlimit_next" is the absolute time (as of clock_gettime() ) that we + are clear to send, after. It is incremented with the delay for every packet + scheduled for sending. Thus, integer arithmetic will make sure we wait for + the correct amount of time, in total. + */ + +#ifndef _WIN32 +static pthread_mutex_t bwlimit_lock = PTHREAD_MUTEX_INITIALIZER; +static struct timespec bwlimit_next = {0, 0L}; +#endif + +uint32_t bwlimit_kbytes = 0; /* desired limit, in kB/s */ + + +/** Throttle traffic, if next packet happens too soon after the previous one + * + * This function is global, across all network operations (and interfaces, perhaps) + * @param tosend Length of current packet being sent out (in bytes) + */ + +#ifdef CLOCK_MONOTONIC +# define PREFERRED_CLOCK CLOCK_MONOTONIC +#else +/* Some OS-es don't have monotonic clock, but we can still use the + * next available one */ +# define PREFERRED_CLOCK CLOCK_REALTIME +#endif + +void EnforceBwLimit(int tosend) +{ + if (!bwlimit_kbytes) + { + /* early return, before any expensive syscalls */ + return; + } + +#ifdef _WIN32 + Log(LOG_LEVEL_WARNING, "Bandwidth limiting with \"bwlimit\" is not supported on Windows."); + (void)tosend; // Avoid "unused" warning. + return; +#else + + const uint32_t u_10e6 = 1000000L; + const uint32_t u_10e9 = 1000000000L; + struct timespec clock_now = {0, 0L}; + + if (pthread_mutex_lock(&bwlimit_lock) == 0) + { + clock_gettime(PREFERRED_CLOCK, &clock_now); + + if ((bwlimit_next.tv_sec < clock_now.tv_sec) || + ( (bwlimit_next.tv_sec == clock_now.tv_sec) && + (bwlimit_next.tv_nsec < clock_now.tv_nsec) ) ) + { + /* penalty has expired, we can immediately send data. But reset the timestamp */ + bwlimit_next = clock_now; + clock_now.tv_sec = 0; + clock_now.tv_nsec = 0L; + } + else + { + clock_now.tv_sec = bwlimit_next.tv_sec - clock_now.tv_sec; + clock_now.tv_nsec = bwlimit_next.tv_nsec - clock_now.tv_nsec; + if (clock_now.tv_nsec < 0L) + { + clock_now.tv_sec --; + clock_now.tv_nsec += u_10e9; + } + } + + uint64_t delay = ((uint64_t) tosend * u_10e6) / bwlimit_kbytes; /* in ns */ + + bwlimit_next.tv_sec += (delay / u_10e9); + bwlimit_next.tv_nsec += (long) (delay % u_10e9); + if (bwlimit_next.tv_nsec >= u_10e9) + { + bwlimit_next.tv_sec++; + bwlimit_next.tv_nsec -= u_10e9; + } + + if (bwlimit_next.tv_sec > 20) + { + /* Upper limit of 20sec for penalty. This will avoid huge wait if + * our clock has jumped >minutes back in time. Still, assuming that + * most of our packets are <= 2048 bytes, the lower bwlimit is bound + * to 102.4 Bytes/sec. With 65k packets (rare) is 3.7kBytes/sec in + * that extreme case. + * With more clients hitting a single server, this lower bound is + * multiplied by num of clients, eg. 102.4kBytes/sec for 1000 reqs. + * simultaneously. + */ + bwlimit_next.tv_sec = 20; + } + pthread_mutex_unlock(&bwlimit_lock); + } + + /* Even if we push our data every few bytes to the network interface, + the software+hardware buffers will queue it and send it in bursts, + anyway. It is more likely that we will waste CPU sys-time calling + nanosleep() for such short delays. + So, sleep only if we have >1ms penalty + */ + if (clock_now.tv_sec > 0 || ( (clock_now.tv_sec == 0) && (clock_now.tv_nsec >= u_10e6)) ) + { + nanosleep(&clock_now, NULL); + } +#endif // !_WIN32 +} + + +/*************************************************************************/ + + +/** + Tries to connect() to server #host, returns the socket descriptor and the + IP address that succeeded in #txtaddr. + + @param #connect_timeout how long to wait for connect(), zero blocks forever + @param #txtaddr If connected successfully return the IP connected in + textual representation + @return Connected socket descriptor or -1 in case of failure. +*/ +int SocketConnect(const char *host, const char *port, + unsigned int connect_timeout, bool force_ipv4, + char *txtaddr, size_t txtaddr_size) +{ + struct addrinfo *response = NULL, *ap; + bool connected = false; + int sd = -1; + + struct addrinfo query = { + .ai_family = force_ipv4 ? AF_INET : AF_UNSPEC, + .ai_socktype = SOCK_STREAM + }; + + int ret = getaddrinfo(host, port, &query, &response); + if (ret != 0) + { + Log(LOG_LEVEL_INFO, + "Unable to find host '%s' service '%s' (%s)", + host, port, gai_strerror(ret)); + if (response != NULL) + { + freeaddrinfo(response); + } + return -1; + } + + for (ap = response; !connected && ap != NULL; ap = ap->ai_next) + { + /* Convert address to string. */ + getnameinfo(ap->ai_addr, ap->ai_addrlen, + txtaddr, txtaddr_size, + NULL, 0, NI_NUMERICHOST); + Log(LOG_LEVEL_VERBOSE, + "Connecting to host %s, port %s as address %s", + host, port, txtaddr); + + sd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol); + if (sd == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't open a socket to '%s' (socket: %s)", + txtaddr, GetErrorStr()); + } + else + { + /* Bind socket to specific interface, if requested. */ + if (BINDINTERFACE[0] != '\0') + { + struct addrinfo query2 = { + .ai_family = force_ipv4 ? AF_INET : AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + /* returned address is for bind() */ + .ai_flags = AI_PASSIVE + }; + + struct addrinfo *response2 = NULL, *ap2; + int ret2 = getaddrinfo(BINDINTERFACE, NULL, &query2, &response2); + if (ret2 != 0) + { + Log(LOG_LEVEL_ERR, + "Unable to lookup interface '%s' to bind. (getaddrinfo: %s)", + BINDINTERFACE, gai_strerror(ret2)); + + if (response2 != NULL) + { + freeaddrinfo(response2); + } + assert(response); /* first getaddrinfo was successful */ + freeaddrinfo(response); + cf_closesocket(sd); + return -1; + } + + for (ap2 = response2; ap2 != NULL; ap2 = ap2->ai_next) + { + if (bind(sd, ap2->ai_addr, ap2->ai_addrlen) == 0) + { + break; + } + } + if (ap2 == NULL) + { + Log(LOG_LEVEL_ERR, + "Unable to bind to interface '%s'. (bind: %s)", + BINDINTERFACE, GetErrorStr()); + } + assert(response2); /* second getaddrinfo was successful */ + freeaddrinfo(response2); + } + + connected = TryConnect(sd, connect_timeout * 1000, + ap->ai_addr, ap->ai_addrlen); + if (!connected) + { + Log(LOG_LEVEL_VERBOSE, "Unable to connect to address %s (%s)", + txtaddr, GetErrorStr()); + cf_closesocket(sd); + sd = -1; + } + } + } + + assert(response != NULL); /* first getaddrinfo was successful */ + freeaddrinfo(response); + + if (connected) + { + Log(LOG_LEVEL_VERBOSE, + "Connected to host %s address %s port %s (socket descriptor %d)", + host, txtaddr, port, sd); + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Unable to connect to host %s port %s (socket descriptor %d)", + host, port, sd); + } + + return sd; +} + + +#if !defined(__MINGW32__) + +#if defined(__hpux) && defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +// HP-UX GCC type-pun warning on FD_SET() macro: +// While the "fd_set" type is defined in /usr/include/sys/_fd_macros.h as a +// struct of an array of "long" values in accordance with the XPG4 standard's +// requirements, the macros for the FD operations "pretend it is an array of +// int32_t's so the binary layout is the same for both Narrow and Wide +// processes," as described in _fd_macros.h. In the FD_SET, FD_CLR, and +// FD_ISSET macros at line 101, the result is cast to an "__fd_mask *" type, +// which is defined as int32_t at _fd_macros.h:82. +// +// This conflict between the "long fds_bits[]" array in the XPG4-compliant +// fd_set structure, and the cast to an int32_t - not long - pointer in the +// macros, causes a type-pun warning if -Wstrict-aliasing is enabled. +// The warning is merely a side effect of HP-UX working as designed, +// so it can be ignored. +#endif + +/** + * Tries to connect for #timeout_ms milliseconds. On success sets the recv() + * timeout to #timeout_ms as well. + * + * @param #timeout_ms How long to wait for connect(), if zero wait forever. + * @return true on success, false otherwise. + **/ +bool TryConnect(int sd, unsigned long timeout_ms, + const struct sockaddr *sa, socklen_t sa_len) +{ + assert(sd != -1); + assert(sa != NULL); + + if (sd >= FD_SETSIZE) + { + Log(LOG_LEVEL_ERR, + "Open connections exceed FD_SETSIZE limit (%d >= %d)", + sd, FD_SETSIZE); + return false; + } + + /* set non-blocking socket */ + int arg = fcntl(sd, F_GETFL, NULL); + int ret = fcntl(sd, F_SETFL, arg | O_NONBLOCK); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to set socket to non-blocking mode (fcntl: %s)", + GetErrorStr()); + } + + ret = connect(sd, sa, sa_len); + if (ret == -1) + { + if (errno != EINPROGRESS) + { + Log(LOG_LEVEL_INFO, "Failed to connect to server (connect: %s)", + GetErrorStr()); + return false; + } + + int errcode; + socklen_t opt_len = sizeof(errcode); + fd_set myset; + FD_ZERO(&myset); + FD_SET(sd, &myset); + + Log(LOG_LEVEL_VERBOSE, "Waiting to connect..."); + + struct timeval tv, *tvp; + if (timeout_ms > 0) + { + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + tvp = &tv; + } + else + { + tvp = NULL; /* wait indefinitely */ + } + + ret = select(sd + 1, NULL, &myset, NULL, tvp); + if (ret == 0) + { + Log(LOG_LEVEL_INFO, "Timeout connecting to server"); + return false; + } + if (ret == -1) + { + if (errno == EINTR) + { + Log(LOG_LEVEL_ERR, + "Socket connect was interrupted by signal"); + } + else + { + Log(LOG_LEVEL_ERR, + "Failure while connecting (select: %s)", + GetErrorStr()); + } + return false; + } + + ret = getsockopt(sd, SOL_SOCKET, SO_ERROR, + (void *) &errcode, &opt_len); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, + "Could not check connection status (getsockopt: %s)", + GetErrorStr()); + return false; + } + + if (errcode != 0) + { + Log(LOG_LEVEL_INFO, "Failed to connect to server: %s", + GetErrorStrFromCode(errcode)); + return false; + } + } + + /* Connection succeeded, return to blocking mode. */ + ret = fcntl(sd, F_SETFL, arg); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to set socket back to blocking mode (fcntl: %s)", + GetErrorStr()); + } + + if (timeout_ms > 0) + { + SetReceiveTimeout(sd, timeout_ms); + } + + return true; +} + +#if defined(__hpux) && defined(__GNUC__) +#pragma GCC diagnostic warning "-Wstrict-aliasing" +#endif + +#endif /* !defined(__MINGW32__) */ + + + +/** + * Set timeout for recv(), in milliseconds. + * @param ms must be > 0. + */ +int SetReceiveTimeout(int fd, unsigned long ms) +{ + assert(ms > 0); + + Log(LOG_LEVEL_VERBOSE, "Setting socket timeout to %lu seconds.", ms/1000); + +/* On windows SO_RCVTIMEO is set by a DWORD indicating the timeout in + * milliseconds, on UNIX it's a struct timeval. */ + +#if !defined(__MINGW32__) + struct timeval tv = { + .tv_sec = ms / 1000, + .tv_usec = (ms % 1000) * 1000 + }; + int ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +#else + int ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &ms, sizeof(ms)); +#endif + + if (ret != 0) + { + Log(LOG_LEVEL_VERBOSE, + "Failed to set socket timeout to %lu milliseconds.", ms); + return -1; + } + + return 0; +} diff --git a/libcfnet/net.h b/libcfnet/net.h new file mode 100644 index 0000000000..59eaf22764 --- /dev/null +++ b/libcfnet/net.h @@ -0,0 +1,54 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* Low Level networking routines. */ + +#ifndef CFENGINE_NET_H +#define CFENGINE_NET_H + + +#include + + +extern uint32_t bwlimit_kbytes; + + +int SendTransaction(ConnectionInfo *conn_info, const char *buffer, int len, char status); +int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more); + +int SetReceiveTimeout(int fd, unsigned long ms); + +int SocketConnect(const char *host, const char *port, + unsigned int connect_timeout, bool force_ipv4, + char *txtaddr, size_t txtaddr_size); + +/** + * @NOTE DO NOT USE THIS FUNCTION. The only reason it is non-static is because + * of a separate implementation for windows in Enterprise. + */ +bool TryConnect(int sd, unsigned long timeout_ms, + const struct sockaddr *sa, socklen_t sa_len); + + +#endif diff --git a/libcfnet/policy_server.c b/libcfnet/policy_server.c new file mode 100644 index 0000000000..85d14091bd --- /dev/null +++ b/libcfnet/policy_server.c @@ -0,0 +1,347 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +//******************************************************************* +// POLICY SERVER VARIABLES: +//******************************************************************* + +static char *POLICY_SERVER = NULL; // full bootstrap argument +static char *POLICY_SERVER_HOST = NULL; // only host part, if present +static char POLICY_SERVER_PORT[CF_MAX_PORT_LEN]; // only port part +static char POLICY_SERVER_IP[CF_MAX_IP_LEN]; // resolved IP + + +//******************************************************************* +// POLICY SERVER SET FUNCTION: +//******************************************************************* + +static bool is_whitespace_empty(const char *str) { + if(NULL_OR_EMPTY(str)) + { + return true; + } + while (str[0] != '\0') + { + if (!isspace(str[0])) + { + return false; + } + ++str; + } + return true; +} + +/** + * @brief Sets the POLICY_SERVER, POLICY_SERVER_HOST and POLICY_SERVER_PORT global variables + */ +void PolicyServerSet(const char *new_policy_server) +{ + // Clean up static variables: + free(POLICY_SERVER); + free(POLICY_SERVER_HOST); + POLICY_SERVER = NULL; + POLICY_SERVER_HOST = NULL; + + POLICY_SERVER_IP[0] = '\0'; + POLICY_SERVER_PORT[0] = '\0'; + + if (is_whitespace_empty(new_policy_server)) + { + return; + } + else + {// Set POLICY_SERVER to be bootstrap argument/policy_server.dat contents + POLICY_SERVER = xstrdup(new_policy_server); + } + + // Parse policy server in a separate buffer: + char *host_or_ip, *port; + char *buffer = xstrdup(new_policy_server); + + AddressType address_type = ParseHostPort(buffer, &host_or_ip, &port); + + if (address_type == ADDRESS_TYPE_OTHER) + { + POLICY_SERVER_HOST = xstrdup(host_or_ip); + } + else // ADDRESS_TYPE_IPV4 or ADDRESS_TYPE_IPV6 + { + assert(strlen(host_or_ip) < sizeof(POLICY_SERVER_IP)); + StringCopy(host_or_ip, POLICY_SERVER_IP, sizeof(POLICY_SERVER_IP)); + } + + if ( ! NULL_OR_EMPTY(port) ) + { + if(strlen(port) < CF_MAX_PORT_LEN) + { + strcpy(POLICY_SERVER_PORT, port); + } + else + { + Log(LOG_LEVEL_WARNING, + "Too long port number in PolicyServerSet: '%s'", + port); + } + } + + free(buffer); +} + + +//******************************************************************* +// POLICY SERVER GET FUNCTIONS: +//******************************************************************* + +static char *CheckEmptyReturn(char *s) +{ + return (NULL_OR_EMPTY(s)) ? NULL : s; +} + +/** + * @brief Used to access the internal POLICY_SERVER variable. + * @return Read-only string, can be 'host:port', same as policy_server.dat + * NULL if not bootstrapped ( not set ). + */ +const char *PolicyServerGet() +{ + return POLICY_SERVER; // Don't use CheckedReturn, var should be NULL! +} + +/** + * @brief Gets the IP address of policy server, does lookup if necessary. + * @return Read-only string, can be IPv4 or IPv6. + * NULL if not bootstrapped or lookup failed. + */ +const char *PolicyServerGetIP() +{ + if (POLICY_SERVER_HOST == NULL) + { + return CheckEmptyReturn(POLICY_SERVER_IP); + } + assert(POLICY_SERVER_HOST[0] != '\0'); + int ret = Hostname2IPString(POLICY_SERVER_IP, POLICY_SERVER_HOST, + CF_MAX_IP_LEN); + if (ret != 0) // Lookup failed + { + return NULL; + } + return CheckEmptyReturn(POLICY_SERVER_IP); +} + +/** + * @brief Gets the host part of what was bootstrapped to (without port). + * @return Read-only string, hostname part of bootstrap argument. + * NULL if not bootstrapped or bootstrapped to IP. + */ +const char *PolicyServerGetHost() +{ + return POLICY_SERVER_HOST; // Don't use CheckedReturn, var should be NULL! +} + +/** + * @brief Gets the port part of the policy server. + * @return Read-only null terminated string of port number. + * NULL if port not specified, or not bootstrapped at all. + */ +const char *PolicyServerGetPort() +{ + return CheckEmptyReturn(POLICY_SERVER_PORT); +} + +//******************************************************************* +// POLICY SERVER FILE FUNCTIONS: +//******************************************************************* + +static char *PolicyServerFilename(const char *workdir) +{ + return StringFormat("%s%cpolicy_server.dat", workdir, FILE_SEPARATOR); +} + +/** + * @brief Reads the policy_server.dat file. + * @param[in] workdir the directory of policy_server.dat usually GetWorkDir() + * @return Trimmed contents of policy_server.dat file. Null terminated. + */ +char *PolicyServerReadFile(const char *workdir) +{ + char contents[CF_MAX_SERVER_LEN] = ""; + + char *filename = PolicyServerFilename(workdir); + FILE *fp = safe_fopen(filename, "r"); + if (fp == NULL) + { + Log( LOG_LEVEL_VERBOSE, "Could not open file '%s' (fopen: %s)", + filename, GetErrorStr() ); + free(filename); + return NULL; + } + + if (fgets(contents, CF_MAX_SERVER_LEN, fp) == NULL) + { + Log( LOG_LEVEL_VERBOSE, "Could not read file '%s' (fgets: %s)", + filename, GetErrorStr() ); + free(filename); + fclose(fp); + return NULL; + } + + free(filename); + fclose(fp); + char *start = TrimWhitespace(contents); + return xstrdup(start); +} + + +/** + * @brief Reads and parses the policy_server.dat file. + * + * @code{.c} + * //Typical usage: + * char *host, *port; + * bool file_read = PolicyServerParseFile(GetWorkDir(), &host, &port); + * printf( "host is %s", file_read ? host : "unavailable" ); + * free(host); free(port); + * @endcode + * + * @param[in] workdir The directory of policy_server.dat usually GetWorkDir() + * @param[out] host pointer at this address will be hostname string (strdup) + * @param[out] port pointer at this address will be port string (strdup) + * @attention host* and port* must be freed. + * @return Boolean indicating success. + */ +bool PolicyServerParseFile(const char *workdir, char **host, char **port) +{ + char *contents = PolicyServerReadFile(workdir); + if (contents == NULL) + { + return false; + } + (*host) = NULL; + (*port) = NULL; + + ParseHostPort(contents, host, port); + + // The file did not contain a host + if (*host == NULL) + { + return false; + } + + (*host) = xstrdup(*host); + if (*port != NULL) + { + (*port) = xstrdup(*port); + } + free(contents); + return true; +} + +/** + * @brief Reads and parses the policy_server.dat file. + * + * @param[in] workdir The directory of policy_server.dat usually GetWorkDir() + * @param[out] ipaddr pointer at this address will be hostname string (strdup) + * @param[out] port pointer at this address will be port string (strdup) + * @attention ipaddr* and port* must be freed. + * @return Boolean indicating success. + * @see PolicyServerParseFile + */ +bool PolicyServerLookUpFile(const char *workdir, char **ipaddr, char **port) +{ + char *host; + bool file_read = PolicyServerParseFile(workdir, &host, port); + if (file_read == false) + { + return false; + } + char tmp_ipaddr[CF_MAX_IP_LEN]; + if (Hostname2IPString(tmp_ipaddr, host, sizeof(tmp_ipaddr)) == -1) + { + Log(LOG_LEVEL_ERR, + "Unable to resolve policy server host: %s", host); + free(host); + free(*port); + (*port) = NULL; + return false; + } + (*ipaddr) = xstrdup(tmp_ipaddr); + free(host); + return true; +} + +/** + * @brief Write new_policy_server to the policy_server.dat file. + * @param[in] workdir The directory of policy_server.dat, usually GetWorkDir() + * @param[in] new_policy_server The host:port string defining the server + * @return True if successful + */ +bool PolicyServerWriteFile(const char *workdir, const char *new_policy_server) +{ + char *filename = PolicyServerFilename(workdir); + + FILE *file = safe_fopen(filename, "w"); + if (file == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to write policy server file '%s' (fopen: %s)", filename, GetErrorStr()); + free(filename); + return false; + } + + fprintf(file, "%s\n", new_policy_server); + fclose(file); + + free(filename); + return true; +} + +/** + * @brief Remove the policy_server.dat file + * @param[in] workdir The directory of policy_server.dat, usually GetWorkDir() + * @return True if successful + */ +bool PolicyServerRemoveFile(const char *workdir) +{ + char *filename = PolicyServerFilename(workdir); + + if (unlink(filename) != 0) + { + Log(LOG_LEVEL_ERR, "Unable to remove file '%s'. (unlink: %s)", filename, GetErrorStr()); + free(filename); + return false; + } + + free(filename); + return true; +} diff --git a/libcfnet/policy_server.h b/libcfnet/policy_server.h new file mode 100644 index 0000000000..19101c0980 --- /dev/null +++ b/libcfnet/policy_server.h @@ -0,0 +1,51 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/** @file + * @brief Access to Policy Server IP Address, hostname and port number. + * + * Provides a simple get/set interface for the policy server variables. + * Does hostname resolution behind the scenes. + */ + +#ifndef CFENGINE_POLICYSERVER_H +#define CFENGINE_POLICYSERVER_H + +#include + +// GET/SET FUNCTIONS: +void PolicyServerSet(const char *new_policy_server); +const char *PolicyServerGet(); +const char *PolicyServerGetIP(); +const char *PolicyServerGetHost(); +const char *PolicyServerGetPort(); + +// POLICY SERVER FILE FUNCTIONS: +char* PolicyServerReadFile(const char *workdir); +bool PolicyServerParseFile(const char *workdir, char **host, char **port); +bool PolicyServerLookUpFile(const char *workdir, char **ipaddr, char **port); +bool PolicyServerWriteFile(const char *workdir, const char *new_policy_server); +bool PolicyServerRemoveFile(const char *workdir); + +#endif diff --git a/libcfnet/protocol.c b/libcfnet/protocol.c new file mode 100644 index 0000000000..3a6f9bcb37 --- /dev/null +++ b/libcfnet/protocol.c @@ -0,0 +1,302 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +Seq *ProtocolOpenDir(AgentConnection *conn, const char *path) +{ + assert(conn != NULL); + assert(path != NULL); + + char buf[CF_MSGSIZE] = {0}; + int tosend = snprintf(buf, CF_MSGSIZE, "OPENDIR %s", path); + if (tosend < 0 || tosend >= CF_MSGSIZE) + { + return NULL; + } + + int ret = SendTransaction(conn->conn_info, buf, tosend, CF_DONE); + if (ret == -1) + { + return NULL; + } + + Seq *seq = SeqNew(0, free); + + int more = 1; + while (more != 0) + { + int len = ReceiveTransaction(conn->conn_info, buf, &more); + if (len == -1) + { + break; + } + + if (BadProtoReply(buf)) + { + Log(LOG_LEVEL_ERR, "Protocol error: %s", buf); + SeqDestroy(seq); + return NULL; + } + + /* + * Iterates over each string in the received transaction and appends + * it to the Seq list, until it either finds the CFD_TERMINATOR + * string, or reaches the end of the message. + */ + for (int i = 0; i < len && buf[i] != '\0'; i += strlen(buf + i) + 1) + { + if (StringEqualN(buf + i, CFD_TERMINATOR, + sizeof(CFD_TERMINATOR) - 1)) + { + more = 0; + break; + } + + char *str = xstrdup(buf + i); + SeqAppend(seq, str); + } + } + + return seq; +} + +bool ProtocolGet(AgentConnection *conn, const char *remote_path, + const char *local_path, const uint32_t file_size, int perms) +{ + assert(conn != NULL); + assert(remote_path != NULL); + assert(local_path != NULL); + assert(file_size != 0); + + perms = (perms == 0) ? CF_PERMS_DEFAULT : perms; + + unlink(local_path); + FILE *file_ptr = safe_fopen_create_perms(local_path, "wx", perms); + if (file_ptr == NULL) + { + Log(LOG_LEVEL_WARNING, "Failed to open file %s (fopen: %s)", + local_path, GetErrorStr()); + return false; + } + + char buf[CF_MSGSIZE] = {0}; + int to_send = snprintf(buf, CF_MSGSIZE, "GET %d %s", + CF_MSGSIZE, remote_path); + + + int ret = SendTransaction(conn->conn_info, buf, to_send, CF_DONE); + if (ret == -1) + { + Log(LOG_LEVEL_WARNING, "Failed to send request for remote file %s:%s", + conn->this_server, remote_path); + unlink(local_path); + fclose(file_ptr); + return false; + } + + char cfchangedstr[sizeof(CF_CHANGEDSTR1 CF_CHANGEDSTR2)]; + snprintf(cfchangedstr, sizeof(cfchangedstr), "%s%s", + CF_CHANGEDSTR1, CF_CHANGEDSTR2); + + bool success = true; + uint32_t received_bytes = 0; + while (received_bytes < file_size) + { + int len = TLSRecv(conn->conn_info->ssl, buf, CF_MSGSIZE); + if (len == -1) + { + Log(LOG_LEVEL_WARNING, "Failed to GET file %s:%s", + conn->this_server, remote_path); + success = false; + break; + } + else if (len > CF_MSGSIZE) + { + Log(LOG_LEVEL_WARNING, + "Incorrect length of incoming packet " + "while retrieving %s:%s, %d > %d", + conn->this_server, remote_path, len, CF_MSGSIZE); + success = false; + break; + } + + if (BadProtoReply(buf)) + { + Log(LOG_LEVEL_ERR, + "Error from server while retrieving file %s:%s: %s", + conn->this_server, remote_path, buf); + success = false; + break; + } + + if (StringEqualN(buf, cfchangedstr, sizeof(cfchangedstr) - 1)) + { + Log(LOG_LEVEL_ERR, + "Remote file %s:%s changed during file transfer", + conn->this_server, remote_path); + success = false; + break; + } + + ret = fwrite(buf, sizeof(char), len, file_ptr); + if (ret < 0) + { + Log(LOG_LEVEL_ERR, + "Failed to write during retrieval of file %s:%s (fwrite: %s)", + conn->this_server, remote_path, GetErrorStr()); + success = false; + break; + } + + received_bytes += len; + } + + if (!success) + { + unlink(local_path); + } + + fclose(file_ptr); + return success; +} + +bool ProtocolStatGet(AgentConnection *conn, const char *remote_path, + const char *local_path, int perms) +{ + assert(conn != NULL); + assert(remote_path != NULL); + + struct stat sb; + bool ret = ProtocolStat(conn, remote_path, &sb); + if (!ret) + { + Log(LOG_LEVEL_ERR, + "Failed to stat remote file %s:%s", + conn->this_server, remote_path); + return false; + } + + return ProtocolGet(conn, remote_path, local_path, sb.st_size, perms); +} + +bool ProtocolStat(AgentConnection *const conn, const char *const remote_path, + struct stat *const stat_buf) +{ + assert(conn != NULL); + assert(remote_path != NULL); + assert(stat_buf != NULL); + + time_t tloc = time(NULL); + if (tloc == (time_t) -1) + { + Log(LOG_LEVEL_WARNING, + "Couldn't read system clock, defaulting to 0 in case server " + "does not care about clock differences (time: %s)", + GetErrorStr()); + tloc = 0; + } + + char buf[CF_BUFSIZE] = {0}; + int to_send = snprintf(buf, CF_BUFSIZE, "SYNCH %jd STAT %s", + (intmax_t) tloc, remote_path); + + int ret = SendTransaction(conn->conn_info, buf, to_send, CF_DONE); + if (ret == -1) + { + Log(LOG_LEVEL_WARNING, + "Could not send stat request for remote file %s:%s.", + conn->this_server, remote_path); + return false; + } + + int recvd_len = ReceiveTransaction(conn->conn_info, buf, NULL) == -1; + if (recvd_len == -1) + { + Log(LOG_LEVEL_WARNING, + "Receiving file statistics from %s failed!", + conn->this_server); + return false; + } + + if (BadProtoReply(buf)) + { + Log(LOG_LEVEL_WARNING, + "Could not stat remote file %s:%s, response: %s", + conn->this_server, remote_path, buf); + return false; + } + + if (!OKProtoReply(buf)) + { + Log(LOG_LEVEL_WARNING, + "Illegal response from server while statting %s:%s", + conn->this_server, remote_path); + return false; + } + + Stat cf_stat; + ret = StatParseResponse(buf, &cf_stat); + if (!ret) + { + Log(LOG_LEVEL_WARNING, + "Failed to parse the response from the server " + "while statting %s:%s", + conn->this_server, remote_path); + return false; + } + + mode_t file_type = FileTypeToMode(cf_stat.cf_type); + if (file_type == 0) + { + Log(LOG_LEVEL_VERBOSE, + "Invalid file type identifier for file %s:%s, %u", + conn->this_server, remote_path, cf_stat.cf_type); + return false; + } + + stat_buf->st_mode = file_type | cf_stat.cf_mode; + stat_buf->st_uid = cf_stat.cf_uid; + stat_buf->st_gid = cf_stat.cf_gid; + stat_buf->st_size = cf_stat.cf_size; + stat_buf->st_mtime = cf_stat.cf_mtime; + stat_buf->st_ctime = cf_stat.cf_ctime; + stat_buf->st_atime = cf_stat.cf_atime; + stat_buf->st_ino = cf_stat.cf_ino; + stat_buf->st_dev = cf_stat.cf_dev; + stat_buf->st_nlink = cf_stat.cf_nlink; + + // Receive link destination, but do nothing + ReceiveTransaction(conn->conn_info, buf, NULL); + + return true; +} diff --git a/libcfnet/protocol.h b/libcfnet/protocol.h new file mode 100644 index 0000000000..23f2009708 --- /dev/null +++ b/libcfnet/protocol.h @@ -0,0 +1,157 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PROTOCOL_H +#define CFENGINE_PROTOCOL_H + +#include +#include +#include // ProtocolVersion + +/** + * Receives a directory listing from a remote host. + * + * The server will use "/var/cfengine" as working directory if absolute path + * is not specified. The server sends the directory entries as a string + * separated by NUL-bytes, and ending with the magic string CFD_TERMINATOR. + * + * The function shall fail if connection is not established, or if the server + * gives a bad response (denoted by a message preceded by "BAD"). + * + * @param [in] conn The connection to use + * @param [in] path Path on remote host + * @return A sequence of filenames in the requested directory on success, NULL + * on failure. + * + * Example (for printing each directory entry): + * @code + * AgentConnection *conn = ServerConnection("127.0.0.1", "666", ...); + * Seq *dir = ProtocolOpenDir(conn, "masterfiles"); + * for (int i = 0; i < SeqLength(dir); i++) + * { + * char *entry = SeqAt(i); + * printf("%s\n", entry); + * } + * @endcode + * + * In the protocol, this will look like this on the server side: + * Received: OPENDIR masterfiles + * Translated to: OPENDIR /var/cfengine/masterfiles + * Sends string: + * ".\0..\0cfe_internal\0cf_promises_release_id\0... + * ...templates\0update.cf\0" CFD_TERMINATOR + */ +Seq *ProtocolOpenDir(AgentConnection *conn, const char *path); + +/** + * Receives a file from a remote host. + * + * The server will use "/var/cfengine" as working directory if absolute path + * is not specified. The client will send a request that looks like this: + * `GET ` + * + * `buf_size` is the local buffer size: how much of the file to receive in + * each transaction. It should be aligned to block size (which is usually + * 4096), but currently the protocol only allows _exactly_ 4095 bytes per + * transaction. + * + * The function shall fail if connection is not established, or if the server + * gives a bad response (denoted by a message preceded by "BAD"). + * + * @param [in] conn The connection to use + * @param [in] remote_path Path on remote host + * @param [in] local_path Path of received file + * @param [in] file_size Size of file to get + * @param [in] perms Permissions of local file + * @return True if file was successfully transferred, false otherwise + * + * Example (for printing each directory entry): + * @code + * AgentConnection *conn = ServerConnection("127.0.0.1", "666", ...); + * bool got_file = ProtocolGet(conn, "masterfiles/update.cf", + * "update.cf", CF_MSGSIZE, 0644); + * if (got_file) + * { + * struct stat sb; + * stat("update.cf", &sb); + * printf("This file is %ld big!\n", sb.st_size); + * } + * @endcode + * + * In the protocol, this will look like this on the server side: + * Received: GET masterfiles/update.cf + * Translated to: GET /var/cfengine/masterfiles/update.cf + */ +bool ProtocolGet(AgentConnection *conn, const char *remote_path, + const char *local_path, const uint32_t file_size, int perms); + + +/** + * Receives a file from a remote host, see documentation for #ProtocolGet + * + * This funtion will first stat the remote path before attempting to receive + * it. + */ +bool ProtocolStatGet(AgentConnection *conn, const char *remote_path, + const char *local_path, int perms); + +/** + * Receives statistics about a remote file. + * + * This is a cacheless version of #cf_remote_stat from stat_cache.c. This + * only supports sending with the latest cfnet protocol. + * + * When the `STAT` request is sent, it is sent together with the current time + * since the Epoch given by the `time` syscall denoted by `SYNCH `. If + * the server is set to deny bad clocks (which is default), it will reject + * `STAT` requests from hosts where the clocks differ too much. + * + * When the server accepts the `STAT` request, it will send each field of the + * `Stat` struct from `stat_cache.h` as numbers delimited by spaces in a + * single string. Since the `Stat` struct is not cached, its fields are + * transferred to the \p stat_buf parameter. + * + * Example + * @code + * AgentConnection *conn = ServerConnection("127.0.0.1", "666", ...); + * struct stat stat_buf; + * ProtocolStat(conn, "masterfiles/update.cf", &stat_buf); + * assert((stat_buf.st_mode & S_IFMT) == S_IFREG); + * @endcode + * + * This is how the above example looks on the server side: + * Received: SYNCH 12356789 STAT masterfiles/update.cf + * Translated to: STAT /var/cfengine/masterfiles/update.cf + * Sends string: + * "OK: 0 33188 0 ..." etc. + * + * @param [in] conn The connection to use + * @param [in] remote_path Path on remote host + * @param [out] stat_buf Where to store statistics + * @return true on success, false on failure. + */ +bool ProtocolStat(AgentConnection *conn, const char *remote_path, + struct stat *stat_buf); + +#endif diff --git a/libcfnet/protocol_version.c b/libcfnet/protocol_version.c new file mode 100644 index 0000000000..31925e9a96 --- /dev/null +++ b/libcfnet/protocol_version.c @@ -0,0 +1,44 @@ +#include +#include +#include + +ProtocolVersion ParseProtocolVersionNetwork(const char *const s) +{ + int version; + const int ret = sscanf(s, "CFE_v%d", &version); + if (ret != 1 || version <= CF_PROTOCOL_UNDEFINED) + { + return CF_PROTOCOL_UNDEFINED; + } + // Note that `version` may be above CF_PROTOCOL_LATEST, if the other side + // supports a newer protocol + return version; +} + +ProtocolVersion ParseProtocolVersionPolicy(const char *const s) +{ + if ((s == NULL) || StringEqual(s, "0") || StringEqual(s, "undefined")) + { + return CF_PROTOCOL_UNDEFINED; + } + if (StringEqual(s, "1") || StringEqual(s, "classic")) + { + return CF_PROTOCOL_CLASSIC; + } + else if (StringEqual(s, "2") || StringEqual(s, "tls")) + { + return CF_PROTOCOL_TLS; + } + else if (StringEqual(s, "3") || StringEqual(s, "cookie")) + { + return CF_PROTOCOL_COOKIE; + } + else if (StringEqual(s, "latest")) + { + return CF_PROTOCOL_LATEST; + } + else + { + return CF_PROTOCOL_UNDEFINED; + } +} diff --git a/libcfnet/protocol_version.h b/libcfnet/protocol_version.h new file mode 100644 index 0000000000..06f7c32382 --- /dev/null +++ b/libcfnet/protocol_version.h @@ -0,0 +1,123 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#ifndef CFENGINE_PROTOCOL_VERSION_H +#define CFENGINE_PROTOCOL_VERSION_H + +/** + Available protocol versions. When connection is initialised ProtocolVersion + is 0, i.e. undefined. It is after the call to ServerConnection() that + protocol version is decided, according to body copy_from and body common + control. All protocol numbers are numbered incrementally starting from 1. + */ +typedef enum +{ + CF_PROTOCOL_UNDEFINED = 0, + CF_PROTOCOL_CLASSIC = 1, + /* --- Greater versions use TLS as secure communications layer --- */ + CF_PROTOCOL_TLS = 2, + CF_PROTOCOL_COOKIE = 3, +} ProtocolVersion; + +/* We use CF_PROTOCOL_LATEST as the default for new connections. */ +#define CF_PROTOCOL_LATEST CF_PROTOCOL_COOKIE + +static inline const char *ProtocolVersionString(const ProtocolVersion p) +{ + switch (p) + { + case CF_PROTOCOL_COOKIE: + return "cookie"; + case CF_PROTOCOL_TLS: + return "tls"; + case CF_PROTOCOL_CLASSIC: + return "classic"; + default: + return "undefined"; + } +} + +static inline bool ProtocolIsKnown(const ProtocolVersion p) +{ + return ((p > CF_PROTOCOL_UNDEFINED) && (p <= CF_PROTOCOL_LATEST)); +} + +static inline bool ProtocolIsTLS(const ProtocolVersion p) +{ + return ((p >= CF_PROTOCOL_TLS) && (p <= CF_PROTOCOL_LATEST)); +} + +static inline bool ProtocolIsTooNew(const ProtocolVersion p) +{ + return (p > CF_PROTOCOL_LATEST); +} + +static inline bool ProtocolIsUndefined(const ProtocolVersion p) +{ + return (p <= CF_PROTOCOL_UNDEFINED); +} + +static inline bool ProtocolIsClassic(const ProtocolVersion p) +{ + return (p == CF_PROTOCOL_CLASSIC); +} + +static inline bool ProtocolTerminateCSV(const ProtocolVersion p) +{ + return (p < CF_PROTOCOL_COOKIE); +} + +/** + * Returns CF_PROTOCOL_TLS or CF_PROTOCOL_CLASSIC (or CF_PROTOCOL_UNDEFINED) + * Maps all versions using TLS to CF_PROTOCOL_TLS for convenience + * in switch statements. + */ +static inline ProtocolVersion ProtocolClassicOrTLS(const ProtocolVersion p) +{ + if (ProtocolIsTLS(p)) + { + return CF_PROTOCOL_TLS; + } + if (ProtocolIsClassic(p)) + { + return CF_PROTOCOL_CLASSIC; + } + return CF_PROTOCOL_UNDEFINED; +} + +/** + * Parses the version string sent over network to enum, e.g. "CFE_v1" -> 1 + */ +ProtocolVersion ParseProtocolVersionNetwork(const char *s); + +/** + * Parses the version string set in policy to enum, e.g. "classic" -> 1 + */ +ProtocolVersion ParseProtocolVersionPolicy(const char *s); + +// Old name for compatibility (enterprise), TODO remove: +#define ProtocolVersionParse ParseProtocolVersionPolicy + +#endif // CFENGINE_PROTOCOL_VERSION_H diff --git a/libcfnet/server_code.c b/libcfnet/server_code.c new file mode 100644 index 0000000000..357f0af396 --- /dev/null +++ b/libcfnet/server_code.c @@ -0,0 +1,303 @@ + +#include +#include + +#include // BINDINTERFACE +#include // PRINTSIZE +#include // CLASSTEXT +#include // GetSignalPipe +#include // DoCleanupAndExit +#include // isdigit + +#if HAVE_SYSTEMD_SD_DAEMON_H +#include // sd_listen_fds +#endif + +/* Wait up to a minute for an in-coming connection. + * + * @param sd The listening socket or -1. + * @param tm_sec timeout in seconds + * @retval > 0 In-coming connection. + * @retval 0 No in-coming connection. + * @retval -1 Error (other than interrupt). + * @retval < -1 Interrupted while waiting. + */ +int WaitForIncoming(int sd, time_t tm_sec) +{ + Log(LOG_LEVEL_DEBUG, "Waiting at incoming select..."); + struct timeval timeout = { .tv_sec = tm_sec }; + int signal_pipe = GetSignalPipe(); + fd_set rset; + FD_ZERO(&rset); + FD_SET(signal_pipe, &rset); + + /* sd might be -1 if "listen" attribute in body server control is set + * to off (enterprise feature for call-collected clients). */ + if (sd != -1) + { + FD_SET(sd, &rset); + } + + int result = select(MAX(sd, signal_pipe) + 1, + &rset, NULL, NULL, &timeout); + if (result == -1) + { + return (errno == EINTR) ? -2 : -1; + } + assert(result >= 0); + + /* Empty the signal pipe, it is there to only detect missed + * signals in-between checking IsPendingTermination() and calling + * select(). */ + unsigned char buf; + while (recv(signal_pipe, &buf, 1, 0) > 0) + { + /* skip */ + } + + /* We have an incoming connection if select() marked sd as ready: */ + if (sd != -1 && result > 0 && FD_ISSET(sd, &rset)) + { + return 1; + } + return 0; +} + +/** + * Orders 'struct addrinfo *' linked list in a descending order based on the + * ai_family, prefering IPV6. + */ +static void OrderAddrInfoResponsesByAIfamily(struct addrinfo **first_response) +{ + /* Below is just a trivial implementation of the Bubble-sort algorithm to + * sort the linked list. */ + struct addrinfo *first = *first_response; + bool change = true; + while (change) + { + change = false; + struct addrinfo *item = first; + struct addrinfo *prev = NULL; + while (item->ai_next != NULL) + { + /* AF_INET6 should be greater than AF_INET, but let's not rely on + * that and use a direct comparison. */ + if ((item->ai_family == AF_INET) && (item->ai_next->ai_family == AF_INET6)) + { + struct addrinfo *orig_next = item->ai_next; + item->ai_next = orig_next->ai_next; + orig_next->ai_next = item; + if (prev != NULL) + { + prev->ai_next = orig_next; + } + else + { + first = orig_next; + } + prev = orig_next; + change = true; + } + else + { + item = item->ai_next; + } + } + } + *first_response = first; +} + +/** + * @param bind_address address to bind to or %NULL to use the default BINDINTERFACE + */ +static int OpenReceiverChannel(char *bind_address) +{ + struct addrinfo *response = NULL, *ap; + struct addrinfo query = { + .ai_flags = AI_PASSIVE, + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM + }; + + if (bind_address == NULL) + { + bind_address = BINDINTERFACE; + } + + /* Listen to INADDR(6)_ANY if BINDINTERFACE unset. */ + char *ptr = NULL; + if (bind_address[0] != '\0') + { + ptr = bind_address; + + /* Just a quick check if the string looks like an IPv4 address to see if + * we should use AI_NUMERICHOST or not. IPv6 addresses could use it too, + * but they are not so easy to recognize and the proper check would be + * more expensive than the optimization. */ + bool is_numeric_host = true; + for (char *c = ptr; is_numeric_host && (*c != '\0'); c++) + { + is_numeric_host = ((*c == '.') || isdigit(*c)); + } + if (is_numeric_host) + { + query.ai_flags |= AI_NUMERICHOST; + } + } + + /* Resolve listening interface. */ + int gres = getaddrinfo(ptr, CFENGINE_PORT_STR, &query, &response); + if (gres != 0) + { + Log(LOG_LEVEL_ERR, "DNS/service lookup failure. (getaddrinfo: %s)", + gai_strerror(gres)); + if (response) + { + freeaddrinfo(response); + } + return -1; + } + + /* Make sure IPV6 addresses/interfaces are preferred over IPV4 ones (binding + * to the IPV6 address makes connections to the IPV4 equivalent work too, in + * particular for INADDR6_ANY (::) and INADDR_ANY (0.0.0.0/0). */ + OrderAddrInfoResponsesByAIfamily(&response); + + int sd = -1; + for (ap = response; ap != NULL; ap = ap->ai_next) + { + sd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol); + if (sd == -1) + { + if (ap->ai_family == AF_INET) + { + Log(LOG_LEVEL_VERBOSE, "Failed to create socket for binding to an IPV4 interface"); + } + else if (ap->ai_family == AF_INET6) + { + Log(LOG_LEVEL_VERBOSE, "Failed to create socket for binding to an IPV6 interface"); + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Failed to create socket for binding to an interface of ai_family %d", + ap->ai_family); + } + continue; + } + + #ifdef IPV6_V6ONLY + /* Properly implemented getaddrinfo(AI_PASSIVE) should return the IPV6 + loopback address first. Some platforms (notably Windows) don't + listen to both address families when binding to it and need this + flag. Some other platforms won't even honour this flag + (openbsd). */ + if (bind_address[0] == '\0' && ap->ai_family == AF_INET6) + { + int no = 0; + if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, + &no, sizeof(no)) == -1) + { + Log(LOG_LEVEL_VERBOSE, + "Failed to clear IPv6-only flag on listening socket" + " (setsockopt: %s)", + GetErrorStr()); + } + } + #endif + + int yes = 1; + if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, + &yes, sizeof(yes)) == -1) + { + Log(LOG_LEVEL_VERBOSE, + "Socket option SO_REUSEADDR was not accepted. (setsockopt: %s)", + GetErrorStr()); + } + + struct linger cflinger = { + .l_onoff = 1, + .l_linger = 60 + }; + if (setsockopt(sd, SOL_SOCKET, SO_LINGER, + &cflinger, sizeof(cflinger)) == -1) + { + Log(LOG_LEVEL_INFO, + "Socket option SO_LINGER was not accepted. (setsockopt: %s)", + GetErrorStr()); + } + + if (bind(sd, ap->ai_addr, ap->ai_addrlen) != -1) + { + if (WouldLog(LOG_LEVEL_DEBUG)) + { + /* Convert IP address to string, no DNS lookup performed. */ + char txtaddr[CF_MAX_IP_LEN] = ""; + getnameinfo(ap->ai_addr, ap->ai_addrlen, + txtaddr, sizeof(txtaddr), + NULL, 0, NI_NUMERICHOST); + Log(LOG_LEVEL_DEBUG, "Bound to address '%s' on '%s' = %d", txtaddr, + CLASSTEXT[VSYSTEMHARDCLASS], VSYSTEMHARDCLASS); + } + break; + } + Log(LOG_LEVEL_ERR, "Could not bind server address. (bind: %s)", GetErrorStr()); + cf_closesocket(sd); + sd = -1; + } + + if (sd == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to bind to all attempted addresses (bind specification: '%s'", + bind_address); + } + + assert(response != NULL); /* getaddrinfo() was successful */ + freeaddrinfo(response); + return sd; +} + +/** + * @param queue_size length of the queue for pending connections + * @param bind_address address to bind to or %NULL to use the default BINDINTERFACE + */ +int InitServer(size_t queue_size, char *bind_address) +{ +#if HAVE_SYSTEMD_SD_DAEMON_H + int n = sd_listen_fds(0); + if (n > 1) + { + Log(LOG_LEVEL_ERR, "Too many file descriptors received from systemd"); + } + else if (n == 1) + { + // we can check here that we have a socket with sd_is_socket_inet(3) + // but why should we limit ourselves + return SD_LISTEN_FDS_START; + } + else +#endif // HAVE_SYSTEMD_SD_DAEMON_H + { + int sd = OpenReceiverChannel(bind_address); + + if (sd == -1) + { + /* More detailed info is logged in case of error in + * OpenReceiverChannel() */ + Log(LOG_LEVEL_ERR, "Unable to start server"); + } + else if (listen(sd, queue_size) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to listen on the '%s' address (listen: %s)", + bind_address, GetErrorStr()); + cf_closesocket(sd); + } + else + { + return sd; + } + } + + DoCleanupAndExit(EXIT_FAILURE); +} diff --git a/libcfnet/server_code.h b/libcfnet/server_code.h new file mode 100644 index 0000000000..dad75e79ab --- /dev/null +++ b/libcfnet/server_code.h @@ -0,0 +1,9 @@ +#ifndef CFENGINE_CFNET_SERVER_CODE_H +#define CFENGINE_CFNET_SERVER_CODE_H + +#include + +int InitServer(size_t queue_size, char *bind_address); +int WaitForIncoming(int sd, time_t tm_sec); + +#endif diff --git a/libcfnet/stat_cache.c b/libcfnet/stat_cache.c new file mode 100644 index 0000000000..2e2dd797e8 --- /dev/null +++ b/libcfnet/stat_cache.c @@ -0,0 +1,406 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include +#include + +#include /* AgentConnection */ +#include /* {Send,Receive}Transaction */ +#include /* BadProtoReply,OKProtoReply */ +#include /* xmemdup */ +#include /* Log */ +#include /* EncryptString */ +#include /* ProgrammingError */ + +static void NewStatCache(Stat *data, AgentConnection *conn) +{ + Stat *sp = xmemdup(data, sizeof(Stat)); + sp->next = conn->cache; + conn->cache = sp; +} + +void DestroyStatCache(Stat *data) +{ + if (data != NULL) + { + free(data->cf_readlink); + free(data->cf_filename); + free(data->cf_server); + free(data); + } +} + +/** + * @brief Find remote stat information for #file in cache and + * return it in #statbuf. + * @return 0 if found, 1 if not found, -1 in case of error. + */ +static int StatFromCache(AgentConnection *conn, const char *file, + struct stat *statbuf, const char *stattype) +{ + for (Stat *sp = conn->cache; sp != NULL; sp = sp->next) + { + /* TODO differentiate ports etc in this stat cache! */ + + if (strcmp(conn->this_server, sp->cf_server) == 0 && + strcmp(file, sp->cf_filename) == 0) + { + if (sp->cf_failed) /* cached failure from cfopendir */ + { + errno = EPERM; + return -1; + } + + if ((strcmp(stattype, "link") == 0) && (sp->cf_lmode != 0)) + { + statbuf->st_mode = sp->cf_lmode; + } + else + { + statbuf->st_mode = sp->cf_mode; + } + + statbuf->st_uid = sp->cf_uid; + statbuf->st_gid = sp->cf_gid; + statbuf->st_size = sp->cf_size; + statbuf->st_atime = sp->cf_atime; + statbuf->st_mtime = sp->cf_mtime; + statbuf->st_ctime = sp->cf_ctime; + statbuf->st_ino = sp->cf_ino; + statbuf->st_dev = sp->cf_dev; + statbuf->st_nlink = sp->cf_nlink; + + return 0; + } + } + + return 1; /* not found */ +} + +/** + * @param #stattype should be either "link" or "file". If a link, this reads + * readlink and sends it back in the same packet. It then + * caches the value for each copy command. + * + */ +int cf_remote_stat(AgentConnection *conn, bool encrypt, const char *file, + struct stat *statbuf, const char *stattype) +{ + assert(conn != NULL); + assert(file != NULL); + assert(statbuf != NULL); + assert(strcmp(stattype, "file") == 0 || + strcmp(stattype, "link") == 0); + + /* We encrypt only for CLASSIC protocol. The TLS protocol is always over + * encrypted layer, so it does not support encrypted (S*) commands. */ + encrypt = encrypt && conn->conn_info->protocol == CF_PROTOCOL_CLASSIC; + + if (strlen(file) > CF_BUFSIZE - 30) + { + Log(LOG_LEVEL_ERR, "Filename too long"); + return -1; + } + + int ret = StatFromCache(conn, file, statbuf, stattype); + if (ret == 0 || ret == -1) /* found or error */ + { + return ret; + } + + /* Not found in cache */ + + char recvbuffer[CF_BUFSIZE]; + memset(recvbuffer, 0, CF_BUFSIZE); + + time_t tloc = time(NULL); + if (tloc == (time_t) -1) + { + Log(LOG_LEVEL_ERR, "Couldn't read system clock (time: %s)", + GetErrorStr()); + tloc = 0; + } + + char sendbuffer[CF_BUFSIZE]; + int tosend; + sendbuffer[0] = '\0'; + + if (encrypt) + { + if (conn->session_key == NULL) + { + Log(LOG_LEVEL_ERR, + "Cannot do encrypted copy without keys (use cf-key)"); + return -1; + } + + char in[CF_BUFSIZE], out[CF_BUFSIZE]; + + snprintf(in, CF_BUFSIZE - 1, "SYNCH %jd STAT %s", + (intmax_t) tloc, file); + int cipherlen = EncryptString(out, sizeof(out), in, strlen(in) + 1, + conn->encryption_type, conn->session_key); + + tosend = cipherlen + CF_PROTO_OFFSET; + + if (tosend < 0) + { + ProgrammingError("cf_remote_stat: tosend (%d) < 0", tosend); + } + else if((unsigned int) tosend > sizeof(sendbuffer)) + { + ProgrammingError("cf_remote_stat: tosend (%d) > sendbuffer (%zd)", + tosend, sizeof(sendbuffer)); + } + + snprintf(sendbuffer, CF_BUFSIZE - 1, "SSYNCH %d", cipherlen); + memcpy(sendbuffer + CF_PROTO_OFFSET, out, cipherlen); + } + else + { + snprintf(sendbuffer, CF_BUFSIZE, "SYNCH %jd STAT %s", + (intmax_t) tloc, file); + tosend = strlen(sendbuffer); + } + + if (SendTransaction(conn->conn_info, sendbuffer, tosend, CF_DONE) == -1) + { + Log(LOG_LEVEL_INFO, + "Transmission failed/refused talking to %.255s:%.255s. (stat: %s)", + conn->this_server, file, GetErrorStr()); + return -1; + } + + if (ReceiveTransaction(conn->conn_info, recvbuffer, NULL) == -1) + { + /* TODO mark connection in the cache as closed. */ + return -1; + } + + if (strstr(recvbuffer, "unsynchronized")) + { + Log(LOG_LEVEL_ERR, + "Clocks differ too much to do copy by date (security), server reported: %s", + recvbuffer + strlen("BAD: ")); + return -1; + } + + if (BadProtoReply(recvbuffer)) + { + Log(LOG_LEVEL_VERBOSE, "Server returned error: %s", + recvbuffer + strlen("BAD: ")); + errno = EPERM; + return -1; + } + + if (!OKProtoReply(recvbuffer)) + { + Log(LOG_LEVEL_ERR, + "Transmission refused or failed statting '%s', got '%s'", + file, recvbuffer); + errno = EPERM; + return -1; + } + + Stat cfst; + + ret = StatParseResponse(recvbuffer, &cfst); + if (!ret) + { + Log(LOG_LEVEL_ERR, "Cannot read STAT reply from '%s'", + conn->this_server); + return -1; + } + + // If remote path is symbolic link, receive actual path here + int recv_len = ReceiveTransaction(conn->conn_info, recvbuffer, NULL); + if (recv_len == -1) + { + /* TODO mark connection in the cache as closed. */ + return -1; + } + + int ok_len = sizeof("OK:"); + /* Received a link destination from server + (recv_len greater than OK response + NUL-byte) */ + if (recv_len > ok_len) + { + // Read from after "OK:" + cfst.cf_readlink = xstrdup(recvbuffer + (ok_len - 1)); + } + else + { + cfst.cf_readlink = NULL; + } + + mode_t file_type = FileTypeToMode(cfst.cf_type); + if (file_type == 0) + { + Log(LOG_LEVEL_ERR, "Invalid file type identifier for file %s:%s, %u", + conn->this_server, file, cfst.cf_type); + return -1; + } + + cfst.cf_mode |= file_type; + + cfst.cf_filename = xstrdup(file); + cfst.cf_server = xstrdup(conn->this_server); + cfst.cf_failed = false; + + if (cfst.cf_lmode != 0) + { + cfst.cf_lmode |= (mode_t) S_IFLNK; + } + + NewStatCache(&cfst, conn); + + if ((cfst.cf_lmode != 0) && (strcmp(stattype, "link") == 0)) + { + statbuf->st_mode = cfst.cf_lmode; + } + else + { + statbuf->st_mode = cfst.cf_mode; + } + + statbuf->st_uid = cfst.cf_uid; + statbuf->st_gid = cfst.cf_gid; + statbuf->st_size = cfst.cf_size; + statbuf->st_mtime = cfst.cf_mtime; + statbuf->st_ctime = cfst.cf_ctime; + statbuf->st_atime = cfst.cf_atime; + statbuf->st_ino = cfst.cf_ino; + statbuf->st_dev = cfst.cf_dev; + statbuf->st_nlink = cfst.cf_nlink; + + return 0; +} + +/*********************************************************************/ + +/* TODO only a server_name is not enough for stat'ing of files... */ +const Stat *StatCacheLookup(const AgentConnection *conn, const char *file_name, + const char *server_name) +{ + for (const Stat *sp = conn->cache; sp != NULL; sp = sp->next) + { + if (strcmp(server_name, sp->cf_server) == 0 && + strcmp(file_name, sp->cf_filename) == 0) + { + return sp; + } + } + + return NULL; +} + +/*********************************************************************/ + +mode_t FileTypeToMode(const FileType type) +{ + /* TODO Match the order of the actual stat struct for easier mode */ + int mode = 0; + switch (type) + { + case FILE_TYPE_REGULAR: + mode |= (mode_t) S_IFREG; + break; + case FILE_TYPE_DIR: + mode |= (mode_t) S_IFDIR; + break; + case FILE_TYPE_CHAR_: + mode |= (mode_t) S_IFCHR; + break; + case FILE_TYPE_FIFO: + mode |= (mode_t) S_IFIFO; + break; + case FILE_TYPE_SOCK: + mode |= (mode_t) S_IFSOCK; + break; + case FILE_TYPE_BLOCK: + mode |= (mode_t) S_IFBLK; + break; + case FILE_TYPE_LINK: + mode |= (mode_t) S_IFLNK; + break; + } + + // mode is 0 if no file types matched + return mode; +} + +/*********************************************************************/ + +bool StatParseResponse(const char *const buf, Stat *const statbuf) +{ + assert(buf != NULL); + assert(statbuf != NULL); + + // use intmax_t here to provide enough space for large values coming over the protocol + intmax_t d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12 = 0, d13 = 0; + int res = sscanf(buf, "OK:" + " %1" PRIdMAX // 01 statbuf->cf_type + " %5" PRIdMAX // 02 statbuf->cf_mode + " %14" PRIdMAX // 03 statbuf->cf_lmode + " %14" PRIdMAX // 04 statbuf->cf_uid + " %14" PRIdMAX // 05 statbuf->cf_gid + " %18" PRIdMAX // 06 statbuf->cf_size + " %14" PRIdMAX // 07 statbuf->cf_atime + " %14" PRIdMAX // 08 statbuf->cf_mtime + " %14" PRIdMAX // 09 statbuf->cf_ctime + " %1" PRIdMAX // 10 statbuf->cf_makeholes + " %14" PRIdMAX // 11 statbuf->cf_ino + " %14" PRIdMAX // 12 statbuf->cf_nlink + " %18" PRIdMAX, // 13 statbuf->cf_dev + &d1, &d2, &d3, &d4, &d5, &d6, &d7, + &d8, &d9, &d10, &d11, &d12, &d13); + if (res < 13) + { + if (res >= 0) + { + Log(LOG_LEVEL_VERBOSE, + "STAT response parsing failed, only %d/13 elements parsed", + res); + } + + return false; + } + + statbuf->cf_type = (FileType) d1; + statbuf->cf_mode = (mode_t) d2; + statbuf->cf_lmode = (mode_t) d3; + statbuf->cf_uid = (uid_t) d4; + statbuf->cf_gid = (gid_t) d5; + statbuf->cf_size = (off_t) d6; + statbuf->cf_atime = (time_t) d7; + statbuf->cf_mtime = (time_t) d8; + statbuf->cf_ctime = (time_t) d9; + statbuf->cf_makeholes = (char) d10; + statbuf->cf_ino = d11; + statbuf->cf_nlink = d12; + statbuf->cf_dev = (dev_t)d13; + + return true; +} diff --git a/libcfnet/stat_cache.h b/libcfnet/stat_cache.h new file mode 100644 index 0000000000..d342c54554 --- /dev/null +++ b/libcfnet/stat_cache.h @@ -0,0 +1,74 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#ifndef CFENGINE_STAT_CACHE_H +#define CFENGINE_STAT_CACHE_H + +#include +#include + + +typedef enum +{ + FILE_TYPE_REGULAR, + FILE_TYPE_LINK, + FILE_TYPE_DIR, + FILE_TYPE_FIFO, + FILE_TYPE_BLOCK, + FILE_TYPE_CHAR_, /* Conflict with winbase.h */ + FILE_TYPE_SOCK +} FileType; + +typedef struct Stat_ Stat; +struct Stat_ +{ + char *cf_filename; /* What file are we statting? */ + char *cf_server; /* Which server did this come from? */ //WHY? Isn't this in AgentConn? + FileType cf_type; /* enum filetype */ + mode_t cf_lmode; /* Mode of link, if link */ + mode_t cf_mode; /* Mode of remote file, not link */ + uid_t cf_uid; /* User ID of the file's owner */ + gid_t cf_gid; /* Group ID of the file's group */ + off_t cf_size; /* File size in bytes */ + time_t cf_atime; /* Time of last access */ + time_t cf_mtime; /* Time of last data modification */ + time_t cf_ctime; /* Time of last file status change */ + char cf_makeholes; /* what we need to know from blksize and blks */ + char *cf_readlink; /* link value or NULL */ + int cf_failed; /* stat returned -1 */ + int cf_nlink; /* Number of hard links */ + int cf_ino; /* inode number on server */ + dev_t cf_dev; /* device number */ + Stat *next; +}; + +void DestroyStatCache(Stat *data); +int cf_remote_stat(AgentConnection *conn, bool encrypt, const char *file, + struct stat *statbuf, const char *stattype); +const Stat *StatCacheLookup(const AgentConnection *conn, const char *file_name, + const char *server_name); +mode_t FileTypeToMode(const FileType type); +bool StatParseResponse(const char *const buf, Stat *statbuf); + + +#endif diff --git a/libcfnet/tls_client.c b/libcfnet/tls_client.c new file mode 100644 index 0000000000..9c2320537f --- /dev/null +++ b/libcfnet/tls_client.c @@ -0,0 +1,379 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include /* SendTransaction, ReceiveTransaction */ +#include /* ParseProtocolVersionNetwork() */ +/* TODO move crypto.h to libutils */ +#include /* LoadSecretKeys */ + +#define MAX_CONNECT_RETRIES 10 + +extern RSA *PRIVKEY, *PUBKEY; + + +/** + * Global SSL context for initiated connections over the TLS protocol. For the + * agent, they are written only once, *after* the common control bundles have + * been evaluated. I.e. GenericAgentPostLoadInit() is called + * after LoadPolicy(). + * + * 1. Common bundles evaluation: LoadPolicy()->PolicyResolve() + * 2. Create TLS contexts: GenericAgentPostLoadInit()->cfnet_init() + */ +static SSL_CTX *SSLCLIENTCONTEXT = NULL; +static X509 *SSLCLIENTCERT = NULL; + + +bool TLSClientIsInitialized() +{ + return (SSLCLIENTCONTEXT != NULL); +} + +/** + * @warning Make sure you've called CryptoInitialize() first! + * + * @TODO if this function is called a second time, it just returns true, and + * does not do nothing more. What if the settings (e.g. tls_min_version) have + * changed? This can happen when cf-serverd reloads policy. Fixing this goes + * much deeper though, as it would require cf-serverd to call + * GenericAgentDiscoverContext() when reloading policy. + */ +bool TLSClientInitialize(const char *tls_min_version, + const char *ciphers) +{ + int ret; + static bool is_initialised = false; + + if (is_initialised) + { + return true; + } + + if (PRIVKEY == NULL || PUBKEY == NULL) + { + /* VERBOSE in case it's a custom, local-only installation. */ + Log(LOG_LEVEL_VERBOSE, "No public/private key pair is loaded," + " please create one using cf-key"); + return false; + } + if (!TLSGenericInitialize()) + { + return false; + } + + SSLCLIENTCONTEXT = SSL_CTX_new(SSLv23_client_method()); + if (SSLCLIENTCONTEXT == NULL) + { + Log(LOG_LEVEL_ERR, "SSL_CTX_new: %s", + TLSErrorString(ERR_get_error())); + goto err1; + } + + TLSSetDefaultOptions(SSLCLIENTCONTEXT, tls_min_version); + + if (!TLSSetCipherList(SSLCLIENTCONTEXT, ciphers)) + { + goto err2; + } + + /* Create cert into memory and load it into SSL context. */ + SSLCLIENTCERT = TLSGenerateCertFromPrivKey(PRIVKEY); + if (SSLCLIENTCERT == NULL) + { + Log(LOG_LEVEL_ERR, + "Failed to generate in-memory-certificate from private key"); + goto err2; + } + + SSL_CTX_use_certificate(SSLCLIENTCONTEXT, SSLCLIENTCERT); + + ret = SSL_CTX_use_RSAPrivateKey(SSLCLIENTCONTEXT, PRIVKEY); + if (ret != 1) + { + Log(LOG_LEVEL_ERR, "Failed to use RSA private key: %s", + TLSErrorString(ERR_get_error())); + goto err3; + } + + /* Verify cert consistency. */ + ret = SSL_CTX_check_private_key(SSLCLIENTCONTEXT); + if (ret != 1) + { + Log(LOG_LEVEL_ERR, "Inconsistent key and TLS cert: %s", + TLSErrorString(ERR_get_error())); + goto err3; + } + + is_initialised = true; + return true; + + err3: + X509_free(SSLCLIENTCERT); + SSLCLIENTCERT = NULL; + err2: + SSL_CTX_free(SSLCLIENTCONTEXT); + SSLCLIENTCONTEXT = NULL; + err1: + return false; +} + +void TLSDeInitialize() +{ + if (PUBKEY) + { + RSA_free(PUBKEY); + PUBKEY = NULL; + } + + if (PRIVKEY) + { + RSA_free(PRIVKEY); + PRIVKEY = NULL; + } + + if (SSLCLIENTCERT != NULL) + { + X509_free(SSLCLIENTCERT); + SSLCLIENTCERT = NULL; + } + + if (SSLCLIENTCONTEXT != NULL) + { + SSL_CTX_free(SSLCLIENTCONTEXT); + SSLCLIENTCONTEXT = NULL; + } +} + + +/** + * 1. Receive "CFE_v%d" server hello + * 2. Send two lines: one "CFE_v%d" with the protocol version we wish to have, + * and another with id, e.g. "IDENTITY USERNAME=blah". + * 3. Receive "OK WELCOME" + * + * @return > 0: success. #conn_info->type has been updated with the negotiated + * protocol version. + * 0: server denial + * -1: error + */ +int TLSClientIdentificationDialog(ConnectionInfo *conn_info, + const char *username) +{ + char line[1024] = ""; + int ret; + + /* Receive CFE_v%d ... That's the first thing the server sends. */ + ret = TLSRecvLines(conn_info->ssl, line, sizeof(line)); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, "Connection was hung up during identification! (0)"); + return -1; + } + + ProtocolVersion wanted_version; + if (conn_info->protocol == CF_PROTOCOL_UNDEFINED) + { + wanted_version = CF_PROTOCOL_LATEST; + } + else + { + wanted_version = conn_info->protocol; + } + + const ProtocolVersion received_version = ParseProtocolVersionNetwork(line); + + if (received_version < wanted_version && ProtocolIsTLS(received_version)) + { + // Downgrade as long as it's still TLS + wanted_version = received_version; + } + else if (ProtocolIsUndefined(received_version) + || ProtocolIsClassic(received_version)) + { + Log(LOG_LEVEL_ERR, "Server sent a bad version number! (0a)"); + return -1; + } + + assert(wanted_version <= received_version); // Server supported version + assert(ProtocolIsTLS(wanted_version)); + + /* Send "CFE_v%d cf-agent version". */ + char version_string[128]; + int len = snprintf(version_string, sizeof(version_string), + "CFE_v%d %s %s\n", + wanted_version, "cf-agent", VERSION); /* TODO argv[0] */ + + ret = TLSSend(conn_info->ssl, version_string, len); + if (ret != len) + { + Log(LOG_LEVEL_ERR, "Connection was hung up during identification! (1)"); + return -1; + } + + strcpy(line, "IDENTITY"); + size_t line_len = strlen(line); + + if (username != NULL) + { + ret = snprintf(&line[line_len], sizeof(line) - line_len, + " USERNAME=%s", username); + if (ret < 0) + { + Log(LOG_LEVEL_ERR, "snprintf failed: %s", GetErrorStr()); + return -1; + } + else if ((unsigned int) ret >= sizeof(line) - line_len) + { + Log(LOG_LEVEL_ERR, "Sending IDENTITY truncated: %s", line); + return -1; + } + line_len += ret; + } + + /* Overwrite the terminating '\0', we don't need it anyway. */ + line[line_len] = '\n'; + line_len++; + + ret = TLSSend(conn_info->ssl, line, line_len); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, + "Connection was hung up during identification! (2)"); + return -1; + } + + /* Server might hang up here, after we sent identification! We + * must get the "OK WELCOME" message for everything to be OK. */ + static const char OK[] = "OK WELCOME"; + size_t OK_len = sizeof(OK) - 1; + ret = TLSRecvLines(conn_info->ssl, line, sizeof(line)); + if (ret < 0) + { + Log(LOG_LEVEL_ERR, + "Connection was hung up during identification! (3)"); + return -1; + } + else if ((size_t) ret < OK_len || strncmp(line, OK, OK_len) != 0) + { + Log(LOG_LEVEL_ERR, + "Peer did not accept our identity! Responded: %s", + line); + return 0; + } + + /* Before it contained the protocol version we requested from the server, + * now we put in the value that was negotiated. */ + conn_info->protocol = wanted_version; + + return 1; +} + +/** + * We directly initiate a TLS handshake with the server. If the server is old + * version (does not speak TLS) the connection will be denied. + * @note the socket file descriptor in #conn_info must be connected and *not* + * non-blocking + * @return -1 in case of error + */ +int TLSTry(ConnectionInfo *conn_info) +{ + assert(conn_info != NULL); + + if (PRIVKEY == NULL || PUBKEY == NULL) + { + Log(LOG_LEVEL_ERR, "No public/private key pair is loaded," + " please create one using cf-key"); + return -1; + } + assert(SSLCLIENTCONTEXT != NULL); + + conn_info->ssl = SSL_new(SSLCLIENTCONTEXT); + if (conn_info->ssl == NULL) + { + Log(LOG_LEVEL_ERR, "SSL_new: %s", + TLSErrorString(ERR_get_error())); + return -1; + } + + /* Pass conn_info inside the ssl struct for TLSVerifyCallback(). */ + SSL_set_ex_data(conn_info->ssl, CONNECTIONINFO_SSL_IDX, conn_info); + + /* Initiate the TLS handshake over the already open TCP socket. */ + SSL_set_fd(conn_info->ssl, conn_info->sd); + + bool connected = false; + bool should_retry = true; + int remaining_tries = MAX_CONNECT_RETRIES; + int ret; + while (!connected && should_retry) + { + ret = SSL_connect(conn_info->ssl); + /* 1 means connected, 0 means shut down, <0 means error + * (see man:SSL_connect(3)) */ + connected = (ret == 1); + if (ret == 0) + { + should_retry = false; + } + else if (ret < 0) + { + int code = TLSLogError(conn_info->ssl, LOG_LEVEL_VERBOSE, + "Attempt to establish TLS connection failed", ret); + /* see man:SSL_connect(3) */ + should_retry = ((remaining_tries > 0) && + ((code == SSL_ERROR_WANT_READ) || (code == SSL_ERROR_WANT_WRITE))); + } + if (!connected && should_retry) + { + sleep(1); + remaining_tries--; + } + } + if (!connected) + { + TLSLogError(conn_info->ssl, LOG_LEVEL_ERR, + "Failed to establish TLS connection", ret); + return -1; + } + + Log(LOG_LEVEL_VERBOSE, "TLS version negotiated: %8s; Cipher: %s,%s", + SSL_get_version(conn_info->ssl), + SSL_get_cipher_name(conn_info->ssl), + SSL_get_cipher_version(conn_info->ssl)); + Log(LOG_LEVEL_VERBOSE, "TLS session established, checking trust..."); + + return 0; +} diff --git a/libcfnet/tls_client.h b/libcfnet/tls_client.h new file mode 100644 index 0000000000..f7a2417bb2 --- /dev/null +++ b/libcfnet/tls_client.h @@ -0,0 +1,46 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_TLS_CLIENT_H +#define CFENGINE_TLS_CLIENT_H + + +#include +#include + +bool TLSClientInitialize(const char *tls_min_version, + const char *ciphers); +void TLSDeInitialize(void); +bool TLSClientIsInitialized(void); + +int TLSClientIdentificationDialog(ConnectionInfo *conn_info, + const char *username); +int TLSTry(ConnectionInfo *conn_info); + +/* Exported for enterprise. */ +int TLSConnect(ConnectionInfo *conn_info, bool trust_server, const Rlist *restrict_keys, + const char *ipaddr, const char *username); + + +#endif diff --git a/libcfnet/tls_generic.c b/libcfnet/tls_generic.c new file mode 100644 index 0000000000..d3882abb5b --- /dev/null +++ b/libcfnet/tls_generic.c @@ -0,0 +1,1082 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include // CF_DEFAULT_DIGEST +#include + +#include +#include +#include + +#include /* LogLevel */ +#include +#include + +/* TODO move crypto.h to libutils */ +#include /* HavePublicKeyByIP */ +#include /* HashPubKey */ + +#include + +/* known TLS versions */ +enum tls_version { + TLS_1_0 = 0, + TLS_1_1 = 1, + TLS_1_2 = 2, + TLS_1_3 = 3, +}; +#define TLS_LAST TLS_1_3 + +/* determine the highest TLS version supported by the available/used version of + * OpenSSL */ +#if defined(SSL_OP_NO_TLSv1_3) +#define TLS_HIGHEST_SUPPORTED TLS_1_3 +#elif defined(SSL_OP_NO_TLSv1_2) +#define TLS_HIGHEST_SUPPORTED TLS_1_2 +#elif defined(SSL_OP_NO_TLSv1_1) +#define TLS_HIGHEST_SUPPORTED TLS_1_1 +#else +#define TLS_HIGHEST_SUPPORTED TLS_1_0 +#endif + +/* the lowest version of TLS we always require */ +#define TLS_LOWEST_REQUIRED TLS_1_0 + +/* the lowest version of TLS we recommend (also the default) */ +#define TLS_LOWEST_RECOMMENDED TLS_1_1 + +#ifndef SSL_OP_NO_TLSv1_3 +#define SSL_OP_NO_TLSv1_3 0 /* no-op when ORed with bit flags */ +#endif +#ifndef SSL_OP_NO_TLSv1_2 +#define SSL_OP_NO_TLSv1_2 0 +#endif +#ifndef SSL_OP_NO_TLSv1_1 +#define SSL_OP_NO_TLSv1_1 0 +#endif + +static const char *const tls_version_strings[TLS_LAST + 1] = {"1.0", "1.1", "1.2", "1.3"}; +static unsigned int tls_disable_flags[TLS_LAST + 1] = {SSL_OP_NO_TLSv1, SSL_OP_NO_TLSv1_1, SSL_OP_NO_TLSv1_2, SSL_OP_NO_TLSv1_3}; + +int CONNECTIONINFO_SSL_IDX = -1; + +#define MAX_READ_RETRIES 5 +#define MAX_WRITE_RETRIES 5 + +const char *TLSErrorString(intmax_t errcode) +{ + const char *errmsg = ERR_reason_error_string((unsigned long) errcode); + return (errmsg != NULL) ? errmsg : "no error message"; +} + +bool TLSGenericInitialize() +{ + static bool is_initialised = false; + + /* We must make sure that SSL_get_ex_new_index() is called only once! */ + if (is_initialised) + { + return true; + } + + /* OpenSSL is needed for TLS. */ + SSL_library_init(); + SSL_load_error_strings(); + + /* Register a unique place to store ConnectionInfo within SSL struct. */ + CONNECTIONINFO_SSL_IDX = + SSL_get_ex_new_index(0, "Pointer to ConnectionInfo", + NULL, NULL, NULL); + + is_initialised = true; + return true; +} + +/** + * @retval 1 equal + * @retval 0 not equal + * @retval -1 error + */ +static int CompareCertToRSA(X509 *cert, RSA *rsa_key) +{ + int ret; + int retval = -1; /* ERROR */ + + EVP_PKEY *cert_pkey = X509_get_pubkey(cert); + if (cert_pkey == NULL) + { + Log(LOG_LEVEL_ERR, "X509_get_pubkey: %s", + TLSErrorString(ERR_get_error())); + goto ret1; + } + if (EVP_PKEY_base_id(cert_pkey) != EVP_PKEY_RSA) + { + Log(LOG_LEVEL_ERR, + "Received key of unknown type, only RSA currently supported!"); + goto ret2; + } + + RSA *cert_rsa_key = EVP_PKEY_get1_RSA(cert_pkey); + if (cert_rsa_key == NULL) + { + Log(LOG_LEVEL_ERR, "TLSVerifyPeer: EVP_PKEY_get1_RSA failed!"); + goto ret2; + } + + EVP_PKEY *rsa_pkey = EVP_PKEY_new(); + if (rsa_pkey == NULL) + { + Log(LOG_LEVEL_ERR, "TLSVerifyPeer: EVP_PKEY_new allocation failed!"); + goto ret3; + } + + ret = EVP_PKEY_set1_RSA(rsa_pkey, rsa_key); + if (ret == 0) + { + Log(LOG_LEVEL_ERR, "TLSVerifyPeer: EVP_PKEY_set1_RSA failed!"); + goto ret4; + } + + ret = EVP_PKEY_cmp(cert_pkey, rsa_pkey); + if (ret == 1) + { + Log(LOG_LEVEL_DEBUG, + "Public key to certificate compare equal"); + retval = 1; /* EQUAL */ + } + else if (ret == 0 || ret == -1) + { + Log(LOG_LEVEL_DEBUG, + "Public key to certificate compare different"); + retval = 0; /* NOT EQUAL */ + } + else + { + Log(LOG_LEVEL_ERR, "OpenSSL EVP_PKEY_cmp: %d %s", + ret, TLSErrorString(ERR_get_error())); + } + + ret4: + EVP_PKEY_free(rsa_pkey); + ret3: + RSA_free(cert_rsa_key); + ret2: + EVP_PKEY_free(cert_pkey); + + ret1: + return retval; +} + +/** + * The only thing we make sure here is that any key change is not allowed. All + * the rest of authentication happens separately *after* the initial + * handshake, thus *after* this callback has returned successfully and TLS + * session has been established. + * @return 0 on error, 1 on success + * @note This is an SSL callback, return type has to be int, not bool + */ +int TLSVerifyCallback(X509_STORE_CTX *store_ctx, + void *arg ARG_UNUSED) +{ + + /* It's kind of tricky to get custom connection-specific info in this + * callback. We first acquire a pointer to the SSL struct of the + * connection and... */ + int ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx(); + SSL *ssl = X509_STORE_CTX_get_ex_data(store_ctx, ssl_idx); + if (ssl == NULL) + { + UnexpectedError("No SSL context during handshake, denying!"); + return 0; + } + + /* ...and then we ask for the custom data we attached there. */ + ConnectionInfo *conn_info = SSL_get_ex_data(ssl, CONNECTIONINFO_SSL_IDX); + if (conn_info == NULL) + { + UnexpectedError("No conn_info at index %d", CONNECTIONINFO_SSL_IDX); + return 0; + } + + /* From that data get the key if the connection is already established. */ + RSA *already_negotiated_key = KeyRSA(conn_info->remote_key); + /* Is there an already negotiated certificate? */ + X509 *previous_tls_cert = SSL_get_peer_certificate(ssl); + + if (previous_tls_cert == NULL) + { + Log(LOG_LEVEL_DEBUG, "TLSVerifyCallback: no ssl->peer_cert"); + if (already_negotiated_key == NULL) + { + Log(LOG_LEVEL_DEBUG, "TLSVerifyCallback: no conn_info->key"); + Log(LOG_LEVEL_DEBUG, + "This must be the initial TLS handshake, accepting"); + return 1; /* ACCEPT HANDSHAKE */ + } + else + { + UnexpectedError("Initial handshake, but old keys differ, denying!"); + return 0; + } + } + else /* TLS renegotiation handshake */ + { + if (already_negotiated_key == NULL) + { + Log(LOG_LEVEL_DEBUG, "TLSVerifyCallback: no conn_info->key"); + Log(LOG_LEVEL_ERR, + "Renegotiation handshake before trust was established, denying!"); + X509_free(previous_tls_cert); + return 0; /* fishy */ + } + else + { + /* previous_tls_cert key should match already_negotiated_key. */ + if (CompareCertToRSA(previous_tls_cert, + already_negotiated_key) != 1) + { + UnexpectedError("Renegotiation caused keys to differ, denying!"); + X509_free(previous_tls_cert); + return 0; + } + else + { + /* THIS IS THE ONLY WAY TO CONTINUE */ + } + } + } + + assert(previous_tls_cert != NULL); + assert(already_negotiated_key != NULL); + + /* At this point we have ensured that previous_tls_cert->key is equal + * to already_negotiated_key, so we might as well forget the former. */ + X509_free(previous_tls_cert); + + /* We want to compare already_negotiated_key to the one the peer + * negotiates in this TLS renegotiation. So, extract first certificate out + * of the chain the peer sent. It should be the only one since we do not + * support certificate chains, we just want the RSA key. */ + STACK_OF(X509) *chain = X509_STORE_CTX_get_chain(store_ctx); + if (chain == NULL) + { + Log(LOG_LEVEL_ERR, + "No certificate chain inside TLS handshake, denying!"); + return 0; + } + + int chain_len = sk_X509_num(chain); + if (chain_len != 1) + { + Log(LOG_LEVEL_ERR, + "More than one certificate presented in TLS handshake, refusing handshake!"); + return 0; + } + + X509 *new_cert = sk_X509_value(chain, 0); + if (new_cert == NULL) + { + UnexpectedError("NULL certificate at the beginning of chain!"); + return 0; + } + + if (CompareCertToRSA(new_cert, already_negotiated_key) != 1) + { + Log(LOG_LEVEL_ERR, + "Peer attempted to change key during TLS renegotiation, denying!"); + return 0; + } + + Log(LOG_LEVEL_DEBUG, + "TLS renegotiation occurred but keys are still the same, accepting"); + return 1; /* ACCEPT HANDSHAKE */ +} + +/** + * @retval 1 if the public key used by the peer in the TLS handshake is the + * same with the one stored for that host. + * @retval 0 if stored key for the host is missing or differs from the one + * received. + * @retval -1 in case of other error (error will be Log()ed). + * @note When return value is != -1 (so no error occurred) the #conn_info struct + * should have been populated, with key received and its hash. + */ +int TLSVerifyPeer(ConnectionInfo *conn_info, const char *remoteip, const char *username) +{ + int ret, retval; + + X509 *received_cert = SSL_get_peer_certificate(conn_info->ssl); + if (received_cert == NULL) + { + Log(LOG_LEVEL_ERR, + "No certificate presented by remote peer (openssl: %s)", + TLSErrorString(ERR_get_error())); + retval = -1; + goto ret1; + } + + EVP_PKEY *received_pubkey = X509_get_pubkey(received_cert); + if (received_pubkey == NULL) + { + Log(LOG_LEVEL_ERR, "X509_get_pubkey: %s", + TLSErrorString(ERR_get_error())); + retval = -1; + goto ret2; + } + if (EVP_PKEY_base_id(received_pubkey) != EVP_PKEY_RSA) + { + Log(LOG_LEVEL_ERR, + "Received key of unknown type, only RSA currently supported!"); + retval = -1; + goto ret3; + } + + RSA *remote_key = EVP_PKEY_get1_RSA(received_pubkey); + if (remote_key == NULL) + { + Log(LOG_LEVEL_ERR, "TLSVerifyPeer: EVP_PKEY_get1_RSA failed!"); + retval = -1; + goto ret3; + } + + Key *key = KeyNew(remote_key, CF_DEFAULT_DIGEST); + conn_info->remote_key = key; + + /* + * Compare the key received with the one stored. + */ + const char *key_hash = KeyPrintableHash(key); + RSA *expected_rsa_key = HavePublicKey(username, remoteip, key_hash); + + if (expected_rsa_key == NULL) + { + /* TODO LOG_LEVEL_NOTICE once cf-serverd logs to a different file. */ + Log(LOG_LEVEL_VERBOSE, + "Received key '%s' not found in ppkeys", key_hash); + retval = 0; + goto ret4; + } + + EVP_PKEY *expected_pubkey = EVP_PKEY_new(); + if (expected_pubkey == NULL) + { + Log(LOG_LEVEL_ERR, "TLSVerifyPeer: EVP_PKEY_new allocation failed!"); + retval = -1; + goto ret5; + } + + ret = EVP_PKEY_set1_RSA(expected_pubkey, expected_rsa_key); + if (ret == 0) + { + Log(LOG_LEVEL_ERR, "TLSVerifyPeer: EVP_PKEY_set1_RSA failed!"); + retval = -1; + goto ret6; + } + + ret = EVP_PKEY_cmp(received_pubkey, expected_pubkey); + if (ret == 1) + { + Log(LOG_LEVEL_VERBOSE, + "Received public key compares equal to the one we have stored"); + retval = 1; /* TRUSTED KEY, equal to the expected one */ + goto ret6; + } + else if (ret == 0 || ret == -1) + { + Log(LOG_LEVEL_NOTICE, + "Received key '%s' compares different to the one in ppkeys", + key_hash); + retval = 0; + goto ret6; + } + else + { + Log(LOG_LEVEL_ERR, "OpenSSL EVP_PKEY_cmp: %d %s", + ret, TLSErrorString(ERR_get_error())); + retval = -1; + goto ret6; + } + + UnexpectedError("Unreachable!"); + return 0; + + ret6: + EVP_PKEY_free(expected_pubkey); + ret5: + RSA_free(expected_rsa_key); + ret4: + if (retval == -1) + { + /* We won't be needing conn_info->remote_key */ + KeyDestroy(&key); + conn_info->remote_key = NULL; + } + ret3: + EVP_PKEY_free(received_pubkey); + ret2: + X509_free(received_cert); + ret1: + return retval; +} + +/** + * @brief Generate and return a dummy in-memory X509 certificate signed with + * the private key passed. It is valid from now to 50 years later... + */ +X509 *TLSGenerateCertFromPrivKey(RSA *privkey) +{ + int ret; + X509 *x509 = X509_new(); + if (x509 == NULL) + { + Log(LOG_LEVEL_ERR, "X509_new: %s", + TLSErrorString(ERR_get_error())); + goto err1; + } + + ASN1_TIME *t1 = X509_gmtime_adj(X509_get_notBefore(x509), 0); + ASN1_TIME *t2 = X509_gmtime_adj(X509_get_notAfter(x509), 60*60*24*365*10); + if (t1 == NULL || t2 == NULL) + { + Log(LOG_LEVEL_ERR, "X509_gmtime_adj: %s", + TLSErrorString(ERR_get_error())); + goto err2; + } + + EVP_PKEY *pkey = EVP_PKEY_new(); + if (pkey == NULL) + { + Log(LOG_LEVEL_ERR, "EVP_PKEY_new: %s", + TLSErrorString(ERR_get_error())); + goto err2; + } + + ret = EVP_PKEY_set1_RSA(pkey, privkey); + if (ret != 1) + { + Log(LOG_LEVEL_ERR, "EVP_PKEY_set1_RSA: %s", + TLSErrorString(ERR_get_error())); + goto err3; + } + + X509_NAME *name = X509_get_subject_name(x509); + if (name == NULL) + { + Log(LOG_LEVEL_ERR, "X509_get_subject_name: %s", + TLSErrorString(ERR_get_error())); + goto err3; + } + + ret = 0; + ret += X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (const unsigned char *) "a", + -1, -1, 0); + ret += X509_set_issuer_name(x509, name); + ret += X509_set_pubkey(x509, pkey); + if (ret < 3) + { + Log(LOG_LEVEL_ERR, "Failed to set certificate details: %s", + TLSErrorString(ERR_get_error())); + goto err3; + } + + const EVP_MD *md = EVP_get_digestbyname("sha384"); + if (md == NULL) + { + Log(LOG_LEVEL_ERR, "OpenSSL: Unknown digest algorithm %s", + "sha384"); + goto err3; + } + + if (getenv("CFENGINE_TEST_PURIFY_OPENSSL") != NULL) + { + RSA_blinding_off(privkey); + } + + /* Not really needed since the other side does not + verify the signature. */ + ret = X509_sign(x509, pkey, md); + /* X509_sign obscurely returns the length of the signature... */ + if (ret == 0) + { + Log(LOG_LEVEL_ERR, "X509_sign: %s", + TLSErrorString(ERR_get_error())); + goto err3; + } + + EVP_PKEY_free(pkey); + + assert(x509 != NULL); + return x509; + + + err3: + EVP_PKEY_free(pkey); + err2: + X509_free(x509); + err1: + return NULL; +} + +static const char *TLSPrimarySSLError(int code) +{ + switch (code) + { + case SSL_ERROR_NONE: + return "TLSGetSSLErrorString: No SSL error!"; + case SSL_ERROR_ZERO_RETURN: + return "TLS session has been terminated (SSL_ERROR_ZERO_RETURN)"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + } + return "Unknown OpenSSL error code!"; +} + +/** + * @brief OpenSSL is missing an SSL_reason_error_string() like + * ERR_reason_error_string(). Provide missing functionality here, + * since it's kind of complicated. + * @param #prepend String to prepend to the SSL error. + * @param #code Return code from the OpenSSL function call. + * @warning Use only for SSL_connect(), SSL_accept(), SSL_do_handshake(), + * SSL_read(), SSL_peek(), SSL_write(), see SSL_get_error man page. + */ +int TLSLogError(SSL *ssl, LogLevel level, const char *prepend, int retcode) +{ + assert(prepend != NULL); + + /* For when retcode==SSL_ERROR_SYSCALL. */ + const char *syserr = (errno != 0) ? GetErrorStr() : ""; + int errcode = SSL_get_error(ssl, retcode); + const char *errstr1 = TLSPrimarySSLError(errcode); + /* For SSL_ERROR_SSL, SSL_ERROR_SYSCALL (man SSL_get_error). It's not + * useful for SSL_read() and SSL_write(). */ + const char *errstr2 = ERR_reason_error_string(ERR_get_error()); + + /* We know the socket is always blocking. However our blocking sockets + * have a timeout set via means of setsockopt(SO_RCVTIMEO), so internally + * OpenSSL can still get the EWOULDBLOCK error code from recv(). In that + * case OpenSSL gives us SSL_ERROR_WANT_READ despite the socket being + * blocking. So we log a proper error message! */ + if (errcode == SSL_ERROR_WANT_READ) + { + Log(level, "%s: receive timeout", prepend); + } + else if (errcode == SSL_ERROR_WANT_WRITE) + { + Log(level, "%s: send timeout", prepend); + } + /* if we got SSL_ERROR_SYSCALL and ERR_get_error() returned 0 then take + * ret into account (man SSL_get_error). */ + else if (errcode == SSL_ERROR_SYSCALL && errstr2 == NULL && + (retcode == 0 || retcode == -1)) + { + /* This is not described in SSL_get_error manual, but play it safe. */ + if ((SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) != 0) + { + Log(level, "%s: remote peer terminated TLS session", + prepend); + } + /* "an EOF was observed that violates the protocol" */ + else if (retcode == 0) + { + Log(level, "%s: socket closed", prepend); + } + /* "the underlying BIO reported an I/O error" */ + else if (retcode == -1) + { + Log(level, "%s: underlying network error (%s)", prepend, syserr); + } + } + else /* generic error printing fallback */ + { + Log(level, "%s: (%d %s) %s %s", + prepend, retcode, errstr1, + (errstr2 == NULL) ? "" : errstr2, /* most likely empty */ + syserr); + } + + return errcode; +} + +static void assert_SSLIsBlocking(const SSL *ssl) +{ +#if !defined(NDEBUG) && !defined(__MINGW32__) + int fd = SSL_get_fd(ssl); + if (fd >= 0) + { + int flags = fcntl(fd, F_GETFL, 0); + if (flags != -1 && (flags & O_NONBLOCK) != 0) + { + ProgrammingError("OpenSSL socket is non-blocking!"); + } + } +#else // silence compiler warning + ssl = NULL; +#endif +} + +/** + * @brief Sends the data stored on the buffer using a TLS session. + * @param ssl SSL information. + * @param buffer Data to send. + * @param length Length of the data to send. + * @return The length of the data sent (always equals #length if SSL is set + * up correctly, see note), or -1 in case of error, or 0 for connection + * closed. + * + * @note Use only for *blocking* sockets. Set + * SSL_CTX_set_mode(SSL_MODE_AUTO_RETRY) and make sure you haven't + * turned on SSL_MODE_ENABLE_PARTIAL_WRITE so that either the + * operation is completed (retval==length) or an error occurred. + * + * @TODO ERR_get_error is only meaningful for some error codes, so check and + * return empty string otherwise. + */ +int TLSSend(SSL *ssl, const char *buffer, int length) +{ + assert(length >= 0); + assert_SSLIsBlocking(ssl); + + if (length == 0) + { + UnexpectedError("TLSSend: Zero length buffer!"); + return 0; + } + + EnforceBwLimit(length); + + int sent = -1; + bool should_retry = true; + int remaining_tries = MAX_WRITE_RETRIES; + while ((sent < 0) && should_retry) + { + sent = SSL_write(ssl, buffer, length); + if (sent <= 0) + { + if ((SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) != 0) + { + Log(LOG_LEVEL_ERR, + "Remote peer terminated TLS session (SSL_write)"); + return 0; + } + + /* else */ + int code = TLSLogError(ssl, LOG_LEVEL_VERBOSE, "SSL write failed", sent); + /* If renegotiation happens, SSL_ERROR_WANT_READ or + * SSL_ERROR_WANT_WRITE can be reported. See man:SSL_write(3).*/ + should_retry = ((remaining_tries > 0) && + ((code == SSL_ERROR_WANT_READ) || (code == SSL_ERROR_WANT_WRITE))); + } + if ((sent < 0) && should_retry) + { + sleep(1); + remaining_tries--; + } + } + if (sent < 0) + { + TLSLogError(ssl, LOG_LEVEL_ERR, "SSL_write", sent); + return -1; + } + + return sent; +} + +/** + * @brief Receives at most #length bytes of data from the SSL session + * and stores it in the buffer. + * @param ssl SSL information. + * @param buffer Buffer, of size at least #toget + 1 to store received data. + * @param toget Length of the data to receive, must be < CF_BUFSIZE. + * + * @return The length of the received data, which should be equal or less + * than the requested amount. + * -1 in case of timeout or error - SSL session is unusable + * 0 if connection was closed + * + * @note Use only for *blocking* sockets. Set + * SSL_CTX_set_mode(SSL_MODE_AUTO_RETRY) to make sure that either + * operation completed or an error occurred. + * @note Still, it may happen for #retval to be less than #toget, if the + * opposite side completed a TLSSend() with number smaller than #toget. + */ +int TLSRecv(SSL *ssl, char *buffer, int toget) +{ + assert(toget > 0); + assert(toget < CF_BUFSIZE); + assert_SSLIsBlocking(ssl); + + int received = -1; + bool should_retry = true; + int remaining_tries = MAX_READ_RETRIES; + while ((received < 0) && should_retry) + { + received = SSL_read(ssl, buffer, toget); + if (received < 0) + { + int code = TLSLogError(ssl, LOG_LEVEL_VERBOSE, "SSL read failed", received); + /* SSL_read() might get an internal recv() timeout, since we've set + * SO_RCVTIMEO. In that case SSL_read() returns SSL_ERROR_WANT_READ. + * Also, if renegotiation happens, SSL_ERROR_WANT_READ or + * SSL_ERROR_WANT_WRITE can be reported. See man:SSL_read(3).*/ + should_retry = ((remaining_tries > 0) && + ((code == SSL_ERROR_WANT_READ) || (code == SSL_ERROR_WANT_WRITE))); + + } + if ((received < 0) && should_retry) + { + sleep(1); + remaining_tries--; + } + } + + if (received < 0) + { + int errcode = TLSLogError(ssl, LOG_LEVEL_ERR, "SSL read after retries", received); + /* if all the retries didn't help, let's at least make sure proper + * cleanup is done */ + if ((errcode == SSL_ERROR_WANT_READ) || (errcode == SSL_ERROR_WANT_WRITE)) + { + /* Make sure that the peer will send us no more data. */ + SSL_shutdown(ssl); + shutdown(SSL_get_fd(ssl), SHUT_RDWR); + + /* Empty possible SSL_read() buffers. */ + int ret = 1; + int bytes_still_buffered = SSL_pending(ssl); + while (bytes_still_buffered > 0 && ret > 0) + { + char tmpbuf[bytes_still_buffered]; + ret = SSL_read(ssl, tmpbuf, bytes_still_buffered); + bytes_still_buffered -= ret; + } + } + + return -1; + } + else if (received == 0) + { + if ((SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) != 0) + { + Log(LOG_LEVEL_VERBOSE, + "Remote peer terminated TLS session (SSL_read)"); + } + else + { + TLSLogError(ssl, LOG_LEVEL_ERR, + "Connection unexpectedly closed (SSL_read)", + received); + } + } + + assert(received < CF_BUFSIZE); + buffer[received] = '\0'; + + return received; +} + +/** + * @brief Repeat receiving until last byte received is '\n'. + * + * @param #buf on return will contain all received lines, and '\0' will be + * appended to it. + * @return Return value is #buf 's length (excluding manually appended '\0') + * or -1 in case of error. + * + * @note This function is intended for line-oriented communication, this means + * the peer sends us one line (or a bunch of lines) and waits for reply, + * so that '\n' is the last character in the underlying SSL_read(). + */ +int TLSRecvLines(SSL *ssl, char *buf, size_t buf_size) +{ + int ret; + size_t got = 0; + buf_size -= 1; /* Reserve one space for terminating '\0' */ + + /* Repeat until we receive end of line. */ + do + { + buf[got] = '\0'; + ret = TLSRecv(ssl, &buf[got], buf_size - got); + if (ret <= 0) + { + Log(LOG_LEVEL_ERR, + "Connection was hung up while receiving line: %s", + buf); + return -1; + } + got += ret; + } while ((buf[got-1] != '\n') && (got < buf_size)); + assert(got <= buf_size); + + /* Append '\0', there is room because buf_size has been decremented. */ + buf[got] = '\0'; + + if ((got == buf_size) && (buf[got-1] != '\n')) + { + Log(LOG_LEVEL_ERR, + "Received line too long, hanging up! Length %zu, line: %s", + got, buf); + return -1; + } + + LogRaw(LOG_LEVEL_DEBUG, "TLSRecvLines(): ", buf, got); + + return (got <= INT_MAX) ? (int) got : -1; +} + +/** + * Set safe OpenSSL defaults commonly used by both clients and servers. + * + * @param min_version the minimum acceptable TLS version for incoming or + * outgoing connections (depending on ssl_ctx), for example + * "1", "1.1", "1.2". + */ +void TLSSetDefaultOptions(SSL_CTX *ssl_ctx, const char *min_version) +{ +#if HAVE_DECL_SSL_CTX_CLEAR_OPTIONS + /* Clear all flags, we do not want compatibility tradeoffs like + * SSL_OP_LEGACY_SERVER_CONNECT. */ + SSL_CTX_clear_options(ssl_ctx, SSL_CTX_get_options(ssl_ctx)); +#else + /* According to OpenSSL code v.0.9.8m, the first option to be added + * by default (SSL_OP_LEGACY_SERVER_CONNECT) was added at the same + * time SSL_CTX_clear_options appeared. Therefore, it is OK not to + * clear options if they are not set. + * If this assertion is proven to be false, output a clear warning + * to let the user know what happens. */ + if (SSL_CTX_get_options(ssl_ctx) != 0) + { + Log(LOG_LEVEL_WARNING, + "This version of CFEngine was compiled against OpenSSL < 0.9.8m, " + "using it with a later OpenSSL version is insecure. " + "The current version uses compatibility workarounds that may allow " + "CVE-2009-3555 exploitation."); + Log(LOG_LEVEL_WARNING, "Please update your CFEngine package or " + "compile it against your current OpenSSL version."); + } +#endif + + /* In any case use only TLS v1 or later. */ + long options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + + assert(TLS_LOWEST_RECOMMENDED >= TLS_LOWEST_REQUIRED); + enum tls_version min_tls_version = TLS_LOWEST_RECOMMENDED; + if (!NULL_OR_EMPTY(min_version)) + { + bool found = false; + for (enum tls_version v = TLS_1_0; !found && v <= TLS_LAST; v++) + { + if (StringEqual(min_version, tls_version_strings[v])) + { + found = true; + if (v < TLS_LOWEST_REQUIRED) + { + Log(LOG_LEVEL_WARNING, "Minimum requested TLS version is %s," + " but minimum version required by CFEngine is %s." + " Using the minimum required version.", + min_version, tls_version_strings[TLS_LOWEST_REQUIRED]); + min_tls_version = TLS_LOWEST_REQUIRED; + } + else if (v < TLS_LOWEST_RECOMMENDED) + { + Log(LOG_LEVEL_WARNING, "Minimum requested TLS version is %s," + " but minimum version recommended by CFEngine is %s.", + min_version, tls_version_strings[TLS_LOWEST_RECOMMENDED]); + min_tls_version = v; + } + else if (v > TLS_HIGHEST_SUPPORTED) + { + Log(LOG_LEVEL_WARNING, "Minimum requested TLS version is %s," + " but maximum version supported by OpenSSL is %s." + " Using the maximum supported version.", + min_version, tls_version_strings[TLS_HIGHEST_SUPPORTED]); + min_tls_version = TLS_HIGHEST_SUPPORTED; + } + else + { + min_tls_version = v; + } + } + } + if (!found) + { + Log(LOG_LEVEL_WARNING, + "Unrecognized requested minimum TLS version '%s'," + " using the minimum required version %s.", + min_version, tls_version_strings[TLS_LOWEST_REQUIRED]); + min_tls_version = TLS_LOWEST_REQUIRED; + } + } + + Log(LOG_LEVEL_VERBOSE, + "Setting minimum acceptable TLS version: %s", tls_version_strings[min_tls_version]); + + /* disable all the lower versions than the minimum requested/determined */ + for (enum tls_version v = TLS_1_0; v < min_tls_version; v++) + { + options |= tls_disable_flags[v]; + } + + /* No session resumption or renegotiation for now. */ + options |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; + +#ifdef SSL_OP_NO_TICKET + /* Disable another way of resumption, session tickets (RFC 5077). */ + options |= SSL_OP_NO_TICKET; +#endif + + SSL_CTX_set_options(ssl_ctx, options); + + + /* Disable both server-side and client-side session caching, to + complement the previous options. Safe for now, might enable for + performance in the future. */ + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_OFF); + + + /* Never bother with retransmissions, SSL_write() should + * always either write the whole amount or fail. */ + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + + /* Set options to always request a certificate from the peer, + either we are client or server. */ + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + /* Always accept that certificate, we do proper checking after TLS + * connection is established since OpenSSL can't pass a connection + * specific pointer to the callback (so we would have to lock). */ + SSL_CTX_set_cert_verify_callback(ssl_ctx, TLSVerifyCallback, NULL); +} + +bool TLSSetCipherList(SSL_CTX *ssl_ctx, const char *cipher_list) +{ + assert(ssl_ctx); + + if (NULL_OR_EMPTY(cipher_list)) + { + Log(LOG_LEVEL_VERBOSE, "Using the OpenSSL's default cipher list"); + /* nothing more to do */ + return true; + } + + Log(LOG_LEVEL_VERBOSE, "Setting cipher list for TLS connections to: %s", + cipher_list); + + const size_t max_len = strlen(cipher_list) + 1; /* NUL byte */ + size_t n_specs = StringCountTokens(cipher_list, max_len - 1, ":"); + + /* TLS 1.3 defines cipher suites, they start with "TLS_" */ + char ciphers[max_len]; + ciphers[0] = '\0'; + size_t ciphers_len = 0; + + char cipher_suites[max_len]; + cipher_suites[0] = '\0'; + size_t cipher_suites_len = 0; + + for (size_t i = 0; i < n_specs; i++) + { + StringRef spec_ref = StringGetToken(cipher_list, max_len, i, ":"); + if (StringStartsWith(spec_ref.data, "TLS_")) + { + StrCat(cipher_suites, max_len, &cipher_suites_len, spec_ref.data, spec_ref.len + 1); + } + else + { + StrCat(ciphers, max_len, &ciphers_len, spec_ref.data, spec_ref.len + 1); + } + } + + /* The above code can leave a trailing colon in the lists because it copies + * the items over *with* the colons splitting them. */ + if ((ciphers_len > 0) && (ciphers[ciphers_len - 1] == ':')) + { + ciphers[ciphers_len - 1] = '\0'; + ciphers_len--; + } + if ((cipher_suites_len > 0) && (cipher_suites[cipher_suites_len - 1] == ':')) + { + cipher_suites[cipher_suites_len - 1] = '\0'; + cipher_suites_len--; + } + + if (ciphers_len != 0) /* TLS <= 1.2 ciphers */ + { + Log(LOG_LEVEL_VERBOSE, "Enabling ciphers '%s' for TLS 1.2 and older", ciphers); + int ret = SSL_CTX_set_cipher_list(ssl_ctx, ciphers); + if (ret != 1) + { + Log(LOG_LEVEL_ERR, "No valid ciphers in the cipher list: %s", cipher_list); + return false; + } + } + +#ifdef HAVE_TLS_1_3 + if (cipher_suites_len != 0) /* TLS >= 1.3 ciphers */ + { + Log(LOG_LEVEL_VERBOSE, "Enabling cipher suites '%s' for TLS 1.3 and newer", cipher_suites); + int ret = SSL_CTX_set_ciphersuites(ssl_ctx, cipher_suites); + if (ret != 1) + { + Log(LOG_LEVEL_ERR, "No valid cipher suites in the list: %s", cipher_list); + return false; + } + } + else + { + /* Allowed ciphers specified, but no TLS 1.3 ciphersuites among them. + Let's disable TLS 1.3 because otherwise OpenSSL uses the default + ciphersuites for TLS 1.3 and thus effectively extends the specified + list of allowed ciphers behind our back. */ + Log(LOG_LEVEL_WARNING, + "Disabling TLS 1.3 because no TLS 1.3 ciphersuites specified in allowed ciphers: '%s'", + cipher_list); + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_3); + } +#else + if (cipher_suites_len != 0) + { + Log(LOG_LEVEL_WARNING, + "Ignoring requested TLS 1.3 ciphersuites '%s', TLS 1.3 not supported", + cipher_suites); + } +#endif + + return true; +} diff --git a/libcfnet/tls_generic.h b/libcfnet/tls_generic.h new file mode 100644 index 0000000000..7ef864744a --- /dev/null +++ b/libcfnet/tls_generic.h @@ -0,0 +1,52 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#ifndef CFENGINE_TLS_GENERIC_H +#define CFENGINE_TLS_GENERIC_H + + +#include + +#include + +#include /* LogLevel */ + + +extern int CONNECTIONINFO_SSL_IDX; + + +bool TLSGenericInitialize(void); +int TLSVerifyCallback(X509_STORE_CTX *ctx, void *arg); +int TLSVerifyPeer(ConnectionInfo *conn_info, const char *remoteip, const char *username); +X509 *TLSGenerateCertFromPrivKey(RSA *privkey); +int TLSLogError(SSL *ssl, LogLevel level, const char *prepend, int code); +int TLSSend(SSL *ssl, const char *buffer, int length); +int TLSRecv(SSL *ssl, char *buffer, int toget); +int TLSRecvLines(SSL *ssl, char *buf, size_t buf_size); +void TLSSetDefaultOptions(SSL_CTX *ssl_ctx, const char *min_version); +const char *TLSErrorString(intmax_t errcode); +bool TLSSetCipherList(SSL_CTX *ssl_ctx, const char *cipher_list); + +#endif diff --git a/libenv/Makefile.am b/libenv/Makefile.am new file mode 100644 index 0000000000..2c66662361 --- /dev/null +++ b/libenv/Makefile.am @@ -0,0 +1,68 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +noinst_LTLIBRARIES = libenv.la + +libenv_la_SOURCES = \ + constants.c constants.h \ + sysinfo.c sysinfo.h sysinfo_priv.h \ + time_classes.c time_classes.h \ + zones.c zones.h + +if !NT +libenv_la_SOURCES += \ + unix_iface.c +endif + +if SOLARIS +libenv_la_LIBADD = -lkstat +endif + +AM_CPPFLAGS = -I$(top_srcdir)/libntech/libutils +AM_CPPFLAGS += $(OPENSSL_CPPFLAGS) # because libutils needs it +AM_CPPFLAGS += $(PCRE2_CPPFLAGS) + +# Those dependencies are ought to go away ASAP +AM_CPPFLAGS += -I$(top_srcdir)/libcfnet +AM_CPPFLAGS += -I$(top_srcdir)/libpromises + +CLEANFILES = *.gcno *.gcda + +# +# Some basic clean ups +# +MOSTLYCLEANFILES = *~ *.orig *.rej + +# +# Get everything removed down to where rebuilding requires: +# "aclocal; autoconf; autoheader; automake --add-missing" +# "configure; make; make install" +# +MAINTAINERCLEANFILES = config.h.in + +# libcompat dependency + +.PRECIOUS: ../libntech/libcompat/libcompat.la + +../libntech/libcompat/libcompat.la: + $(MAKE) -C ../libntech/libcompat $(AM_MAKEFLAGS) libcompat.la diff --git a/libenv/constants.c b/libenv/constants.c new file mode 100644 index 0000000000..cfb0b52e1a --- /dev/null +++ b/libenv/constants.c @@ -0,0 +1,47 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include + +#ifdef _WIN32 +#define LINESEP "\r\n" +#else +#define LINESEP "\n" +#endif + +void LoadSystemConstants(EvalContext *ctx) +{ + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_CONST, "at", "@", CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_CONST, "dollar", "$", CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_CONST, "n", "\n", CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_CONST, "r", "\r", CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_CONST, "t", "\t", CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_CONST, "endl", "\n", CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_CONST, "dirsep", FILE_SEPARATOR_STR, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_CONST, "linesep", LINESEP, CF_DATA_TYPE_STRING, "source=agent"); + /* NewScalar("const","0","\0",cf_str); - this cannot work */ +} diff --git a/libenv/constants.h b/libenv/constants.h new file mode 100644 index 0000000000..a51c4c6b75 --- /dev/null +++ b/libenv/constants.h @@ -0,0 +1,37 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CONSTANTS_H +#define CFENGINE_CONSTANTS_H + + +#include + +/* TODO libpromises depends on libenv, the opposite should not happen! */ +#include + + +void LoadSystemConstants(EvalContext *ctx); + +#endif diff --git a/libenv/sysinfo.c b/libenv/sysinfo.c new file mode 100644 index 0000000000..145ad7d927 --- /dev/null +++ b/libenv/sysinfo.c @@ -0,0 +1,3640 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#define PCRE2_CODE_UNIT_WIDTH 8 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* StringMatchFull */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* GetCurrentUserName() */ + +#ifdef HAVE_ZONE_H +# include +#endif + +// HP-UX mpctl() for $(sys.cpus) on HP-UX - Mantis #1069 +#ifdef HAVE_SYS_MPCTL_H +# include +#endif + +/* Linux. + WARNING keep this before the #include because of glibc bug: + https://sourceware.org/bugzilla/show_bug.cgi?id=140 */ +#ifdef HAVE_STRUCT_SYSINFO_UPTIME +# include +#endif + +/* BSD, MAC OS X uptime calculation use KERN_BOOTTIME sysctl. */ +#ifdef HAVE_SYS_SYSCTL_H +# ifdef HAVE_SYS_PARAM_H +# include +# endif +# ifdef __linux__ +# include +# else +# include +# endif +#endif + + +/*****************************************************/ +// Uptime calculation settings for GetUptimeSeconds() - Mantis #1134 + +/* Listed here in priority order, i.e. first come the platform-specific + * ways. If nothing works, one of the last, most generic ways should. */ + +#ifndef __MINGW32__ /* Windows is implemented in Enterprise */ + +// HP-UX: pstat_getproc(2) on init (pid 1) +#if defined(__hpux) +# include +# include +# define BOOT_TIME_WITH_PSTAT_GETPROC + +// Solaris: kstat() for kernel statistics +// See http://dsc.sun.com/solaris/articles/kstatc.html +// BSD also has a kstat.h (albeit in sys), so check __sun just to be paranoid + +/** + * @WARNING: Commented out because inside a Solaris 10 zone this gives the + * uptime of the host machine (the hypervisor). We thus choose to + * use UTMP for Solaris. + */ +/* +#elif defined(__sun) && defined(HAVE_KSTAT_H) +# include +# define BOOT_TIME_WITH_KSTAT +*/ + +// BSD: sysctl(3) to get kern.boottime, CPU count, etc. +// See http://www.unix.com/man-page/FreeBSD/3/sysctl/ +// Linux also has sys/sysctl.h, so we check KERN_BOOTTIME to make sure it's BSD +#elif defined(HAVE_SYS_SYSCTL_H) && defined(KERN_BOOTTIME) +# define BOOT_TIME_WITH_SYSCTL + +// GNU/Linux: struct sysinfo.uptime +#elif defined(HAVE_STRUCT_SYSINFO_UPTIME) +# define BOOT_TIME_WITH_SYSINFO + +/* Generic System V way, available in most platforms. */ +#elif defined(HAVE_UTMP_H) +# include +# define BOOT_TIME_WITH_UTMP + +/* POSIX alternative (utmp.h does not exist on BSDs). */ +#elif defined(HAVE_UTMPX_H) +# include +# define BOOT_TIME_WITH_UTMPX + +#else +// Most generic way: {stat("/proc/1")}.st_ctime +// TODO in Solaris zones init is not guaranteed to be PID 1! +#define BOOT_TIME_WITH_PROCFS + +#endif + +/* Fallback uptime calculation: Parse the "uptime" command in case the + * platform-specific way fails or returns absurd number. */ +static time_t GetBootTimeFromUptimeCommand(time_t now); + +#endif /* ifndef __MINGW32__ */ + +#define LSB_RELEASE_FILENAME "/etc/lsb-release" +#define DEBIAN_VERSION_FILENAME "/etc/debian_version" +#define DEBIAN_ISSUE_FILENAME "/etc/issue" + + +/*****************************************************/ + +static char *FindNextInteger(char *str, char **num); + +void CalculateDomainName(const char *nodename, const char *dnsname, + char *fqname, size_t fqname_size, + char *uqname, size_t uqname_size, + char *domain, size_t domain_size); + +#ifdef __linux__ +static int Linux_Fedora_Version(EvalContext *ctx); +static int Linux_Redhat_Version(EvalContext *ctx); +static void Linux_Amazon_Version(EvalContext *ctx); +static void Linux_Alpine_Version(EvalContext *ctx); +static void Linux_Oracle_VM_Server_Version(EvalContext *ctx); +static void Linux_Oracle_Version(EvalContext *ctx); +static int Linux_Suse_Version(EvalContext *ctx); +static int Linux_Slackware_Version(EvalContext *ctx, char *filename); +static int Linux_Debian_Version(EvalContext *ctx); +static int Linux_Misc_Version(EvalContext *ctx); +static int Linux_Mandrake_Version(EvalContext *ctx); +static int Linux_Mandriva_Version(EvalContext *ctx); +static int Linux_Mandriva_Version_Real(EvalContext *ctx, char *filename, char *relstring, char *vendor); +static int VM_Version(EvalContext *ctx); +static int Xen_Domain(EvalContext *ctx); +static int EOS_Version(EvalContext *ctx); +static int MiscOS(EvalContext *ctx); +static void OpenVZ_Detect(EvalContext *ctx); + +static bool ReadLine(const char *filename, char *buf, int bufsize); +static FILE *ReadFirstLine(const char *filename, char *buf, int bufsize); +#endif + +#ifdef XEN_CPUID_SUPPORT +static void Xen_Cpuid(uint32_t idx, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx); +static bool Xen_Hv_Check(void); +#endif + +static void GetCPUInfo(EvalContext *ctx); + +static const char *const CLASSATTRIBUTES[][3] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = {"-", "-", "-"}, /* as appear here are matched. The fields are sysname and machine */ + [PLATFORM_CONTEXT_OPENVZ] = {"virt_host_vz_vzps", ".*", ".*"}, /* VZ with vzps */ + [PLATFORM_CONTEXT_HP] = {"hp-ux", ".*", ".*"}, /* hpux */ + [PLATFORM_CONTEXT_AIX] = {"aix", ".*", ".*"}, /* aix */ + [PLATFORM_CONTEXT_LINUX] = {"linux", ".*", ".*"}, /* linux */ + [PLATFORM_CONTEXT_BUSYBOX] = {"busybox", ".*", ".*"}, /* linux w/ busybox - warning uname returns linux */ + [PLATFORM_CONTEXT_SOLARIS] = {"sunos", ".*", + "5\\.1[1-9].*"}, /* new solaris, SunOS >= 5.11 */ + [PLATFORM_CONTEXT_SUN_SOLARIS] = {"sunos", ".*", + "5\\.([2-9]|10)(\\..*)?"}, /* old solaris, SunOS < 5.11 */ + [PLATFORM_CONTEXT_FREEBSD] = {"freebsd", ".*", ".*"}, /* freebsd */ + [PLATFORM_CONTEXT_NETBSD] = {"netbsd", ".*", ".*"}, /* NetBSD */ + [PLATFORM_CONTEXT_CRAYOS] = {"sn.*", "cray*", ".*"}, /* cray */ + [PLATFORM_CONTEXT_WINDOWS_NT] = {"cygwin_nt.*", ".*", ".*"}, /* NT (cygwin) */ + [PLATFORM_CONTEXT_SYSTEMV] = {"unix_sv", ".*", ".*"}, /* Unixware */ + [PLATFORM_CONTEXT_OPENBSD] = {"openbsd", ".*", ".*"}, /* OpenBSD */ + [PLATFORM_CONTEXT_CFSCO] = {"sco_sv", ".*", ".*"}, /* SCO */ + [PLATFORM_CONTEXT_DARWIN] = {"darwin", ".*", ".*"}, /* Darwin, aka MacOS X */ + [PLATFORM_CONTEXT_QNX] = {"qnx", ".*", ".*"}, /* qnx */ + [PLATFORM_CONTEXT_DRAGONFLY] = {"dragonfly", ".*", ".*"}, /* dragonfly */ + [PLATFORM_CONTEXT_MINGW] = {"windows_nt.*", ".*", ".*"}, /* NT (native) */ + [PLATFORM_CONTEXT_VMWARE] = {"vmkernel", ".*", ".*"}, /* VMWARE / ESX */ + [PLATFORM_CONTEXT_ANDROID] = {"android", ".*", ".*"}, /* android: Warning uname returns linux */ +}; + +static const char *const VRESOLVCONF[] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = "-", + [PLATFORM_CONTEXT_OPENVZ] = "/etc/resolv.conf", /* virt_host_vz_vzps */ + [PLATFORM_CONTEXT_HP] = "/etc/resolv.conf", /* hpux */ + [PLATFORM_CONTEXT_AIX] = "/etc/resolv.conf", /* aix */ + [PLATFORM_CONTEXT_LINUX] = "/etc/resolv.conf", /* linux */ + [PLATFORM_CONTEXT_BUSYBOX] = "/etc/resolv.conf", /* linux */ + [PLATFORM_CONTEXT_SOLARIS] = "/etc/resolv.conf", /* new solaris */ + [PLATFORM_CONTEXT_SUN_SOLARIS] = "/etc/resolv.conf", /* old solaris */ + [PLATFORM_CONTEXT_FREEBSD] = "/etc/resolv.conf", /* freebsd */ + [PLATFORM_CONTEXT_NETBSD] = "/etc/resolv.conf", /* netbsd */ + [PLATFORM_CONTEXT_CRAYOS] = "/etc/resolv.conf", /* cray */ + [PLATFORM_CONTEXT_WINDOWS_NT] = "/etc/resolv.conf", /* NT */ + [PLATFORM_CONTEXT_SYSTEMV] = "/etc/resolv.conf", /* Unixware */ + [PLATFORM_CONTEXT_OPENBSD] = "/etc/resolv.conf", /* openbsd */ + [PLATFORM_CONTEXT_CFSCO] = "/etc/resolv.conf", /* sco */ + [PLATFORM_CONTEXT_DARWIN] = "/etc/resolv.conf", /* darwin */ + [PLATFORM_CONTEXT_QNX] = "/etc/resolv.conf", /* qnx */ + [PLATFORM_CONTEXT_DRAGONFLY] = "/etc/resolv.conf", /* dragonfly */ + [PLATFORM_CONTEXT_MINGW] = "", /* mingw */ + [PLATFORM_CONTEXT_VMWARE] = "/etc/resolv.conf", /* vmware */ + [PLATFORM_CONTEXT_ANDROID] = "", /* android */ +}; + +static const char *const VMAILDIR[] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = "-", + [PLATFORM_CONTEXT_OPENVZ] = "/var/spool/mail", /* virt_host_vz_vzps */ + [PLATFORM_CONTEXT_HP] = "/var/mail", /* hpux */ + [PLATFORM_CONTEXT_AIX] = "/var/spool/mail", /* aix */ + [PLATFORM_CONTEXT_LINUX] = "/var/spool/mail", /* linux */ + [PLATFORM_CONTEXT_BUSYBOX] = "", /* linux */ + [PLATFORM_CONTEXT_SOLARIS] = "/var/mail", /* new solaris */ + [PLATFORM_CONTEXT_SUN_SOLARIS] = "/var/mail", /* old solaris */ + [PLATFORM_CONTEXT_FREEBSD] = "/var/mail", /* freebsd */ + [PLATFORM_CONTEXT_NETBSD] = "/var/mail", /* netbsd */ + [PLATFORM_CONTEXT_CRAYOS] = "/usr/mail", /* cray */ + [PLATFORM_CONTEXT_WINDOWS_NT] = "N/A", /* NT */ + [PLATFORM_CONTEXT_SYSTEMV] = "/var/mail", /* Unixware */ + [PLATFORM_CONTEXT_OPENBSD] = "/var/mail", /* openbsd */ + [PLATFORM_CONTEXT_CFSCO] = "/var/spool/mail", /* sco */ + [PLATFORM_CONTEXT_DARWIN] = "/var/mail", /* darwin */ + [PLATFORM_CONTEXT_QNX] = "/var/spool/mail", /* qnx */ + [PLATFORM_CONTEXT_DRAGONFLY] = "/var/mail", /* dragonfly */ + [PLATFORM_CONTEXT_MINGW] = "", /* mingw */ + [PLATFORM_CONTEXT_VMWARE] = "/var/spool/mail", /* vmware */ + [PLATFORM_CONTEXT_ANDROID] = "", /* android */ +}; + +static const char *const VEXPORTS[] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = "-", + [PLATFORM_CONTEXT_OPENVZ] = "/etc/exports", /* virt_host_vz_vzps */ + [PLATFORM_CONTEXT_HP] = "/etc/exports", /* hpux */ + [PLATFORM_CONTEXT_AIX] = "/etc/exports", /* aix */ + [PLATFORM_CONTEXT_LINUX] = "/etc/exports", /* linux */ + [PLATFORM_CONTEXT_BUSYBOX] = "", /* linux */ + [PLATFORM_CONTEXT_SOLARIS] = "/etc/dfs/dfstab", /* new solaris */ + [PLATFORM_CONTEXT_SUN_SOLARIS] = "/etc/dfs/dfstab", /* old solaris */ + [PLATFORM_CONTEXT_FREEBSD] = "/etc/exports", /* freebsd */ + [PLATFORM_CONTEXT_NETBSD] = "/etc/exports", /* netbsd */ + [PLATFORM_CONTEXT_CRAYOS] = "/etc/exports", /* cray */ + [PLATFORM_CONTEXT_WINDOWS_NT] = "/etc/exports", /* NT */ + [PLATFORM_CONTEXT_SYSTEMV] = "/etc/dfs/dfstab", /* Unixware */ + [PLATFORM_CONTEXT_OPENBSD] = "/etc/exports", /* openbsd */ + [PLATFORM_CONTEXT_CFSCO] = "/etc/dfs/dfstab", /* sco */ + [PLATFORM_CONTEXT_DARWIN] = "/etc/exports", /* darwin */ + [PLATFORM_CONTEXT_QNX] = "/etc/exports", /* qnx */ + [PLATFORM_CONTEXT_DRAGONFLY] = "/etc/exports", /* dragonfly */ + [PLATFORM_CONTEXT_MINGW] = "", /* mingw */ + [PLATFORM_CONTEXT_VMWARE] = "none", /* vmware */ + [PLATFORM_CONTEXT_ANDROID] = "" , /* android */ +}; + + +/*******************************************************************/ + +void CalculateDomainName(const char *nodename, const char *dnsname, + char *fqname, size_t fqname_size, + char *uqname, size_t uqname_size, + char *domain, size_t domain_size) +{ + if (strstr(dnsname, ".")) + { + strlcpy(fqname, dnsname, fqname_size); + } + else + { + strlcpy(fqname, nodename, fqname_size); + } + + if ((strncmp(nodename, fqname, strlen(nodename)) == 0) && (fqname[strlen(nodename)] == '.')) + { + /* If hostname is not qualified */ + strlcpy(domain, fqname + strlen(nodename) + 1, domain_size); + strlcpy(uqname, nodename, uqname_size); + } + else + { + /* If hostname is qualified */ + + char *p = strchr(nodename, '.'); + + if (p != NULL) + { + strlcpy(uqname, nodename, MIN(uqname_size, p - nodename + 1)); + strlcpy(domain, p + 1, domain_size); + } + else + { + strlcpy(uqname, nodename, uqname_size); + strlcpy(domain, "", domain_size); + } + } +} + +/*******************************************************************/ + +void DetectDomainName(EvalContext *ctx, const char *orig_nodename) +{ + char nodename[CF_BUFSIZE]; + + strlcpy(nodename, orig_nodename, sizeof(nodename)); + ToLowerStrInplace(nodename); + + char dnsname[CF_BUFSIZE] = ""; + char fqn[CF_BUFSIZE]; + + if (gethostname(fqn, sizeof(fqn)) != -1) + { + struct hostent *hp; + + if ((hp = gethostbyname(fqn))) + { + strlcpy(dnsname, hp->h_name, sizeof(dnsname)); + ToLowerStrInplace(dnsname); + } + } + + nt_static_assert(sizeof(VFQNAME) > 255); + nt_static_assert(sizeof(VUQNAME) > 255); + nt_static_assert(sizeof(VDOMAIN) > 255); + CalculateDomainName(nodename, dnsname, VFQNAME, sizeof(VFQNAME), + VUQNAME, sizeof(VUQNAME), VDOMAIN, sizeof(VDOMAIN)); + + // Note: We don't expect hostnames or domain names above 255 + // Not supported by DNS: + // https://tools.ietf.org/html/rfc2181#section-11 + // So let's print some warnings: + if (strlen(VUQNAME) > 255) + { + Log(LOG_LEVEL_WARNING, + "Long host name '%s' (%zu bytes) will may cause issues", + VUQNAME, + strlen(VUQNAME)); + } + if (strlen(VDOMAIN) > 255) + { + Log(LOG_LEVEL_WARNING, + "Long domain name '%s' (%zu bytes) will may cause issues", + VDOMAIN, + strlen(VDOMAIN)); + } + +/* + * VFQNAME = a.b.c.d -> + * NewClass("a.b.c.d") + * NewClass("b.c.d") + * NewClass("c.d") + * NewClass("d") + */ + char *ptr = VFQNAME; + + do + { + EvalContextClassPutHard(ctx, ptr, "inventory,attribute_name=none,source=agent,derived-from=sys.fqhost"); + + ptr = strchr(ptr, '.'); + if (ptr != NULL) + { + ptr++; + } + } while (ptr != NULL); + + EvalContextClassPutHard(ctx, VUQNAME, "source=agent,derived-from=sys.uqhost"); + EvalContextClassPutHard(ctx, VDOMAIN, "source=agent,derived-from=sys.domain"); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "host", nodename, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=none"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "uqhost", VUQNAME, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=none"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost", VFQNAME, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=Host name"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "domain", VDOMAIN, CF_DATA_TYPE_STRING, "source=agent"); +} + +/*******************************************************************/ + +void DiscoverVersion(EvalContext *ctx) +{ + int major = 0; + int minor = 0; + int patch = 0; + char workbuf[CF_BUFSIZE]; + if (sscanf(Version(), "%d.%d.%d", &major, &minor, &patch) == 3) + { + snprintf(workbuf, CF_MAXVARSIZE, "%d", major); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cf_version_major", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + snprintf(workbuf, CF_MAXVARSIZE, "%d", minor); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cf_version_minor", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + snprintf(workbuf, CF_MAXVARSIZE, "%d", patch); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cf_version_patch", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + } + else + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cf_version_major", "BAD VERSION " VERSION, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cf_version_minor", "BAD VERSION " VERSION, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cf_version_patch", "BAD VERSION " VERSION, CF_DATA_TYPE_STRING, "source=agent"); + } + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cf_version_release", RELEASE, CF_DATA_TYPE_STRING, "source=agent"); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "local_libdir", "lib", CF_DATA_TYPE_STRING, "source=agent"); + + snprintf(workbuf, CF_BUFSIZE, "%s%cinputs%clib", GetWorkDir(), FILE_SEPARATOR, FILE_SEPARATOR); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "libdir", workbuf, CF_DATA_TYPE_STRING, "source=agent"); +} + +static void GetNameInfo3(EvalContext *ctx) +{ + int i; + char *sp, workbuf[CF_BUFSIZE]; + time_t tloc; + struct hostent *hp; + struct sockaddr_in cin; + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + const char* const workdir = GetWorkDir(); + const char* const bindir = GetBinDir(); + +#ifdef _AIX + char real_version[_SYS_NMLN]; +#endif +#if defined(HAVE_SYSINFO) && (defined(SI_ARCHITECTURE) || defined(SI_PLATFORM)) + long sz; +#endif + +#define COMPONENTS_SIZE 17 + // This is used for $(sys.cf_agent), $(sys.cf_serverd) ... : + char *components[COMPONENTS_SIZE] = { "cf-twin", "cf-agent", "cf-serverd", "cf-monitord", "cf-know", + "cf-report", "cf-key", "cf-runagent", "cf-execd", "cf-hub", "cf-reactor", + "cf-promises", "cf-upgrade", "cf-net", "cf-check", "cf-secret", + NULL + }; + int have_component[COMPONENTS_SIZE] = {0}; + struct stat sb; + char name[CF_MAXVARSIZE], quoteName[CF_MAXVARSIZE + 2], shortname[CF_MAXVARSIZE]; + + if (uname(&VSYSNAME) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't get kernel name info!. (uname: %s)", GetErrorStr()); + memset(&VSYSNAME, 0, sizeof(VSYSNAME)); + } + +#ifdef _AIX + snprintf(real_version, _SYS_NMLN, "%.80s.%.80s", VSYSNAME.version, VSYSNAME.release); + strlcpy(VSYSNAME.release, real_version, _SYS_NMLN); +#endif +#if defined(__ANDROID__) && !defined(__TERMUX__) + /* + * uname cannot differentiate android from linux + */ + strcpy(VSYSNAME.sysname, "android"); +#endif +#ifdef __BUSYBOX__ + /* + * uname cannot differentiate a busybox toolset from a normal GNU linux toolset + */ + strcpy(VSYSNAME.sysname, "busybox"); +#endif + + ToLowerStrInplace(VSYSNAME.sysname); + ToLowerStrInplace(VSYSNAME.machine); + +#ifdef _AIX + switch (_system_configuration.architecture) + { + case POWER_RS: + strlcpy(VSYSNAME.machine, "power", _SYS_NMLN); + break; + case POWER_PC: + strlcpy(VSYSNAME.machine, "powerpc", _SYS_NMLN); + break; + case IA64: + strlcpy(VSYSNAME.machine, "ia64", _SYS_NMLN); + break; + } +#endif + +/* + * solarisx86 is a historically defined class for Solaris on x86. We have to + * define it manually now. + */ +#ifdef __sun + if (strcmp(VSYSNAME.machine, "i86pc") == 0) + { + EvalContextClassPutHard(ctx, "solarisx86", "inventory,attribute_name=none,source=agent"); + } +#endif + + DetectDomainName(ctx, VSYSNAME.nodename); + + if ((tloc = time((time_t *) NULL)) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't read system clock"); + } + else + { + snprintf(workbuf, CF_BUFSIZE, "%jd", (intmax_t) tloc); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "systime", workbuf, CF_DATA_TYPE_INT, "time_based,source=agent"); + snprintf(workbuf, CF_BUFSIZE, "%jd", (intmax_t) tloc / SECONDS_PER_DAY); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "sysday", workbuf, CF_DATA_TYPE_INT, "time_based,source=agent"); + i = GetUptimeMinutes(tloc); + if (i != -1) + { + snprintf(workbuf, CF_BUFSIZE, "%d", i); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "uptime", workbuf, CF_DATA_TYPE_INT, "inventory,time_based,source=agent,attribute_name=Uptime minutes"); + } + } + + bool found = false; + for (i = 0; !found && (i < PLATFORM_CONTEXT_MAX); i++) + { + char sysname[CF_BUFSIZE]; + strlcpy(sysname, VSYSNAME.sysname, CF_BUFSIZE); + ToLowerStrInplace(sysname); + + /* FIXME: review those strcmps. Moved out from StringMatch */ + if (!strcmp(CLASSATTRIBUTES[i][0], sysname) + || StringMatchFull(CLASSATTRIBUTES[i][0], sysname)) + { + if (!strcmp(CLASSATTRIBUTES[i][1], VSYSNAME.machine) + || StringMatchFull(CLASSATTRIBUTES[i][1], VSYSNAME.machine)) + { + if (!strcmp(CLASSATTRIBUTES[i][2], VSYSNAME.release) + || StringMatchFull(CLASSATTRIBUTES[i][2], VSYSNAME.release)) + { + EvalContextClassPutHard(ctx, CLASSTEXT[i], "inventory,attribute_name=none,source=agent,derived-from=sys.class"); + + found = true; + + VSYSTEMHARDCLASS = (PlatformContext) i; + VPSHARDCLASS = (PlatformContext) i; /* this one can be overriden at vz detection */ + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "class", CLASSTEXT[i], CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=Kernel"); + } + } + else + { + Log(LOG_LEVEL_DEBUG, "I recognize '%s' but not '%s'", VSYSNAME.sysname, VSYSNAME.machine); + continue; + } + } + } + + if (!found) + { + i = 0; + } + + Log(LOG_LEVEL_VERBOSE, "%s - ready", NameVersion()); + Banner("Environment discovery"); + + snprintf(workbuf, CF_BUFSIZE, "%s", CLASSTEXT[i]); // See: cppcheck_suppressions.txt + + + Log(LOG_LEVEL_VERBOSE, "Host name is: %s", VSYSNAME.nodename); + Log(LOG_LEVEL_VERBOSE, "Operating System Type is %s", VSYSNAME.sysname); + Log(LOG_LEVEL_VERBOSE, "Operating System Release is %s", VSYSNAME.release); + Log(LOG_LEVEL_VERBOSE, "Architecture = %s", VSYSNAME.machine); + Log(LOG_LEVEL_VERBOSE, "CFEngine detected operating system description is %s", workbuf); + Log(LOG_LEVEL_VERBOSE, "The time is now %s", ctime(&tloc)); + + snprintf(workbuf, CF_MAXVARSIZE, "%s", ctime(&tloc)); + Chop(workbuf, CF_EXPANDSIZE); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "date", workbuf, CF_DATA_TYPE_STRING, "time_based,source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cdate", CanonifyName(workbuf), CF_DATA_TYPE_STRING, "time_based,source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "os", VSYSNAME.sysname, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "release", VSYSNAME.release, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=Kernel Release"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "version", VSYSNAME.version, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "arch", VSYSNAME.machine, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=Architecture"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "workdir", workdir, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "fstab", VFSTAB[VSYSTEMHARDCLASS], CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "resolv", VRESOLVCONF[VSYSTEMHARDCLASS], CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "maildir", VMAILDIR[VSYSTEMHARDCLASS], CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "exports", VEXPORTS[VSYSTEMHARDCLASS], CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "logdir", GetLogDir(), CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "piddir", GetPidDir(), CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "statedir", GetStateDir(), CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "masterdir", GetMasterDir(), CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "inputdir", GetInputDir(), CF_DATA_TYPE_STRING, "source=agent"); + + snprintf(workbuf, CF_BUFSIZE, "%s", bindir); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "bindir", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + + snprintf(workbuf, CF_BUFSIZE, "%s%cpromises.cf", GetInputDir(), FILE_SEPARATOR); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "default_policy_path", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + + snprintf(workbuf, CF_BUFSIZE, "%s%cfailsafe.cf", GetInputDir(), FILE_SEPARATOR); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "failsafe_policy_path", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + + snprintf(workbuf, CF_BUFSIZE, "%s%cupdate.cf", GetInputDir(), FILE_SEPARATOR); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "update_policy_path", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + +/* FIXME: type conversion */ + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cf_version", (char *) Version(), CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=CFEngine version"); + + DiscoverVersion(ctx); + + if (PUBKEY) + { + char pubkey_digest[CF_HOSTKEY_STRING_SIZE] = { 0 }; + + HashPubKey(PUBKEY, digest, CF_DEFAULT_DIGEST); + HashPrintSafe(pubkey_digest, sizeof(pubkey_digest), digest, + CF_DEFAULT_DIGEST, true); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "key_digest", pubkey_digest, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=CFEngine ID"); + + snprintf(workbuf, CF_MAXVARSIZE - 1, "PK_%s", pubkey_digest); + CanonifyNameInPlace(workbuf); + EvalContextClassPutHard(ctx, workbuf, "inventory,attribute_name=none,source=agent,derived-from=sys.key_digest"); + } + + for (i = 0; components[i] != NULL; i++) + { + snprintf(shortname, CF_MAXVARSIZE - 1, "%s", CanonifyName(components[i])); + +#if defined(_WIN32) + // twin has own dir, and is named agent + if (i == 0) + { + snprintf(name, CF_MAXVARSIZE - 1, "%s-twin%ccf-agent.exe", bindir, FILE_SEPARATOR); + } + else + { + snprintf(name, CF_MAXVARSIZE - 1, "%s%c%s.exe", bindir, FILE_SEPARATOR, components[i]); + } +#else + snprintf(name, CF_MAXVARSIZE - 1, "%s%c%s", bindir, FILE_SEPARATOR, components[i]); +#endif + + have_component[i] = false; + + if (stat(name, &sb) != -1) + { + snprintf(quoteName, sizeof(quoteName), "\"%s\"", name); + // Sets $(sys.cf_agent), $(sys.cf_serverd) $(sys.cf_execd) etc. + // to their respective /bin paths + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, shortname, quoteName, CF_DATA_TYPE_STRING, "cfe_internal,source=agent"); + have_component[i] = true; + } + } + +// If no twin, fail over the agent + + if (!have_component[0]) + { + snprintf(shortname, CF_MAXVARSIZE - 1, "%s", CanonifyName(components[0])); + +#if defined(_WIN32) + snprintf(name, CF_MAXVARSIZE - 1, "%s%c%s.exe", bindir, FILE_SEPARATOR, + components[1]); +#else + snprintf(name, CF_MAXVARSIZE - 1, "%s%c%s", bindir, FILE_SEPARATOR, components[1]); +#endif + + if (stat(name, &sb) != -1) + { + snprintf(quoteName, sizeof(quoteName), "\"%s\"", name); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, shortname, quoteName, CF_DATA_TYPE_STRING, "cfe_internal,source=agent"); + } + } + +/* Windows special directories and tools */ + +#ifdef __MINGW32__ + if (NovaWin_GetWinDir(workbuf, sizeof(workbuf))) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "windir", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + } + + if (NovaWin_GetSysDir(workbuf, sizeof(workbuf))) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "winsysdir", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + + char filename[CF_BUFSIZE]; + if (snprintf(filename, sizeof(filename), "%s%s", workbuf, "\\WindowsPowerShell\\v1.0\\powershell.exe") < sizeof(filename)) + { + if (NovaWin_FileExists(filename)) + { + EvalContextClassPutHard(ctx, "powershell", "inventory,attribute_name=none,source=agent"); + Log(LOG_LEVEL_VERBOSE, "Additional hard class defined as: %s", "powershell"); + } + } + } + + if (NovaWin_GetProgDir(workbuf, sizeof(workbuf))) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "winprogdir", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + } + +# ifdef _WIN64 +// only available on 64 bit windows systems + if (NovaWin_GetEnv("PROGRAMFILES(x86)", workbuf, sizeof(workbuf))) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "winprogdir86", workbuf, CF_DATA_TYPE_STRING, "source=agent"); + } + +# else/* NOT _WIN64 */ + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "winprogdir86", "", CF_DATA_TYPE_STRING, "source=agent"); + +# endif +#endif /* !__MINGW32__ */ + + EnterpriseContext(ctx); + + snprintf(workbuf, sizeof(workbuf), "%u_bit", (unsigned) sizeof(void*) * 8); + EvalContextClassPutHard(ctx, workbuf, "source=agent"); + Log(LOG_LEVEL_VERBOSE, "Additional hard class defined as: %s", CanonifyName(workbuf)); + + snprintf(workbuf, CF_BUFSIZE, "%s_%s", VSYSNAME.sysname, VSYSNAME.release); + EvalContextClassPutHard(ctx, workbuf, "inventory,attribute_name=none,source=agent,derived-from=sys.sysname,derived-from=sys.release"); + + EvalContextClassPutHard(ctx, VSYSNAME.machine, "source=agent,derived-from=sys.machine"); + Log(LOG_LEVEL_VERBOSE, "Additional hard class defined as: %s", CanonifyName(workbuf)); + + snprintf(workbuf, CF_BUFSIZE, "%s_%s", VSYSNAME.sysname, VSYSNAME.machine); + EvalContextClassPutHard(ctx, workbuf, "source=agent,derived-from=sys.sysname,derived-from=sys.machine"); + Log(LOG_LEVEL_VERBOSE, "Additional hard class defined as: %s", CanonifyName(workbuf)); + + snprintf(workbuf, CF_BUFSIZE, "%s_%s_%s", VSYSNAME.sysname, VSYSNAME.machine, VSYSNAME.release); + EvalContextClassPutHard(ctx, workbuf, "source=agent,derived-from=sys.sysname,derived-from=sys.machine,derived-from=sys.release"); + Log(LOG_LEVEL_VERBOSE, "Additional hard class defined as: %s", CanonifyName(workbuf)); + +#ifdef HAVE_SYSINFO +# ifdef SI_ARCHITECTURE + sz = sysinfo(SI_ARCHITECTURE, workbuf, CF_BUFSIZE); + if (sz == -1) + { + Log(LOG_LEVEL_VERBOSE, "cfengine internal: sysinfo returned -1"); + } + else + { + EvalContextClassPutHard(ctx, workbuf, "inventory,attribute_name=none,source=agent"); + Log(LOG_LEVEL_VERBOSE, "Additional hard class defined as: %s", workbuf); + } +# endif +# ifdef SI_PLATFORM + sz = sysinfo(SI_PLATFORM, workbuf, CF_BUFSIZE); + if (sz == -1) + { + Log(LOG_LEVEL_VERBOSE, "cfengine internal: sysinfo returned -1"); + } + else + { + EvalContextClassPutHard(ctx, workbuf, "inventory,attribute_name=none,source=agent"); + Log(LOG_LEVEL_VERBOSE, "Additional hard class defined as: %s", workbuf); + } +# endif +#endif + + snprintf(workbuf, CF_BUFSIZE, "%s_%s_%s_%s", VSYSNAME.sysname, VSYSNAME.machine, VSYSNAME.release, + VSYSNAME.version); + + if (strlen(workbuf) > CF_MAXVARSIZE - 2) + { + Log(LOG_LEVEL_VERBOSE, "cfengine internal: $(arch) overflows CF_MAXVARSIZE! Truncating"); + } + + sp = xstrdup(CanonifyName(workbuf)); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "long_arch", sp, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextClassPutHard(ctx, sp, "source=agent,derived-from=sys.long_arch"); + free(sp); + + snprintf(workbuf, CF_BUFSIZE, "%s_%s", VSYSNAME.sysname, VSYSNAME.machine); + sp = xstrdup(CanonifyName(workbuf)); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "ostype", sp, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextClassPutHard(ctx, sp, "inventory,attribute_name=none,source=agent,derived-from=sys.ostype"); + free(sp); + + if (!found) + { + Log(LOG_LEVEL_ERR, "I don't understand what architecture this is"); + } + + char compile_str[] = "compiled_on_"; + size_t compile_str_len = sizeof(compile_str); + strcpy(workbuf, compile_str); + + strlcat(workbuf, CanonifyName(AUTOCONF_SYSNAME), + CF_BUFSIZE - compile_str_len); + EvalContextClassPutHard(ctx, workbuf, "source=agent"); + Log(LOG_LEVEL_VERBOSE, "GNU autoconf class from compile time: %s", workbuf); + +/* Get IP address from nameserver */ + + if ((hp = gethostbyname(VFQNAME)) == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Hostname lookup failed on node name '%s'", VSYSNAME.nodename); + return; + } + else + { + memset(&cin, 0, sizeof(cin)); + cin.sin_addr.s_addr = ((struct in_addr *) (hp->h_addr))->s_addr; + Log(LOG_LEVEL_VERBOSE, "Address given by nameserver: %s", inet_ntoa(cin.sin_addr)); + strcpy(VIPADDRESS, inet_ntoa(cin.sin_addr)); + + for (i = 0; hp->h_aliases[i] != NULL; i++) + { + Log(LOG_LEVEL_DEBUG, "Adding alias '%s'", hp->h_aliases[i]); + EvalContextClassPutHard(ctx, hp->h_aliases[i], "inventory,attribute_name=none,source=agent,based-on=sys.fqhost"); + } + } + +#ifdef HAVE_GETZONEID + zoneid_t zid; + char zone[ZONENAME_MAX]; + char vbuff[CF_BUFSIZE]; + + zid = getzoneid(); + getzonenamebyid(zid, zone, ZONENAME_MAX); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "zone", zone, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=Solaris zone"); + snprintf(vbuff, CF_BUFSIZE - 1, "zone_%s", zone); + EvalContextClassPutHard(ctx, vbuff, "source=agent,derived-from=sys.zone"); + + if (strcmp(zone, "global") == 0) + { + Log(LOG_LEVEL_VERBOSE, "CFEngine seems to be running inside a global solaris zone of name '%s'", zone); + } + else + { + Log(LOG_LEVEL_VERBOSE, "CFEngine seems to be running inside a local solaris zone of name '%s'", zone); + } +#endif +} + +/*******************************************************************/ + +void LoadSlowlyVaryingObservations(EvalContext *ctx) +{ + CF_DB *dbp; + CF_DBC *dbcp; + char *key; + void *stored; + int ksize, vsize; + + if (!OpenDB(&dbp, dbid_static)) + { + return; + } + + /* Acquire a cursor for the database. */ + if (!NewDBCursor(dbp, &dbcp)) + { + Log(LOG_LEVEL_INFO, "Unable to scan class db"); + CloseDB(dbp); + return; + } + + while (NextDB(dbcp, &key, &ksize, &stored, &vsize)) + { + if (key == NULL || stored == NULL) + { + continue; + } + + char lval[1024]; + int type_i; + int ret = sscanf(key, "%1023[^:]:%d", lval, &type_i); + if (ret == 2) + { + DataType type = type_i; + switch (type) + { + case CF_DATA_TYPE_STRING: + case CF_DATA_TYPE_INT: + case CF_DATA_TYPE_REAL: + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_MON, + lval, stored, type, + "monitoring,source=observation"); + break; + + case CF_DATA_TYPE_STRING_LIST: + { + Rlist *list = RlistFromSplitString(stored, ','); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_MON, + lval, list, CF_DATA_TYPE_STRING_LIST, + "monitoring,source=observation"); + RlistDestroy(list); + break; + } + case CF_DATA_TYPE_COUNTER: + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_MON, + lval, stored, CF_DATA_TYPE_STRING, + "monitoring,source=observation"); + break; + + default: + Log(LOG_LEVEL_ERR, + "Unexpected monitoring type %d found in dbid_static database", + (int) type); + } + } + } + + DeleteDBCursor(dbcp); + CloseDB(dbp); +} + +static void Get3Environment(EvalContext *ctx) +{ + char env[CF_BUFSIZE], context[CF_BUFSIZE], name[CF_MAXVARSIZE], value[CF_BUFSIZE]; + struct stat statbuf; + time_t now = time(NULL); + + Log(LOG_LEVEL_VERBOSE, "Looking for environment from cf-monitord..."); + + snprintf(env, CF_BUFSIZE, "%s/%s", GetStateDir(), CF_ENV_FILE); + MapName(env); + + FILE *fp = safe_fopen(env, "r"); + if (fp == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Unable to detect environment from cf-monitord"); + return; + } + + int fd = fileno(fp); + if (fstat(fd, &statbuf) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Unable to detect environment from cf-monitord"); + fclose(fp); + return; + } + + if (statbuf.st_mtime < (now - 60 * 60)) + { + Log(LOG_LEVEL_VERBOSE, "Environment data are too old - discarding"); + unlink(env); + fclose(fp); + return; + } + + snprintf(value, CF_MAXVARSIZE - 1, "%s", ctime(&statbuf.st_mtime)); + if (Chop(value, CF_EXPANDSIZE) == -1) + { + Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator"); + } + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_MON, "env_time", value, CF_DATA_TYPE_STRING, "time_based,source=agent"); + + Log(LOG_LEVEL_VERBOSE, "Loading environment..."); + + for(;;) + { + name[0] = '\0'; + value[0] = '\0'; + + if (fgets(context, sizeof(context), fp) == NULL) + { + if (ferror(fp)) + { + UnexpectedError("Failed to read line from stream"); + break; + } + else /* feof */ + { + break; + } + } + + + if (*context == '@') + { + nt_static_assert((sizeof(name)) > 255); + nt_static_assert((sizeof(value)) > 255); + if (sscanf(context + 1, "%255[^=]=%255[^\n]", name, value) == 2) + { + Log(LOG_LEVEL_DEBUG, + "Setting new monitoring list '%s' => '%s'", + name, value); + Rlist *list = RlistParseShown(value); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_MON, + name, list, + CF_DATA_TYPE_STRING_LIST, + "monitoring,source=environment"); + + RlistDestroy(list); + } + else + { + Log(LOG_LEVEL_ERR, + "Failed to parse '%s' as '@variable=list' monitoring list", + context); + } + } + else if (strchr(context, '=')) + { + nt_static_assert((sizeof(name)) > 255); + nt_static_assert((sizeof(value)) > 255); + if (sscanf(context, "%255[^=]=%255[^\n]", name, value) == 2) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_MON, + name, value, + CF_DATA_TYPE_STRING, + "monitoring,source=environment"); + Log(LOG_LEVEL_DEBUG, + "Setting new monitoring scalar '%s' => '%s'", + name, value); + } + else + { + Log(LOG_LEVEL_ERR, + "Failed to parse '%s' as 'variable=value' monitoring scalar", + context); + } + } + else + { + StripTrailingNewline(context, CF_BUFSIZE); + EvalContextClassPutHard(ctx, context, "monitoring,source=environment"); + } + } + + fclose(fp); + Log(LOG_LEVEL_VERBOSE, "Environment data loaded"); + + LoadSlowlyVaryingObservations(ctx); +} + +static void BuiltinClasses(EvalContext *ctx) +{ + char vbuff[CF_BUFSIZE]; + + EvalContextClassPutHard(ctx, "any", "source=agent"); /* This is a reserved word / wildcard */ + + snprintf(vbuff, CF_BUFSIZE, "cfengine_%s", CanonifyName(Version())); + CreateHardClassesFromCanonification(ctx, vbuff, "inventory,attribute_name=none,source=agent"); + + CreateHardClassesFromFeatures(ctx, "source=agent"); +} + +/*******************************************************************/ + +void CreateHardClassesFromCanonification(EvalContext *ctx, const char *canonified, char *tags) +{ + char buf[CF_MAXVARSIZE]; + + strlcpy(buf, canonified, sizeof(buf)); + + EvalContextClassPutHard(ctx, buf, tags); + + char *sp; + + while ((sp = strrchr(buf, '_'))) + { + *sp = 0; + EvalContextClassPutHard(ctx, buf, tags); + } +} + +static void SetFlavor(EvalContext *ctx, const char *flavor) +{ + EvalContextClassPutHard(ctx, flavor, "inventory,attribute_name=none,source=agent,derived-from=sys.flavor"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "flavour", flavor, CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "flavor", flavor, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=none"); +} + +#if defined __linux__ || __MINGW32__ + +/** + * @brief Combines OS and version string to define flavor variable and class + * + * @note Input strings should be canonified before calling + */ +static void SetFlavor2( + EvalContext *const ctx, + const char *const id, + const char *const major_version) +{ + assert(ctx != NULL); + assert(id != NULL); + assert(major_version != NULL); + + char *flavor; + xasprintf(&flavor, "%s_%s", id, major_version); + SetFlavor(ctx, flavor); + free(flavor); +} + +#endif + +#ifdef __linux__ + +/** + * @brief Combines OS and version string to define multiple hard classes + * + * @note Input strings should be canonified before calling + */ +static void DefineVersionedHardClasses( + EvalContext *const ctx, + const char *const tags, + const char *const id, + const char *const version) +{ + assert(ctx != NULL); + assert(id != NULL); + assert(version != NULL); + + char *class; + xasprintf(&class, "%s_%s", id, version); + + // Strip away version number to define multiple hard classes + // Example: coreos_1185_3_0 -> coreos_1185_3 -> coreos_1185 -> coreos + char *last_underscore = strrchr(class, '_'); + while ( last_underscore != NULL ) + { + EvalContextClassPutHard(ctx, class, tags); + *last_underscore = '\0'; + last_underscore = strrchr(class, '_'); + } + EvalContextClassPutHard(ctx, class, tags); + free(class); +} + +static void OSReleaseParse(EvalContext *ctx, const char *file_path) +{ + JsonElement *os_release_json = JsonReadDataFile("system info discovery", + file_path, DATAFILETYPE_ENV, + 100 * 1024); + if (os_release_json != NULL) + { + char *tags; + xasprintf(&tags, + "inventory,attribute_name=none,source=agent,derived-from-file=%s", + file_path); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "os_release", + os_release_json, CF_DATA_TYPE_CONTAINER, + tags); + const char *const_os_release_id = JsonObjectGetAsString(os_release_json, "ID"); + const char *const_os_release_version_id = JsonObjectGetAsString(os_release_json, "VERSION_ID"); + char *os_release_id = SafeStringDuplicate(const_os_release_id); + char *os_release_version_id = SafeStringDuplicate(const_os_release_version_id); + + if (os_release_id != NULL) + { + CanonifyNameInPlace(os_release_id); + + const char *alias = NULL; + if (StringEqual(os_release_id, "rhel")) + { + alias = "redhat"; + } + else if (StringEqual(os_release_id, "opensuse") || + StringEqual(os_release_id, "sles")) + { + alias = "suse"; + } + + if (os_release_version_id == NULL) + { + // if VERSION_ID doesn't exist, define only one hard class: + EvalContextClassPutHard(ctx, os_release_id, tags); + if (alias != NULL) + { + EvalContextClassPutHard(ctx, alias, tags); + } + } + else // if VERSION_ID exists set flavor and multiple hard classes: + { + CanonifyNameInPlace(os_release_version_id); + + // Set the flavor to be ID + major version (derived from VERSION_ID) + char *first_underscore = strchr(os_release_version_id, '_'); + if (first_underscore != NULL) + { + // Temporarily modify os_release_version_id to be major version + *first_underscore = '\0'; + SetFlavor2(ctx, os_release_id, os_release_version_id); + *first_underscore = '_'; + } + else + { + SetFlavor2(ctx, os_release_id, os_release_version_id); + } + + // One of the hard classes is already set by SetFlavor + // but it seems excessive to try to skip this: + DefineVersionedHardClasses(ctx, tags, os_release_id, os_release_version_id); + if (alias != NULL) + { + DefineVersionedHardClasses(ctx, tags, alias, os_release_version_id); + } + } + } + free(os_release_version_id); + free(os_release_id); + free(tags); + JsonDestroy(os_release_json); + } +} +#endif + +static void OSClasses(EvalContext *ctx) +{ +#ifdef __linux__ + +/* First we check if init process is systemd, and set "systemd" hard class. */ + + { + char init_path[CF_BUFSIZE]; + if (ReadLine("/proc/1/cmdline", init_path, sizeof(init_path))) + { + /* Follow possible symlinks. */ + + char resolved_path[PATH_MAX]; /* realpath() needs PATH_MAX */ + if (realpath(init_path, resolved_path) != NULL && + strlen(resolved_path) < sizeof(init_path)) + { + strcpy(init_path, resolved_path); + } + + /* Check if string ends with "/systemd". */ + char *p; + char *next_p = NULL; + const char *term = "/systemd"; + do + { + p = next_p; + next_p = strstr(next_p ? next_p+strlen(term) : init_path, term); + } + while (next_p); + + if (p != NULL && + p[strlen("/systemd")] == '\0') + { + EvalContextClassPutHard(ctx, "systemd", + "inventory,attribute_name=none,source=agent"); + } + } + } + + + struct stat statbuf; + + // os-release is used to set sys.os_release, sys.flavor and hard classes + if (access("/etc/os-release", R_OK) != -1) + { + OSReleaseParse(ctx, "/etc/os-release"); + } + else if (access("/usr/lib/os-release", R_OK) != -1) + { + OSReleaseParse(ctx, "/usr/lib/os-release"); + } + +/* Mandrake/Mandriva, Fedora and Oracle VM Server supply /etc/redhat-release, so + we test for those distributions first */ + + if (stat("/etc/mandriva-release", &statbuf) != -1) + { + Linux_Mandriva_Version(ctx); + } + else if (stat("/etc/mandrake-release", &statbuf) != -1) + { + Linux_Mandrake_Version(ctx); + } + else if (stat("/etc/fedora-release", &statbuf) != -1) + { + Linux_Fedora_Version(ctx); + } + else if (stat("/etc/ovs-release", &statbuf) != -1) + { + Linux_Oracle_VM_Server_Version(ctx); + } + else if (stat("/etc/redhat-release", &statbuf) != -1) + { + Linux_Redhat_Version(ctx); + } + +/* Oracle Linux >= 6 supplies separate /etc/oracle-release alongside + /etc/redhat-release, use it to precisely identify version */ + + if (stat("/etc/oracle-release", &statbuf) != -1) + { + Linux_Oracle_Version(ctx); + } + + if (stat("/etc/generic-release", &statbuf) != -1) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be a sun cobalt system."); + SetFlavor(ctx, "SunCobalt"); + } + + if (stat("/etc/SuSE-release", &statbuf) != -1) + { + Linux_Suse_Version(ctx); + } + + if (stat("/etc/system-release", &statbuf) != -1) + { + Linux_Amazon_Version(ctx); + } + +# define SLACKWARE_ANCIENT_VERSION_FILENAME "/etc/slackware-release" +# define SLACKWARE_VERSION_FILENAME "/etc/slackware-version" + if (stat(SLACKWARE_VERSION_FILENAME, &statbuf) != -1) + { + Linux_Slackware_Version(ctx, SLACKWARE_VERSION_FILENAME); + } + else if (stat(SLACKWARE_ANCIENT_VERSION_FILENAME, &statbuf) != -1) + { + Linux_Slackware_Version(ctx, SLACKWARE_ANCIENT_VERSION_FILENAME); + } + + if (stat(DEBIAN_VERSION_FILENAME, &statbuf) != -1) + { + Linux_Debian_Version(ctx); + } + + if (stat(LSB_RELEASE_FILENAME, &statbuf) != -1) + { + Linux_Misc_Version(ctx); + } + + if (stat("/usr/bin/aptitude", &statbuf) != -1) + { + Log(LOG_LEVEL_VERBOSE, "This system seems to have the aptitude package system"); + EvalContextClassPutHard(ctx, "have_aptitude", "inventory,attribute_name=none,source=agent"); + } + + if (stat("/etc/UnitedLinux-release", &statbuf) != -1) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be a UnitedLinux system."); + SetFlavor(ctx, "UnitedLinux"); + } + + if (stat("/etc/alpine-release", &statbuf) != -1) + { + Linux_Alpine_Version(ctx); + } + + if (stat("/etc/gentoo-release", &statbuf) != -1) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be a gentoo system."); + SetFlavor(ctx, "gentoo"); + } + + if (stat("/etc/arch-release", &statbuf) != -1) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be an Arch Linux system."); + SetFlavor(ctx, "archlinux"); + } + + if (stat("/proc/vmware/version", &statbuf) != -1 || stat("/etc/vmware-release", &statbuf) != -1) + { + VM_Version(ctx); + } + else if (stat("/etc/vmware", &statbuf) != -1 && S_ISDIR(statbuf.st_mode)) + { + VM_Version(ctx); + } + + if (stat("/proc/xen/capabilities", &statbuf) != -1) + { + Xen_Domain(ctx); + } + if (stat("/etc/Eos-release", &statbuf) != -1) + { + EOS_Version(ctx); + SetFlavor(ctx, "Eos"); + } + + if (stat("/etc/issue", &statbuf) != -1) + { + MiscOS(ctx); + } + + if (stat("/proc/self/status", &statbuf) != -1) + { + OpenVZ_Detect(ctx); + } + +#else + + char vbuff[CF_MAXVARSIZE]; + +#ifdef _AIX + strlcpy(vbuff, VSYSNAME.version, CF_MAXVARSIZE); +#else + strlcpy(vbuff, VSYSNAME.release, CF_MAXVARSIZE); +#endif + + + for (char *sp = vbuff; *sp != '\0'; sp++) + { + if (*sp == '-') + { + *sp = '\0'; + break; + } + } + + char context[CF_BUFSIZE]; + snprintf(context, CF_BUFSIZE, "%s_%s", VSYSNAME.sysname, vbuff); + SetFlavor(ctx, context); + + +#ifdef __hpux + /* + * Define a hard class with just the version major number of HP-UX + * + * For example, when being run on HP-UX B.11.23 the following class will + * be defined: hpux_11 + */ + + // Extract major version number + char *major = NULL; + for (char *sp = vbuff; *sp != '\0'; sp++) + { + if (major == NULL && isdigit(*sp)) + { + major = sp; + } + else if (!isdigit(*sp)) + { + *sp = '\0'; + } + } + + if (major != NULL) + { + snprintf(context, CF_BUFSIZE, "hpux_%s", major); + EvalContextClassPutHard(ctx, context, "source=agent,derived-from=sys.flavor"); + } +#endif + +#ifdef __FreeBSD__ + /* + * Define a hard class with just the version major number on FreeBSD + * + * For example, when being run on either FreeBSD 10.0 or 10.1 a class + * called freebsd_10 will be defined + */ + for (char *sp = vbuff; *sp != '\0'; sp++) + { + if (*sp == '.') + { + *sp = '\0'; + break; + } + } + + snprintf(context, CF_BUFSIZE, "%s_%s", VSYSNAME.sysname, vbuff); + EvalContextClassPutHard(ctx, context, "source=agent,derived-from=sys.flavor"); +#endif + +#endif + +#ifdef XEN_CPUID_SUPPORT + if (Xen_Hv_Check()) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be a xen hv system."); + EvalContextClassPutHard(ctx, "xen", "inventory,attribute_name=Virtual host,source=agent"); + EvalContextClassPutHard(ctx, "xen_domu_hv", "source=agent"); + } +#endif /* XEN_CPUID_SUPPORT */ + + GetCPUInfo(ctx); + +#ifdef __CYGWIN__ + + for (char *sp = VSYSNAME.sysname; *sp != '\0'; sp++) + { + if (*sp == '-') + { + sp++; + if (strncmp(sp, "5.0", 3) == 0) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be Windows 2000"); + EvalContextClassPutHard(ctx, "Win2000", "inventory,attribute_name=none,source=agent"); + } + + if (strncmp(sp, "5.1", 3) == 0) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be Windows XP"); + EvalContextClassPutHard(ctx, "WinXP", "inventory,attribute_name=none,source=agent"); + } + + if (strncmp(sp, "5.2", 3) == 0) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be Windows Server 2003"); + EvalContextClassPutHard(ctx, "WinServer2003", "inventory,attribute_name=none,source=agent"); + } + + if (strncmp(sp, "6.1", 3) == 0) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be Windows Vista"); + EvalContextClassPutHard(ctx, "WinVista", "inventory,attribute_name=none,source=agent"); + } + + if (strncmp(sp, "6.3", 3) == 0) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be Windows Server 2008"); + EvalContextClassPutHard(ctx, "WinServer2008", "inventory,attribute_name=none,source=agent"); + } + } + } + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "crontab", "", CF_DATA_TYPE_STRING, "source=agent"); + +#endif /* __CYGWIN__ */ + +#ifdef __MINGW32__ + EvalContextClassPutHard(ctx, VSYSNAME.release, "inventory,attribute_name=none,source=agent,derived-from=sys.release"); // code name - e.g. Windows Vista + EvalContextClassPutHard(ctx, VSYSNAME.version, "inventory,attribute_name=none,source=agent,derived-from=sys.version"); // service pack number - e.g. Service Pack 3 + + if (strstr(VSYSNAME.sysname, "workstation")) + { + EvalContextClassPutHard(ctx, "WinWorkstation", "inventory,attribute_name=Windows roles,source=agent,derived-from=sys.sysname"); + } + else if (strstr(VSYSNAME.sysname, "server")) + { + EvalContextClassPutHard(ctx, "WinServer", "inventory,attribute_name=Windows roles,source=agent,derived-from=sys.sysname"); + } + else if (strstr(VSYSNAME.sysname, "domain controller")) + { + EvalContextClassPutHard(ctx, "DomainController", "inventory,attribute_name=Windows roles,source=agent,derived-from=sys.sysname"); + EvalContextClassPutHard(ctx, "WinServer", "inventory,attribute_name=Windows roles,source=agent,derived-from=sys.sysname"); + } + else + { + EvalContextClassPutHard(ctx, "unknown_ostype", "source=agent,derived-from=sys.sysname"); + } + + char *release = SafeStringDuplicate(VSYSNAME.release); + char *major = NULL; + FindNextInteger(release, &major); + if (NULL_OR_EMPTY(major)) + { + SetFlavor(ctx, "windows"); + } + else + { + SetFlavor2(ctx, "windows", major); + } + free(release); + +#endif /* __MINGW32__ */ + +#ifndef _WIN32 + char user_name[CF_SMALLBUF]; + if (GetCurrentUserName(user_name, sizeof(user_name))) + { + char vbuff[CF_BUFSIZE]; + + if (EvalContextClassGet(ctx, NULL, "SUSE")) + { + snprintf(vbuff, CF_BUFSIZE, "/var/spool/cron/tabs/%s", user_name); + } + else if (EvalContextClassGet(ctx, NULL, "redhat")) + { + snprintf(vbuff, CF_BUFSIZE, "/var/spool/cron/%s", user_name); + } + else if (EvalContextClassGet(ctx, NULL, "freebsd")) + { + snprintf(vbuff, CF_BUFSIZE, "/var/cron/tabs/%s", user_name); + } + else + { + snprintf(vbuff, CF_BUFSIZE, "/var/spool/cron/crontabs/%s", user_name); + } + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "crontab", vbuff, CF_DATA_TYPE_STRING, "source=agent"); + } + +#endif + +#if defined(__ANDROID__) + SetFlavor(ctx, "android"); +#endif + +/* termux flavor should over-ride android flavor */ +#if defined(__TERMUX__) + SetFlavor(ctx, "termux"); +#endif + +#ifdef __sun + if (StringMatchFull("joyent.*", VSYSNAME.version)) + { + EvalContextClassPutHard(ctx, "smartos", "inventory,attribute_name=none,source=agent,derived-from=sys.version"); + EvalContextClassPutHard(ctx, "smartmachine", "source=agent,derived-from=sys.version"); + } +#endif + + /* FIXME: this variable needs redhat/SUSE/debian classes to be defined and + * hence can't be initialized earlier */ + + if (EvalContextClassGet(ctx, NULL, "redhat")) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "doc_root", "/var/www/html", CF_DATA_TYPE_STRING, "source=agent"); + } + + if (EvalContextClassGet(ctx, NULL, "SUSE")) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "doc_root", "/srv/www/htdocs", CF_DATA_TYPE_STRING, "source=agent"); + } + + if (EvalContextClassGet(ctx, NULL, "debian")) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "doc_root", "/var/www", CF_DATA_TYPE_STRING, "source=agent"); + } +} + +/*********************************************************************************/ + +#ifdef __linux__ +static void Linux_Oracle_VM_Server_Version(EvalContext *ctx) +{ + char relstring[CF_MAXVARSIZE]; + char *r; + int major, minor, patch; + int revcomps; + +#define ORACLE_VM_SERVER_REL_FILENAME "/etc/ovs-release" +#define ORACLE_VM_SERVER_ID "Oracle VM server" + + Log(LOG_LEVEL_VERBOSE, "This appears to be Oracle VM Server"); + EvalContextClassPutHard(ctx, "redhat", "inventory,attribute_name=none,source=agent"); + EvalContextClassPutHard(ctx, "oraclevmserver", "inventory,attribute_name=Virtual host,source=agent"); + + if (!ReadLine(ORACLE_VM_SERVER_REL_FILENAME, relstring, sizeof(relstring))) + { + return; + } + + if (strncmp(relstring, ORACLE_VM_SERVER_ID, strlen(ORACLE_VM_SERVER_ID))) + { + Log(LOG_LEVEL_VERBOSE, "Could not identify distribution from %s", ORACLE_VM_SERVER_REL_FILENAME); + return; + } + + if ((r = strstr(relstring, "release ")) == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Could not find distribution version in %s", ORACLE_VM_SERVER_REL_FILENAME); + return; + } + + revcomps = sscanf(r + strlen("release "), "%d.%d.%d", &major, &minor, &patch); + + if (revcomps > 0) + { + char buf[CF_BUFSIZE]; + + snprintf(buf, CF_BUFSIZE, "oraclevmserver_%d", major); + SetFlavor(ctx, buf); + } + + if (revcomps > 1) + { + char buf[CF_BUFSIZE]; + + snprintf(buf, CF_BUFSIZE, "oraclevmserver_%d_%d", major, minor); + EvalContextClassPutHard(ctx, buf, "inventory,attribute_name=none,source=agent"); + } + + if (revcomps > 2) + { + char buf[CF_BUFSIZE]; + + snprintf(buf, CF_BUFSIZE, "oraclevmserver_%d_%d_%d", major, minor, patch); + EvalContextClassPutHard(ctx, buf, "inventory,attribute_name=none,source=agent"); + } +} + +/*********************************************************************************/ + +static void Linux_Oracle_Version(EvalContext *ctx) +{ + char relstring[CF_MAXVARSIZE]; + char *r; + int major, minor; + +#define ORACLE_REL_FILENAME "/etc/oracle-release" +#define ORACLE_ID "Oracle Linux Server" + + Log(LOG_LEVEL_VERBOSE, "This appears to be Oracle Linux"); + EvalContextClassPutHard(ctx, "oracle", "inventory,attribute_name=none,source=agent"); + + if (!ReadLine(ORACLE_REL_FILENAME, relstring, sizeof(relstring))) + { + return; + } + + if (strncmp(relstring, ORACLE_ID, strlen(ORACLE_ID))) + { + Log(LOG_LEVEL_VERBOSE, "Could not identify distribution from %s", ORACLE_REL_FILENAME); + return; + } + + if ((r = strstr(relstring, "release ")) == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Could not find distribution version in %s", ORACLE_REL_FILENAME); + return; + } + + if (sscanf(r + strlen("release "), "%d.%d", &major, &minor) == 2) + { + char buf[CF_BUFSIZE]; + + snprintf(buf, CF_BUFSIZE, "oracle_%d", major); + SetFlavor(ctx, buf); + + snprintf(buf, CF_BUFSIZE, "oracle_%d_%d", major, minor); + EvalContextClassPutHard(ctx, buf, "inventory,attribute_name=none,source=agent"); + } +} + +/*********************************************************************************/ + +static int Linux_Fedora_Version(EvalContext *ctx) +{ +#define FEDORA_ID "Fedora" +#define RELEASE_FLAG "release " + +/* We are looking for one of the following strings... + * + * Fedora Core release 1 (Yarrow) + * Fedora release 7 (Zodfoobar) + */ + +#define FEDORA_REL_FILENAME "/etc/fedora-release" + +/* The full string read in from fedora-release */ + char relstring[CF_MAXVARSIZE]; + + Log(LOG_LEVEL_VERBOSE, "This appears to be a fedora system."); + EvalContextClassPutHard(ctx, "redhat", "inventory,attribute_name=none,source=agent"); + EvalContextClassPutHard(ctx, "fedora", "inventory,attribute_name=none,source=agent"); + +/* Grab the first line from the file and then close it. */ + + if (!ReadLine(FEDORA_REL_FILENAME, relstring, sizeof(relstring))) + { + return 1; + } + + Log(LOG_LEVEL_VERBOSE, "Looking for fedora core linux info..."); + + char *vendor = ""; + if (!strncmp(relstring, FEDORA_ID, strlen(FEDORA_ID))) + { + vendor = "fedora"; + } + else + { + Log(LOG_LEVEL_VERBOSE, "Could not identify OS distro from %s", FEDORA_REL_FILENAME); + return 2; + } + +/* Now, grok the release. We assume that all the strings will + * have the word 'release' before the numerical release. + */ + int major = -1; + char strmajor[PRINTSIZE(major)]; + char *release = strstr(relstring, RELEASE_FLAG); + + if (release == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Could not find a numeric OS release in %s", FEDORA_REL_FILENAME); + return 2; + } + else + { + release += strlen(RELEASE_FLAG); + + strmajor[0] = '\0'; + if (sscanf(release, "%d", &major) != 0) + { + xsnprintf(strmajor, sizeof(strmajor), "%d", major); + } + } + + if (major != -1 && vendor[0] != '\0') + { + char classbuf[CF_MAXVARSIZE]; + classbuf[0] = '\0'; + strcat(classbuf, vendor); + EvalContextClassPutHard(ctx,classbuf, "inventory,attribute_name=none,source=agent"); + strcat(classbuf, "_"); + strcat(classbuf, strmajor); + SetFlavor(ctx, classbuf); + } + + return 0; +} + +/*********************************************************************************/ + +static int Linux_Redhat_Version(EvalContext *ctx) +{ +#define REDHAT_ID "Red Hat Linux" +#define REDHAT_ENT_ID "Red Hat Enterprise Linux" +#define REDHAT_AS_ID "Red Hat Enterprise Linux AS" +#define REDHAT_AS21_ID "Red Hat Linux Advanced Server" +#define REDHAT_ES_ID "Red Hat Enterprise Linux ES" +#define REDHAT_WS_ID "Red Hat Enterprise Linux WS" +#define REDHAT_C_ID "Red Hat Enterprise Linux Client" +#define REDHAT_S_ID "Red Hat Enterprise Linux Server" +#define REDHAT_W_ID "Red Hat Enterprise Linux Workstation" +#define REDHAT_CN_ID "Red Hat Enterprise Linux ComputeNode" +#define MANDRAKE_ID "Linux Mandrake" +#define MANDRAKE_10_1_ID "Mandrakelinux" +#define WHITEBOX_ID "White Box Enterprise Linux" +#define CENTOS_ID "CentOS" +#define SCIENTIFIC_SL_ID "Scientific Linux SL" +#define SCIENTIFIC_SL6_ID "Scientific Linux" +#define SCIENTIFIC_CERN_ID "Scientific Linux CERN" +#define RELEASE_FLAG "release " +#define ORACLE_4_5_ID "Enterprise Linux Enterprise Linux Server" + +/* We are looking for one of the following strings... + * + * Red Hat Linux release 6.2 (Zoot) + * Red Hat Enterprise Linux release 8.0 (Ootpa) + * Red Hat Linux Advanced Server release 2.1AS (Pensacola) + * Red Hat Enterprise Linux AS release 3 (Taroon) + * Red Hat Enterprise Linux WS release 3 (Taroon) + * Red Hat Enterprise Linux Client release 5 (Tikanga) + * Red Hat Enterprise Linux Server release 5 (Tikanga) + * Linux Mandrake release 7.1 (helium) + * Red Hat Enterprise Linux ES release 2.1 (Panama) + * White Box Enterprise linux release 3.0 (Liberation) + * Scientific Linux SL Release 4.0 (Beryllium) + * CentOS release 4.0 (Final) + */ + +#define RH_REL_FILENAME "/etc/redhat-release" + + Log(LOG_LEVEL_VERBOSE, "This appears to be a redhat (or redhat-based) system."); + EvalContextClassPutHard(ctx, "redhat", "inventory,attribute_name=none,source=agent,derived-from-file="RH_REL_FILENAME); + + /* Grab the first line from the file and then close it. */ + char relstring[CF_MAXVARSIZE]; + if (!ReadLine(RH_REL_FILENAME, relstring, sizeof(relstring))) + { + return 1; + } + + Log(LOG_LEVEL_VERBOSE, "Looking for redhat linux info in '%s'", relstring); + + /* First, try to grok the vendor and edition (if any) */ + char *edition = ""; /* as (Advanced Server, Enterprise) */ + char *vendor = ""; /* Red Hat, Mandrake */ + if (!strncmp(relstring, REDHAT_ES_ID, strlen(REDHAT_ES_ID))) + { + vendor = "redhat"; + edition = "es"; + } + else if (!strncmp(relstring, REDHAT_WS_ID, strlen(REDHAT_WS_ID))) + { + vendor = "redhat"; + edition = "ws"; + } + else if (!strncmp(relstring, REDHAT_WS_ID, strlen(REDHAT_WS_ID))) + { + vendor = "redhat"; + edition = "ws"; + } + else if (!strncmp(relstring, REDHAT_AS_ID, strlen(REDHAT_AS_ID)) || + !strncmp(relstring, REDHAT_AS21_ID, strlen(REDHAT_AS21_ID))) + { + vendor = "redhat"; + edition = "as"; + } + else if (!strncmp(relstring, REDHAT_S_ID, strlen(REDHAT_S_ID))) + { + vendor = "redhat"; + edition = "s"; + } + else if (!strncmp(relstring, REDHAT_C_ID, strlen(REDHAT_C_ID)) + || !strncmp(relstring, REDHAT_W_ID, strlen(REDHAT_W_ID))) + { + vendor = "redhat"; + edition = "c"; + } + else if (!strncmp(relstring, REDHAT_CN_ID, strlen(REDHAT_CN_ID))) + { + vendor = "redhat"; + edition = "cn"; + } + else if (!strncmp(relstring, REDHAT_ID, strlen(REDHAT_ID))) + { + vendor = "redhat"; + } + else if (!strncmp(relstring, REDHAT_ENT_ID, strlen(REDHAT_ENT_ID))) + { + vendor = "redhat"; + } + else if (!strncmp(relstring, MANDRAKE_ID, strlen(MANDRAKE_ID))) + { + vendor = "mandrake"; + } + else if (!strncmp(relstring, MANDRAKE_10_1_ID, strlen(MANDRAKE_10_1_ID))) + { + vendor = "mandrake"; + } + else if (!strncmp(relstring, WHITEBOX_ID, strlen(WHITEBOX_ID))) + { + vendor = "whitebox"; + } + else if (!strncmp(relstring, SCIENTIFIC_SL_ID, strlen(SCIENTIFIC_SL_ID))) + { + vendor = "scientific"; + edition = "sl"; + } + else if (!strncmp(relstring, SCIENTIFIC_CERN_ID, strlen(SCIENTIFIC_CERN_ID))) + { + vendor = "scientific"; + edition = "cern"; + } + else if (!strncmp(relstring, SCIENTIFIC_SL6_ID, strlen(SCIENTIFIC_SL6_ID))) + { + vendor = "scientific"; + edition = "sl"; + } + else if (!strncmp(relstring, CENTOS_ID, strlen(CENTOS_ID))) + { + vendor = "centos"; + } + else if (!strncmp(relstring, ORACLE_4_5_ID, strlen(ORACLE_4_5_ID))) + { + vendor = "oracle"; + edition = "s"; + } + else + { + Log(LOG_LEVEL_VERBOSE, "Could not identify OS distro from %s", RH_REL_FILENAME); + return 2; + } + +/* Now, grok the release. For AS, we neglect the AS at the end of the + * numerical release because we already figured out that it *is* AS + * from the information above. We assume that all the strings will + * have the word 'release' before the numerical release. + */ + +/* Convert relstring to lowercase so that vendors like + Scientific Linux don't fall through the cracks. + */ + + for (size_t i = 0; i < strlen(relstring); i++) + { + relstring[i] = tolower(relstring[i]); + } + + /* Where the numerical release will be found */ + int major = -1, minor = -1; + char strmajor[PRINTSIZE(major)], strminor[PRINTSIZE(minor)]; + + char *release = strstr(relstring, RELEASE_FLAG); + if (release == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Could not find a numeric OS release in %s", RH_REL_FILENAME); + return 2; + } + else + { + release += strlen(RELEASE_FLAG); + if (sscanf(release, "%d.%d", &major, &minor) == 2) + { + xsnprintf(strmajor, sizeof(strmajor), "%d", major); + xsnprintf(strminor, sizeof(strminor), "%d", minor); + } + /* red hat 9 is *not* red hat 9.0. + * and same thing with RHEL AS 3 + */ + else if (sscanf(release, "%d", &major) == 1) + { + xsnprintf(strmajor, sizeof(strmajor), "%d", major); + minor = -2; + } + } + + char classbuf[CF_MAXVARSIZE]; + if (major != -1 && minor != -1 && (strcmp(vendor, "") != 0)) + { + classbuf[0] = '\0'; + strcat(classbuf, vendor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent,derived-from-file="RH_REL_FILENAME); + strcat(classbuf, "_"); + + if (strcmp(edition, "") != 0) + { + strcat(classbuf, edition); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent,derived-from-file="RH_REL_FILENAME); + strcat(classbuf, "_"); + } + + strcat(classbuf, strmajor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent,derived-from-file="RH_REL_FILENAME); + + if (minor != -2) + { + strcat(classbuf, "_"); + strcat(classbuf, strminor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent,derived-from-file="RH_REL_FILENAME); + } + } + + // Now a version without the edition + if (major != -1 && minor != -1 && vendor[0] != '\0') + { + classbuf[0] = '\0'; + strcat(classbuf, vendor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent,derived-from-file="RH_REL_FILENAME); + strcat(classbuf, "_"); + + strcat(classbuf, strmajor); + + SetFlavor(ctx, classbuf); + + if (minor != -2) + { + strcat(classbuf, "_"); + strcat(classbuf, strminor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent,derived-from-file="RH_REL_FILENAME); + } + } + + return 0; +} + +/******************************************************************/ + +static int Linux_Suse_Version(EvalContext *ctx) +{ +#define SUSE_REL_FILENAME "/etc/SuSE-release" +/* Check if it's a SUSE Enterprise version (all in lowercase) */ +#define SUSE_SLES8_ID "suse sles-8" +#define SUSE_SLES_ID "suse linux enterprise server" +#define SUSE_SLED_ID "suse linux enterprise desktop" +#define SUSE_RELEASE_FLAG "linux " + + char classbuf[CF_MAXVARSIZE]; + + Log(LOG_LEVEL_VERBOSE, "This appears to be a SUSE system."); + EvalContextClassPutHard(ctx, "SUSE", "inventory,attribute_name=none,source=agent"); + EvalContextClassPutHard(ctx, "suse", "inventory,attribute_name=none,source=agent"); + + /* The correct spelling for SUSE is "SUSE" but CFEngine used to use "SuSE". + * Keep this for backwards compatibility until CFEngine 3.7 + */ + EvalContextClassPutHard(ctx, "SuSE", "inventory,attribute_name=none,source=agent"); + + /* Grab the first line from the SuSE-release file and then close it. */ + char relstring[CF_MAXVARSIZE]; + + FILE *fp = ReadFirstLine(SUSE_REL_FILENAME, relstring, sizeof(relstring)); + if (fp == NULL) + { + return 1; + } + + char vbuf[CF_BUFSIZE], strversion[CF_MAXVARSIZE], strpatch[CF_MAXVARSIZE]; + strversion[0] = '\0'; + strpatch[0] = '\0'; + + int major = -1, minor = -1; + while (fgets(vbuf, sizeof(vbuf), fp) != NULL) + { + if (strncmp(vbuf, "VERSION", strlen("version")) == 0) + { + strlcpy(strversion, vbuf, sizeof(strversion)); + sscanf(vbuf, "VERSION = %d", &major); + } + + if (strncmp(vbuf, "PATCH", strlen("PATCH")) == 0) + { + strlcpy(strpatch, vbuf, sizeof(strpatch)); + sscanf(vbuf, "PATCHLEVEL = %d", &minor); + } + } + if (ferror(fp)) + { + UnexpectedError("Failed to read line from stream"); + } + else + { + assert(feof(fp)); + } + + fclose(fp); + + /* Check if it's a SUSE Enterprise version */ + + Log(LOG_LEVEL_VERBOSE, "Looking for SUSE enterprise info in '%s'", relstring); + + /* Convert relstring to lowercase to handle rename of SuSE to + * SUSE with SUSE 10.0. + */ + + for (size_t i = 0; i < strlen(relstring); i++) + { + relstring[i] = tolower(relstring[i]); + } + + /* Check if it's a SUSE Enterprise version (all in lowercase) */ + + if (!strncmp(relstring, SUSE_SLES8_ID, strlen(SUSE_SLES8_ID))) + { + classbuf[0] = '\0'; + strcat(classbuf, "SLES8"); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + } + else if (strncmp(relstring, "sles", 4) == 0) + { + Item *list, *ip; + + nt_static_assert((sizeof(vbuf)) > 255); + sscanf(relstring, "%255[-_a-zA-Z0-9]", vbuf); + EvalContextClassPutHard(ctx, vbuf, "inventory,attribute_name=none,source=agent"); + + list = SplitString(vbuf, '-'); + + for (ip = list; ip != NULL; ip = ip->next) + { + EvalContextClassPutHard(ctx, ip->name, "inventory,attribute_name=none,source=agent"); + } + + DeleteItemList(list); + } + else + { + for (int version = 9; version < 13; version++) + { + snprintf(vbuf, CF_BUFSIZE, "%s %d ", SUSE_SLES_ID, version); + Log(LOG_LEVEL_DEBUG, "Checking for SUSE [%s]", vbuf); + + if (!strncmp(relstring, vbuf, strlen(vbuf))) + { + snprintf(classbuf, CF_MAXVARSIZE, "SLES%d", version); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + } + else + { + snprintf(vbuf, CF_BUFSIZE, "%s %d ", SUSE_SLED_ID, version); + Log(LOG_LEVEL_DEBUG, "Checking for SUSE [%s]", vbuf); + + if (!strncmp(relstring, vbuf, strlen(vbuf))) + { + snprintf(classbuf, CF_MAXVARSIZE, "SLED%d", version); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + } + } + } + } + + /* Determine release version. We assume that the version follows + * the string "SuSE Linux" or "SUSE LINUX". + */ + + char *release = strstr(relstring, SUSE_RELEASE_FLAG); + if (release == NULL) + { + release = strstr(relstring, "opensuse"); + if (release == NULL) + { + release = strversion; + } + } + + if (release == NULL) + { + Log(LOG_LEVEL_VERBOSE, + "Could not find a numeric OS release in %s", + SUSE_REL_FILENAME); + return 2; + } + else + { + char strmajor[CF_MAXVARSIZE], strminor[CF_MAXVARSIZE]; + if (strchr(release, '.')) + { + sscanf(release, "%*s %d.%d", &major, &minor); + xsnprintf(strmajor, sizeof(strmajor), "%d", major); + xsnprintf(strminor, sizeof(strminor), "%d", minor); + + if (major != -1 && minor != -1) + { + strcpy(classbuf, "SUSE"); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + strcat(classbuf, "_"); + strcat(classbuf, strmajor); + SetFlavor(ctx, classbuf); + strcat(classbuf, "_"); + strcat(classbuf, strminor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + + /* The correct spelling for SUSE is "SUSE" but CFEngine used to use "SuSE". + * Keep this for backwards compatibility until CFEngine 3.7 + */ + strcpy(classbuf, "SuSE"); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + strcat(classbuf, "_"); + strcat(classbuf, strmajor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + strcat(classbuf, "_"); + strcat(classbuf, strminor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + + Log(LOG_LEVEL_VERBOSE, "Discovered SUSE version %s", classbuf); + return 0; + } + } + else + { + nt_static_assert((sizeof(strmajor)) > 255); + nt_static_assert((sizeof(strminor)) > 255); + sscanf(strversion, "VERSION = %255s", strmajor); + sscanf(strpatch, "PATCHLEVEL = %255s", strminor); + + if (major != -1 && minor != -1) + { + strcpy(classbuf, "SLES"); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + strcat(classbuf, "_"); + strcat(classbuf, strmajor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + strcat(classbuf, "_"); + strcat(classbuf, strminor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + + snprintf(classbuf, CF_MAXVARSIZE, "SUSE_%d", major); + SetFlavor(ctx, classbuf); + + /* The correct spelling for SUSE is "SUSE" but CFEngine used to use "SuSE". + * Keep this for backwards compatibility until CFEngine 3.7 + */ + snprintf(classbuf, CF_MAXVARSIZE, "SuSE_%d", major); + EvalContextClassPutHard(ctx, classbuf, "source=agent"); + + Log(LOG_LEVEL_VERBOSE, "Discovered SUSE version %s", classbuf); + return 0; + } + } + } + + Log(LOG_LEVEL_VERBOSE, "Could not find a numeric OS release in %s", SUSE_REL_FILENAME); + + return 0; +} + +/******************************************************************/ + +static int Linux_Slackware_Version(EvalContext *ctx, char *filename) +{ + int major = -1; + int minor = -1; + int release = -1; + char classname[CF_MAXVARSIZE] = ""; + char buffer[CF_MAXVARSIZE]; + + Log(LOG_LEVEL_VERBOSE, "This appears to be a slackware system."); + EvalContextClassPutHard(ctx, "slackware", "inventory,attribute_name=none,source=agent"); + + if (!ReadLine(filename, buffer, sizeof(buffer))) + { + return 1; + } + + Log(LOG_LEVEL_VERBOSE, "Looking for Slackware version..."); + switch (sscanf(buffer, "Slackware %d.%d.%d", &major, &minor, &release)) + { + case 3: + Log(LOG_LEVEL_VERBOSE, "This appears to be a Slackware %u.%u.%u system.", major, minor, release); + snprintf(classname, CF_MAXVARSIZE, "slackware_%u_%u_%u", major, minor, release); + EvalContextClassPutHard(ctx, classname, "inventory,attribute_name=none,source=agent"); + /* Fall-through */ + case 2: + Log(LOG_LEVEL_VERBOSE, "This appears to be a Slackware %u.%u system.", major, minor); + snprintf(classname, CF_MAXVARSIZE, "slackware_%u_%u", major, minor); + EvalContextClassPutHard(ctx, classname, "inventory,attribute_name=none,source=agent"); + /* Fall-through */ + case 1: + Log(LOG_LEVEL_VERBOSE, "This appears to be a Slackware %u system.", major); + snprintf(classname, CF_MAXVARSIZE, "slackware_%u", major); + EvalContextClassPutHard(ctx, classname, "inventory,attribute_name=none,source=agent"); + break; + case 0: + Log(LOG_LEVEL_VERBOSE, "No Slackware version number found."); + return 2; + } + return 0; +} + +/* + * @brief Purge /etc/issue escapes on debian + * + * On debian, /etc/issue can include special characters escaped with + * '\\' or '@'. This function removes such escape sequences. + * + * @param[in,out] buffer: string to be sanitized + */ +static void LinuxDebianSanitizeIssue(char *buffer) +{ + bool escaped = false; + char *dst = buffer, *src = buffer, *tail = dst; + while (*src != '\0') + { + char here = *src; + src++; + if (here == '\\' || here == '@' || escaped) + { + /* Skip over escapes and the character each acts on. */ + escaped = !escaped; + } + else + { + /* Copy everything else verbatim: */ + *dst = here; + dst++; + /* Keep track of (just after) last non-space: */ + if (!isspace(here)) + { + tail = dst; + } + } + } + + assert(tail == dst || isspace(*tail)); + *tail = '\0'; +} + +/******************************************************************/ + +static int Linux_Misc_Version(EvalContext *ctx) +{ + char flavor[CF_MAXVARSIZE]; + char version[256]; + char buffer[CF_BUFSIZE]; + + const char *os = NULL; + version[0] = '\0'; + + FILE *fp = safe_fopen(LSB_RELEASE_FILENAME, "r"); + if (fp != NULL) + { + while (!feof(fp)) + { + if (fgets(buffer, CF_BUFSIZE, fp) == NULL) + { + if (ferror(fp)) + { + break; + } + continue; + } + + if (strstr(buffer, "Cumulus")) + { + EvalContextClassPutHard(ctx, "cumulus", "inventory,attribute_name=none,source=agent"); + os = "cumulus"; + } + + char *sp = strstr(buffer, "DISTRIB_RELEASE="); + if (sp) + { + version[0] = '\0'; + nt_static_assert((sizeof(version)) > 255); + sscanf(sp + strlen("DISTRIB_RELEASE="), "%255[^\n]", version); + CanonifyNameInPlace(version); + } + } + fclose(fp); + } + + if (os != NULL && version[0] != '\0') + { + snprintf(flavor, CF_MAXVARSIZE, "%s_%s", os, version); + SetFlavor(ctx, flavor); + return 1; + } + + return 0; +} + +/******************************************************************/ + +static int Linux_Debian_Version(EvalContext *ctx) +{ + int major = -1; + int release = -1; + int result; + char classname[CF_MAXVARSIZE], buffer[CF_MAXVARSIZE], os[CF_MAXVARSIZE]; + char version[256]; // Size must agree with sscanfs below + + Log(LOG_LEVEL_VERBOSE, "This appears to be a debian system."); + EvalContextClassPutHard( + ctx, + "debian", + "inventory,attribute_name=none,source=agent,derived-from-file="DEBIAN_VERSION_FILENAME); + + buffer[0] = classname[0] = '\0'; + + Log(LOG_LEVEL_VERBOSE, "Looking for Debian version..."); + + if (!ReadLine(DEBIAN_VERSION_FILENAME, buffer, sizeof(buffer))) + { + return 1; + } + + result = sscanf(buffer, "%d.%d", &major, &release); + + switch (result) + { + case 2: + Log(LOG_LEVEL_VERBOSE, "This appears to be a Debian %u.%u system.", major, release); + snprintf(classname, CF_MAXVARSIZE, "debian_%u_%u", major, release); + EvalContextClassPutHard( + ctx, + classname, + "inventory,attribute_name=none,source=agent,derived-from-file="DEBIAN_VERSION_FILENAME); + snprintf(classname, CF_MAXVARSIZE, "debian_%u", major); + SetFlavor(ctx, classname); + break; + + case 1: + Log(LOG_LEVEL_VERBOSE, "This appears to be a Debian %u system.", major); + snprintf(classname, CF_MAXVARSIZE, "debian_%u", major); + SetFlavor(ctx, classname); + break; + + default: + version[0] = '\0'; + nt_static_assert((sizeof(version)) > 25); + sscanf(buffer, "%25[^/]", version); + if (strlen(version) > 0) + { + snprintf(classname, CF_MAXVARSIZE, "debian_%s", version); + EvalContextClassPutHard( + ctx, + classname, + "inventory,attribute_name=none,source=agent,derived-from-file="DEBIAN_VERSION_FILENAME); + } + break; + } + + if (!ReadLine(DEBIAN_ISSUE_FILENAME, buffer, sizeof(buffer))) + { + return 1; + } + + os[0] = '\0'; + nt_static_assert((sizeof(os)) > 250); + sscanf(buffer, "%250s", os); + + if (strcmp(os, "Debian") == 0) + { + LinuxDebianSanitizeIssue(buffer); + nt_static_assert((sizeof(version)) > 255); + sscanf(buffer, "%*s %*s %255[^./]", version); + snprintf(buffer, CF_MAXVARSIZE, "debian_%s", version); + EvalContextClassPutHard( + ctx, + "debian", + "inventory,attribute_name=none,source=agent,derived-from-file="DEBIAN_ISSUE_FILENAME); + SetFlavor(ctx, buffer); + } + else if (strcmp(os, "Ubuntu") == 0) + { + LinuxDebianSanitizeIssue(buffer); + char minor[256] = {0}; + nt_static_assert((sizeof(version)) > 255); + sscanf(buffer, "%*s %255[^.].%255s", version, minor); + snprintf(buffer, CF_MAXVARSIZE, "ubuntu_%s", version); + SetFlavor(ctx, buffer); + EvalContextClassPutHard( + ctx, + "ubuntu", + "inventory,attribute_name=none,source=agent,derived-from-file="DEBIAN_ISSUE_FILENAME); + if (release >= 0) + { + snprintf(buffer, CF_MAXVARSIZE, "ubuntu_%s_%s", version, minor); + EvalContextClassPutHard( + ctx, + buffer, + "inventory,attribute_name=none,source=agent,derived-from-file="DEBIAN_ISSUE_FILENAME); + } + } + + return 0; +} + +/******************************************************************/ + +static int Linux_Mandrake_Version(EvalContext *ctx) +{ +/* We are looking for one of the following strings... */ +#define MANDRAKE_ID "Linux Mandrake" +#define MANDRAKE_REV_ID "Mandrake Linux" +#define MANDRAKE_10_1_ID "Mandrakelinux" + +#define MANDRAKE_REL_FILENAME "/etc/mandrake-release" + + char relstring[CF_MAXVARSIZE]; + char *vendor = NULL; + + Log(LOG_LEVEL_VERBOSE, "This appears to be a mandrake system."); + EvalContextClassPutHard(ctx, "Mandrake", "inventory,attribute_name=none,source=agent"); + + if (!ReadLine(MANDRAKE_REL_FILENAME, relstring, sizeof(relstring))) + { + return 1; + } + + Log(LOG_LEVEL_VERBOSE, "Looking for Mandrake linux info in '%s'", relstring); + +/* Older Mandrakes had the 'Mandrake Linux' string in reverse order */ + + if (!strncmp(relstring, MANDRAKE_ID, strlen(MANDRAKE_ID))) + { + vendor = "mandrake"; + } + else if (!strncmp(relstring, MANDRAKE_REV_ID, strlen(MANDRAKE_REV_ID))) + { + vendor = "mandrake"; + } + + else if (!strncmp(relstring, MANDRAKE_10_1_ID, strlen(MANDRAKE_10_1_ID))) + { + vendor = "mandrake"; + } + else + { + Log(LOG_LEVEL_VERBOSE, "Could not identify OS distro from %s", MANDRAKE_REL_FILENAME); + return 2; + } + + return Linux_Mandriva_Version_Real(ctx, MANDRAKE_REL_FILENAME, relstring, vendor); +} + +/******************************************************************/ + +static int Linux_Mandriva_Version(EvalContext *ctx) +{ +/* We are looking for the following strings... */ +#define MANDRIVA_ID "Mandriva Linux" + +#define MANDRIVA_REL_FILENAME "/etc/mandriva-release" + + char relstring[CF_MAXVARSIZE]; + char *vendor = NULL; + + Log(LOG_LEVEL_VERBOSE, "This appears to be a mandriva system."); + EvalContextClassPutHard(ctx, "Mandrake", "inventory,attribute_name=none,source=agent"); + EvalContextClassPutHard(ctx, "Mandriva", "inventory,attribute_name=none,source=agent"); + + if (!ReadLine(MANDRIVA_REL_FILENAME, relstring, sizeof(relstring))) + { + return 1; + } + + Log(LOG_LEVEL_VERBOSE, "Looking for Mandriva linux info in '%s'", relstring); + + if (!strncmp(relstring, MANDRIVA_ID, strlen(MANDRIVA_ID))) + { + vendor = "mandriva"; + } + else + { + Log(LOG_LEVEL_VERBOSE, "Could not identify OS distro from '%s'", MANDRIVA_REL_FILENAME); + return 2; + } + + return Linux_Mandriva_Version_Real(ctx, MANDRIVA_REL_FILENAME, relstring, vendor); + +} + +/******************************************************************/ + +static int Linux_Mandriva_Version_Real(EvalContext *ctx, char *filename, char *relstring, char *vendor) +{ + int major = -1, minor = -1; + char strmajor[PRINTSIZE(major)], strminor[PRINTSIZE(minor)]; + +#define RELEASE_FLAG "release " + char *release = strstr(relstring, RELEASE_FLAG); + if (release == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Could not find a numeric OS release in %s", filename); + return 2; + } + else + { + release += strlen(RELEASE_FLAG); + if (sscanf(release, "%d.%d", &major, &minor) == 2) + { + xsnprintf(strmajor, sizeof(strmajor), "%d", major); + xsnprintf(strminor, sizeof(strminor), "%d", minor); + } + else + { + Log(LOG_LEVEL_VERBOSE, "Could not break down release version numbers in %s", filename); + } + } + + if (major != -1 && minor != -1 && strcmp(vendor, "")) + { + char classbuf[CF_MAXVARSIZE]; + classbuf[0] = '\0'; + strcat(classbuf, vendor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + strcat(classbuf, "_"); + strcat(classbuf, strmajor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + if (minor != -2) + { + strcat(classbuf, "_"); + strcat(classbuf, strminor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + } + } + + return 0; +} + +/******************************************************************/ + +static void Linux_Amazon_Version(EvalContext *ctx) +{ + char buffer[CF_BUFSIZE]; + + // Amazon Linux release 2 (Karoo) + + if (ReadLine("/etc/system-release", buffer, sizeof(buffer))) + { + if (strstr(buffer, "Amazon") != NULL) + { + EvalContextClassPutHard(ctx, "amazon_linux", + "inventory,attribute_name=none,source=agent" + ",derived-from-file=/etc/system-release"); + + char version[128]; + if (sscanf(buffer, "%*s %*s %*s %127s", version) == 1) + { + char class[CF_MAXVARSIZE]; + + CanonifyNameInPlace(version); + snprintf(class, sizeof(class), "amazon_linux_%s", version); + EvalContextClassPutHard(ctx, class, + "inventory,attribute_name=none,source=agent" + ",derived-from-file=/etc/system-release"); + SetFlavor(ctx, class); + } + else + { + SetFlavor(ctx, "amazon_linux"); + } + // We set this class for backwards compatibility + EvalContextClassPutHard(ctx, "AmazonLinux", + "inventory,attribute_name=none,source=agent," + "derived-from=sys.flavor"); + } + } +} + +/******************************************************************/ + +static void Linux_Alpine_Version(EvalContext *ctx) +{ + char buffer[CF_BUFSIZE]; + + Log(LOG_LEVEL_VERBOSE, "This appears to be an AlpineLinux system."); + + EvalContextClassPutHard(ctx, "alpine_linux", + "inventory,attribute_name=none,source=agent" + ",derived-from-file=/etc/alpine-release"); + + if (ReadLine("/etc/alpine-release", buffer, sizeof(buffer))) + { + char version[128]; + if (sscanf(buffer, "%127s", version) == 1) + { + char class[CF_MAXVARSIZE]; + CanonifyNameInPlace(version); + snprintf(class, sizeof(class), "alpine_linux_%s", version); + EvalContextClassPutHard(ctx, class, + "inventory,attribute_name=none,source=agent" + ",derived-from-file=/etc/alpine-release"); + } + } + SetFlavor(ctx, "alpinelinux"); +} + +/******************************************************************/ + +static int EOS_Version(EvalContext *ctx) + +{ + char buffer[CF_BUFSIZE]; + + // e.g. Arista Networks EOS 4.10.2 + + if (ReadLine("/etc/Eos-release", buffer, sizeof(buffer))) + { + if (strstr(buffer, "EOS")) + { + char version[256], class[CF_MAXVARSIZE]; + EvalContextClassPutHard(ctx, "eos", "inventory,attribute_name=none,source=agent"); + EvalContextClassPutHard(ctx, "arista", "source=agent"); + version[0] = '\0'; + sscanf(buffer, "%*s %*s %*s %255s", version); + CanonifyNameInPlace(version); + snprintf(class, CF_MAXVARSIZE, "eos_%s", version); + EvalContextClassPutHard(ctx, class, "inventory,attribute_name=none,source=agent"); + } + } + + return 0; +} + +/******************************************************************/ + +static int MiscOS(EvalContext *ctx) + +{ char buffer[CF_BUFSIZE]; + + // e.g. BIG-IP 10.1.0 Build 3341.1084 + + if (ReadLine("/etc/issue", buffer, sizeof(buffer))) + { + if (strstr(buffer, "BIG-IP")) + { + char version[256], build[256], class[CF_MAXVARSIZE]; + EvalContextClassPutHard(ctx, "big_ip", "inventory,attribute_name=none,source=agent"); + sscanf(buffer, "%*s %255s %*s %255s", version, build); + CanonifyNameInPlace(version); + CanonifyNameInPlace(build); + snprintf(class, CF_MAXVARSIZE, "big_ip_%s", version); + EvalContextClassPutHard(ctx, class, "inventory,attribute_name=none,source=agent"); + snprintf(class, CF_MAXVARSIZE, "big_ip_%s_%s", version, build); + EvalContextClassPutHard(ctx, class, "inventory,attribute_name=none,source=agent"); + SetFlavor(ctx, "BIG-IP"); + } + } + + return 0; +} + +/******************************************************************/ + +static int VM_Version(EvalContext *ctx) +{ + char *sp, buffer[CF_BUFSIZE], classbuf[CF_BUFSIZE], version[256]; + int major, minor, bug; + int sufficient = 0; + + Log(LOG_LEVEL_VERBOSE, "This appears to be a VMware Server ESX/xSX system."); + EvalContextClassPutHard(ctx, "VMware", "inventory,attribute_name=Virtual host,source=agent"); + +/* VMware Server ESX >= 3 has version info in /proc */ + if (ReadLine("/proc/vmware/version", buffer, sizeof(buffer))) + { + if (sscanf(buffer, "VMware ESX Server %d.%d.%d", &major, &minor, &bug) > 0) + { + snprintf(classbuf, CF_BUFSIZE, "VMware ESX Server %d", major); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + snprintf(classbuf, CF_BUFSIZE, "VMware ESX Server %d.%d", major, minor); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + snprintf(classbuf, CF_BUFSIZE, "VMware ESX Server %d.%d.%d", major, minor, bug); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + sufficient = 1; + } + else if (sscanf(buffer, "VMware ESX Server %255s", version) > 0) + { + snprintf(classbuf, CF_BUFSIZE, "VMware ESX Server %s", version); + EvalContextClassPutHard(ctx, classbuf, "inventory,attribute_name=none,source=agent"); + sufficient = 1; + } + } + +/* Fall back to checking for other files */ + + if (sufficient < 1 && (ReadLine("/etc/vmware-release", buffer, sizeof(buffer)) + || ReadLine("/etc/issue", buffer, sizeof(buffer)))) + { + EvalContextClassPutHard(ctx, buffer, "inventory,attribute_name=none,source=agent"); + + /* Strip off the release code name e.g. "(Dali)" */ + if ((sp = strchr(buffer, '(')) != NULL) + { + *sp = 0; + Chop(buffer, CF_EXPANDSIZE); + EvalContextClassPutHard(ctx, buffer, "inventory,attribute_name=none,source=agent"); + } + sufficient = 1; + } + + return sufficient < 1 ? 1 : 0; +} + +/******************************************************************/ + +static int Xen_Domain(EvalContext *ctx) +{ + int sufficient = 0; + + Log(LOG_LEVEL_VERBOSE, "This appears to be a xen pv system."); + EvalContextClassPutHard(ctx, "xen", "inventory,attribute_name=Virtual host,source=agent"); + +/* xen host will have "control_d" in /proc/xen/capabilities, xen guest will not */ + + FILE *fp = safe_fopen("/proc/xen/capabilities", "r"); + if (fp != NULL) + { + size_t buffer_size = CF_BUFSIZE; + char *buffer = xmalloc(buffer_size); + + for (;;) + { + ssize_t res = CfReadLine(&buffer, &buffer_size, fp); + if (res == -1) + { + if (!feof(fp)) + { + /* Failure reading Xen capabilites. Do we care? */ + fclose(fp); + free(buffer); + return 1; + } + else + { + break; + } + } + + if (strstr(buffer, "control_d")) + { + EvalContextClassPutHard(ctx, "xen_dom0", "inventory,attribute_name=Virtual host,source=agent"); + sufficient = 1; + } + } + + if (!sufficient) + { + EvalContextClassPutHard(ctx, "xen_domu_pv", "inventory,attribute_name=Virtual host,source=agent"); + sufficient = 1; + } + + fclose(fp); + free(buffer); + } + + return sufficient < 1 ? 1 : 0; +} + +/******************************************************************/ +static void OpenVZ_Detect(EvalContext *ctx) +{ +/* paths to file defining the type of vm (guest or host) */ +#define OPENVZ_HOST_FILENAME "/proc/bc/0" +#define OPENVZ_GUEST_FILENAME "/proc/vz" +/* path to the vzps binary */ +#define OPENVZ_VZPS_FILE "/bin/vzps" + struct stat statbuf; + + /* The file /proc/bc/0 is present on host + The file /proc/vz is present on guest + If the host has /bin/vzps, we should use it for checking processes + */ + + if (stat(OPENVZ_HOST_FILENAME, &statbuf) != -1) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be an OpenVZ/Virtuozzo/Parallels Cloud Server host system.\n"); + EvalContextClassPutHard(ctx, "virt_host_vz", "inventory,attribute_name=Virtual host,source=agent"); + /* if the file /bin/vzps is there, it is safe to use the processes promise type */ + if (stat(OPENVZ_VZPS_FILE, &statbuf) != -1) + { + EvalContextClassPutHard(ctx, "virt_host_vz_vzps", "inventory,attribute_name=Virtual host,source=agent"); + /* here we must redefine the value of VPSHARDCLASS */ + for (int i = 0; i < PLATFORM_CONTEXT_MAX; i++) + { + if (!strcmp(CLASSATTRIBUTES[i][0], "virt_host_vz_vzps")) + { + VPSHARDCLASS = (PlatformContext) i; + break; + } + } + } + else + { + Log(LOG_LEVEL_NOTICE, "This OpenVZ/Virtuozzo/Parallels Cloud Server host system does not have vzps installed; the processes promise type may not work as expected.\n"); + } + } + else if (stat(OPENVZ_GUEST_FILENAME, &statbuf) != -1) + { + Log(LOG_LEVEL_VERBOSE, "This appears to be an OpenVZ/Virtuozzo/Parallels Cloud Server guest system.\n"); + EvalContextClassPutHard(ctx, "virt_guest_vz", "inventory,attribute_name=Virtual host,source=agent"); + } +} + +/******************************************************************/ + +static bool ReadLine(const char *filename, char *buf, int bufsize) +{ + FILE *fp = ReadFirstLine(filename, buf, bufsize); + + if (fp == NULL) + { + return false; + } + else + { + fclose(fp); + return true; + } +} + +static FILE *ReadFirstLine(const char *filename, char *buf, int bufsize) +{ + FILE *fp = safe_fopen(filename, "r"); + + if (fp == NULL) + { + return NULL; + } + + if (fgets(buf, bufsize, fp) == NULL) + { + fclose(fp); + return NULL; + } + + StripTrailingNewline(buf, CF_EXPANDSIZE); + + return fp; +} +#endif /* __linux__ */ + +/******************************************************************/ + +#ifdef XEN_CPUID_SUPPORT + +/* Borrowed and modified from Xen source/tools/libxc/xc_cpuid_x86.c */ + +static void Xen_Cpuid(uint32_t idx, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) +{ +# ifdef __i386__ + /* On i386, %ebx register needs to be saved before usage and restored + * thereafter for PIC-compliant code on i386. */ + asm("push %%ebx; cpuid; mov %%ebx,%1; pop %%ebx" + : "=a"(*eax), "=r"(*ebx), "=c"(*ecx), "=d"(*edx) + : "0" (idx), "2" (0) ); +# else + asm("cpuid" + : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx) + : "0" (idx), "2" (0) ); +# endif +} + +/******************************************************************/ + +static bool Xen_Hv_Check(void) +{ + /* CPUID interface to Xen from arch-x86/cpuid.h: + * Leaf 1 (0x40000000) + * EAX: Largest Xen-information leaf. All leaves up to an including @EAX + * are supported by the Xen host. + * EBX-EDX: "XenVMMXenVMM" signature, allowing positive identification + * of a Xen host. + * + * Additional information can be found in the Hypervisor CPUID + * Interface Proposal (https://lkml.org/lkml/2008/10/1/246) + */ + + uint32_t eax, base; + union + { + uint32_t u[3]; + char s[13]; + } sig = {{0}}; + + /* + * For compatibility with other hypervisor interfaces, the Xen cpuid leaves + * can be found at the first otherwise unused 0x100 aligned boundary starting + * from 0x40000000. + * + * e.g If viridian extensions are enabled for an HVM domain, the Xen cpuid + * leaves will start at 0x40000100 + */ + for (base = 0x40000000; base < 0x40010000; base += 0x100) + { + Xen_Cpuid(base, &eax, &sig.u[0], &sig.u[1], &sig.u[2]); + + if (memcmp("XenVMMXenVMM", &sig.s[0], 12) == 0) + { + /* The highest basic calling parameter (largest value that EAX can + * be set to before calling CPUID) is returned in EAX. */ + if ((eax - base) < 2) + { + Log(LOG_LEVEL_DEBUG, + "Insufficient Xen CPUID Leaves (eax=%x at base %x)", + eax, base); + return false; + } + Log(LOG_LEVEL_DEBUG, + "Found Xen CPUID Leaf (eax=%x at base %x)", eax, base); + return true; + } + } + + return false; +} + +#endif /* XEN_CPUID_SUPPORT */ + +static void GetCPUInfo(EvalContext *ctx) +{ +#if defined(MINGW) || defined(NT) + Log(LOG_LEVEL_VERBOSE, "!! cpu count not implemented on Windows platform"); + return; +#else + int count = 0; + + // http://preview.tinyurl.com/c9l2sh - StackOverflow on cross-platform CPU counting +#if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_ONLN) + // Linux, AIX, Solaris, Darwin >= 10.4 + count = (int)sysconf(_SC_NPROCESSORS_ONLN); +#endif + +#if defined(HAVE_SYS_SYSCTL_H) && defined(HW_NCPU) + // BSD-derived platforms + int mib[2] = { CTL_HW, HW_NCPU }; + size_t len; + + len = sizeof(count); + if(sysctl(mib, 2, &count, &len, NULL, 0) < 0) + { + Log(LOG_LEVEL_ERR, "!! failed to get cpu count: %s", strerror(errno)); + } +#endif + +#ifdef HAVE_SYS_MPCTL_H +// Itanium processors have Intel Hyper-Threading virtual-core capability, +// and the MPC_GETNUMCORES_SYS operation counts each HT virtual core, +// which is equivalent to what the /proc/stat scan delivers for Linux. +// +// A build on 11i v3 PA would have the GETNUMCORES define, but if run on an +// 11i v1 system it would fail since that OS release has only GETNUMSPUS. +// So in the presence of GETNUMCORES, we check for an invalid arg error +// and fall back to GETNUMSPUS if necessary. An 11i v1 build would work +// normally on 11i v3, because on PA-RISC cores == spus since there's no +// HT on PA-RISC, and 11i v1 only runs on PA-RISC. +# ifdef MPC_GETNUMCORES_SYS + count = mpctl(MPC_GETNUMCORES_SYS, 0, 0); + if (count == -1 && errno == EINVAL) + { + count = mpctl(MPC_GETNUMSPUS_SYS, 0, 0); + } +# else + count = mpctl(MPC_GETNUMSPUS_SYS, 0, 0); // PA-RISC processor count +# endif +#endif /* HAVE_SYS_MPCTL_H */ + + if (count < 1) + { + Log(LOG_LEVEL_VERBOSE, "invalid processor count: %d", count); + return; + } + Log(LOG_LEVEL_VERBOSE, "Found %d processor%s", count, count > 1 ? "s" : ""); + + char buf[CF_SMALLBUF] = "1_cpu"; + if (count == 1) + { + EvalContextClassPutHard(ctx, buf, "source=agent,derived-from=sys.cpus"); // "1_cpu" from init - change if buf is ever used above + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cpus", "1", CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=CPU logical cores"); + } + else + { + snprintf(buf, CF_SMALLBUF, "%d_cpus", count); + EvalContextClassPutHard(ctx, buf, "source=agent,derived-from=sys.cpus"); + snprintf(buf, CF_SMALLBUF, "%d", count); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "cpus", buf, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=CPU logical cores"); + } +#endif /* MINGW || NT */ +} + +/******************************************************************/ + +// Implemented in Windows specific section. +#ifndef __MINGW32__ + +/** + Return the number of seconds the system has been online given the current + time() as an argument, or return -1 if unavailable or unimplemented. +*/ +int GetUptimeSeconds(time_t now) +{ + time_t boot_time = 0; + errno = 0; + +#if defined(BOOT_TIME_WITH_SYSINFO) // Most GNU, Linux platforms + + struct sysinfo s; + if (sysinfo(&s) == 0) + { + // Don't return yet, sanity checking below + boot_time = now - s.uptime; + } + +#elif defined(BOOT_TIME_WITH_KSTAT) // Solaris platform + + /* From command line you can get this with: + kstat -p unix:0:system_misc:boot_time */ + kstat_ctl_t *kc = kstat_open(); + if(kc != 0) + { + kstat_t *kp = kstat_lookup(kc, "unix", 0, "system_misc"); + if(kp != 0) + { + if (kstat_read(kc, kp, NULL) != -1) + { + kstat_named_t *knp = kstat_data_lookup(kp, "boot_time"); + if (knp != NULL) + { + boot_time = knp->value.ui32; + } + } + } + kstat_close(kc); + } + +#elif defined(BOOT_TIME_WITH_PSTAT_GETPROC) // HP-UX platform only + + struct pst_status p; + if (pstat_getproc(&p, sizeof(p), 0, 1) == 1) + { + boot_time = (time_t)p.pst_start; + } + +#elif defined(BOOT_TIME_WITH_SYSCTL) // BSD-derived platforms + + int mib[2] = { CTL_KERN, KERN_BOOTTIME }; + struct timeval boot; + size_t len = sizeof(boot); + if (sysctl(mib, 2, &boot, &len, NULL, 0) == 0) + { + boot_time = boot.tv_sec; + } + +#elif defined(BOOT_TIME_WITH_PROCFS) + + struct stat p; + if (stat("/proc/1", &p) == 0) + { + boot_time = p.st_ctime; + } + +#elif defined(BOOT_TIME_WITH_UTMP) /* SystemV, highly portable way */ + + struct utmp query = { .ut_type = BOOT_TIME }; + struct utmp *result; + setutent(); + result = getutid(&query); + if (result != NULL) + { + boot_time = result->ut_time; + } + endutent(); + +#elif defined(BOOT_TIME_WITH_UTMPX) /* POSIX way */ + + struct utmpx query = { .ut_type = BOOT_TIME }; + struct utmpx *result; + setutxent(); + result = getutxid(&query); + if (result != NULL) + { + boot_time = result->ut_tv.tv_sec; + } + endutxent(); + +#endif + + if(errno) + { + Log(LOG_LEVEL_VERBOSE, "boot time discovery error: %s", GetErrorStr()); + } + + if(boot_time > now || boot_time <= 0) + { + Log(LOG_LEVEL_VERBOSE, "invalid boot time found; trying uptime command"); + boot_time = GetBootTimeFromUptimeCommand(now); + } + + return boot_time > 0 ? now - boot_time : -1; +} +#endif // !__MINGW32__ + +int GetUptimeMinutes(time_t now) +{ + return GetUptimeSeconds(now) / SECONDS_PER_MINUTE; +} + +/******************************************************************/ + +// Last resort: parse the output of the uptime command with a PCRE regexp +// and convert the uptime to boot time using "now" argument. +// +// The regexp needs to match all variants of the uptime command's output. +// Solaris 8/9/10: 10:45am up 109 day(s), 19:56, 1 user, load average: +// HP-UX 11.11: 9:24am up 1 min, 1 user, load average: +// 8:23am up 23 hrs, 0 users, load average: +// 9:33am up 2 days, 10 mins, 0 users, load average: +// 11:23am up 2 days, 2 hrs, 0 users, load average: +// Red Hat Linux: 10:51:23 up 5 days, 19:54, 1 user, load average: +// +// UPTIME_BACKREFS must be set to this regexp's maximum backreference +// index number (i.e., the count of left-parentheses): +#define UPTIME_REGEXP " up (\\d+ day[^,]*,|) *(\\d+( ho?u?r|:(\\d+))|(\\d+) min)" +#define UPTIME_BACKREFS 5 + +static time_t GetBootTimeFromUptimeCommand(time_t now) +{ + FILE *uptimecmd; + char *backref = NULL; + const char *uptimepath = "/usr/bin/uptime"; + time_t uptime = 0; + + int err_code; + size_t err_offset; + pcre2_code *regex = pcre2_compile((PCRE2_SPTR) UPTIME_REGEXP, PCRE2_ZERO_TERMINATED, 0, + &err_code, &err_offset, NULL); + if (regex == NULL) + { + Log(LOG_LEVEL_DEBUG, "failed to compile regexp to parse uptime command"); + return(-1); + } + + // Try "/usr/bin/uptime" first, then "/bin/uptime" + uptimecmd = cf_popen(uptimepath, "r", false); + uptimecmd = uptimecmd ? uptimecmd : cf_popen((uptimepath + 4), "r", false); + if (!uptimecmd) + { + Log(LOG_LEVEL_ERR, "uptime failed: (cf_popen: %s)", GetErrorStr()); + pcre2_code_free(regex); + return -1; + } + + size_t uptime_output_size = CF_SMALLBUF; + char *uptime_output = xmalloc(uptime_output_size); + ssize_t n_read = CfReadLine(&uptime_output, &uptime_output_size, uptimecmd); + + cf_pclose(uptimecmd); + if (n_read == -1 && !feof(uptimecmd)) + { + Log(LOG_LEVEL_ERR, "Reading uptime output failed. (getline: '%s')", GetErrorStr()); + pcre2_code_free(regex); + return -1; + } + + pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL); + if ((n_read > 0) && + (pcre2_match(regex, (PCRE2_SPTR) uptime_output, PCRE2_ZERO_TERMINATED, + 0, 0, match_data, NULL) > 1)) + { + size_t *ovector = pcre2_get_ovector_pointer(match_data); + for (int i = 1; i <= UPTIME_BACKREFS ; i++) + { + if (ovector[i * 2 + 1] - ovector[i * 2] == 0) // strlen(backref) + { + continue; + } + backref = uptime_output + ovector[i * 2]; + // atoi() ignores non-digits, so no need to null-terminate backref + + time_t seconds; + switch(i) + { + case 1: // Day + seconds = SECONDS_PER_DAY; + break; + case 2: // Hour + seconds = SECONDS_PER_HOUR; + break; + case 4: // Minute + case 5: + seconds = SECONDS_PER_MINUTE; + break; + default: + seconds = 0; + } + uptime += ((time_t) atoi(backref)) * seconds; + } + } + else + { + Log(LOG_LEVEL_ERR, "uptime PCRE match failed: regexp: '%s', uptime: '%s'", UPTIME_REGEXP, uptime_output); + } + pcre2_match_data_free(match_data); + pcre2_code_free(regex); + Log(LOG_LEVEL_VERBOSE, "Reading boot time from uptime command successful."); + return(uptime ? (now - uptime) : -1); +} + +/*****************************************************************************/ + +/* TODO accept a const char * and move the ifdefs away from + * evalfunction.c:FnCallGetUserInfo() to here. */ +JsonElement* GetUserInfo(const void *passwd) +{ +#ifdef __MINGW32__ + return NULL; + +#else /* !__MINGW32__ */ + + // we lose the const to set pw if passwd is NULL + struct passwd *pw = (struct passwd*) passwd; + + if (pw == NULL) + { + pw = getpwuid(getuid()); + } + + if (pw == NULL) + { + return NULL; + } + + JsonElement *result = JsonObjectCreate(10); + JsonObjectAppendString(result, "username", pw->pw_name); + JsonObjectAppendString(result, "description", pw->pw_gecos); + JsonObjectAppendString(result, "home_dir", pw->pw_dir); + JsonObjectAppendString(result, "shell", pw->pw_shell); + JsonObjectAppendInteger(result, "uid", pw->pw_uid); + JsonObjectAppendInteger(result, "gid", pw->pw_gid); + //JsonObjectAppendBool(result, "locked", IsAccountLocked(pw->pw_name, pw)); + // TODO: password: { format: "hash", data: { ...GetPasswordHash()... } } + // TODO: group_primary: name of group + // TODO: groups_secondary: [ names of groups ] + // TODO: gids_secondary: [ gids of groups ] + + return result; +#endif +} + +/*****************************************************************************/ + +void GetSysVars(EvalContext *ctx) +{ + /* Get info for current user. */ + JsonElement *info = GetUserInfo(NULL); + if (info != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "user_data", + info, CF_DATA_TYPE_CONTAINER, + "source=agent,user_info"); + JsonDestroy(info); + } +} + +/*****************************************************************************/ + +void GetDefVars(EvalContext *ctx) +{ + EvalContextVariablePutSpecial( + ctx, SPECIAL_SCOPE_DEF, "jq", + "jq --compact-output --monochrome-output --ascii-output --unbuffered --sort-keys", + CF_DATA_TYPE_STRING, "invocation,source=agent,command_name=jq"); +} + +/*****************************************************************************/ + +static void SysOSNameHuman(EvalContext *ctx) +{ + // This function sets the $(sys.os_name_human) variable + assert(ctx != NULL); + const char *const lval = "os_name_human"; + + /** + * Order is important. The first if-statement is more specific and wins. + * E.g. prefer Ubuntu over Debian. + */ + if (EvalContextClassGet(ctx, NULL, "ubuntu") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Ubuntu", CF_DATA_TYPE_STRING, + "source=agent,derived-from=ubuntu"); + } + else if (EvalContextClassGet(ctx, NULL, "debian") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Debian", CF_DATA_TYPE_STRING, + "source=agent,derived-from=debian"); + } + else if (EvalContextClassGet(ctx, NULL, "centos") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "CentOS", CF_DATA_TYPE_STRING, + "source=agent,derived-from=centos"); + } + else if (EvalContextClassGet(ctx, NULL, "fedora") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Fedora", CF_DATA_TYPE_STRING, + "source=agent,derived-from=fedora"); + } + else if (EvalContextClassGet(ctx, NULL, "redhat") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "RHEL", CF_DATA_TYPE_STRING, + "source=agent,derived-from=redhat"); + } + else if (EvalContextClassGet(ctx, NULL, "aix") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "AIX", CF_DATA_TYPE_STRING, + "source=agent,derived-from=aix"); + } + else if (EvalContextClassGet(ctx, NULL, "hpux") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "HP-UX", CF_DATA_TYPE_STRING, + "source=agent,derived-from=hpux"); + } + else if (EvalContextClassGet(ctx, NULL, "opensuse") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "OpenSUSE", CF_DATA_TYPE_STRING, + "source=agent,derived-from=opensuse"); + } + else if (EvalContextClassGet(ctx, NULL, "suse") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "SUSE", CF_DATA_TYPE_STRING, + "source=agent,derived-from=suse"); + } + else if (EvalContextClassGet(ctx, NULL, "macos") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "macOS", CF_DATA_TYPE_STRING, + "source=agent,derived-from=macos"); + } + else if (EvalContextClassGet(ctx, NULL, "windows") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Windows", CF_DATA_TYPE_STRING, + "source=agent,derived-from=windows"); + } + else if (EvalContextClassGet(ctx, NULL, "freebsd") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "FreeBSD", CF_DATA_TYPE_STRING, + "source=agent,derived-from=freebsd"); + } + else if (EvalContextClassGet(ctx, NULL, "openbsd") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "OpenBSD", CF_DATA_TYPE_STRING, + "source=agent,derived-from=openbsd"); + } + else if (EvalContextClassGet(ctx, NULL, "netbsd") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "NetBSD", CF_DATA_TYPE_STRING, + "source=agent,derived-from=netbsd"); + } + else if (EvalContextClassGet(ctx, NULL, "solaris") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Solaris", CF_DATA_TYPE_STRING, + "source=agent,derived-from=solaris"); + } + else if (EvalContextClassGet(ctx, NULL, "amazon_linux") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Amazon", CF_DATA_TYPE_STRING, + "source=agent,derived-from=amazon_linux"); + } + else if (EvalContextClassGet(ctx, NULL, "arch") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Arch", CF_DATA_TYPE_STRING, + "source=agent,derived-from=arch"); + } + else if (EvalContextClassGet(ctx, NULL, "postmarketos") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "postmarketOS", CF_DATA_TYPE_STRING, + "source=agent,derived-from=postmarketos"); + } + else if (EvalContextClassGet(ctx, NULL, "alpine") != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Alpine", CF_DATA_TYPE_STRING, + "source=agent,derived-from=alpine"); + } + else + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, lval, + "Unknown", CF_DATA_TYPE_STRING, + "source=agent"); + Log(LOG_LEVEL_WARNING, + "Operating System not properly recognized, setting sys.os_name_human to \"Unknown\", please submit a bug report for us to fix this"); + } +} + +/*****************************************************************************/ + +/** + * Find next integer from string in place. Leading zero's are included. + * + * @param [in] str string to extract next integer from + * @param [out] num pointer to start of next integer or %NULL if no integer + * number was found + * + * @return pointer to the remaining string in `str` or %NULL if no remainder + * + * @note `str` will be mutated + */ +static char *FindNextInteger(char *str, char **num) +{ + if (str == NULL) + { + *num = NULL; + return NULL; + } + + char *ptr = str; + while (*ptr != '\0' && !isdigit(*ptr)) + { + ++ptr; + } + if (*ptr == '\0') + { + /* no integer found */ + *num = NULL; + return NULL; + } + *num = ptr++; + + while (*ptr != '\0' && isdigit(*ptr)) + { + ++ptr; + } + if (*ptr == '\0') + { + /* no remainder */ + return NULL; + } + *ptr++ = '\0'; + + return ptr; +} + +static void SysOsVersionMajor(EvalContext *ctx) +{ + const char *const_flavor = EvalContextVariableGetSpecialString( + ctx, SPECIAL_SCOPE_SYS, "flavor"); + char *flavor = SafeStringDuplicate(const_flavor); + + char *major; + char *next = FindNextInteger(flavor, &major); + + if (flavor != NULL) + { + if (StringStartsWith(const_flavor, "solaris") || + StringStartsWith(const_flavor, "sunos")) + { + /* In this case the major version comes second. + * E.g. SunOS 5.11 where 11 is the major release number. + * Thus, we need to get the next number. + */ + FindNextInteger(next, &major); + } + } + + if (NULL_OR_EMPTY(major)) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, + "os_version_major", "Unknown", + CF_DATA_TYPE_STRING, "source=agent"); + } + else + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, + "os_version_major", major, + CF_DATA_TYPE_STRING, + "source=agent,derived-from=flavor"); + } + + free(flavor); +} + +/*****************************************************************************/ + +void DetectEnvironment(EvalContext *ctx) +{ + GetNameInfo3(ctx); + GetInterfacesInfo(ctx); + GetNetworkingInfo(ctx); + Get3Environment(ctx); + BuiltinClasses(ctx); + OSClasses(ctx); + GetSysVars(ctx); + GetDefVars(ctx); + SysOSNameHuman(ctx); + SysOsVersionMajor(ctx); +} diff --git a/libenv/sysinfo.h b/libenv/sysinfo.h new file mode 100644 index 0000000000..8938358ade --- /dev/null +++ b/libenv/sysinfo.h @@ -0,0 +1,43 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_SYSINFO_H +#define CFENGINE_SYSINFO_H + +/* TODO libpromises depends on libenv, the opposite should not happen! */ +#include + +void DetectEnvironment(EvalContext *ctx); + +void CreateHardClassesFromCanonification(EvalContext *ctx, const char *canonified, char *tags); +int GetUptimeMinutes(time_t now); +int GetUptimeSeconds(time_t now); + +void GetInterfacesInfo(EvalContext *ctx); +void GetNetworkingInfo(EvalContext *ctx); +JsonElement* GetNetworkingConnections(EvalContext *ctx); + +JsonElement* GetUserInfo(const void *passwd); + +#endif diff --git a/libenv/sysinfo_priv.h b/libenv/sysinfo_priv.h new file mode 100644 index 0000000000..d728e4542c --- /dev/null +++ b/libenv/sysinfo_priv.h @@ -0,0 +1,33 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_SYSINFO_PRIV_H +#define CFENGINE_SYSINFO_PRIV_H + +#include + +void DiscoverVersion(EvalContext *ctx); +void DetectDomainName(EvalContext *ctx, const char *orig_nodename); + +#endif diff --git a/libenv/time_classes.c b/libenv/time_classes.c new file mode 100644 index 0000000000..7058869637 --- /dev/null +++ b/libenv/time_classes.c @@ -0,0 +1,137 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +static void RemoveTimeClass(EvalContext *ctx, const char* tags) +{ + Rlist *tags_rlist = RlistFromSplitString(tags, ','); + ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true); + StringSet *global_matches = ClassesMatching(ctx, iter, ".*", tags_rlist, false); + ClassTableIteratorDestroy(iter); + + StringSetIterator it = StringSetIteratorInit(global_matches); + const char *element = NULL; + while ((element = StringSetIteratorNext(&it))) + { + EvalContextClassRemove(ctx, NULL, element); + } + + StringSetDestroy(global_matches); + + RlistDestroy(tags_rlist); +} + +static void AddTimeClass(EvalContext *ctx, time_t time, const char* tags) +{ + // The first element is the local timezone + const char* tz_prefix[2] = { "", "GMT_" }; + const char* tz_function[2] = { "localtime_r", "gmtime_r" }; + struct tm tz_parsed_time[2]; + const struct tm* tz_tm[2] = { + localtime_r(&time, &(tz_parsed_time[0])), + gmtime_r(&time, &(tz_parsed_time[1])) + }; + + for (int tz = 0; tz < 2; tz++) + { + char buf[CF_BUFSIZE]; + int day_text_index, quarter, interval_start, interval_end; + + if (tz_tm[tz] == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to parse passed time. (%s: %s)", tz_function[tz], GetErrorStr()); + return; + } + +/* Lifecycle */ + + snprintf(buf, CF_BUFSIZE, "%sLcycle_%d", tz_prefix[tz], ((tz_parsed_time[tz].tm_year + 1900) % 3)); + EvalContextClassPutHard(ctx, buf, tags); + +/* Year */ + + snprintf(buf, CF_BUFSIZE, "%sYr%04d", tz_prefix[tz], tz_parsed_time[tz].tm_year + 1900); + EvalContextClassPutHard(ctx, buf, tags); + +/* Month */ + + snprintf(buf, CF_BUFSIZE, "%s%s", tz_prefix[tz], MONTH_TEXT[tz_parsed_time[tz].tm_mon]); + EvalContextClassPutHard(ctx, buf, tags); + +/* Day of week */ + +/* Monday is 1 in tm_wday, 0 in DAY_TEXT + Tuesday is 2 in tm_wday, 1 in DAY_TEXT + ... + Sunday is 0 in tm_wday, 6 in DAY_TEXT */ + day_text_index = (tz_parsed_time[tz].tm_wday + 6) % 7; + snprintf(buf, CF_BUFSIZE, "%s%s", tz_prefix[tz], DAY_TEXT[day_text_index]); + EvalContextClassPutHard(ctx, buf, tags); + +/* Day */ + + snprintf(buf, CF_BUFSIZE, "%sDay%d", tz_prefix[tz], tz_parsed_time[tz].tm_mday); + EvalContextClassPutHard(ctx, buf, tags); + +/* Shift */ + + snprintf(buf, CF_BUFSIZE, "%s%s", tz_prefix[tz], SHIFT_TEXT[tz_parsed_time[tz].tm_hour / 6]); + EvalContextClassPutHard(ctx, buf, tags); + +/* Hour */ + + snprintf(buf, CF_BUFSIZE, "%sHr%02d", tz_prefix[tz], tz_parsed_time[tz].tm_hour); + EvalContextClassPutHard(ctx, buf, tags); + snprintf(buf, CF_BUFSIZE, "%sHr%d", tz_prefix[tz], tz_parsed_time[tz].tm_hour); + EvalContextClassPutHard(ctx, buf, tags); + +/* Quarter */ + + quarter = tz_parsed_time[tz].tm_min / 15 + 1; + + snprintf(buf, CF_BUFSIZE, "%sQ%d", tz_prefix[tz], quarter); + EvalContextClassPutHard(ctx, buf, tags); + snprintf(buf, CF_BUFSIZE, "%sHr%02d_Q%d", tz_prefix[tz], tz_parsed_time[tz].tm_hour, quarter); + EvalContextClassPutHard(ctx, buf, tags); + +/* Minute */ + + snprintf(buf, CF_BUFSIZE, "%sMin%02d", tz_prefix[tz], tz_parsed_time[tz].tm_min); + EvalContextClassPutHard(ctx, buf, tags); + + interval_start = (tz_parsed_time[tz].tm_min / 5) * 5; + interval_end = (interval_start + 5) % 60; + + snprintf(buf, CF_BUFSIZE, "%sMin%02d_%02d", tz_prefix[tz], interval_start, interval_end); + EvalContextClassPutHard(ctx, buf, tags); + } +} + +void UpdateTimeClasses(EvalContext *ctx, time_t t) +{ + RemoveTimeClass(ctx, "cfengine_internal_time_based_autoremove"); + AddTimeClass(ctx, t, "time_based,cfengine_internal_time_based_autoremove,source=agent"); +} diff --git a/libenv/time_classes.h b/libenv/time_classes.h new file mode 100644 index 0000000000..ccc6945fe4 --- /dev/null +++ b/libenv/time_classes.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_TIME_CLASSES_H +#define CFENGINE_TIME_CLASSES_H + +#include + +void UpdateTimeClasses(EvalContext *ctx, time_t t); + +#endif diff --git a/libenv/unix_iface.c b/libenv/unix_iface.c new file mode 100644 index 0000000000..06b2eb1d57 --- /dev/null +++ b/libenv/unix_iface.c @@ -0,0 +1,1586 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include /* SeqStringFromString */ +#include /* StringMatchFull */ +#include /* StringCaptureData() */ +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_JAIL_H +# include +#endif + +#ifdef HAVE_GETIFADDRS +# include +# ifdef HAVE_NET_IF_DL_H +# include +# endif +#endif + +#ifdef HAVE_NET_IF_ARP_H +# include +#endif + +#define CF_IFREQ 2048 /* Reportedly the largest size that does not segfault 32/64 bit */ +#define CF_IGNORE_INTERFACES "ignore_interfaces.rx" + +#define IPV6_PREFIX "ipv6_" + +#ifndef __MINGW32__ + +# if defined(HAVE_STRUCT_SOCKADDR_SA_LEN) && !defined(__NetBSD__) +# ifdef _SIZEOF_ADDR_IFREQ +# define SIZEOF_IFREQ(x) _SIZEOF_ADDR_IFREQ(x) +# else +# define SIZEOF_IFREQ(x) \ + ((x).ifr_addr.sa_len > sizeof(struct sockaddr) ? \ + (sizeof(struct ifreq) - sizeof(struct sockaddr) + \ + (x).ifr_addr.sa_len) : sizeof(struct ifreq)) +# endif +# else +# define SIZEOF_IFREQ(x) sizeof(struct ifreq) +# endif + +#ifdef _AIX +#include +#include +static int aix_get_mac_addr(const char *device_name, uint8_t mac[6]); +#endif + +static void FindV6InterfacesInfo(EvalContext *ctx, Rlist **interfaces, Rlist **hardware, Rlist **ips); +static bool IgnoreJailInterface(int ifaceidx, struct sockaddr_in *inaddr); +static bool IgnoreInterface(char *name); +static void InitIgnoreInterfaces(void); + +static Rlist *IGNORE_INTERFACES = NULL; /* GLOBAL_E */ + +typedef void (*ProcPostProcessFn)(void *ctx, void *json); +typedef JsonElement * (*ProcTiebreakerFn)(JsonElement *prev_item, JsonElement *this_item); + + +/*********************************************************************/ + + +/******************************************************************/ + +static bool IgnoreJailInterface( +#if !defined(HAVE_JAIL_GET) + ARG_UNUSED int ifaceidx, ARG_UNUSED struct sockaddr_in *inaddr +#else + int ifaceidx, struct sockaddr_in *inaddr +#endif + ) +{ +/* FreeBSD jails */ +# ifdef HAVE_JAIL_GET + struct iovec fbsd_jparams[4]; + struct in_addr fbsd_jia; + int fbsd_lastjid = 0; + + *(const void **) &fbsd_jparams[0].iov_base = "lastjid"; + fbsd_jparams[0].iov_len = sizeof("lastjid"); + fbsd_jparams[1].iov_base = &fbsd_lastjid; + fbsd_jparams[1].iov_len = sizeof(fbsd_lastjid); + + *(const void **) &fbsd_jparams[2].iov_base = "ip4.addr"; + fbsd_jparams[2].iov_len = sizeof("ip4.addr"); + fbsd_jparams[3].iov_len = sizeof(struct in_addr); + fbsd_jparams[3].iov_base = &fbsd_jia; + + while ((fbsd_lastjid = jail_get(fbsd_jparams, 4, 0)) > 0) + { + if (fbsd_jia.s_addr == inaddr->sin_addr.s_addr) + { + Log(LOG_LEVEL_VERBOSE, "Interface %d belongs to a FreeBSD jail %s", ifaceidx, inet_ntoa(fbsd_jia)); + return true; + } + } +# endif + + return false; +} + +/******************************************************************/ + +static void GetMacAddress(EvalContext *ctx, ARG_UNUSED int fd, struct ifreq *ifr, struct ifreq *ifp, Rlist **interfaces, + Rlist **hardware) +{ + char name[CF_MAXVARSIZE]; + + snprintf(name, sizeof(name), "hardware_mac[%s]", CanonifyName(ifp->ifr_name)); + + // mac address on a loopback interface doesn't make sense + if (ifr->ifr_flags & IFF_LOOPBACK) + { + return; + } + +# if defined(SIOCGIFHWADDR) && defined(HAVE_STRUCT_IFREQ_IFR_HWADDR) && !defined(__ANDROID__) + char hw_mac[CF_MAXVARSIZE]; + + if ((ioctl(fd, SIOCGIFHWADDR, ifr) == -1)) + { + Log(LOG_LEVEL_ERR, "Couldn't get mac address for '%s' interface. (ioctl: %s)", ifr->ifr_name, GetErrorStr()); + return; + } + + snprintf(hw_mac, sizeof(hw_mac), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + (unsigned char) ifr->ifr_hwaddr.sa_data[0], + (unsigned char) ifr->ifr_hwaddr.sa_data[1], + (unsigned char) ifr->ifr_hwaddr.sa_data[2], + (unsigned char) ifr->ifr_hwaddr.sa_data[3], + (unsigned char) ifr->ifr_hwaddr.sa_data[4], + (unsigned char) ifr->ifr_hwaddr.sa_data[5]); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, hw_mac, CF_DATA_TYPE_STRING, "source=agent"); + if (!RlistContainsString(*hardware, hw_mac)) + { + RlistAppend(hardware, hw_mac, RVAL_TYPE_SCALAR); + } + if (!RlistContainsString(*interfaces, ifp->ifr_name)) + { + RlistAppend(interfaces, ifp->ifr_name, RVAL_TYPE_SCALAR); + } + + snprintf(name, sizeof(name), "mac_%s", CanonifyName(hw_mac)); + EvalContextClassPutHard(ctx, name, "inventory,attribute_name=none,source=agent"); + +# elif defined(HAVE_GETIFADDRS) && !defined(__sun) + char hw_mac[CF_MAXVARSIZE]; + char *mac_pointer = NULL; + struct ifaddrs *ifaddr, *ifa; + + if (getifaddrs(&ifaddr) == -1) + { + Log(LOG_LEVEL_ERR, "!! Could not get interface %s addresses", + ifp->ifr_name); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, "mac_unknown", CF_DATA_TYPE_STRING, "source=agent"); + EvalContextClassPutHard(ctx, "mac_unknown", "source=agent"); + return; + } + for (ifa = ifaddr; ifa != NULL; ifa=ifa->ifa_next) + { + if ( strcmp(ifa->ifa_name, ifp->ifr_name) == 0) + { + if (ifa->ifa_addr == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Interface '%s' has no address information", ifa->ifa_name); + continue; + } +#if AF_LINK + if (ifa->ifa_addr->sa_family == AF_LINK) + { + struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr; + mac_pointer = (char *) LLADDR(sdl); + } +#elif AF_PACKET + if (ifa->ifa_addr->sa_family == AF_PACKET) + { + struct sockaddr_ll *sll = (struct sockaddr_ll *)ifa->ifa_addr; + mac_pointer = (char *) sll->sll_addr; + } +#else +# error "AF_LINK or AF_PACKET must be available for GetMacAddress() currently" +#endif + if (mac_pointer == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Couldn't get Physical-layer address from interface '%s'", ifr->ifr_name); + continue; + } + + snprintf(hw_mac, sizeof(hw_mac), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + (unsigned char) mac_pointer[0], + (unsigned char) mac_pointer[1], + (unsigned char) mac_pointer[2], + (unsigned char) mac_pointer[3], + (unsigned char) mac_pointer[4], + (unsigned char) mac_pointer[5]); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, hw_mac, CF_DATA_TYPE_STRING, "source=agent"); + if (!RlistContainsString(*hardware, hw_mac)) + { + RlistAppend(hardware, hw_mac, RVAL_TYPE_SCALAR); + } + RlistAppend(interfaces, ifa->ifa_name, RVAL_TYPE_SCALAR); + + snprintf(name, sizeof(name), "mac_%s", CanonifyName(hw_mac)); + EvalContextClassPutHard(ctx, name, "source=agent"); + } + + } + freeifaddrs(ifaddr); + +# elif defined(_AIX) && !defined(HAVE_GETIFADDRS) + char hw_mac[CF_MAXVARSIZE]; + char mac[CF_MAXVARSIZE]; + + if (aix_get_mac_addr(ifp->ifr_name, mac) == 0) + { + sprintf(hw_mac, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, hw_mac, CF_DATA_TYPE_STRING, "source=agent"); + + if (!RlistContainsString(*hardware, hw_mac)) + { + RlistAppend(hardware, hw_mac, RVAL_TYPE_SCALAR); + } + if (!RlistContainsString(*interfaces, ifp->ifr_name)) + { + RlistAppend(interfaces, ifp->ifr_name, RVAL_TYPE_SCALAR); + } + + snprintf(name, CF_MAXVARSIZE, "mac_%s", CanonifyName(hw_mac)); + EvalContextClassPutHard(ctx, name, "inventory,attribute_name=none,source=agent"); + } + else + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, "mac_unknown", CF_DATA_TYPE_STRING, "source=agent"); + EvalContextClassPutHard(ctx, "mac_unknown", "source=agent"); + } + +# elif defined(SIOCGARP) + + struct arpreq arpreq; + + ((struct sockaddr_in *) &arpreq.arp_pa)->sin_addr.s_addr = + ((struct sockaddr_in *) &ifp->ifr_addr)->sin_addr.s_addr; + + if (ioctl(fd, SIOCGARP, &arpreq) == -1) + { + // ENXIO happens if there is no MAC address assigned, which is not that + // uncommon. + LogLevel log_level = + (errno == ENXIO) ? LOG_LEVEL_VERBOSE : LOG_LEVEL_ERR; + Log(log_level, + "Could not get interface '%s' addresses (ioctl(SIOCGARP): %s)", + ifp->ifr_name, GetErrorStr()); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, + "mac_unknown", CF_DATA_TYPE_STRING, + "source=agent"); + EvalContextClassPutHard(ctx, "mac_unknown", "source=agent"); + return; + } + + char hw_mac[CF_MAXVARSIZE]; + + snprintf(hw_mac, sizeof(hw_mac), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + (unsigned char) arpreq.arp_ha.sa_data[0], + (unsigned char) arpreq.arp_ha.sa_data[1], + (unsigned char) arpreq.arp_ha.sa_data[2], + (unsigned char) arpreq.arp_ha.sa_data[3], + (unsigned char) arpreq.arp_ha.sa_data[4], + (unsigned char) arpreq.arp_ha.sa_data[5]); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, + hw_mac, CF_DATA_TYPE_STRING, + "source=agent"); + + if (!RlistContainsString(*hardware, hw_mac)) + { + RlistAppend(hardware, hw_mac, RVAL_TYPE_SCALAR); + } + + if (!RlistContainsString(*interfaces, ifp->ifr_name)) + { + RlistAppend(interfaces, ifp->ifr_name, RVAL_TYPE_SCALAR); + } + + snprintf(name, sizeof(name), "mac_%s", CanonifyName(hw_mac)); + EvalContextClassPutHard(ctx, name, + "inventory,attribute_name=none,source=agent"); + +# else + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, + "mac_unknown", CF_DATA_TYPE_STRING, + "source=agent"); + EvalContextClassPutHard(ctx, "mac_unknown", "source=agent"); +# endif +} + +/******************************************************************/ + +static void GetInterfaceFlags(EvalContext *ctx, struct ifreq *ifr, Rlist **flags) +{ + char name[CF_MAXVARSIZE]; + char buffer[CF_BUFSIZE] = ""; + char *fp = NULL; + + snprintf(name, sizeof(name), "interface_flags[%s]", ifr->ifr_name); + + if (ifr->ifr_flags & IFF_UP) strcat(buffer, " up"); + if (ifr->ifr_flags & IFF_BROADCAST) strcat(buffer, " broadcast"); + if (ifr->ifr_flags & IFF_DEBUG) strcat(buffer, " debug"); + if (ifr->ifr_flags & IFF_LOOPBACK) strcat(buffer, " loopback"); + if (ifr->ifr_flags & IFF_POINTOPOINT) strcat(buffer, " pointopoint"); + +#ifdef IFF_NOTRAILERS + if (ifr->ifr_flags & IFF_NOTRAILERS) strcat(buffer, " notrailers"); +#endif + + if (ifr->ifr_flags & IFF_RUNNING) strcat(buffer, " running"); + if (ifr->ifr_flags & IFF_NOARP) strcat(buffer, " noarp"); + if (ifr->ifr_flags & IFF_PROMISC) strcat(buffer, " promisc"); + if (ifr->ifr_flags & IFF_ALLMULTI) strcat(buffer, " allmulti"); + if (ifr->ifr_flags & IFF_MULTICAST) strcat(buffer, " multicast"); + + // If a least 1 flag is found + if (strlen(buffer) > 1) + { + // Skip leading space + fp = buffer + 1; + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, fp, CF_DATA_TYPE_STRING, "source=agent"); + RlistAppend(flags, fp, RVAL_TYPE_SCALAR); + } +} + +/******************************************************************/ + +void GetInterfacesInfo(EvalContext *ctx) +{ + bool address_set = false; + int fd, len, i, j; + struct ifreq ifbuf[CF_IFREQ], ifr, *ifp; + struct ifconf list; + struct sockaddr_in *sin; + char *sp, workbuf[CF_BUFSIZE]; + char ip[CF_MAXVARSIZE]; + char name[CF_MAXVARSIZE]; + Rlist *interfaces = NULL, *hardware = NULL, *flags = NULL, *ips = NULL; + + /* This function may be called many times, while interfaces come and go */ + /* TODO cache results for non-daemon processes? */ + EvalContextDeleteIpAddresses(ctx); + + memset(ifbuf, 0, sizeof(ifbuf)); + + InitIgnoreInterfaces(); + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't open socket. (socket: %s)", GetErrorStr()); + DoCleanupAndExit(EXIT_FAILURE); + } + + list.ifc_len = sizeof(ifbuf); + list.ifc_req = ifbuf; + + /* WARNING: *BSD use unsigned long as second argument to ioctl() while + * POSIX specifies *signed* int. Using the largest possible signed type is + * the best strategy.*/ +#ifdef SIOCGIFCONF + intmax_t request = SIOCGIFCONF; +#else + intmax_t request = OSIOCGIFCONF; +#endif + int ret = ioctl(fd, request, &list); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, + "Couldn't get interfaces (ioctl(SIOCGIFCONF): %s)", + GetErrorStr()); + DoCleanupAndExit(EXIT_FAILURE); + } + + if (list.ifc_len < (int) sizeof(struct ifreq)) + { + Log(LOG_LEVEL_VERBOSE, + "Interface list returned is too small (%d bytes), " + "assuming no interfaces present", list.ifc_len); + list.ifc_len = 0; + } + + char last_name[sizeof(ifp->ifr_name)] = ""; + + for (j = 0, len = 0, ifp = list.ifc_req; len < list.ifc_len; + len += SIZEOF_IFREQ(*ifp), j++, ifp = (struct ifreq *) ((char *) ifp + SIZEOF_IFREQ(*ifp))) + { + + if (ifp->ifr_addr.sa_family == 0) + { + continue; + } + + if (strlen(ifp->ifr_name) == 0) + { + continue; + } + + /* Skip network interfaces listed in ignore_interfaces.rx */ + + if (IgnoreInterface(ifp->ifr_name)) + { + continue; + } + else + { + Log(LOG_LEVEL_VERBOSE, "Interface %d: %s", j + 1, ifp->ifr_name); + } + + /* If interface name appears a second time in a row then it has more + than one IP addresses (linux: ip addr add $IP dev $IF). + But the variable is already added so don't set it again. */ + if (strcmp(last_name, ifp->ifr_name) != 0) + { + strcpy(last_name, ifp->ifr_name); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "interface", last_name, CF_DATA_TYPE_STRING, "source=agent"); + } + + snprintf(workbuf, sizeof(workbuf), "net_iface_%s", CanonifyName(ifp->ifr_name)); + EvalContextClassPutHard(ctx, workbuf, "source=agent"); + + /* TODO IPv6 should be handled transparently */ + if (ifp->ifr_addr.sa_family == AF_INET) + { + strlcpy(ifr.ifr_name, ifp->ifr_name, sizeof(ifp->ifr_name)); + + if (ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) + { + Log(LOG_LEVEL_ERR, "No such network device. (ioctl: %s)", GetErrorStr()); + continue; + } + else + { + GetInterfaceFlags(ctx, &ifr, &flags); + } + + if (ifr.ifr_flags & IFF_UP) + { + sin = (struct sockaddr_in *) &ifp->ifr_addr; + + if (IgnoreJailInterface(j + 1, sin)) + { + Log(LOG_LEVEL_VERBOSE, "Ignoring interface %d", j + 1); + continue; + } + + /* No DNS lookup, just convert IP address to string. */ + char txtaddr[CF_MAX_IP_LEN] = ""; + nt_static_assert(sizeof(VIPADDRESS) >= sizeof(txtaddr)); + + getnameinfo((struct sockaddr *) sin, sizeof(*sin), + txtaddr, sizeof(txtaddr), + NULL, 0, NI_NUMERICHOST); + + Log(LOG_LEVEL_DEBUG, "Adding hostip '%s'", txtaddr); + EvalContextClassPutHard(ctx, txtaddr, "inventory,attribute_name=none,source=agent"); + + if (strcmp(txtaddr, "0.0.0.0") == 0) + { + /* TODO remove, interface address can't be 0.0.0.0 and + * even then DNS is not a safe way to set a variable... */ + Log(LOG_LEVEL_VERBOSE, "Cannot discover hardware IP, using DNS value"); + nt_static_assert(sizeof(ip) >= sizeof(VIPADDRESS) + sizeof("ipv4_")); + strcpy(ip, "ipv4_"); + strcat(ip, VIPADDRESS); + EvalContextAddIpAddress(ctx, VIPADDRESS, NULL); // we don't know the interface + RlistAppendScalar(&ips, VIPADDRESS); + + for (sp = ip + strlen(ip) - 1; (sp > ip); sp--) + { + if (*sp == '.') + { + *sp = '\0'; + EvalContextClassPutHard(ctx, ip, "inventory,attribute_name=none,source=agent"); + } + } + + strcpy(ip, VIPADDRESS); + i = 3; + + for (sp = ip + strlen(ip) - 1; (sp > ip); sp--) + { + if (*sp == '.') + { + *sp = '\0'; + snprintf(name, sizeof(name), "ipv4_%d[%s]", i--, CanonifyName(VIPADDRESS)); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, ip, CF_DATA_TYPE_STRING, "source=agent"); + } + } + continue; + } + + nt_static_assert(sizeof(ip) >= sizeof(txtaddr) + sizeof("ipv4_")); + strcpy(ip, "ipv4_"); + strcat(ip, txtaddr); + EvalContextClassPutHard(ctx, ip, "inventory,attribute_name=none,source=agent"); + + /* VIPADDRESS has already been set to the DNS address of + * VFQNAME by GetNameInfo3() during initialisation. Here we + * reset VIPADDRESS to the address of the first non-loopback + * interface. */ + if (!address_set && !(ifr.ifr_flags & IFF_LOOPBACK)) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "ipv4", txtaddr, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=none"); + + strcpy(VIPADDRESS, txtaddr); + Log(LOG_LEVEL_VERBOSE, "IP address of host set to %s", + VIPADDRESS); + address_set = true; + } + + EvalContextAddIpAddress(ctx, txtaddr, CanonifyName(ifp->ifr_name)); + RlistAppendScalar(&ips, txtaddr); + + for (sp = ip + strlen(ip) - 1; (sp > ip); sp--) + { + if (*sp == '.') + { + *sp = '\0'; + EvalContextClassPutHard(ctx, ip, "inventory,attribute_name=none,source=agent"); + } + } + + // Set the IPv4 on interface array + + strcpy(ip, txtaddr); + + snprintf(name, sizeof(name), "ipv4[%s]", CanonifyName(ifp->ifr_name)); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, ip, CF_DATA_TYPE_STRING, "source=agent"); + + // generate the reverse mapping + snprintf(name, sizeof(name), "ip2iface[%s]", txtaddr); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, CanonifyName(ifp->ifr_name), CF_DATA_TYPE_STRING, "source=agent"); + + i = 3; + + for (sp = ip + strlen(ip) - 1; (sp > ip); sp--) + { + if (*sp == '.') + { + *sp = '\0'; + + snprintf(name, sizeof(name), "ipv4_%d[%s]", i--, CanonifyName(ifp->ifr_name)); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, name, ip, CF_DATA_TYPE_STRING, "source=agent"); + } + } + } + + // Set the hardware/mac address array + GetMacAddress(ctx, fd, &ifr, ifp, &interfaces, &hardware); + } + } + + close(fd); + + FindV6InterfacesInfo(ctx, &interfaces, &hardware, &ips); + + if (interfaces) + { + // Define sys.interfaces: + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "interfaces", interfaces, CF_DATA_TYPE_STRING_LIST, + "inventory,source=agent,attribute_name=Interfaces"); + } + if (hardware) + { + // Define sys.hardware_addresses: + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "hardware_addresses", hardware, CF_DATA_TYPE_STRING_LIST, + "inventory,source=agent,attribute_name=MAC addresses"); + } + if (flags) + { + // Define sys.hardware_flags: + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "hardware_flags", flags, CF_DATA_TYPE_STRING_LIST, + "source=agent"); + } + if (ips) + { + // Define sys.ip_addresses: + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "ip_addresses", ips, CF_DATA_TYPE_STRING_LIST, + "source=agent"); + } + + RlistDestroy(interfaces); + RlistDestroy(hardware); + RlistDestroy(flags); + RlistDestroy(ips); +} + +/*******************************************************************/ + +static void FindV6InterfacesInfo(EvalContext *ctx, Rlist **interfaces, Rlist **hardware, Rlist **ips) +{ + assert(interfaces != NULL); + assert(hardware != NULL); + + FILE *pp = NULL; + +/* Whatever the manuals might say, you cannot get IPV6 + interface configuration from the ioctls. This seems + to be implemented in a non standard way across OSes + BSDi has done getifaddrs(), solaris 8 has a new ioctl, Stevens + book shows the suggestion which has not been implemented... +*/ + + Log(LOG_LEVEL_VERBOSE, "Trying to locate my IPv6 address"); + +#if defined(__CYGWIN__) + /* NT cannot do this */ + return; +#elif defined(__hpux) + if ((pp = cf_popen("/usr/sbin/ifconfig -a", "r", true)) == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Could not find interface info"); + return; + } +#elif defined(_AIX) + if ((pp = cf_popen("/etc/ifconfig -a", "r", true)) == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Could not find interface info"); + return; + } +#else + if ((!FileCanOpen("/sbin/ifconfig", "r") || ((pp = cf_popen("/sbin/ifconfig -a", "r", true)) == NULL)) && + (!FileCanOpen("/bin/ifconfig", "r") || ((pp = cf_popen("/bin/ifconfig -a", "r", true)) == NULL))) + { + Log(LOG_LEVEL_VERBOSE, "Could not find interface info"); + return; + } +#endif + +/* Don't know the output format of ifconfig on all these .. hope for the best*/ + + char ifconfig_line[CF_BUFSIZE]; + char current_interface[CF_BUFSIZE]; + current_interface[0] = '\0'; + for(;;) + { + if (fgets(ifconfig_line, sizeof(ifconfig_line), pp) == NULL) + { + if (ferror(pp)) + { + UnexpectedError("Failed to read line from stream"); + break; + } + else /* feof */ + { + break; + } + } + + if (!isspace(ifconfig_line[0])) // Name of interface followed by colon + { + // Find colon: + const char *colon = strchr(ifconfig_line, ':'); + if (colon != NULL) + { + // Bytes to "copy" includes colon(src)/NUL-byte(dst): + const size_t bytes_to_copy = colon - ifconfig_line + 1; + assert(bytes_to_copy <= sizeof(current_interface)); + + const size_t src_length = StringCopy( + ifconfig_line, current_interface, bytes_to_copy); + // StringCopy effectively returns the length of the src string, + // up to (inclusive) a maximum of passed in buffer size + + // We know there was more data, at least a colon: + assert(src_length == bytes_to_copy); + NDEBUG_UNUSED const size_t dst_length = src_length - 1; + + // We copied everything up to, but not including, the colon: + assert(ifconfig_line[dst_length] == ':'); + assert(current_interface[dst_length] == '\0'); + } + } + + + const char *const stripped_ifconfig_line = + TrimWhitespace(ifconfig_line); + if (StringStartsWith(stripped_ifconfig_line, "ether ")) + { + Seq *const ether_line = + SeqStringFromString(stripped_ifconfig_line, ' '); + const size_t length = SeqLength(ether_line); + + if (length < 2) + { + Log(LOG_LEVEL_ERR, + "Failed to parse hw_mac address for: %s", + current_interface); + } + else + { + const char *const hw_mac = SeqAt(ether_line, 1); + + if (!RlistContainsString(*hardware, hw_mac)) + { + Log(LOG_LEVEL_VERBOSE, + "Adding MAC address: %s for %s", + hw_mac, + current_interface); + + RlistAppendString(hardware, hw_mac); + + char variable_name[CF_MAXVARSIZE]; + + snprintf( + variable_name, + sizeof(variable_name), + "hardware_mac[%s]", + CanonifyName(current_interface)); + + EvalContextVariablePutSpecial( + ctx, + SPECIAL_SCOPE_SYS, + variable_name, + hw_mac, + CF_DATA_TYPE_STRING, + "source=agent,derived-from=ifconfig"); + } + } + + SeqDestroy(ether_line); + } + + if (strcasestr(ifconfig_line, "inet6")) + { + Item *ip, *list = NULL; + char *sp; + + list = SplitStringAsItemList(ifconfig_line, ' '); + + for (ip = list; ip != NULL; ip = ip->next) + { + for (sp = ip->name; *sp != '\0'; sp++) + { + if (*sp == '/') /* Remove CIDR mask */ + { + *sp = '\0'; + } + } + + if ((IsIPV6Address(ip->name)) && ((strcmp(ip->name, "::1") != 0))) + { + char prefixed_ip[CF_MAX_IP_LEN + sizeof(IPV6_PREFIX)] = {0}; + Log(LOG_LEVEL_VERBOSE, "Found IPv6 address %s", ip->name); + + if (current_interface[0] != '\0' + && !IgnoreInterface(current_interface)) + { + EvalContextAddIpAddress( + ctx, ip->name, current_interface); + EvalContextClassPutHard( + ctx, + ip->name, + "inventory,attribute_name=none,source=agent"); + + xsnprintf( + prefixed_ip, + sizeof(prefixed_ip), + IPV6_PREFIX "%s", + ip->name); + EvalContextClassPutHard( + ctx, + prefixed_ip, + "inventory,attribute_name=none,source=agent"); + + // Add IPv6 address to sys.ip_addresses + RlistAppendString(ips, ip->name); + + if (!RlistContainsString( + *interfaces, current_interface)) + { + RlistAppendString(interfaces, current_interface); + } + } + } + } + + DeleteItemList(list); + } + } + + cf_pclose(pp); +} + +/*******************************************************************/ + +static void InitIgnoreInterfaces() +{ + FILE *fin; + char filename[CF_BUFSIZE], regex[256]; + + int ret = snprintf( + filename, + sizeof(filename), + "%s%c%s", + GetWorkDir(), + FILE_SEPARATOR, + CF_IGNORE_INTERFACES); + assert(ret >= 0 && (size_t) ret < sizeof(filename)); + + if ((fin = fopen(filename, "r")) == NULL) + { + Log((errno == ENOENT) ? LOG_LEVEL_VERBOSE : LOG_LEVEL_ERR, + "Failed to open interface exception file %s: %s", + filename, + GetErrorStr()); + + /* LEGACY: The 'ignore_interfaces.rx' file was previously located in + * $(sys.inputdir). Consequently, if the file is found in this + * directory but not in $(sys.workdir), we will still process it, but + * issue a warning. */ + ret = snprintf( + filename, + sizeof(filename), + "%s%c%s", + GetInputDir(), + FILE_SEPARATOR, + CF_IGNORE_INTERFACES); + assert(ret >= 0 && (size_t) ret < sizeof(filename)); + + if ((fin = fopen(filename, "r")) == NULL) + { + Log((errno == ENOENT) ? LOG_LEVEL_VERBOSE : LOG_LEVEL_ERR, + "Failed to open interface exception file %s: %s", + filename, + GetErrorStr()); + return; + } + + Log(LOG_LEVEL_WARNING, + "Found interface exception file %s in %s but it should be in %s. " + "Please consider moving it to the appropriate location.", + CF_IGNORE_INTERFACES, + GetInputDir(), + GetWorkDir()); + } + + while (!feof(fin)) + { + regex[0] = '\0'; + int scanCount = fscanf(fin, "%255s", regex); + if (ferror(fin) != 0) + { + Log(LOG_LEVEL_ERR, + "Failed to read interface exception file %s: %s", + filename, + GetErrorStr()); + break; + } + + if (scanCount != 0 && *regex != '\0') + { + RlistPrependScalarIdemp(&IGNORE_INTERFACES, regex); + } + } + + fclose(fin); +} + +/*******************************************************************/ + +static bool IgnoreInterface(char *name) +{ + Rlist *rp; + + for (rp = IGNORE_INTERFACES; rp != NULL; rp=rp->next) + { + /* FIXME: review this strcmp. Moved out from StringMatch */ + if (!strcmp(RlistScalarValue(rp), name) + || StringMatchFull(RlistScalarValue(rp), name)) + { + Log(LOG_LEVEL_VERBOSE, "Ignoring interface '%s' because it matches '%s'",name,CF_IGNORE_INTERFACES); + return true; + } + } + + return false; +} + +#ifdef _AIX +static int aix_get_mac_addr(const char *device_name, uint8_t mac[6]) +{ + size_t ksize; + struct kinfo_ndd *ndd; + int count, i; + + ksize = getkerninfo(KINFO_NDD, 0, 0, 0); + if (ksize == 0) + { + errno = ENOSYS; + return -1; + } + + ndd = (struct kinfo_ndd *)xmalloc(ksize); + if (ndd == NULL) + { + errno = ENOMEM; + return -1; + } + + if (getkerninfo(KINFO_NDD, ndd, &ksize, 0) == -1) + { + errno = ENOSYS; + return -1; + } + + count= ksize/sizeof(struct kinfo_ndd); + for (i=0;i= RankIPv6Address(prev_addr)) + { + return this_item; + } + else + { + return prev_item; + } +} + +/*******************************************************************/ + +static const char* GetPortStateString(ARG_LINUX_ONLY int state) +{ +# if defined (__linux__) + switch (state) + { + case TCP_ESTABLISHED: return "ESTABLISHED"; + case TCP_SYN_SENT: return "SYN_SENT"; + case TCP_SYN_RECV: return "SYN_RECV"; + case TCP_FIN_WAIT1: return "FIN_WAIT1"; + case TCP_FIN_WAIT2: return "FIN_WAIT2"; + case TCP_TIME_WAIT: return "TIME_WAIT"; + case TCP_CLOSE: return "CLOSE"; + case TCP_CLOSE_WAIT: return "CLOSE_WAIT"; + case TCP_LAST_ACK: return "LAST_ACK"; + case TCP_LISTEN: return "LISTEN"; + case TCP_CLOSING: return "CLOSING"; + } + +# endif + return "UNKNOWN"; +} + +// used in evalfunction.c but defined here so +// JsonRewriteParsedIPAddress() etc. can stay local +void NetworkingPortsPostProcessInfo(ARG_UNUSED void *passed_ctx, void *json) +{ + JsonElement *conn = json; + + if (conn != NULL) + { + JsonRewriteParsedIPAddress(conn, "raw_local", "local", true); + JsonRewriteParsedIPAddress(conn, "raw_remote", "remote", true); + + long num_state = JsonExtractParsedNumber(conn, "raw_state", "temp_state", false, false); + + if (JsonObjectGetAsString(conn, "temp_state") != NULL) + { + JsonObjectRemoveKey(conn, "temp_state"); + JsonObjectAppendString(conn, "state", GetPortStateString(num_state)); + } + } +} + +/*******************************************************************/ + +static JsonElement* GetNetworkingStatsInfo(const char *filename) +{ + JsonElement *stats = NULL; + assert(filename); + + FILE *fin = safe_fopen(filename, "rt"); + if (fin) + { + Log(LOG_LEVEL_VERBOSE, "Reading netstat info from %s", filename); + size_t header_line_size = CF_BUFSIZE; + char *header_line = xmalloc(header_line_size); + stats = JsonObjectCreate(2); + + while (CfReadLine(&header_line, &header_line_size, fin) != -1) + { + char* colon_ptr = strchr(header_line, ':'); + if (colon_ptr != NULL && + colon_ptr+2 < header_line + strlen(header_line)) + { + JsonElement *stat = JsonObjectCreate(3); + Buffer *type = BufferNewFrom(header_line, colon_ptr - header_line); + size_t type_length = BufferSize(type); + Rlist *info = RlistFromSplitString(colon_ptr+2, ' '); + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + if (CfReadLine(&line, &line_size, fin) != -1) + { + if (strlen(line) > type_length+2) + { + Rlist *data = RlistFromSplitString(line+type_length+2, ' '); + for (const Rlist *rp = info, *rdp = data; + rp != NULL && rdp != NULL; + rp = rp->next, rdp = rdp->next) + { + JsonObjectAppendString(stat, RlistScalarValue(rp), RlistScalarValue(rdp)); + } + RlistDestroy(data); + } + } + + JsonObjectAppendElement(stats, BufferData(type), stat); + + free(line); + RlistDestroy(info); + BufferDestroy(type); + } + + } + + free(header_line); + + fclose(fin); + } + + return stats; +} + +/*******************************************************************/ + +// always returns the parsed data. If the key is not NULL, also +// creates a sys.KEY variable. + +JsonElement* GetProcFileInfo(EvalContext *ctx, const char* filename, const char* key, const char* extracted_key, ProcPostProcessFn post, ProcTiebreakerFn tiebreak, const char* pattern) +{ + JsonElement *info = NULL; + bool extract_key_mode = (extracted_key != NULL); + + FILE *fin = safe_fopen(filename, "rt"); + if (fin) + { + Log(LOG_LEVEL_VERBOSE, "Reading %s info from %s", key, filename); + + Regex *regex = CompileRegex(pattern); + if (regex != NULL) + { + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + + info = extract_key_mode ? JsonObjectCreate(10) : JsonArrayCreate(10); + + while (CfReadLine(&line, &line_size, fin) != -1) + { + JsonElement *item = StringCaptureData(regex, NULL, line); + + if (item != NULL) + { + if (post != NULL) + { + (*post)(ctx, item); + } + + if (extract_key_mode) + { + const char *extracted_key_value = JsonObjectGetAsString(item, extracted_key); + + if (extracted_key_value == NULL) + { + Log(LOG_LEVEL_ERR, "While parsing %s, looked to extract key %s but couldn't find it in line %s", filename, extracted_key, line); + } + else + { + JsonElement *prev_item = JsonObjectGet(info, extracted_key_value); + + Log(LOG_LEVEL_DEBUG, "While parsing %s, got key %s from line %s", filename, extracted_key_value, line); + + if (prev_item != NULL && tiebreak != NULL) + { + JsonElement *winner = (*tiebreak)(prev_item, item); + + if (winner == prev_item) + { + Log(LOG_LEVEL_DEBUG, "Multiple entries for key %s, preferring previous value", extracted_key_value); + + JsonDestroy(item); + item = NULL; + } + else + { + Log(LOG_LEVEL_DEBUG, "Multiple entries for key %s, preferring new value", extracted_key_value); + } + } + + if (item != NULL) + { + JsonObjectAppendElement(info, extracted_key_value, item); + } + } + } + else + { + JsonArrayAppendElement(info, item); + } + } + } + + free(line); + + if (key != NULL) + { + Buffer *varname = BufferNew(); + BufferPrintf(varname, "%s", key); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, BufferData(varname), info, CF_DATA_TYPE_CONTAINER, + "networking,/proc,source=agent,procfs"); + BufferDestroy(varname); + } + + RegexDestroy(regex); + } + + fclose(fin); + } + + return info; +} + +/*******************************************************************/ + +void GetNetworkingInfo(EvalContext *ctx) +{ + const char *procdir_root = GetRelocatedProcdirRoot(); + + Buffer *pbuf = BufferNew(); + + JsonElement *inet = JsonObjectCreate(2); + + BufferPrintf(pbuf, "%s/proc/net/netstat", procdir_root); + JsonElement *inet_stats = GetNetworkingStatsInfo(BufferData(pbuf)); + + if (inet_stats != NULL) + { + JsonObjectAppendElement(inet, "stats", inet_stats); + } + + BufferPrintf(pbuf, "%s/proc/net/route", procdir_root); + JsonElement *routes = GetProcFileInfo(ctx, BufferData(pbuf), NULL, NULL, &NetworkingRoutesPostProcessInfo, NULL, + // format: Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT + // eth0 00000000 0102A8C0 0003 0 0 1024 00000000 0 0 0 + "^(?\\S+)\\t(?[[:xdigit:]]+)\\t(?[[:xdigit:]]+)\\t(?[[:xdigit:]]+)\\t(?\\d+)\\t(?\\d+)\\t(?[[:xdigit:]]+)\\t(?[[:xdigit:]]+)\\t(?\\d+)\\t(?\\d+)\\t(?[[:xdigit:]]+)"); + + if (routes != NULL && + JsonGetElementType(routes) == JSON_ELEMENT_TYPE_CONTAINER) + { + JsonObjectAppendElement(inet, "routes", routes); + + JsonIterator iter = JsonIteratorInit(routes); + const JsonElement *default_route = NULL; + long lowest_metric = 0; + const JsonElement *route = NULL; + while ((route = JsonIteratorNextValue(&iter))) + { + JsonElement *active = JsonObjectGet(route, "active_default_gateway"); + if (active != NULL && + JsonGetElementType(active) == JSON_ELEMENT_TYPE_PRIMITIVE && + JsonGetPrimitiveType(active) == JSON_PRIMITIVE_TYPE_BOOL && + JsonPrimitiveGetAsBool(active)) + { + JsonElement *metric = JsonObjectGet(route, "metric"); + if (metric != NULL && + JsonGetElementType(metric) == JSON_ELEMENT_TYPE_PRIMITIVE && + JsonGetPrimitiveType(metric) == JSON_PRIMITIVE_TYPE_INTEGER && + (default_route == NULL || + JsonPrimitiveGetAsInteger(metric) < lowest_metric)) + { + default_route = route; + } + } + } + + if (default_route != NULL) + { + JsonObjectAppendString(inet, "default_gateway", JsonObjectGetAsString(default_route, "gateway")); + JsonObjectAppendElement(inet, "default_route", JsonCopy(default_route)); + } + } + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "inet", inet, CF_DATA_TYPE_CONTAINER, + "networking,/proc,source=agent,procfs"); + JsonDestroy(inet); + + JsonElement *inet6 = JsonObjectCreate(3); + + BufferPrintf(pbuf, "%s/proc/net/snmp6", procdir_root); + JsonElement *inet6_stats = GetProcFileInfo(ctx, BufferData(pbuf), NULL, NULL, NULL, NULL, + "^\\s*(?\\S+)\\s+(?\\d+)"); + + if (inet6_stats != NULL) + { + // map the key to the value (as a number) in the "stats" map + JsonElement *rewrite = JsonObjectCreate(JsonLength(inet6_stats)); + JsonIterator iter = JsonIteratorInit(inet6_stats); + const JsonElement *stat = NULL; + while ((stat = JsonIteratorNextValue(&iter))) + { + long num = 0; + const char* key = JsonObjectGetAsString(stat, "key"); + const char* value = JsonObjectGetAsString(stat, "value"); + if (key && value && + sscanf(value, "%ld", &num) == 1) + { + JsonObjectAppendInteger(rewrite, key, num); + } + } + + JsonObjectAppendElement(inet6, "stats", rewrite); + JsonDestroy(inet6_stats); + } + + BufferPrintf(pbuf, "%s/proc/net/ipv6_route", procdir_root); + JsonElement *inet6_routes = GetProcFileInfo(ctx, BufferData(pbuf), NULL, NULL, &NetworkingIPv6RoutesPostProcessInfo, NULL, + // format: dest dest_prefix source source_prefix next_hop metric refcnt use flags interface + // fe800000000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth0 + "^(?[[:xdigit:]]+)\\s+(?[[:xdigit:]]+)\\s+" + "(?[[:xdigit:]]+)\\s+(?[[:xdigit:]]+)\\s+" + "(?[[:xdigit:]]+)\\s+(?[[:xdigit:]]+)\\s+" + "(?\\d+)\\s+(?\\d+)\\s+" + "(?[[:xdigit:]]+)\\s+(?\\S+)"); + + if (inet6_routes != NULL) + { + JsonObjectAppendElement(inet6, "routes", inet6_routes); + } + + BufferPrintf(pbuf, "%s/proc/net/if_inet6", procdir_root); + JsonElement *inet6_addresses = GetProcFileInfo(ctx, BufferData(pbuf), NULL, "interface", &NetworkingIPv6AddressesPostProcessInfo, &NetworkingIPv6AddressesTiebreaker, + // format: address device_number prefix_length scope flags interface_name + // 00000000000000000000000000000001 01 80 10 80 lo + // fe80000000000000004249fffebdd7b4 04 40 20 80 docker0 + // fe80000000000000c27cd1fffe3eada6 02 40 20 80 enp4s0 + "^(?[[:xdigit:]]+)\\s+(?[[:xdigit:]]+)\\s+" + "(?[[:xdigit:]]+)\\s+(?[[:xdigit:]]+)\\s+" + "(?[[:xdigit:]]+)\\s+(?\\S+)"); + + if (inet6_addresses != NULL) + { + JsonObjectAppendElement(inet6, "addresses", inet6_addresses); + } + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "inet6", inet6, CF_DATA_TYPE_CONTAINER, + "networking,/proc,source=agent,procfs"); + JsonDestroy(inet6); + + // Inter-| Receive | Transmit + // face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + // eth0: 74850544807 75236137 0 0 0 0 0 1108775 63111535625 74696758 0 0 0 0 0 0 + + BufferPrintf(pbuf, "%s/proc/net/dev", procdir_root); + JsonElement *interfaces_data = + GetProcFileInfo(ctx, BufferData(pbuf), "interfaces_data", "device", NULL, NULL, + "^\\s*(?[^:]+)\\s*:\\s*" + // All of the below are just decimal digits separated by spaces + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)\\s+" + "(?\\d+)"); + JsonDestroy(interfaces_data); + BufferDestroy(pbuf); +} + +JsonElement* GetNetworkingConnections(EvalContext *ctx) +{ + const char *procdir_root = GetRelocatedProcdirRoot(); + JsonElement *json = JsonObjectCreate(5); + const char* ports_regex = "^\\s*\\d+:\\s+(?[0-9A-F:]+)\\s+(?[0-9A-F:]+)\\s+(?[0-9]+)"; + + JsonElement *data = NULL; + Buffer *pbuf = BufferNew(); + + BufferPrintf(pbuf, "%s/proc/net/tcp", procdir_root); + data = GetProcFileInfo(ctx, BufferData(pbuf), NULL, NULL, &NetworkingPortsPostProcessInfo, NULL, ports_regex); + if (data != NULL) + { + JsonObjectAppendElement(json, "tcp", data); + } + + BufferPrintf(pbuf, "%s/proc/net/tcp6", procdir_root); + data = GetProcFileInfo(ctx, BufferData(pbuf), NULL, NULL, &NetworkingPortsPostProcessInfo, NULL, ports_regex); + if (data != NULL) + { + JsonObjectAppendElement(json, "tcp6", data); + } + + BufferPrintf(pbuf, "%s/proc/net/udp", procdir_root); + data = GetProcFileInfo(ctx, BufferData(pbuf), NULL, NULL, &NetworkingPortsPostProcessInfo, NULL, ports_regex); + if (data != NULL) + { + JsonObjectAppendElement(json, "udp", data); + } + + BufferPrintf(pbuf, "%s/proc/net/udp6", procdir_root); + data = GetProcFileInfo(ctx, BufferData(pbuf), NULL, NULL, &NetworkingPortsPostProcessInfo, NULL, ports_regex); + if (data != NULL) + { + JsonObjectAppendElement(json, "udp6", data); + } + BufferDestroy(pbuf); + + if (JsonLength(json) < 1) + { + // nothing was collected, this is a failure + JsonDestroy(json); + return NULL; + } + + return json; +} + +#endif /* !__MINGW32__ */ diff --git a/libenv/zones.c b/libenv/zones.c new file mode 100644 index 0000000000..fea003df3d --- /dev/null +++ b/libenv/zones.c @@ -0,0 +1,106 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#ifdef HAVE_ZONE_H +# include +#endif + +#ifndef __MINGW32__ + +bool IsGlobalZone() +{ +#ifdef HAVE_GETZONEID + zoneid_t zid; + char zone[ZONENAME_MAX]; + + zid = getzoneid(); + getzonenamebyid(zid, zone, ZONENAME_MAX); + + if (strcmp(zone, "global") == 0) + { + return true; + } +#endif + + return false; +} + +bool ForeignZone(char *s) +{ +// We want to keep the banner + + if (strstr(s, "PID")) + { + return false; + } + +# ifdef HAVE_GETZONEID + zoneid_t zid; + char *sp, zone[ZONENAME_MAX]; + + zid = getzoneid(); + getzonenamebyid(zid, zone, ZONENAME_MAX); + + if (strcmp(zone, "global") == 0) + { + if (StringStartsWith(s, "global") && isspace(s[6])) + { + + for (sp = s + strlen(s) - 1; isspace(*sp); sp--) + { + *sp = '\0'; + } + + return false; + } + else + { + return true; + } + } +# endif + return false; +} + +#ifdef HAVE_GETZONEID +#define ZONE_ONLY +#else +#define ZONE_ONLY ARG_UNUSED +#endif +int CurrentZoneName(ZONE_ONLY char *s) +{ +# ifdef HAVE_GETZONEID + zoneid_t zid = getzoneid(); + + if (zid >= 0) + { + return getzonenamebyid(zid, s, ZONENAME_MAX); + } +# endif + return -1; +} +#endif // !__MINGW32__ diff --git a/libenv/zones.h b/libenv/zones.h new file mode 100644 index 0000000000..fb8534f309 --- /dev/null +++ b/libenv/zones.h @@ -0,0 +1,36 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ZONES_H +#define CFENGINE_ZONES_H + +#include + +#ifndef __MINGW32__ +bool IsGlobalZone(); +bool ForeignZone(char *s); +int CurrentZoneName(char *s);/* s must point to at least ZONENAME_MAX bytes of space */ +#endif + +#endif // ZONES_H diff --git a/libntech b/libntech new file mode 160000 index 0000000000..5f195b2449 --- /dev/null +++ b/libntech @@ -0,0 +1 @@ +Subproject commit 5f195b24496b28271f7f26a128ab37345d9d4dda diff --git a/libpromises/.gitignore b/libpromises/.gitignore new file mode 100644 index 0000000000..9c8939fd9e --- /dev/null +++ b/libpromises/.gitignore @@ -0,0 +1,3 @@ +bootstrap.inc +enterprise_extension.c +enterprise_extension.h diff --git a/libpromises/LMDB.md b/libpromises/LMDB.md new file mode 100644 index 0000000000..5fee023dd4 --- /dev/null +++ b/libpromises/LMDB.md @@ -0,0 +1,316 @@ +# Overview of LMDB databases in /var/cfengine/state + +Documentation to help understand the data in our LMDB databases. +This is not meant as user-level documentation. +It has technical details and shows C source code. + +## What is LMDB? + +Lightning Memory-mapped Database (LMDB) is a high performance local key-value store database. +We use LMDB to store data which is shared between different CFEngine binaries and agent runs. +Examples include incoming/outgoing network connections (cf_lastseen), promise locks (cf_lock), performance information and state data about the host. + +In our LMDB databases, we use NUL-terminated C strings as keys, and values may either be plain text (C strings) or binary data (C structs). +LMDB database files `*.lmdb` are platform specific, the representation and sizes of various types in C are different on different systems and architectures. + +## Getting a list of all databases + +### Community policy server + +On a fresh community install, these LMDB databases are created: + +``` +$ ls -al /var/cfengine/state/*.lmdb +-rw-r--r-- 1 root root 12288 Aug 23 08:37 /var/cfengine/state/cf_lastseen.lmdb +-rw------- 1 root root 32768 Aug 23 08:49 /var/cfengine/state/cf_lock.lmdb +-rw------- 1 root root 49152 Aug 23 08:49 /var/cfengine/state/cf_observations.lmdb +-rw-r--r-- 1 root root 8192 Aug 23 08:37 /var/cfengine/state/cf_state.lmdb +-rw------- 1 root root 16384 Aug 23 08:37 /var/cfengine/state/history.lmdb +-rw------- 1 root root 8192 Aug 23 08:37 /var/cfengine/state/nova_measures.lmdb +-rw------- 1 root root 8192 Aug 23 08:37 /var/cfengine/state/nova_static.lmdb +-rw------- 1 root root 581632 Aug 23 08:47 /var/cfengine/state/packages_installed_apt_get.lmdb +-rw-r--r-- 1 root root 8192 Aug 23 08:37 '/var/cfengine/state/packages_installed_$(package_module_knowledge.platform_default).lmdb' +-rw------- 1 root root 28672 Aug 23 08:47 /var/cfengine/state/packages_updates_apt_get.lmdb +-rw-r--r-- 1 root root 32768 Aug 23 08:47 /var/cfengine/state/performance.lmdb +``` + +### Enterprise hub package + +Similarly, installing an enterprise hub package: + +``` +$ ls -al /var/cfengine/state/*.lmdb +-rw------- 1 root root 32768 Aug 23 09:00 /var/cfengine/state/cf_changes.lmdb +-rw------- 1 root root 28672 Aug 23 09:00 /var/cfengine/state/cf_lastseen.lmdb +-rw------- 1 root root 61440 Aug 23 09:01 /var/cfengine/state/cf_lock.lmdb +-rw------- 1 root root 28672 Aug 23 09:00 /var/cfengine/state/cf_observations.lmdb +-rw------- 1 root root 20480 Aug 23 09:00 /var/cfengine/state/cf_state.lmdb +-rw------- 1 root root 16384 Aug 23 09:00 /var/cfengine/state/history.lmdb +-rw------- 1 root root 32768 Aug 23 09:01 /var/cfengine/state/nova_agent_execution.lmdb +-rw------- 1 root root 8192 Aug 23 09:00 /var/cfengine/state/nova_measures.lmdb +-rw------- 1 root root 20480 Aug 23 09:00 /var/cfengine/state/nova_static.lmdb +-rw------- 1 root root 12288 Aug 23 09:00 /var/cfengine/state/nova_track.lmdb +-rw------- 1 root root 425984 Aug 23 09:01 /var/cfengine/state/packages_installed_apt_get.lmdb +-rw------- 1 root root 8192 Aug 23 09:00 '/var/cfengine/state/packages_installed_$(package_module_knowledge.platform_default).lmdb' +-rw------- 1 root root 32768 Aug 23 09:01 /var/cfengine/state/packages_updates_apt_get.lmdb +-rw------- 1 root root 32768 Aug 23 09:01 /var/cfengine/state/performance.lmdb +``` + +### Examining the source code + +From `dbm_api.c`, these look like all the database files which are possible: + +```C +static const char *const DB_PATHS_STATEDIR[] = { + [dbid_classes] = "cf_classes", + [dbid_variables] = "cf_variables", + [dbid_performance] = "performance", + [dbid_checksums] = "checksum_digests", + [dbid_filestats] = "stats", + [dbid_changes] = "cf_changes", + [dbid_observations] = "cf_observations", + [dbid_state] = "cf_state", + [dbid_lastseen] = "cf_lastseen", + [dbid_audit] = "cf_audit", + [dbid_locks] = "cf_lock", + [dbid_history] = "history", + [dbid_measure] = "nova_measures", + [dbid_static] = "nova_static", + [dbid_scalars] = "nova_pscalar", + [dbid_windows_registry] = "mswin", + [dbid_cache] = "nova_cache", + [dbid_license] = "nova_track", + [dbid_value] = "nova_value", + [dbid_agent_execution] = "nova_agent_execution", + [dbid_bundles] = "bundles", + [dbid_packages_installed] = "packages_installed", + [dbid_packages_updates] = "packages_updates" +}; +``` + +Some of them are deprecated: + +```C +typedef enum +{ + dbid_classes, // Deprecated + dbid_variables, // Deprecated + dbid_performance, + dbid_checksums, // Deprecated + dbid_filestats, // Deprecated + dbid_changes, + dbid_observations, + dbid_state, + dbid_lastseen, + dbid_audit, + dbid_locks, + dbid_history, + dbid_measure, + dbid_static, + dbid_scalars, + dbid_windows_registry, + dbid_cache, + dbid_license, + dbid_value, + dbid_agent_execution, + dbid_bundles, // Deprecated + dbid_packages_installed, //new package promise installed packages list + dbid_packages_updates, //new package promise list of available updates + + dbid_max +} dbid; +``` + +## Individual database files + +### cf_lastseen.lmdb + +See `lastseen.c` for more details. + +#### Example cf-check dump + +``` +$ cf-check dump -a /var/cfengine/state/cf_lastseen.lmdb +key: 0x7fba331acf34[16] a192.168.100.10, data: 0x7fba331acf44[37] MD5=6fcc943142f461f4c0aa59abe871bc57 +key: 0x7fba331acf72[38] kMD5=6fcc943142f461f4c0aa59abe871bc57, data: 0x7fba331acf98[15] 192.168.100.10 +key: 0x7fba331acfb0[39] qoMD5=6fcc943142f461f4c0aa59abe871bc57, data: 0x7fba331acfd7[40] Fc] +``` + +#### Example mdb_dump + +``` +$ mdb_dump -n -p /var/cfengine/state/cf_lastseen.lmdb +VERSION=3 +format=print +type=btree +mapsize=104857600 +maxreaders=126 +db_pagesize=4096 +HEADER=END + a192.168.100.10\00 + MD5=6fcc943142f461f4c0aa59abe871bc57\00 + kMD5=6fcc943142f461f4c0aa59abe871bc57\00 + 192.168.100.10\00 + qoMD5=6fcc943142f461f4c0aa59abe871bc57\00 + F\bfc]\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00 +DATA=END +``` + +#### Version entry + +Denotes the database schema used by this database file. + +``` +key: "version" +value: "1" +``` + +#### Address entries (reverse lookup) + +``` +key: a
    (IPv6 or IPv6) +value: +``` + +#### Quality entries + +``` +key: q (direction: 'i' for incoming, 'o' for outgoing) +value: struct KeyHostSeen +``` + +##### KeyHostSeen struct + +From `lastseen.h`: + +```C +typedef struct +{ + time_t lastseen; + QPoint Q; +} KeyHostSeen; +``` + +The QPoint is a rolling weighted average of the time between connections: + +``` + newq.Q = QAverage(q.Q, newq.lastseen - q.lastseen, 0.4); +``` + +#### Hostkey entries +``` +key: k ("MD5-ffffefefeefef..." or "SHA-abacabaca...") +value:
    (IPv4 or IPv6) +``` + +### cf_lock.lmdb + +See `locks.c` for more details. + +#### Example cf-check dump + +``` +root@dev ~ $ cf-check dump -a /var/cfengine/state/cf_lock.lmdb +key: 0x7f6b0f4e4b36[33] 05315dcd049a9e89c6d85520d505f600, data: 0x7f6b0f4e4b57[24] = +key: 0x7f6b0f4e4c3e[33] 0c8f50c64db3673c7f2d8eca5d8a475b, data: 0x7f6b0f4e4c5f[24] = +key: 0x7f6b0f4e48a2[33] 0f121ab519b8b23bae54714d3f1a83e1, data: 0x7f6b0f4e48c3[24] = +key: 0x7f6b0f4e4d74[33] 37e5b5e86d2e7474b4106be6f395401c, data: 0x7f6b0f4e4d95[24] = +key: 0x7f6b0f4e49aa[33] 398e0e0964c2608b419b50cf5d038fd6, data: 0x7f6b0f4e49cb[24] = +[...] +key: 0x7f6b0f4e4d46[13] lock_horizon, data: 0x7f6b0f4e4d53[24] +root@dev ~ $ +``` + +#### Example mdb_dump + +``` +root@dev ~ $ mdb_dump -n -p /var/cfengine/state/cf_lock.lmdb +VERSION=3 +format=print +type=btree +mapsize=104857600 +maxreaders=210 +db_pagesize=4096 +HEADER=END + 05315dcd049a9e89c6d85520d505f600\00 + \f1=\00\00\00\00\00\00,\d4c]\00\00\00\00r\16\00\00\00\00\00\00 + 0c8f50c64db3673c7f2d8eca5d8a475b\00 + \f1=\00\00\00\00\00\00,\d4c]\00\00\00\00r\16\00\00\00\00\00\00 + 0f121ab519b8b23bae54714d3f1a83e1\00 + \f1=\00\00\00\00\00\00,\d4c]\00\00\00\00r\16\00\00\00\00\00\00 + 37e5b5e86d2e7474b4106be6f395401c\00 + \f1=\00\00\00\00\00\00,\d4c]\00\00\00\00r\16\00\00\00\00\00\00 + 398e0e0964c2608b419b50cf5d038fd6\00 + \f1=\00\00\00\00\00\00,\d4c]\00\00\00\00r\16\00\00\00\00\00\00 +[...] + lock_horizon\00 + \00\00\00\00\00\00\00\00F\bfc]\00\00\00\00\00\00\00\00\00\00\00\00 +DATA=END +root@dev ~ $ +``` + +#### Lock keys + +For each promise, 2 strings are generated like this: + +```C +char cflock[CF_BUFSIZE] = ""; +snprintf(cflock, CF_BUFSIZE, "lock.%.100s.%s.%.100s_%d_%s", + bundle_name, cc_operator, cc_operand, sum, str_digest); + +char cflast[CF_BUFSIZE] = ""; +snprintf(cflast, CF_BUFSIZE, "last.%.100s.%s.%.100s_%d_%s", + bundle_name, cc_operator, cc_operand, sum, str_digest); + +Log(LOG_LEVEL_DEBUG, "Locking bundle '%s' with lock '%s'", + bundle_name, cflock); +``` + +The result can be seen in debug log: + +``` +root@dev ~ $ cf-agent --log-level debug | grep -F Locking + debug: Locking bundle 'inventory_control' with lock 'lock.inventory_control.reports.-dev.inventory_control__LSB_module_enabled_5317_MD5=17cc5c06fc415f344762d927f53723b7' + debug: Locking bundle 'inventory_control' with lock 'lock.inventory_control.reports.-dev.inventory_control__dmidecode_module_enabled_4618_MD5=ecdad15e8a457659d321be7aaf3465e0' + debug: Locking bundle 'inventory_control' with lock 'lock.inventory_control.reports.-dev.inventory_control__mtab_module_enabled_5516_MD5=926e972f2d6bee317a0c1b09c0a05029' + debug: Locking bundle 'inventory_control' with lock 'lock.inventory_control.reports.-dev.inventory_control__fstab_module_enabled_4364_MD5=f743f4810ceac5dc7f29c246b27ef4f4' + debug: Locking bundle 'inventory_control' with lock 'lock.inventory_control.reports.-dev.inventory_control__proc_module_enabled_5328_MD5=b55e6f18cba2d498fc49cf9643edc9fe' + debug: Locking bundle 'inventory_control' with lock 'lock.inventory_control.reports.-dev.inventory_control__package_refresh_module_enabled_5998_MD5=8ed765eab0a379b899bb20ffff361149' + debug: Locking bundle 'inventory_autorun' with lock 'lock.inventory_autorun.methods.usebundle.handle.-dev.method_packages_refresh__7849_MD5=bf16d30d8791bfd0e9b9bdde5a2e3f1d' +[...] +root@dev ~ $ +``` + +These strings are then hashed using MD5, and the MD5 digest is used as a key in LMDB. + +#### Lock values + +This is the C struct stored as a value in LMDB: + +```C +typedef struct +{ + pid_t pid; + time_t time; + time_t process_start_time; +} LockData; +``` + +Seems to be created like this: + +```C +static bool WriteLockDataCurrent(CF_DB *dbp, const char *lock_id) +{ + LockData lock_data = { 0 }; + lock_data.pid = getpid(); + lock_data.time = time(NULL); + lock_data.process_start_time = GetProcessStartTime(getpid()); + + return WriteLockData(dbp, lock_id, &lock_data); +} +``` + +Quite simple: + +* A PID and a start time of the process which locked this promise. +* The time the promise was locked. diff --git a/libpromises/Makefile.am b/libpromises/Makefile.am new file mode 100644 index 0000000000..4162972d18 --- /dev/null +++ b/libpromises/Makefile.am @@ -0,0 +1,268 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +if !BUILTIN_EXTENSIONS +projlib_LTLIBRARIES = libpromises.la +AM_LDFLAGS = -version-info 3:6:0 -no-undefined +else +noinst_LTLIBRARIES = libpromises.la +AM_LDFLAGS = +endif + +AM_LDFLAGS += $(CORE_LDFLAGS) $(LMDB_LDFLAGS) $(TOKYOCABINET_LDFLAGS) $(QDBM_LDFLAGS) \ + $(PCRE2_LDFLAGS) $(OPENSSL_LDFLAGS) $(SQLITE3_LDFLAGS) $(LIBACL_LDFLAGS) $(LIBYAML_LDFLAGS) $(LIBCURL_LDFLAGS) + +AM_CPPFLAGS = \ + -I$(srcdir)/../libntech/libutils -I$(srcdir)/../libcfecompat \ + -I$(srcdir)/../libcfnet \ + -I$(srcdir)/../libenv \ + -I$(srcdir)/../cf-check \ + $(CORE_CPPFLAGS) $(ENTERPRISE_CPPFLAGS) \ + $(LMDB_CPPFLAGS) $(TOKYOCABINET_CPPFLAGS) $(QDBM_CPPFLAGS) \ + $(PCRE2_CPPFLAGS) $(OPENSSL_CPPFLAGS) $(SQLITE3_CPPFLAGS) $(LIBACL_CPPFLAGS) $(LIBYAML_CPPFLAGS) $(LIBCURL_CPPFLAGS) + +AM_CFLAGS = $(CORE_CFLAGS) $(ENTERPRISE_CFLAGS) \ + $(LMDB_CFLAGS) $(TOKYOCABINET_CFLAGS) $(QDBM_CFLAGS) \ + $(PCRE2_CFLAGS) $(OPENSSL_CFLAGS) $(SQLITE3_CFLAGS) $(LIBACL_CFLAGS) $(LIBYAML_CFLAGS) $(LIBCURL_CFLAGS) + +AM_YFLAGS = -d + +LIBS = $(LMDB_LIBS) $(TOKYOCABINET_LIBS) $(QDBM_LIBS) \ + $(PCRE2_LIBS) $(OPENSSL_LIBS) $(SQLITE3_LIBS) $(LIBACL_LIBS) $(LIBYAML_LIBS) $(LIBCURL_LIBS) + +# The lib providing sd_listen_fds() is not needed in libpromises, it's actually +# needed in libcfnet. But adding it here is an easy way to make sure it's +# available for all the binaries that might need it. Once libcfnet doesn't +# require libpromises, we can do this properly. +LIBS += $(SYSTEMD_SOCKET_LIBS) + +libpromises_la_LIBADD = ../libntech/libutils/libutils.la ../libcfecompat/libcfecompat.la \ + ../libcfnet/libcfnet.la \ + ../libenv/libenv.la $(ENTERPRISE_LDADD) + +libpromises_la_SOURCES = \ + acl_tools.h acl_tools_posix.c \ + actuator.c actuator.h \ + assoc.c assoc.h \ + attributes.c attributes.h \ + audit.c audit.h \ + bootstrap.c bootstrap.h bootstrap.inc failsafe.cf \ + cf-windows-functions.h \ + cf3.defs.h \ + cf3.extern.h \ + cf3globals.c \ + cf3lex.l \ + cf3parse.y cf3parse.h \ + cf3parse_logic.h \ + changes_chroot.c changes_chroot.h \ + chflags.c chflags.h \ + class.c class.h \ + cmdb.c cmdb.h \ + constants.c \ + conversion.c conversion.h \ + crypto.c crypto.h \ + dbm_api.c dbm_api.h dbm_api_types.h dbm_priv.h \ + dbm_migration.c dbm_migration.h \ + dbm_migration_lastseen.c \ + dbm_lmdb.c \ + dbm_quick.c \ + dbm_tokyocab.c \ + enterprise_stubs.c enterprise_extension.c enterprise_extension.h \ + eval_context.c eval_context.h \ + evalfunction.c evalfunction.h \ + exec_tools.c exec_tools.h \ + expand.c expand.h \ + extensions.c extensions.h \ + extensions_template.c.pre extensions_template.h.pre \ + feature.c feature.h \ + files_copy.c files_copy.h \ + files_interfaces.c files_interfaces.h \ + files_lib.c files_lib.h \ + files_links.c files_links.h \ + files_names.c files_names.h \ + files_operators.c files_operators.h \ + files_repository.c files_repository.h \ + fncall.c fncall.h \ + generic_agent.c generic_agent.h \ + global_mutex.c global_mutex.h \ + granules.c granules.h \ + instrumentation.c instrumentation.h \ + item_lib.c item_lib.h \ + iteration.c iteration.h \ + keyring.c keyring.h \ + lastseen.c lastseen.h \ + loading.c loading.h \ + locks.c locks.h \ + logic_expressions.c logic_expressions.h \ + matching.c matching.h \ + match_scope.c match_scope.h \ + math_eval.c math_eval.h math.pc \ + mod_access.c mod_access.h \ + mod_common.c mod_common.h \ + mod_custom.c mod_custom.h \ + mod_databases.c mod_databases.h \ + mod_environ.c mod_environ.h \ + mod_exec.c mod_exec.h \ + mod_files.c mod_files.h \ + mod_measurement.c mod_measurement.h \ + mod_methods.c mod_methods.h \ + mod_outputs.c mod_outputs.h \ + mod_packages.c mod_packages.h \ + mod_process.c mod_process.h \ + mod_report.c mod_report.h \ + mod_services.c mod_services.h \ + mod_storage.c mod_storage.h \ + mod_knowledge.c mod_knowledge.h \ + mod_users.c mod_users.h \ + modes.c \ + monitoring_read.c monitoring_read.h \ + ornaments.c ornaments.h \ + policy.c policy.h \ + parser.c parser.h \ + parser_helpers.h \ + parser_state.h \ + patches.c \ + pipes.h pipes.c \ + processes_select.c processes_select.h \ + process_lib.h process_unix_priv.h \ + promises.c promises.h \ + prototypes3.h \ + rlist.c rlist.h \ + scope.c scope.h \ + shared_lib.c shared_lib.h \ + signals.c signals.h \ + sort.c sort.h \ + storage_tools.c \ + string_expressions.c string_expressions.h \ + syntax.c syntax.h \ + syslog_client.c syslog_client.h \ + systype.c systype.h \ + timeout.c timeout.h \ + unix.c unix.h \ + var_expressions.c var_expressions.h \ + variable.c variable.h \ + vars.c vars.h \ + verify_classes.c verify_classes.h \ + verify_reports.c \ + verify_vars.c verify_vars.h \ + ../cf-check/backup.c ../cf-check/backup.h \ + ../cf-check/diagnose.c ../cf-check/diagnose.h \ + ../cf-check/lmdump.c ../cf-check/lmdump.h \ + ../cf-check/repair.c ../cf-check/repair.h \ + ../cf-check/replicate_lmdb.c ../cf-check/replicate_lmdb.h \ + ../cf-check/utilities.c ../cf-check/utilities.h \ + ../cf-check/validate.c ../cf-check/validate.h + +if !NT + +libpromises_la_SOURCES += \ + process_unix.c \ + pipes_unix.c + +if LINUX +libpromises_la_SOURCES += \ + process_linux.c +endif + +if AIX +libpromises_la_SOURCES += \ + process_aix.c +endif + +if HPUX +libpromises_la_SOURCES += \ + process_hpux.c +endif + +if SOLARIS +libpromises_la_SOURCES += \ + process_solaris.c +endif + +if FREEBSD +libpromises_la_SOURCES += \ + process_freebsd.c +endif + +if !LINUX +if !AIX +if !HPUX +if !SOLARIS +if !FREEBSD +libpromises_la_SOURCES += \ + process_unix_stub.c +endif +endif +endif +endif +endif + +endif # !NT + +if !NDEBUG +if LINUX +libpromises_la_SOURCES += \ + dbm_test_api.c dbm_test_api.h +endif +endif + + +########## Sources pre-generated and distributed in the tarball ########## +# Make sure you include the generated file as a dependency to a target +# (e.g. libpromises_la) so that it's generated during "make dist" + +BUILT_SOURCES = cf3lex.c cf3parse.h cf3parse.c \ + enterprise_extension.c enterprise_extension.h \ + bootstrap.inc +EXTRA_SCRIPTS = text2cstring.pl enterprise_extension.sed +EXTRA_DIST = $(EXTRA_SCRIPTS) + +enterprise_extension.c: extensions_template.c.pre enterprise_extension.sed + $(V_SED) $(SED) -f $(srcdir)/enterprise_extension.sed $< > $@ +enterprise_extension.h: extensions_template.h.pre enterprise_extension.sed + $(V_SED) $(SED) -f $(srcdir)/enterprise_extension.sed $< > $@ + +bootstrap.inc: failsafe.cf text2cstring.pl + $(V_PERL) $(PERL) $(srcdir)/text2cstring.pl $< > $@ + + +# +# Proper compilation progress printing +# +V_PERL = $(cf__v_PERL_$(V)) +cf__v_PERL_ = $(cf__v_PERL_$(AM_DEFAULT_VERBOSITY)) +cf__v_PERL_0 = @echo " PERL " "$@"; +cf__v_PERL_1 = +V_SED = $(cf__v_SED_$(V)) +cf__v_SED_ = $(cf__v_SED_$(AM_DEFAULT_VERBOSITY)) +cf__v_SED_0 = @echo " SED " "$@"; +cf__v_SED_1 = + + +# +# Some basic clean ups +# +# Use "make mostlyclean" if you want to keep the BUILT_SOURCES +# +CLEANFILES = $(BUILT_SOURCES) +MOSTLYCLEANFILES = *.gcno *.gcda *~ *.orig *.rej diff --git a/libpromises/acl_tools.h b/libpromises/acl_tools.h new file mode 100644 index 0000000000..80d1176db4 --- /dev/null +++ b/libpromises/acl_tools.h @@ -0,0 +1,43 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ACL_TOOLS_H +#define CFENGINE_ACL_TOOLS_H + +bool CopyACLs(const char *src, const char *dst, bool *change); + +/** + * Allow access to the given file ONLY for the given users using ACLs. Existing + * 'user:...' ACL entries are replaced by new entries allowing access for the + * given users. + * + * @param path file to set the ACLs on + * @param users users to allow access for + * @param allow_writes whether to allow write access (read access is always allowed) + * @param allow_execute whether to allow execute access (read access is always allowed) + * @return Whether the change of ACLs was successful or not + */ +bool AllowAccessForUsers(const char *path, StringSet *users, bool allow_writes, bool allow_execute); + +#endif // CFENGINE_ACL_TOOLS_H diff --git a/libpromises/acl_tools_posix.c b/libpromises/acl_tools_posix.c new file mode 100644 index 0000000000..5fc2304763 --- /dev/null +++ b/libpromises/acl_tools_posix.c @@ -0,0 +1,279 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include + +#ifdef HAVE_ACL_H +# include +#endif + +#ifdef HAVE_SYS_ACL_H +# include +#endif + +#ifdef HAVE_ACL_LIBACL_H +# include +#endif + +#ifdef HAVE_LIBACL +#include /* GetUserID() */ + +bool CopyACLs(const char *src, const char *dst, bool *change) +{ + acl_t acls; + struct stat statbuf; + int ret; + + acls = acl_get_file(src, ACL_TYPE_ACCESS); + if (!acls) + { + if (errno == ENOTSUP) + { + if (change != NULL) + { + *change = false; + } + return true; + } + else + { + Log(LOG_LEVEL_ERR, "Can't copy ACLs from '%s'. (acl_get_file: %s)", src, GetErrorStr()); + return false; + } + } + ret = acl_set_file(dst, ACL_TYPE_ACCESS, acls); + acl_free(acls); + if (ret != 0) + { + if (errno == ENOTSUP) + { + if (change != NULL) + { + *change = false; + } + return true; + } + else + { + Log(LOG_LEVEL_ERR, "Can't copy ACLs to '%s'. (acl_set_file: %s)", dst, GetErrorStr()); + return false; + } + } + + if (stat(src, &statbuf) != 0) + { + Log(LOG_LEVEL_ERR, "Can't copy ACLs from '%s'. (stat: %s)", src, GetErrorStr()); + return false; + } + if (!S_ISDIR(statbuf.st_mode)) + { + if (change != NULL) + { + *change = false; + } + return true; + } + + // For directory, copy default ACL too. + acls = acl_get_file(src, ACL_TYPE_DEFAULT); + if (!acls) + { + Log(LOG_LEVEL_ERR, "Can't copy ACLs from '%s'. (acl_get_file: %s)", src, GetErrorStr()); + return false; + } + ret = acl_set_file(dst, ACL_TYPE_DEFAULT, acls); + acl_free(acls); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Can't copy ACLs to '%s'. (acl_set_file: %s)", dst, GetErrorStr()); + return false; + } + if (change != NULL) + { + *change = true; + } + return true; +} + +bool AllowAccessForUsers(const char *path, StringSet *users, bool allow_writes, bool allow_execute) +{ + assert(path != NULL); + assert(users != NULL); + + acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS); + if (acl == NULL) + { + Log(LOG_LEVEL_ERR, "Failed to get ACLs for '%s': %s", path, GetErrorStr()); + return false; + } + + acl_entry_t entry; + int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); + while (ret > 0) + { + acl_tag_t entry_tag; + if (acl_get_tag_type(entry, &entry_tag) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to get ACL entry type: %s", GetErrorStr()); + acl_free(acl); + return false; + } + if (entry_tag == ACL_USER) + { + if (acl_delete_entry(acl, entry) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to remove user ACL entry: %s", GetErrorStr()); + acl_free(acl); + return false; + } + } + ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); + } + if (ret == -1) + { + Log(LOG_LEVEL_ERR, "Failed to get ACL entries: %s", GetErrorStr()); + acl_free(acl); + return false; + } + + StringSetIterator iter = StringSetIteratorInit(users); + const char *user = NULL; + while ((user = StringSetIteratorNext(&iter)) != NULL) + { + uid_t uid; + if (!GetUserID(user, &uid, LOG_LEVEL_ERR)) + { + /* errors already logged */ + acl_free(acl); + return false; + } + + if (acl_create_entry(&acl, &entry) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to create a new ACL entry: %s", GetErrorStr()); + acl_free(acl); + return false; + } + if (acl_set_tag_type(entry, ACL_USER) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to set ACL entry type: %s", GetErrorStr()); + acl_free(acl); + return false; + } + if (acl_set_qualifier(entry, &uid) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to set ACL entry qualifier: %s", GetErrorStr()); + acl_free(acl); + return false; + } + + acl_permset_t permset; + if (acl_get_permset(entry, &permset) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to get permset: %s", GetErrorStr()); + acl_free(acl); + return false; + } + if (acl_clear_perms(permset) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to clear permset: %s", GetErrorStr()); + acl_free(acl); + return false; + } + + if (acl_add_perm(permset, ACL_READ) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to add read permission to set: %s", GetErrorStr()); + acl_free(acl); + return false; + } + + if (allow_writes && (acl_add_perm(permset, ACL_WRITE) == -1)) + { + Log(LOG_LEVEL_ERR, "Failed to add write permission to set: %s", GetErrorStr()); + acl_free(acl); + return false; + } + + if (allow_execute && (acl_add_perm(permset, ACL_EXECUTE) == -1)) + { + Log(LOG_LEVEL_ERR, "Failed to add execute permission to set: %s", GetErrorStr()); + acl_free(acl); + return false; + } + + if (acl_set_permset(entry, permset) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to set permset: %s", GetErrorStr()); + acl_free(acl); + return false; + } + } + + if (acl_calc_mask(&acl) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to recalculate mask: %s", GetErrorStr()); + acl_free(acl); + return false; + } + + if (acl_valid(acl) != 0) + { + Log(LOG_LEVEL_ERR, "Ended up with an invalid ACL"); + acl_free(acl); + return false; + } + + if (acl_set_file(path, ACL_TYPE_ACCESS, acl) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to set ACL: %s", GetErrorStr()); + acl_free(acl); + return false; + } + + acl_free(acl); + return true; +} + +#elif !defined(__MINGW32__) /* !HAVE_LIBACL */ + +bool CopyACLs(ARG_UNUSED const char *src, ARG_UNUSED const char *dst, bool *change) +{ + if (change != NULL) + { + *change = false; + } + return true; +} + +bool AllowAccessForUsers(ARG_UNUSED const char *path, ARG_UNUSED StringSet *users, + ARG_UNUSED bool allow_writes, ARG_UNUSED bool allow_execute) +{ + Log(LOG_LEVEL_ERR, "ACL manipulation not supported"); + return false; +} +#endif diff --git a/libpromises/actuator.c b/libpromises/actuator.c new file mode 100644 index 0000000000..5bf7fda34d --- /dev/null +++ b/libpromises/actuator.c @@ -0,0 +1,91 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#include + +#include + +PromiseResult PromiseResultUpdate(PromiseResult prior, PromiseResult evidence) +{ + switch (prior) + { + case PROMISE_RESULT_DENIED: + case PROMISE_RESULT_FAIL: + case PROMISE_RESULT_INTERRUPTED: + case PROMISE_RESULT_TIMEOUT: + return prior; + + case PROMISE_RESULT_WARN: + switch (evidence) + { + case PROMISE_RESULT_DENIED: + case PROMISE_RESULT_FAIL: + case PROMISE_RESULT_INTERRUPTED: + case PROMISE_RESULT_TIMEOUT: + case PROMISE_RESULT_WARN: + return evidence; + + case PROMISE_RESULT_CHANGE: + case PROMISE_RESULT_NOOP: + case PROMISE_RESULT_SKIPPED: + return prior; + default: + ProgrammingError("Unexpected promise result"); + } + case PROMISE_RESULT_SKIPPED: + return evidence; + + case PROMISE_RESULT_NOOP: + if (evidence == PROMISE_RESULT_SKIPPED) + { + return prior; + } + else + { + return evidence; + } + + case PROMISE_RESULT_CHANGE: + switch (evidence) + { + case PROMISE_RESULT_DENIED: + case PROMISE_RESULT_FAIL: + case PROMISE_RESULT_INTERRUPTED: + case PROMISE_RESULT_TIMEOUT: + case PROMISE_RESULT_WARN: + return evidence; + + case PROMISE_RESULT_CHANGE: + case PROMISE_RESULT_NOOP: + case PROMISE_RESULT_SKIPPED: + return prior; + } + } + + ProgrammingError("Never reach"); +} + +bool PromiseResultIsOK(PromiseResult result) +{ + return (result == PROMISE_RESULT_CHANGE) || (result == PROMISE_RESULT_NOOP); +} diff --git a/libpromises/actuator.h b/libpromises/actuator.h new file mode 100644 index 0000000000..b5f01cf898 --- /dev/null +++ b/libpromises/actuator.h @@ -0,0 +1,35 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ACTUATOR_H +#define CFENGINE_ACTUATOR_H + +#include + +typedef PromiseResult PromiseActuator(EvalContext *ctx, const Promise *pp, void *param); + +PromiseResult PromiseResultUpdate(PromiseResult prior, PromiseResult evidence); +bool PromiseResultIsOK(PromiseResult result); + +#endif diff --git a/libpromises/assoc.c b/libpromises/assoc.c new file mode 100644 index 0000000000..41c42d79f2 --- /dev/null +++ b/libpromises/assoc.c @@ -0,0 +1,58 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +CfAssoc *NewAssoc(const char *lval, Rval rval, DataType dt) +{ + CfAssoc *ap; + + ap = xmalloc(sizeof(CfAssoc)); + +/* Make a private copy because promises are ephemeral in expansion phase */ + + ap->lval = xstrdup(lval); + ap->rval = RvalCopy(rval); + ap->dtype = dt; + + return ap; +} + +/*******************************************************************/ + +void DeleteAssoc(CfAssoc *ap) +{ + if (ap == NULL) + { + return; + } + + free(ap->lval); + RvalDestroy(ap->rval); + + free(ap); + +} diff --git a/libpromises/assoc.h b/libpromises/assoc.h new file mode 100644 index 0000000000..aa92b42871 --- /dev/null +++ b/libpromises/assoc.h @@ -0,0 +1,41 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ASSOC_H +#define CFENGINE_ASSOC_H + +#include + +/* variable reference linkage , with metatype*/ +typedef struct +{ + char *lval; + Rval rval; + DataType dtype; +} CfAssoc; + +CfAssoc *NewAssoc(const char *lval, Rval rval, DataType dt); +void DeleteAssoc(CfAssoc *ap); + +#endif diff --git a/libpromises/attributes.c b/libpromises/attributes.c new file mode 100644 index 0000000000..f1f0f86e74 --- /dev/null +++ b/libpromises/attributes.c @@ -0,0 +1,1867 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include // ParseProtocolVersionPolicy() +#include /* StringEqual() */ + +#define CF_DEFINECLASSES "classes" +#define CF_TRANSACTION "action" + +static FilePerms GetPermissionConstraints(const EvalContext *ctx, const Promise *pp); +static TransactionContext GetTransactionConstraints(const EvalContext *ctx, const Promise *pp); + +void ClearFilesAttributes(Attributes *whom) +{ + UidListDestroy(whom->perms.owners); + GidListDestroy(whom->perms.groups); +} + +Attributes GetFilesAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + +// default for file copy + + attr.havedepthsearch = PromiseGetConstraintAsBoolean(ctx, "depth_search", pp); + attr.haveselect = PromiseGetConstraintAsBoolean(ctx, "file_select", pp); + attr.haverename = PromiseGetConstraintAsBoolean(ctx, "rename", pp); + attr.havedelete = PromiseGetConstraintAsBoolean(ctx, "delete", pp); + attr.content = PromiseGetConstraintAsRval(pp, "content", RVAL_TYPE_SCALAR); + attr.haveperms = PromiseGetConstraintAsBoolean(ctx, "perms", pp); + attr.havechange = PromiseGetConstraintAsBoolean(ctx, "changes", pp); + attr.havecopy = PromiseGetConstraintAsBoolean(ctx, "copy_from", pp); + attr.havelink = PromiseGetConstraintAsBoolean(ctx, "link_from", pp); + + attr.edit_template = PromiseGetConstraintAsRval(pp, "edit_template", RVAL_TYPE_SCALAR); + attr.edit_template_string = PromiseGetConstraintAsRval(pp, "edit_template_string", RVAL_TYPE_SCALAR); + attr.template_method = PromiseGetConstraintAsRval(pp, "template_method", RVAL_TYPE_SCALAR); + attr.template_data = PromiseGetConstraintAsRval(pp, "template_data", RVAL_TYPE_CONTAINER); + + if (!attr.template_method ) + { + attr.template_method = "cfengine"; + } + + attr.haveeditline = PromiseBundleOrBodyConstraintExists(ctx, "edit_line", pp); + attr.haveeditxml = PromiseBundleOrBodyConstraintExists(ctx, "edit_xml", pp); + attr.haveedit = (attr.haveeditline) || (attr.haveeditxml) || (attr.edit_template) || (attr.edit_template_string); + +/* Files, specialist */ + + attr.repository = PromiseGetConstraintAsRval(pp, "repository", RVAL_TYPE_SCALAR); + attr.create = PromiseGetConstraintAsBoolean(ctx, "create", pp); + attr.touch = PromiseGetConstraintAsBoolean(ctx, "touch", pp); + attr.transformer = PromiseGetConstraintAsRval(pp, "transformer", RVAL_TYPE_SCALAR); + attr.move_obstructions = PromiseGetConstraintAsBoolean(ctx, "move_obstructions", pp); + attr.pathtype = PromiseGetConstraintAsRval(pp, "pathtype", RVAL_TYPE_SCALAR); + attr.file_type = PromiseGetConstraintAsRval(pp, "file_type", RVAL_TYPE_SCALAR); + + attr.acl = GetAclConstraints(ctx, pp); + attr.perms = GetPermissionConstraints(ctx, pp); + attr.select = GetSelectConstraints(ctx, pp); + attr.delete = GetDeleteConstraints(ctx, pp); + attr.rename = GetRenameConstraints(ctx, pp); + attr.change = GetChangeMgtConstraints(ctx, pp); + attr.copy = GetCopyConstraints(ctx, pp); + attr.link = GetLinkConstraints(ctx, pp); + attr.edits = GetEditDefaults(ctx, pp); + + if (attr.edit_template || attr.edit_template_string) + { + attr.edits.empty_before_use = true; + attr.edits.inherit = true; + } + +/* Files, multiple use */ + + attr.recursion = GetRecursionConstraints(ctx, pp); + +/* Common ("included") */ + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +Attributes GetReportsAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.transaction = GetTransactionConstraints(ctx, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + attr.report = GetReportConstraints(ctx, pp); + return attr; +} + +/*******************************************************************/ + +Attributes GetEnvironmentsAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.transaction = GetTransactionConstraints(ctx, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + attr.env = GetEnvironmentsConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +Attributes GetServicesAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.transaction = GetTransactionConstraints(ctx, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + attr.service = GetServicesConstraints(ctx, pp); + attr.havebundle = PromiseBundleOrBodyConstraintExists(ctx, "service_bundle", pp); + + return attr; +} + +/*******************************************************************/ + +Attributes GetPackageAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.transaction = GetTransactionConstraints(ctx, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + attr.packages = GetPackageConstraints(ctx, pp); + attr.new_packages = GetNewPackageConstraints(ctx, pp); + return attr; +} + +/*******************************************************************/ + +static User GetUserConstraints(const EvalContext *ctx, const Promise *pp) +{ + User u; + char *value; + + value = PromiseGetConstraintAsRval(pp, "policy", RVAL_TYPE_SCALAR); + u.policy = UserStateFromString(value); + + u.uid = PromiseGetConstraintAsRval(pp, "uid", RVAL_TYPE_SCALAR); + + value = PromiseGetConstraintAsRval(pp, "format", RVAL_TYPE_SCALAR); + u.password_format = PasswordFormatFromString(value); + u.password = PromiseGetConstraintAsRval(pp, "data", RVAL_TYPE_SCALAR); + u.description = PromiseGetConstraintAsRval(pp, "description", RVAL_TYPE_SCALAR); + + u.group_primary = PromiseGetConstraintAsRval(pp, "group_primary", RVAL_TYPE_SCALAR); + u.home_dir = PromiseGetConstraintAsRval(pp, "home_dir", RVAL_TYPE_SCALAR); + u.shell = PromiseGetConstraintAsRval(pp, "shell", RVAL_TYPE_SCALAR); + + u.groups_secondary = PromiseGetConstraintAsList(ctx, "groups_secondary", pp); + + const Constraint *cp = PromiseGetImmediateConstraint(pp, "groups_secondary"); + u.groups_secondary_given = (cp != NULL); + + if (value && ((u.policy) == USER_STATE_NONE)) + { + Log(LOG_LEVEL_ERR, "Unsupported user policy '%s' in users promise", value); + PromiseRef(LOG_LEVEL_ERR, pp); + } + + return u; +} + +Attributes GetUserAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.havebundle = PromiseBundleOrBodyConstraintExists(ctx, "home_bundle", pp); + + attr.inherit = PromiseGetConstraintAsBoolean(ctx, "home_bundle_inherit", pp); + + attr.transaction = GetTransactionConstraints(ctx, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + attr.users = GetUserConstraints(ctx, pp); + return attr; +} + +/*******************************************************************/ + +Attributes GetDatabaseAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.transaction = GetTransactionConstraints(ctx, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + attr.database = GetDatabaseConstraints(ctx, pp); + return attr; +} + +/*******************************************************************/ + +Attributes GetClassContextAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes a = ZeroAttributes; + + a.transaction = GetTransactionConstraints(ctx, pp); + a.classes = GetClassDefinitionConstraints(ctx, pp); + a.context = GetContextConstraints(ctx, pp); + + return a; +} + +/*******************************************************************/ + +Attributes GetExecAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.contain = GetExecContainConstraints(ctx, pp); + attr.havecontain = PromiseGetConstraintAsBoolean(ctx, "contain", pp); + + attr.args = PromiseGetConstraintAsRval(pp, "args", RVAL_TYPE_SCALAR); + attr.arglist = PromiseGetConstraintAsList(ctx, "arglist", pp); + attr.module = PromiseGetConstraintAsBoolean(ctx, "module", pp); + + // Possible to suppress info level messages per commands promise: + if (PromiseBundleOrBodyConstraintExists(ctx, "inform", pp)) + { + attr.inform = PromiseGetConstraintAsBoolean(ctx, "inform", pp); + } + else + { + attr.inform = true; // Default to printing the inform messages + } + +/* Common ("included") */ + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +Attributes GetProcessAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.signals = PromiseGetConstraintAsList(ctx, "signals", pp); + attr.process_stop = PromiseGetConstraintAsRval(pp, "process_stop", RVAL_TYPE_SCALAR); + attr.haveprocess_count = PromiseGetConstraintAsBoolean(ctx, "process_count", pp); + attr.haveselect = PromiseGetConstraintAsBoolean(ctx, "process_select", pp); + attr.restart_class = PromiseGetConstraintAsRval(pp, "restart_class", RVAL_TYPE_SCALAR); + + attr.process_count = GetMatchesConstraints(ctx, pp); + attr.process_select = GetProcessFilterConstraints(ctx, pp); + +/* Common ("included") */ + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +Attributes GetStorageAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.mount = GetMountConstraints(ctx, pp); + attr.volume = GetVolumeConstraints(ctx, pp); + attr.havevolume = PromiseGetConstraintAsBoolean(ctx, "volume", pp); + attr.havemount = PromiseGetConstraintAsBoolean(ctx, "mount", pp); + +/* Common ("included") */ + + if (attr.edits.maxfilesize <= 0) + { + attr.edits.maxfilesize = EDITFILESIZE; + } + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +Attributes GetMethodAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.havebundle = PromiseBundleOrBodyConstraintExists(ctx, "usebundle", pp); + + attr.inherit = PromiseGetConstraintAsBoolean(ctx, "inherit", pp); + +/* Common ("included") */ + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +Attributes GetMeasurementAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.measure = GetMeasurementConstraint(ctx, pp); + +/* Common ("included") */ + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ +/* Level */ +/*******************************************************************/ + +Services GetServicesConstraints(const EvalContext *ctx, const Promise *pp) +{ + Services s; + + s.service_type = PromiseGetConstraintAsRval(pp, "service_type", RVAL_TYPE_SCALAR); + s.service_policy = PromiseGetConstraintAsRval(pp, "service_policy", RVAL_TYPE_SCALAR); + s.service_autostart_policy = PromiseGetConstraintAsRval(pp, "service_autostart_policy", RVAL_TYPE_SCALAR); + s.service_args = PromiseGetConstraintAsRval(pp, "service_args", RVAL_TYPE_SCALAR); + s.service_depend = PromiseGetConstraintAsList(ctx, "service_dependencies", pp); + s.service_depend_chain = PromiseGetConstraintAsRval(pp, "service_dependence_chain", RVAL_TYPE_SCALAR); + + return s; +} + +/*******************************************************************/ + +Environments GetEnvironmentsConstraints(const EvalContext *ctx, const Promise *pp) +{ + Environments e; + + e.cpus = PromiseGetConstraintAsInt(ctx, "env_cpus", pp); + e.memory = PromiseGetConstraintAsInt(ctx, "env_memory", pp); + e.disk = PromiseGetConstraintAsInt(ctx, "env_disk", pp); + e.baseline = PromiseGetConstraintAsRval(pp, "env_baseline", RVAL_TYPE_SCALAR); + e.spec = PromiseGetConstraintAsRval(pp, "env_spec", RVAL_TYPE_SCALAR); + e.host = PromiseGetConstraintAsRval(pp, "environment_host", RVAL_TYPE_SCALAR); + + e.addresses = PromiseGetConstraintAsList(ctx, "env_addresses", pp); + e.name = PromiseGetConstraintAsRval(pp, "env_name", RVAL_TYPE_SCALAR); + e.type = PromiseGetConstraintAsRval(pp, "environment_type", RVAL_TYPE_SCALAR); + e.state = EnvironmentStateFromString(PromiseGetConstraintAsRval(pp, "environment_state", RVAL_TYPE_SCALAR)); + + return e; +} + +/*******************************************************************/ + +ExecContain GetExecContainConstraints(const EvalContext *ctx, const Promise *pp) +{ + ExecContain e; + + e.shelltype = ShellTypeFromString(PromiseGetConstraintAsRval(pp, "useshell", RVAL_TYPE_SCALAR)); + e.umask = PromiseGetConstraintAsOctal(ctx, "umask", pp); + e.owner = PromiseGetConstraintAsUid(ctx, "exec_owner", pp); + e.group = PromiseGetConstraintAsGid(ctx, "exec_group", pp); + e.preview = PromiseGetConstraintAsBoolean(ctx, "preview", pp); + if (PromiseBundleOrBodyConstraintExists(ctx, "no_output", pp)) + { + e.nooutput = PromiseGetConstraintAsBoolean(ctx, "no_output", pp); + } + else + { + e.nooutput = PromiseGetConstraintAsBoolean(ctx, "module", pp); + } + e.timeout = PromiseGetConstraintAsInt(ctx, "exec_timeout", pp); + e.chroot = PromiseGetConstraintAsRval(pp, "chroot", RVAL_TYPE_SCALAR); + e.chdir = PromiseGetConstraintAsRval(pp, "chdir", RVAL_TYPE_SCALAR); + + return e; +} + +/*******************************************************************/ + +DirectoryRecursion GetRecursionConstraints(const EvalContext *ctx, const Promise *pp) +{ + DirectoryRecursion r; + + r.travlinks = PromiseGetConstraintAsBoolean(ctx, "traverse_links", pp); + r.rmdeadlinks = PromiseGetConstraintAsBoolean(ctx, "rmdeadlinks", pp); + r.depth = PromiseGetConstraintAsInt(ctx, "depth", pp); + + if (r.depth == CF_NOINT) + { + r.depth = 0; + } + + r.xdev = PromiseGetConstraintAsBoolean(ctx, "xdev", pp); + r.include_dirs = PromiseGetConstraintAsList(ctx, "include_dirs", pp); + r.exclude_dirs = PromiseGetConstraintAsList(ctx, "exclude_dirs", pp); + r.include_basedir = PromiseGetConstraintAsBoolean(ctx, "include_basedir", pp); + return r; +} + +/*******************************************************************/ + +Acl GetAclConstraints(const EvalContext *ctx, const Promise *pp) +{ + Acl ac; + + ac.acl_method = AclMethodFromString(PromiseGetConstraintAsRval(pp, "acl_method", RVAL_TYPE_SCALAR)); + ac.acl_type = AclTypeFromString(PromiseGetConstraintAsRval(pp, "acl_type", RVAL_TYPE_SCALAR)); + ac.acl_default = AclDefaultFromString(PromiseGetConstraintAsRval(pp, "acl_default", RVAL_TYPE_SCALAR)); + if (ac.acl_default == ACL_DEFAULT_NONE) + { + /* Deprecated attribute. */ + ac.acl_default = AclDefaultFromString(PromiseGetConstraintAsRval(pp, "acl_directory_inherit", RVAL_TYPE_SCALAR)); + } + ac.acl_entries = PromiseGetConstraintAsList(ctx, "aces", pp); + ac.acl_default_entries = PromiseGetConstraintAsList(ctx, "specify_default_aces", pp); + if (ac.acl_default_entries == NULL) + { + /* Deprecated attribute. */ + ac.acl_default_entries = PromiseGetConstraintAsList(ctx, "specify_inherit_aces", pp); + } + ac.acl_inherit = AclInheritFromString(PromiseGetConstraintAsRval(pp, "acl_inherit", RVAL_TYPE_SCALAR)); + return ac; +} + +/*******************************************************************/ + +static FilePerms GetPermissionConstraints(const EvalContext *ctx, const Promise *pp) +{ + FilePerms p; + char *mode_value; + Rlist *list; + + mode_value = PromiseGetConstraintAsRval(pp, "mode", RVAL_TYPE_SCALAR); + + p.plus = CF_SAMEMODE; + p.minus = CF_SAMEMODE; + + if (!ParseModeString(mode_value, &p.plus, &p.minus)) + { + Log(LOG_LEVEL_ERR, "Problem validating a mode string"); + PromiseRef(LOG_LEVEL_ERR, pp); + } + + list = PromiseGetConstraintAsList(ctx, "bsdflags", pp); + + p.plus_flags = 0; + p.minus_flags = 0; + + if (list && (!ParseFlagString(list, &p.plus_flags, &p.minus_flags))) + { + Log(LOG_LEVEL_ERR, "Problem validating a BSD flag string"); + PromiseRef(LOG_LEVEL_ERR, pp); + } + + p.owners = Rlist2UidList((Rlist *) PromiseGetConstraintAsRval(pp, "owners", RVAL_TYPE_LIST), pp); + p.groups = Rlist2GidList((Rlist *) PromiseGetConstraintAsRval(pp, "groups", RVAL_TYPE_LIST), pp); + + p.findertype = PromiseGetConstraintAsRval(pp, "findertype", RVAL_TYPE_SCALAR); + p.rxdirs = PromiseGetConstraintAsBooleanWithDefault(ctx, "rxdirs", pp, + false, (mode_value != NULL)); + + return p; +} + +/*******************************************************************/ + +FileSelect GetSelectConstraints(const EvalContext *ctx, const Promise *pp) +{ + FileSelect s; + char *value; + Rlist *rp; + mode_t plus, minus; + u_long fplus, fminus; + int entries = false; + + s.name = (Rlist *) PromiseGetConstraintAsRval(pp, "leaf_name", RVAL_TYPE_LIST); + s.path = (Rlist *) PromiseGetConstraintAsRval(pp, "path_name", RVAL_TYPE_LIST); + s.filetypes = (Rlist *) PromiseGetConstraintAsRval(pp, "file_types", RVAL_TYPE_LIST); + s.issymlinkto = (Rlist *) PromiseGetConstraintAsRval(pp, "issymlinkto", RVAL_TYPE_LIST); + + s.perms = PromiseGetConstraintAsList(ctx, "search_mode", pp); + + for (rp = s.perms; rp != NULL; rp = rp->next) + { + plus = 0; + minus = 0; + value = RlistScalarValue(rp); + + if (!ParseModeString(value, &plus, &minus)) + { + Log(LOG_LEVEL_ERR, "Problem validating a mode string"); + PromiseRef(LOG_LEVEL_ERR, pp); + } + } + + s.bsdflags = PromiseGetConstraintAsList(ctx, "search_bsdflags", pp); + + fplus = 0; + fminus = 0; + + if (!ParseFlagString(s.bsdflags, &fplus, &fminus)) + { + Log(LOG_LEVEL_ERR, "Problem validating a BSD flag string"); + PromiseRef(LOG_LEVEL_ERR, pp); + } + + if ((s.name) || (s.path) || (s.filetypes) || (s.issymlinkto) || (s.perms) || (s.bsdflags)) + { + entries = true; + } + + s.owners = (Rlist *) PromiseGetConstraintAsRval(pp, "search_owners", RVAL_TYPE_LIST); + s.groups = (Rlist *) PromiseGetConstraintAsRval(pp, "search_groups", RVAL_TYPE_LIST); + + value = PromiseGetConstraintAsRval(pp, "search_size", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, (long *) &s.min_size, (long *) &s.max_size)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + + value = PromiseGetConstraintAsRval(pp, "ctime", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, (long *) &s.min_ctime, (long *) &s.max_ctime)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + + value = PromiseGetConstraintAsRval(pp, "atime", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, (long *) &s.min_atime, (long *) &s.max_atime)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + value = PromiseGetConstraintAsRval(pp, "mtime", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, (long *) &s.min_mtime, (long *) &s.max_mtime)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + + s.exec_regex = PromiseGetConstraintAsRval(pp, "exec_regex", RVAL_TYPE_SCALAR); + s.exec_program = PromiseGetConstraintAsRval(pp, "exec_program", RVAL_TYPE_SCALAR); + + if ((s.owners) || (s.min_size) || (s.exec_regex) || (s.exec_program)) + { + entries = true; + } + + if ((s.result = PromiseGetConstraintAsRval(pp, "file_result", RVAL_TYPE_SCALAR)) == NULL) + { + if (!entries) + { + Log(LOG_LEVEL_ERR, "file_select body missing its a file_result return value"); + } + } + + return s; +} + +/*******************************************************************/ + +LogLevel ActionAttributeLogLevelFromString(const char *log_level) +{ + if (!log_level) + { + return LOG_LEVEL_ERR; + } + + if (StringEqual(log_level, "inform") || StringEqual(log_level, "info")) + { + return LOG_LEVEL_INFO; + } + else if (StringEqual(log_level, "verbose")) + { + return LOG_LEVEL_VERBOSE; + } + else if (StringEqual(log_level, "error") || StringEqual(log_level, "log")) + { + /* XXX: what is 'log' and why is it the same as 'error'? */ + return LOG_LEVEL_ERR; + } + else + { + Log(LOG_LEVEL_WARNING, "Unrecognized 'log_level' attribute value: %s", log_level); + return LOG_LEVEL_ERR; + } +} + +static TransactionContext GetTransactionConstraints(const EvalContext *ctx, const Promise *pp) +{ + TransactionContext t; + char *value; + + value = PromiseGetConstraintAsRval(pp, "action_policy", RVAL_TYPE_SCALAR); + + if (value && ((strcmp(value, "warn") == 0) || (strcmp(value, "nop") == 0))) + { + t.action = cfa_warn; + } + else + { + t.action = cfa_fix; // default + } + + t.background = PromiseGetConstraintAsBoolean(ctx, "background", pp); + t.ifelapsed = PromiseGetConstraintAsInt(ctx, "ifelapsed", pp); + t.expireafter = PromiseGetConstraintAsInt(ctx, "expireafter", pp); + + /* Warn if promise locking was used with a promise that doesn't support it. + * XXX: EvalContextGetPass() takes 'EvalContext *' instead of 'const EvalContext *'*/ + if ((strcmp("access", PromiseGetPromiseType(pp)) == 0 || + strcmp("classes", PromiseGetPromiseType(pp)) == 0 || + strcmp("defaults", PromiseGetPromiseType(pp)) == 0 || + strcmp("meta", PromiseGetPromiseType(pp)) == 0 || + strcmp("roles", PromiseGetPromiseType(pp)) == 0 || + strcmp("vars", PromiseGetPromiseType(pp)) == 0)) + { + if (t.ifelapsed != CF_NOINT) + { + Log(LOG_LEVEL_WARNING, + "ifelapsed attribute specified in action body for %s promise '%s'," + " but %s promises do not support promise locking", + PromiseGetPromiseType(pp), pp->promiser, + PromiseGetPromiseType(pp)); + } + if (t.expireafter != CF_NOINT) + { + Log(LOG_LEVEL_WARNING, + "expireafter attribute specified in action body for %s promise '%s'," + " but %s promises do not support promise locking", + PromiseGetPromiseType(pp), pp->promiser, + PromiseGetPromiseType(pp)); + } + } + + if (t.ifelapsed == CF_NOINT) + { + t.ifelapsed = VIFELAPSED; + } + + if (t.expireafter == CF_NOINT) + { + t.expireafter = VEXPIREAFTER; + } + + t.audit = PromiseGetConstraintAsBoolean(ctx, "audit", pp); + t.log_string = PromiseGetConstraintAsRval(pp, "log_string", RVAL_TYPE_SCALAR); + t.log_priority = SyslogPriorityFromString(PromiseGetConstraintAsRval(pp, "log_priority", RVAL_TYPE_SCALAR)); + + t.log_kept = PromiseGetConstraintAsRval(pp, "log_kept", RVAL_TYPE_SCALAR); + t.log_repaired = PromiseGetConstraintAsRval(pp, "log_repaired", RVAL_TYPE_SCALAR); + t.log_failed = PromiseGetConstraintAsRval(pp, "log_failed", RVAL_TYPE_SCALAR); + + value = PromiseGetConstraintAsRval(pp, "log_level", RVAL_TYPE_SCALAR); + t.log_level = ActionAttributeLogLevelFromString(value); + + value = PromiseGetConstraintAsRval(pp, "report_level", RVAL_TYPE_SCALAR); + t.report_level = ActionAttributeLogLevelFromString(value); + + t.measure_id = PromiseGetConstraintAsRval(pp, "measurement_class", RVAL_TYPE_SCALAR); + + return t; +} + +/*******************************************************************/ + +bool IsClassesBodyConstraint(const char *constraint) +{ + return ( + StringEqual(constraint, "classes") || + StringEqual(constraint, "classes_name") || + StringEqual(constraint, "scope") || + StringEqual(constraint, "promise_repaired") || + StringEqual(constraint, "repair_failed") || + StringEqual(constraint, "repair_denied") || + StringEqual(constraint, "repair_timeout") || + StringEqual(constraint, "promise_kept") || + StringEqual(constraint, "cancel_repaired") || + StringEqual(constraint, "cancel_kept") || + StringEqual(constraint, "cancel_notkept") || + StringEqual(constraint, "kept_returncodes") || + StringEqual(constraint, "repaired_returncodes") || + StringEqual(constraint, "failed_returncodes") || + StringEqual(constraint, "persist_time") || + StringEqual(constraint, "timer_policy") + ); +} + +DefineClasses GetClassDefinitionConstraints(const EvalContext *ctx, const Promise *pp) +{ + DefineClasses c; + char *pt = NULL; + + { + const char *context_scope = PromiseGetConstraintAsRval(pp, "scope", RVAL_TYPE_SCALAR); + c.scope = ContextScopeFromString(context_scope); + } + c.change = (Rlist *) PromiseGetConstraintAsList(ctx, "promise_repaired", pp); + c.failure = (Rlist *) PromiseGetConstraintAsList(ctx, "repair_failed", pp); + c.denied = (Rlist *) PromiseGetConstraintAsList(ctx, "repair_denied", pp); + c.timeout = (Rlist *) PromiseGetConstraintAsList(ctx, "repair_timeout", pp); + c.kept = (Rlist *) PromiseGetConstraintAsList(ctx, "promise_kept", pp); + + c.del_change = (Rlist *) PromiseGetConstraintAsList(ctx, "cancel_repaired", pp); + c.del_kept = (Rlist *) PromiseGetConstraintAsList(ctx, "cancel_kept", pp); + c.del_notkept = (Rlist *) PromiseGetConstraintAsList(ctx, "cancel_notkept", pp); + + c.retcode_kept = (Rlist *) PromiseGetConstraintAsList(ctx, "kept_returncodes", pp); + c.retcode_repaired = (Rlist *) PromiseGetConstraintAsList(ctx, "repaired_returncodes", pp); + c.retcode_failed = (Rlist *) PromiseGetConstraintAsList(ctx, "failed_returncodes", pp); + + c.persist = PromiseGetConstraintAsInt(ctx, "persist_time", pp); + + if (c.persist == CF_NOINT) + { + c.persist = 0; + } + + pt = PromiseGetConstraintAsRval(pp, "timer_policy", RVAL_TYPE_SCALAR); + + if (pt && (strncmp(pt, "abs", 3) == 0)) + { + c.timer = CONTEXT_STATE_POLICY_PRESERVE; + } + else + { + c.timer = CONTEXT_STATE_POLICY_RESET; + } + + return c; +} + +/*******************************************************************/ + +FileDelete GetDeleteConstraints(const EvalContext *ctx, const Promise *pp) +{ + FileDelete f; + char *value; + + value = PromiseGetConstraintAsRval(pp, "dirlinks", RVAL_TYPE_SCALAR); + + if (value && (strcmp(value, "keep") == 0)) + { + f.dirlinks = TIDY_LINK_KEEP; + } + else + { + f.dirlinks = TIDY_LINK_DELETE; + } + + f.rmdirs = PromiseGetConstraintAsBoolean(ctx, "rmdirs", pp); + return f; +} + +/*******************************************************************/ + +FileRename GetRenameConstraints(const EvalContext *ctx, const Promise *pp) +{ + FileRename r; + char *value; + + value = PromiseGetConstraintAsRval(pp, "disable_mode", RVAL_TYPE_SCALAR); + + if (!ParseModeString(value, &r.plus, &r.minus)) + { + Log(LOG_LEVEL_ERR, "Problem validating a mode string"); + PromiseRef(LOG_LEVEL_ERR, pp); + } + + r.disable = PromiseGetConstraintAsBoolean(ctx, "disable", pp); + r.disable_suffix = PromiseGetConstraintAsRval(pp, "disable_suffix", RVAL_TYPE_SCALAR); + r.newname = PromiseGetConstraintAsRval(pp, "newname", RVAL_TYPE_SCALAR); + r.rotate = PromiseGetConstraintAsInt(ctx, "rotate", pp); + + return r; +} + +/*******************************************************************/ + +ENTERPRISE_FUNC_0ARG_DEFINE_STUB(HashMethod, GetBestFileChangeHashMethod) +{ + return HASH_METHOD_BEST; +} + +FileChange GetChangeMgtConstraints(const EvalContext *ctx, const Promise *pp) +{ + FileChange c; + char *value; + + value = PromiseGetConstraintAsRval(pp, "hash", RVAL_TYPE_SCALAR); + + if (value && (strcmp(value, "best") == 0)) + { + c.hash = GetBestFileChangeHashMethod(); + } + else if (value && (strcmp(value, "md5") == 0)) + { + c.hash = HASH_METHOD_MD5; + } + else if (value && (strcmp(value, "sha1") == 0)) + { + c.hash = HASH_METHOD_SHA1; + } + else if (value && (strcmp(value, "sha256") == 0)) + { + c.hash = HASH_METHOD_SHA256; + } + else if (value && (strcmp(value, "sha384") == 0)) + { + c.hash = HASH_METHOD_SHA384; + } + else if (value && (strcmp(value, "sha512") == 0)) + { + c.hash = HASH_METHOD_SHA512; + } + else + { + c.hash = CF_DEFAULT_DIGEST; + } + + if (FIPS_MODE && (c.hash == HASH_METHOD_MD5)) + { + Log(LOG_LEVEL_ERR, "FIPS mode is enabled, and md5 is not an approved algorithm"); + PromiseRef(LOG_LEVEL_ERR, pp); + } + + value = PromiseGetConstraintAsRval(pp, "report_changes", RVAL_TYPE_SCALAR); + + if (value && (strcmp(value, "content") == 0)) + { + c.report_changes = FILE_CHANGE_REPORT_CONTENT_CHANGE; + } + else if (value && (strcmp(value, "stats") == 0)) + { + c.report_changes = FILE_CHANGE_REPORT_STATS_CHANGE; + } + else if (value && (strcmp(value, "all") == 0)) + { + c.report_changes = FILE_CHANGE_REPORT_ALL; + } + else + { + c.report_changes = FILE_CHANGE_REPORT_NONE; + } + + if (PromiseGetConstraintAsRval(pp, "update_hashes", RVAL_TYPE_SCALAR)) + { + c.update = PromiseGetConstraintAsBoolean(ctx, "update_hashes", pp); + } + else + { + c.update = GetChecksumUpdatesDefault(ctx); + } + + c.report_diffs = PromiseGetConstraintAsBoolean(ctx, "report_diffs", pp); + return c; +} + +/*******************************************************************/ + +FileCopy GetCopyConstraints(const EvalContext *ctx, const Promise *pp) +{ + FileCopy f; + long min, max; + const char *value; + + f.source = PromiseGetConstraintAsRval(pp, "source", RVAL_TYPE_SCALAR); + f.servers = PromiseGetConstraintAsList(ctx, "servers", pp); + + value = PromiseGetConstraintAsRval(pp, "compare", RVAL_TYPE_SCALAR); + if (value == NULL) + { + value = DEFAULT_COPYTYPE; + } + f.compare = FileComparatorFromString(value); + + value = PromiseGetConstraintAsRval(pp, "link_type", RVAL_TYPE_SCALAR); + f.link_type = FileLinkTypeFromString(value); + + char *protocol_version = PromiseGetConstraintAsRval(pp, "protocol_version", + RVAL_TYPE_SCALAR); + + /* Default is undefined, which leaves the choice to body common. */ + f.protocol_version = CF_PROTOCOL_UNDEFINED; + if (protocol_version != NULL) + { + ProtocolVersion parsed = ParseProtocolVersionPolicy(protocol_version); + if (ProtocolIsKnown(parsed)) + { + f.protocol_version = parsed; + } + } + + f.port = PromiseGetConstraintAsRval(pp, "portnumber", RVAL_TYPE_SCALAR); + f.timeout = (short) PromiseGetConstraintAsInt(ctx, "timeout", pp); + f.link_instead = PromiseGetConstraintAsList(ctx, "linkcopy_patterns", pp); + f.copy_links = PromiseGetConstraintAsList(ctx, "copylink_patterns", pp); + + value = PromiseGetConstraintAsRval(pp, "copy_backup", RVAL_TYPE_SCALAR); + + if (value && (strcmp(value, "false") == 0)) + { + f.backup = BACKUP_OPTION_NO_BACKUP; + } + else if (value && (strcmp(value, "timestamp") == 0)) + { + f.backup = BACKUP_OPTION_TIMESTAMP; + } + else + { + f.backup = BACKUP_OPTION_BACKUP; + } + + f.stealth = PromiseGetConstraintAsBoolean(ctx, "stealth", pp); + f.collapse = PromiseGetConstraintAsBoolean(ctx, "collapse_destination_dir", pp); + f.preserve = PromiseGetConstraintAsBoolean(ctx, "preserve", pp); + f.type_check = PromiseGetConstraintAsBoolean(ctx, "type_check", pp); + f.force_update = PromiseGetConstraintAsBoolean(ctx, "force_update", pp); + f.force_ipv4 = PromiseGetConstraintAsBoolean(ctx, "force_ipv4", pp); + f.check_root = PromiseGetConstraintAsBoolean(ctx, "check_root", pp); + + value = PromiseGetConstraintAsRval(pp, "copy_size", RVAL_TYPE_SCALAR); + if (!IntegerRangeFromString(value, &min, &max)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + + f.min_size = (size_t) min; + f.max_size = (size_t) max; + + f.trustkey = PromiseGetConstraintAsBoolean(ctx, "trustkey", pp); + f.encrypt = PromiseGetConstraintAsBoolean(ctx, "encrypt", pp); + f.verify = PromiseGetConstraintAsBoolean(ctx, "verify", pp); + f.purge = PromiseGetConstraintAsBoolean(ctx, "purge", pp); + f.missing_ok = PromiseGetConstraintAsBoolean(ctx, "missing_ok", pp); + f.destination = NULL; + + return f; +} + +/*******************************************************************/ + +FileLink GetLinkConstraints(const EvalContext *ctx, const Promise *pp) +{ + FileLink f; + char *value; + + f.source = PromiseGetConstraintAsRval(pp, "source", RVAL_TYPE_SCALAR); + value = PromiseGetConstraintAsRval(pp, "link_type", RVAL_TYPE_SCALAR); + f.link_type = FileLinkTypeFromString(value); + f.copy_patterns = PromiseGetConstraintAsList(ctx, "copy_patterns", pp); + + value = PromiseGetConstraintAsRval(pp, "when_no_source", RVAL_TYPE_SCALAR); + + if (value && (strcmp(value, "force") == 0)) + { + f.when_no_file = cfa_force; + } + else if (value && (strcmp(value, "delete") == 0)) + { + f.when_no_file = cfa_delete; + } + else + { + f.when_no_file = cfa_skip; + } + + value = PromiseGetConstraintAsRval(pp, "when_linking_children", RVAL_TYPE_SCALAR); + + if (value && (strcmp(value, "override_file") == 0)) + { + f.when_linking_children = cfa_override; + } + else + { + f.when_linking_children = cfa_onlynonexisting; + } + + f.link_children = PromiseGetConstraintAsBoolean(ctx, "link_children", pp); + + return f; +} + +/*******************************************************************/ + +EditDefaults GetEditDefaults(const EvalContext *ctx, const Promise *pp) +{ + EditDefaults e = { 0 }; + char *value; + + e.maxfilesize = PromiseGetConstraintAsInt(ctx, "max_file_size", pp); + + if (e.maxfilesize == CF_NOINT) + { + e.maxfilesize = EDITFILESIZE; + } + + value = PromiseGetConstraintAsRval(pp, "edit_backup", RVAL_TYPE_SCALAR); + + if (value && (strcmp(value, "false") == 0)) + { + e.backup = BACKUP_OPTION_NO_BACKUP; + } + else if (value && (strcmp(value, "timestamp") == 0)) + { + e.backup = BACKUP_OPTION_TIMESTAMP; + } + else if (value && (strcmp(value, "rotate") == 0)) + { + e.backup = BACKUP_OPTION_ROTATE; + e.rotate = PromiseGetConstraintAsInt(ctx, "rotate", pp); + } + else + { + e.backup = BACKUP_OPTION_BACKUP; + } + + e.empty_before_use = PromiseGetConstraintAsBoolean(ctx, "empty_file_before_editing", pp); + + e.joinlines = PromiseGetConstraintAsBoolean(ctx, "recognize_join", pp); + + e.inherit = PromiseGetConstraintAsBoolean(ctx, "inherit", pp); + + return e; +} + +/*******************************************************************/ + +ContextConstraint GetContextConstraints(const EvalContext *ctx, const Promise *pp) +{ + ContextConstraint a; + + a.nconstraints = 0; + a.expression = NULL; + a.persistent = PromiseGetConstraintAsInt(ctx, "persistence", pp); + + { + const char *context_scope = PromiseGetConstraintAsRval(pp, "scope", RVAL_TYPE_SCALAR); + a.scope = ContextScopeFromString(context_scope); + } + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + + for (int k = 0; CF_CLASSBODY[k].lval != NULL; k++) + { + if (strcmp(cp->lval, "persistence") == 0 || strcmp(cp->lval, "scope") == 0) + { + continue; + } + + if (strcmp(cp->lval, CF_CLASSBODY[k].lval) == 0) + { + a.expression = cp; + a.nconstraints++; + } + } + } + + return a; +} + +/*******************************************************************/ + +Packages GetPackageConstraints(const EvalContext *ctx, const Promise *pp) +{ + Packages p = {0}; + + bool has_package_method = + PromiseBundleOrBodyConstraintExists(ctx, "package_method", pp); + bool has_generic_package_method = false; + + if (!has_package_method) + { + /* Check if we have generic package_method. */ + const Policy *policy = PolicyFromPromise(pp); + Seq *bodies_and_args = EvalContextResolveBodyExpression(ctx, policy, "generic", "package_method");; // at position 0 we'll have the body, then its rval, then the same for each of its inherit_from parents + if (bodies_and_args != NULL && + SeqLength(bodies_and_args) > 0) + { + Log(LOG_LEVEL_INFO, "Package promise had no package_method attribute so it's being assigned a value of 'generic' as default."); + const Body *bp = SeqAt(bodies_and_args, 0); // guaranteed to be non-NULL + CopyBodyConstraintsToPromise((EvalContext*)ctx, (Promise*)pp, bp); + has_generic_package_method = true; + } + SeqDestroy(bodies_and_args); + } + + + p.package_version = PromiseGetConstraintAsRval(pp, "package_version", RVAL_TYPE_SCALAR); + p.package_architectures = PromiseGetConstraintAsList(ctx, "package_architectures", pp); + p.package_select = PackageVersionComparatorFromString(PromiseGetConstraintAsRval(pp, "package_select", RVAL_TYPE_SCALAR)); + p.package_policy = PackageActionFromString(PromiseGetConstraintAsRval(pp, "package_policy", RVAL_TYPE_SCALAR)); + + if (p.package_version == NULL && p.package_architectures == NULL && + p.package_select == PACKAGE_VERSION_COMPARATOR_NONE && + p.package_policy == PACKAGE_ACTION_NONE) + { + p.is_empty = true; + } + else + { + p.is_empty = false; + } + + if (p.package_policy == PACKAGE_ACTION_NONE) // Default action => package add + { + p.package_policy = PACKAGE_ACTION_ADD; + } + + p.has_package_method = has_package_method | has_generic_package_method; + + /* body package_method constraints */ + p.package_add_command = PromiseGetConstraintAsRval(pp, "package_add_command", RVAL_TYPE_SCALAR); + p.package_arch_regex = PromiseGetConstraintAsRval(pp, "package_arch_regex", RVAL_TYPE_SCALAR); + p.package_changes = PackageActionPolicyFromString(PromiseGetConstraintAsRval(pp, "package_changes", RVAL_TYPE_SCALAR)); + p.package_delete_command = PromiseGetConstraintAsRval(pp, "package_delete_command", RVAL_TYPE_SCALAR); + p.package_delete_convention = PromiseGetConstraintAsRval(pp, "package_delete_convention", RVAL_TYPE_SCALAR); + p.package_file_repositories = PromiseGetConstraintAsList(ctx, "package_file_repositories", pp); + p.package_installed_regex = PromiseGetConstraintAsRval(pp, "package_installed_regex", RVAL_TYPE_SCALAR); + p.package_default_arch_command = PromiseGetConstraintAsRval(pp, "package_default_arch_command", RVAL_TYPE_SCALAR); + p.package_list_arch_regex = PromiseGetConstraintAsRval(pp, "package_list_arch_regex", RVAL_TYPE_SCALAR); + p.package_list_command = PromiseGetConstraintAsRval(pp, "package_list_command", RVAL_TYPE_SCALAR); + p.package_name_regex = PromiseGetConstraintAsRval(pp, "package_name_regex", RVAL_TYPE_SCALAR); + p.package_list_update_command = PromiseGetConstraintAsRval(pp, "package_list_update_command", RVAL_TYPE_SCALAR); + p.package_list_update_ifelapsed = PromiseGetConstraintAsInt(ctx, "package_list_update_ifelapsed", pp); + p.package_list_version_regex = PromiseGetConstraintAsRval(pp, "package_list_version_regex", RVAL_TYPE_SCALAR); + p.package_name_convention = PromiseGetConstraintAsRval(pp, "package_name_convention", RVAL_TYPE_SCALAR); + p.package_patch_name_regex = PromiseGetConstraintAsRval(pp, "package_patch_name_regex", RVAL_TYPE_SCALAR); + p.package_noverify_regex = PromiseGetConstraintAsRval(pp, "package_noverify_regex", RVAL_TYPE_SCALAR); + p.package_noverify_returncode = PromiseGetConstraintAsInt(ctx, "package_noverify_returncode", pp); + p.package_patch_arch_regex = PromiseGetConstraintAsRval(pp, "package_patch_arch_regex", RVAL_TYPE_SCALAR); + p.package_patch_command = PromiseGetConstraintAsRval(pp, "package_patch_command", RVAL_TYPE_SCALAR); + p.package_patch_installed_regex = PromiseGetConstraintAsRval(pp, "package_patch_installed_regex", RVAL_TYPE_SCALAR); + p.package_patch_list_command = PromiseGetConstraintAsRval(pp, "package_patch_list_command", RVAL_TYPE_SCALAR); + p.package_list_name_regex = PromiseGetConstraintAsRval(pp, "package_list_name_regex", RVAL_TYPE_SCALAR); + p.package_patch_version_regex = PromiseGetConstraintAsRval(pp, "package_patch_version_regex", RVAL_TYPE_SCALAR); + p.package_update_command = PromiseGetConstraintAsRval(pp, "package_update_command", RVAL_TYPE_SCALAR); + p.package_verify_command = PromiseGetConstraintAsRval(pp, "package_verify_command", RVAL_TYPE_SCALAR); + p.package_version_regex = PromiseGetConstraintAsRval(pp, "package_version_regex", RVAL_TYPE_SCALAR); + p.package_multiline_start = PromiseGetConstraintAsRval(pp, "package_multiline_start", RVAL_TYPE_SCALAR); + if (PromiseGetConstraint(pp, "package_commands_useshell") == NULL) + { + p.package_commands_useshell = true; + } + else + { + p.package_commands_useshell = PromiseGetConstraintAsBoolean(ctx, "package_commands_useshell", pp); + } + p.package_version_less_command = PromiseGetConstraintAsRval(pp, "package_version_less_command", RVAL_TYPE_SCALAR); + p.package_version_equal_command = PromiseGetConstraintAsRval(pp, "package_version_equal_command", RVAL_TYPE_SCALAR); + + return p; +} + +/*******************************************************************/ + +static const char *new_packages_actions[] = +{ + "absent", + "present", + NULL +}; + +NewPackages GetNewPackageConstraints(const EvalContext *ctx, const Promise *pp) +{ + NewPackages p = {0}; + NewPackages empty = {0}; + + p.package_version = PromiseGetConstraintAsRval(pp, "version", RVAL_TYPE_SCALAR); + p.package_architecture = PromiseGetConstraintAsRval(pp, "architecture", RVAL_TYPE_SCALAR); + p.package_options = PromiseGetConstraintAsList(ctx, "options", pp); + + p.is_empty = (memcmp(&p, &empty, sizeof(NewPackages)) == 0); + + bool have_policy = PromiseBundleOrBodyConstraintExists(ctx, "policy", pp); + bool have_package_policy = PromiseBundleOrBodyConstraintExists(ctx, "package_policy", pp); + if (!have_policy && !have_package_policy) + { + Log(LOG_LEVEL_DEBUG, "Package promise has no policy or package_policy attribute. Checking if package_module_knowledge.platform_default is defined to default the policy attribute to 'present' and force use of v2 package promise (package_module)."); + + VarRef *ref = VarRefParseFromScope("package_module_knowledge.platform_default", NULL); + const void *ret = EvalContextVariableGet(ctx, ref, NULL); + if (ret != NULL) + { + Log(LOG_LEVEL_INFO, "Package promise had no policy or package_policy attribute and package_module_knowledge.platform_default is defined so defaulting to v2 package promise (package_module) and setting 'policy' attribute to 'present'."); + PromiseAppendConstraint((Promise*)pp, "policy", (Rval) {xstrdup("present"), RVAL_TYPE_SCALAR }, false); + } + VarRefDestroy(ref); + } + p.package_policy = GetNewPackagePolicy(PromiseGetConstraintAsRval(pp, "policy", RVAL_TYPE_SCALAR), + new_packages_actions); + + /* We can have only policy specified in v2 package promise (package_module) definition. */ + if (p.package_policy != NEW_PACKAGE_ACTION_NONE) + { + p.is_empty = false; + } + + /* If we have promise package manager specified. + * IMPORTANT: this must be done after is_empty flag is set as we can have + * some default options for new package promise specified and still use + * old promise inside policy. */ + char *local_promise_manager = + PromiseGetConstraintAsRval(pp, "package_module_name", RVAL_TYPE_SCALAR); + if (local_promise_manager) + { + p.module_body = GetPackageModuleFromContext(ctx, local_promise_manager); + } + else + { + p.module_body = GetDefaultPackageModuleFromContext(ctx); + } + p.package_inventory = GetDefaultInventoryFromContext(ctx); + + /* If global options are not override by promise specific ones. */ + if (!p.package_options && p.module_body) + { + p.package_options = p.module_body->options; + } + + return p; +} + +/*******************************************************************/ + +ProcessSelect GetProcessFilterConstraints(const EvalContext *ctx, const Promise *pp) +{ + ProcessSelect p; + char *value; + int entries = 0; + + p.owner = PromiseGetConstraintAsList(ctx, "process_owner", pp); + + value = PromiseGetConstraintAsRval(pp, "pid", RVAL_TYPE_SCALAR); + + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, &p.min_pid, &p.max_pid)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + value = PromiseGetConstraintAsRval(pp, "ppid", RVAL_TYPE_SCALAR); + + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, &p.min_ppid, &p.max_ppid)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + value = PromiseGetConstraintAsRval(pp, "pgid", RVAL_TYPE_SCALAR); + + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, &p.min_pgid, &p.max_pgid)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + value = PromiseGetConstraintAsRval(pp, "rsize", RVAL_TYPE_SCALAR); + + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, &p.min_rsize, &p.max_rsize)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + value = PromiseGetConstraintAsRval(pp, "vsize", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, &p.min_vsize, &p.max_vsize)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + value = PromiseGetConstraintAsRval(pp, "ttime_range", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, (long *) &p.min_ttime, (long *) &p.max_ttime)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + value = PromiseGetConstraintAsRval(pp, "stime_range", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, (long *) &p.min_stime, (long *) &p.max_stime)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + + p.status = PromiseGetConstraintAsRval(pp, "status", RVAL_TYPE_SCALAR); + p.command = PromiseGetConstraintAsRval(pp, "command", RVAL_TYPE_SCALAR); + p.tty = PromiseGetConstraintAsRval(pp, "tty", RVAL_TYPE_SCALAR); + + value = PromiseGetConstraintAsRval(pp, "priority", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, &p.min_pri, &p.max_pri)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + value = PromiseGetConstraintAsRval(pp, "threads", RVAL_TYPE_SCALAR); + if (value) + { + entries++; + } + + if (!IntegerRangeFromString(value, &p.min_thread, &p.max_thread)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + + if ((p.owner) || (p.status) || (p.command) || (p.tty)) + { + entries = true; + } + + if ((p.process_result = PromiseGetConstraintAsRval(pp, "process_result", RVAL_TYPE_SCALAR)) == NULL) + { + if (entries) + { + Log(LOG_LEVEL_ERR, "process_select body missing its a process_result return value"); + } + } + + return p; +} + +/*******************************************************************/ + +ProcessCount GetMatchesConstraints(const EvalContext *ctx, const Promise *pp) +{ + ProcessCount p; + char *value; + + value = PromiseGetConstraintAsRval(pp, "match_range", RVAL_TYPE_SCALAR); + if (!IntegerRangeFromString(value, &p.min_range, &p.max_range)) + { + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Could not make sense of integer range [%s]", value); + } + p.in_range_define = PromiseGetConstraintAsList(ctx, "in_range_define", pp); + p.out_of_range_define = PromiseGetConstraintAsList(ctx, "out_of_range_define", pp); + + return p; +} + +Attributes GetInsertionAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.havelocation = PromiseGetConstraintAsBoolean(ctx, "location", pp); + attr.location = GetLocationAttributes(pp); + + attr.sourcetype = PromiseGetConstraintAsRval(pp, "insert_type", RVAL_TYPE_SCALAR); + attr.expandvars = PromiseGetConstraintAsBoolean(ctx, "expand_scalars", pp); + + attr.haveinsertselect = PromiseGetConstraintAsBoolean(ctx, "insert_select", pp); + attr.line_select = GetInsertSelectConstraints(ctx, pp); + + attr.insert_match = PromiseGetConstraintAsList(ctx, "whitespace_policy", pp); + +/* Common ("included") */ + + attr.haveregion = PromiseGetConstraintAsBoolean(ctx, "select_region", pp); + attr.region = GetRegionConstraints(ctx, pp); + + attr.xml = GetXmlConstraints(pp); + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +EditLocation GetLocationAttributes(const Promise *pp) +{ + EditLocation e; + char *value; + + e.line_matching = PromiseGetConstraintAsRval(pp, "select_line_matching", RVAL_TYPE_SCALAR); + + value = PromiseGetConstraintAsRval(pp, "before_after", RVAL_TYPE_SCALAR); + + if (value && (strcmp(value, "before") == 0)) + { + e.before_after = EDIT_ORDER_BEFORE; + } + else + { + e.before_after = EDIT_ORDER_AFTER; + } + + e.first_last = PromiseGetConstraintAsRval(pp, "first_last", RVAL_TYPE_SCALAR); + return e; +} + +/*******************************************************************/ + +Attributes GetDeletionAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.not_matching = PromiseGetConstraintAsBoolean(ctx, "not_matching", pp); + + attr.havedeleteselect = PromiseGetConstraintAsBoolean(ctx, "delete_select", pp); + attr.line_select = GetDeleteSelectConstraints(ctx, pp); + + /* common */ + + attr.haveregion = PromiseGetConstraintAsBoolean(ctx, "select_region", pp); + attr.region = GetRegionConstraints(ctx, pp); + + attr.xml = GetXmlConstraints(pp); + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +Attributes GetColumnAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.havecolumn = PromiseGetConstraintAsBoolean(ctx, "edit_field", pp); + attr.column = GetColumnConstraints(ctx, pp); + + /* common */ + + attr.haveregion = PromiseGetConstraintAsBoolean(ctx, "select_region", pp); + attr.region = GetRegionConstraints(ctx, pp); + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +Attributes GetReplaceAttributes(const EvalContext *ctx, const Promise *pp) +{ + Attributes attr = ZeroAttributes; + + attr.havereplace = PromiseGetConstraintAsBoolean(ctx, "replace_patterns", pp); + attr.replace = GetReplaceConstraints(pp); + + attr.havecolumn = PromiseGetConstraintAsBoolean(ctx, "replace_with", pp); + + /* common */ + + attr.haveregion = PromiseGetConstraintAsBoolean(ctx, "select_region", pp); + attr.region = GetRegionConstraints(ctx, pp); + + attr.xml = GetXmlConstraints(pp); + + attr.havetrans = PromiseGetConstraintAsBoolean(ctx, CF_TRANSACTION, pp); + attr.transaction = GetTransactionConstraints(ctx, pp); + + attr.haveclasses = PromiseGetConstraintAsBoolean(ctx, CF_DEFINECLASSES, pp); + attr.classes = GetClassDefinitionConstraints(ctx, pp); + + return attr; +} + +/*******************************************************************/ + +EditXml GetXmlConstraints(const Promise *pp) +{ + EditXml x; + + x.havebuildxpath = ((x.build_xpath = PromiseGetConstraintAsRval(pp, "build_xpath", RVAL_TYPE_SCALAR)) != NULL); + x.haveselectxpath = ((x.select_xpath = PromiseGetConstraintAsRval(pp, "select_xpath", RVAL_TYPE_SCALAR)) != NULL); + x.haveattributevalue = ((x.attribute_value = PromiseGetConstraintAsRval(pp, "attribute_value", RVAL_TYPE_SCALAR)) != NULL); + + return x; +} + +/*******************************************************************/ + +EditRegion GetRegionConstraints(const EvalContext *ctx, const Promise *pp) +{ + EditRegion e; + + e.select_start = PromiseGetConstraintAsRval(pp, "select_start", RVAL_TYPE_SCALAR); + e.select_end = PromiseGetConstraintAsRval(pp, "select_end", RVAL_TYPE_SCALAR); + e.include_start = PromiseGetConstraintAsBoolean(ctx, "include_start_delimiter", pp); + e.include_end = PromiseGetConstraintAsBoolean(ctx, "include_end_delimiter", pp); + + // set the value based on body agent control + char *local_select_end = PromiseGetConstraintAsRval(pp, "select_end_match_eof", RVAL_TYPE_SCALAR); + if (local_select_end != NULL) + { + if (strcmp(local_select_end, "true") == 0) + { + e.select_end_match_eof = true; + } + else + { + e.select_end_match_eof = false; + } + } + else + { + e.select_end_match_eof = EvalContextGetSelectEndMatchEof(ctx); + } + return e; +} + +/*******************************************************************/ + +EditReplace GetReplaceConstraints(const Promise *pp) +{ + EditReplace r; + + r.replace_value = PromiseGetConstraintAsRval(pp, "replace_value", RVAL_TYPE_SCALAR); + r.occurrences = PromiseGetConstraintAsRval(pp, "occurrences", RVAL_TYPE_SCALAR); + + return r; +} + +/*******************************************************************/ + +EditColumn GetColumnConstraints(const EvalContext *ctx, const Promise *pp) +{ + EditColumn c; + char *value; + + c.column_separator = PromiseGetConstraintAsRval(pp, "field_separator", RVAL_TYPE_SCALAR); + c.select_column = PromiseGetConstraintAsInt(ctx, "select_field", pp); + + if (((c.select_column) != CF_NOINT) && (PromiseGetConstraintAsBoolean(ctx, "start_fields_from_zero", pp))) + { + c.select_column++; + } + + value = PromiseGetConstraintAsRval(pp, "value_separator", RVAL_TYPE_SCALAR); + + if (value) + { + c.value_separator = *value; + } + else + { + c.value_separator = '\0'; + } + + c.column_value = PromiseGetConstraintAsRval(pp, "field_value", RVAL_TYPE_SCALAR); + c.column_operation = PromiseGetConstraintAsRval(pp, "field_operation", RVAL_TYPE_SCALAR); + c.extend_columns = PromiseGetConstraintAsBoolean(ctx, "extend_fields", pp); + c.blanks_ok = PromiseGetConstraintAsBoolean(ctx, "allow_blank_fields", pp); + return c; +} + +/*******************************************************************/ +/* Storage */ +/*******************************************************************/ + +StorageMount GetMountConstraints(const EvalContext *ctx, const Promise *pp) +{ + StorageMount m; + + m.mount_type = PromiseGetConstraintAsRval(pp, "mount_type", RVAL_TYPE_SCALAR); + m.mount_source = PromiseGetConstraintAsRval(pp, "mount_source", RVAL_TYPE_SCALAR); + m.mount_server = PromiseGetConstraintAsRval(pp, "mount_server", RVAL_TYPE_SCALAR); + m.mount_options = PromiseGetConstraintAsList(ctx, "mount_options", pp); + m.editfstab = PromiseGetConstraintAsBoolean(ctx, "edit_fstab", pp); + m.unmount = PromiseGetConstraintAsBoolean(ctx, "unmount", pp); + + return m; +} + +/*******************************************************************/ + +StorageVolume GetVolumeConstraints(const EvalContext *ctx, const Promise *pp) +{ + StorageVolume v; + char *value; + + v.check_foreign = PromiseGetConstraintAsBoolean(ctx, "check_foreign", pp); + value = PromiseGetConstraintAsRval(pp, "freespace", RVAL_TYPE_SCALAR); + + v.freespace = (long) IntFromString(value); + value = PromiseGetConstraintAsRval(pp, "sensible_size", RVAL_TYPE_SCALAR); + v.sensible_size = (int) IntFromString(value); + value = PromiseGetConstraintAsRval(pp, "sensible_count", RVAL_TYPE_SCALAR); + v.sensible_count = (int) IntFromString(value); + v.scan_arrivals = PromiseGetConstraintAsBoolean(ctx, "scan_arrivals", pp); + +// defaults + if (v.sensible_size == CF_NOINT) + { + v.sensible_size = 1000; + } + + if (v.sensible_count == CF_NOINT) + { + v.sensible_count = 2; + } + + return v; +} + +Report GetReportConstraints(const EvalContext *ctx, const Promise *pp) +{ + Report r = {0}; + + r.result = PromiseGetConstraintAsRval(pp, "bundle_return_value_index", RVAL_TYPE_SCALAR); + + if (PromiseGetConstraintAsRval(pp, "lastseen", RVAL_TYPE_SCALAR)) + { + r.havelastseen = true; + r.lastseen = PromiseGetConstraintAsInt(ctx, "lastseen", pp); + + if (r.lastseen == CF_NOINT) + { + r.lastseen = 0; + } + } + else + { + r.havelastseen = false; + r.lastseen = 0; + } + + if (!PromiseGetConstraintAsReal(ctx, "intermittency", pp, &r.intermittency)) + { + r.intermittency = 0; + } + + r.haveprintfile = PromiseGetConstraintAsBoolean(ctx, "printfile", pp); + r.filename = PromiseGetConstraintAsRval(pp, "file_to_print", RVAL_TYPE_SCALAR); + r.numlines = PromiseGetConstraintAsInt(ctx, "number_of_lines", pp); + + if (r.numlines == CF_NOINT) + { + r.numlines = 5; + } + + r.showstate = PromiseGetConstraintAsList(ctx, "showstate", pp); + + r.friend_pattern = PromiseGetConstraintAsRval(pp, "friend_pattern", RVAL_TYPE_SCALAR); + + r.to_file = PromiseGetConstraintAsRval(pp, "report_to_file", RVAL_TYPE_SCALAR); + + if ((r.result) && ((r.haveprintfile) || (r.filename) || (r.showstate) || (r.to_file) || (r.lastseen))) + { + Log(LOG_LEVEL_ERR, "bundle_return_value promise for '%s' in bundle '%s' with too many constraints (ignored)", pp->promiser, PromiseGetBundle(pp)->name); + } + + return r; +} + +/*******************************************************************/ + +LineSelect GetInsertSelectConstraints(const EvalContext *ctx, const Promise *pp) +{ + LineSelect s; + + s.startwith_from_list = PromiseGetConstraintAsList(ctx, "insert_if_startwith_from_list", pp); + s.not_startwith_from_list = PromiseGetConstraintAsList(ctx, "insert_if_not_startwith_from_list", pp); + s.match_from_list = PromiseGetConstraintAsList(ctx, "insert_if_match_from_list", pp); + s.not_match_from_list = PromiseGetConstraintAsList(ctx, "insert_if_not_match_from_list", pp); + s.contains_from_list = PromiseGetConstraintAsList(ctx, "insert_if_contains_from_list", pp); + s.not_contains_from_list = PromiseGetConstraintAsList(ctx, "insert_if_not_contains_from_list", pp); + + return s; +} + +/*******************************************************************/ + +LineSelect GetDeleteSelectConstraints(const EvalContext *ctx, const Promise *pp) +{ + LineSelect s; + + s.startwith_from_list = PromiseGetConstraintAsList(ctx, "delete_if_startwith_from_list", pp); + s.not_startwith_from_list = PromiseGetConstraintAsList(ctx, "delete_if_not_startwith_from_list", pp); + s.match_from_list = PromiseGetConstraintAsList(ctx, "delete_if_match_from_list", pp); + s.not_match_from_list = PromiseGetConstraintAsList(ctx, "delete_if_not_match_from_list", pp); + s.contains_from_list = PromiseGetConstraintAsList(ctx, "delete_if_contains_from_list", pp); + s.not_contains_from_list = PromiseGetConstraintAsList(ctx, "delete_if_not_contains_from_list", pp); + + return s; +} + +/*******************************************************************/ + +Measurement GetMeasurementConstraint(const EvalContext *ctx, const Promise *pp) +{ + Measurement m; + char *value; + + m.stream_type = PromiseGetConstraintAsRval(pp, "stream_type", RVAL_TYPE_SCALAR); + + value = PromiseGetConstraintAsRval(pp, "data_type", RVAL_TYPE_SCALAR); + m.data_type = DataTypeFromString(value); + + if (m.data_type == CF_DATA_TYPE_NONE) + { + m.data_type = CF_DATA_TYPE_STRING; + } + + m.history_type = PromiseGetConstraintAsRval(pp, "history_type", RVAL_TYPE_SCALAR); + m.select_line_matching = PromiseGetConstraintAsRval(pp, "select_line_matching", RVAL_TYPE_SCALAR); + m.select_line_number = PromiseGetConstraintAsInt(ctx, "select_line_number", pp); + m.policy = MeasurePolicyFromString(PromiseGetConstraintAsRval(pp, "select_multiline_policy", RVAL_TYPE_SCALAR)); + + m.extraction_regex = PromiseGetConstraintAsRval(pp, "extraction_regex", RVAL_TYPE_SCALAR); + m.units = PromiseGetConstraintAsRval(pp, "units", RVAL_TYPE_SCALAR); + m.growing = PromiseGetConstraintAsBoolean(ctx, "track_growing_file", pp); + return m; +} + +/*******************************************************************/ + +Database GetDatabaseConstraints(const EvalContext *ctx, const Promise *pp) +{ + Database d; + char *value; + + d.db_server_owner = PromiseGetConstraintAsRval(pp, "db_server_owner", RVAL_TYPE_SCALAR); + d.db_server_password = PromiseGetConstraintAsRval(pp, "db_server_password", RVAL_TYPE_SCALAR); + d.db_server_host = PromiseGetConstraintAsRval(pp, "db_server_host", RVAL_TYPE_SCALAR); + d.db_connect_db = PromiseGetConstraintAsRval(pp, "db_server_connection_db", RVAL_TYPE_SCALAR); + d.type = PromiseGetConstraintAsRval(pp, "database_type", RVAL_TYPE_SCALAR); + d.server = PromiseGetConstraintAsRval(pp, "database_server", RVAL_TYPE_SCALAR); + d.columns = PromiseGetConstraintAsList(ctx, "database_columns", pp); + d.rows = PromiseGetConstraintAsList(ctx, "database_rows", pp); + d.operation = PromiseGetConstraintAsRval(pp, "database_operation", RVAL_TYPE_SCALAR); + d.exclude = PromiseGetConstraintAsList(ctx, "registry_exclude", pp); + + value = PromiseGetConstraintAsRval(pp, "db_server_type", RVAL_TYPE_SCALAR); + d.db_server_type = DatabaseTypeFromString(value); + + if (value && ((d.db_server_type) == DATABASE_TYPE_NONE)) + { + Log(LOG_LEVEL_ERR, "Unsupported database type '%s' in databases promise", value); + PromiseRef(LOG_LEVEL_ERR, pp); + } + + return d; +} diff --git a/libpromises/attributes.h b/libpromises/attributes.h new file mode 100644 index 0000000000..f7089c1688 --- /dev/null +++ b/libpromises/attributes.h @@ -0,0 +1,87 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ATTRIBUTES_H +#define CFENGINE_ATTRIBUTES_H + +#include + +LogLevel ActionAttributeLogLevelFromString(const char *log_level); +bool IsClassesBodyConstraint(const char *constraint); +Attributes GetClassContextAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetColumnAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetDatabaseAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetDeletionAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetEnvironmentsAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetExecAttributes(const EvalContext *ctx, const Promise *pp); +void ClearFilesAttributes(Attributes *whom); +/* Every return from GetFilesAttributes() must be passed to + * ClearFilesAttributes() when you're done with it. */ +Attributes GetFilesAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetInferencesAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetInsertionAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetMeasurementAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetMethodAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetOccurrenceAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetPackageAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetUserAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetProcessAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetReplaceAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetReportsAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetServicesAttributes(const EvalContext *ctx, const Promise *pp); +Attributes GetStorageAttributes(const EvalContext *ctx, const Promise *pp); + +Acl GetAclConstraints(const EvalContext *ctx, const Promise *pp); +ContextConstraint GetContextConstraints(const EvalContext *ctx, const Promise *pp); +Database GetDatabaseConstraints(const EvalContext *ctx, const Promise *pp); +DefineClasses GetClassDefinitionConstraints(const EvalContext *ctx, const Promise *pp); +EditColumn GetColumnConstraints(const EvalContext *ctx, const Promise *pp); +EditDefaults GetEditDefaults(const EvalContext *ctx, const Promise *pp); +EditLocation GetLocationAttributes(const Promise *pp); +EditXml GetXmlConstraints(const Promise *pp); +EditRegion GetRegionConstraints(const EvalContext *ctx, const Promise *pp); +EditReplace GetReplaceConstraints(const Promise *pp); +Environments GetEnvironmentsConstraints(const EvalContext *ctx, const Promise *pp); +ExecContain GetExecContainConstraints(const EvalContext *ctx, const Promise *pp); +ENTERPRISE_FUNC_0ARG_DECLARE(HashMethod, GetBestFileChangeHashMethod); +FileChange GetChangeMgtConstraints(const EvalContext *ctx, const Promise *pp); +FileCopy GetCopyConstraints(const EvalContext *ctx, const Promise *pp); +FileDelete GetDeleteConstraints(const EvalContext *ctx, const Promise *pp); +FileLink GetLinkConstraints(const EvalContext *ctx, const Promise *pp); +FileRename GetRenameConstraints(const EvalContext *ctx, const Promise *pp); +FileSelect GetSelectConstraints(const EvalContext *ctx, const Promise *pp); +LineSelect GetDeleteSelectConstraints(const EvalContext *ctx, const Promise *pp); +LineSelect GetInsertSelectConstraints(const EvalContext *ctx, const Promise *pp); +Measurement GetMeasurementConstraint(const EvalContext *ctx, const Promise *pp); +Packages GetPackageConstraints(const EvalContext *ctx, const Promise *pp); +NewPackages GetNewPackageConstraints(const EvalContext *ctx, const Promise *pp); +ProcessCount GetMatchesConstraints(const EvalContext *ctx, const Promise *pp); +ProcessSelect GetProcessFilterConstraints(const EvalContext *ctx, const Promise *pp); +DirectoryRecursion GetRecursionConstraints(const EvalContext *ctx, const Promise *pp); +Report GetReportConstraints(const EvalContext *ctx, const Promise *pp); +Services GetServicesConstraints(const EvalContext *ctx, const Promise *pp); +StorageMount GetMountConstraints(const EvalContext *ctx, const Promise *pp); +StorageVolume GetVolumeConstraints(const EvalContext *ctx, const Promise *pp); + +#endif diff --git a/libpromises/audit.c b/libpromises/audit.c new file mode 100644 index 0000000000..6a5f5915ef --- /dev/null +++ b/libpromises/audit.c @@ -0,0 +1,110 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include +#include +#include + +int PR_KEPT = 0; /* GLOBAL_X */ +int PR_REPAIRED = 0; /* GLOBAL_X */ +int PR_NOTKEPT = 0; /* GLOBAL_X */ + +static bool END_AUDIT_REQUIRED = false; /* GLOBAL_X */ + +void BeginAudit() +{ + END_AUDIT_REQUIRED = true; +} + +void UpdatePromiseCounters(PromiseResult status) +{ + switch (status) + { + case PROMISE_RESULT_CHANGE: + PR_REPAIRED++; + break; + + case PROMISE_RESULT_NOOP: + PR_KEPT++; + break; + + case PROMISE_RESULT_WARN: + case PROMISE_RESULT_TIMEOUT: + case PROMISE_RESULT_FAIL: + case PROMISE_RESULT_DENIED: + case PROMISE_RESULT_INTERRUPTED: + PR_NOTKEPT++; + break; + + default: + ProgrammingError("Unexpected status '%c' has been passed to UpdatePromiseCounters", status); + } +} + +void EndAudit(const EvalContext *ctx, int background_tasks) +{ + if (!END_AUDIT_REQUIRED) + { + return; + } + + double total = (double) (PR_KEPT + PR_NOTKEPT + PR_REPAIRED) / 100.0; + + const char *version = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_VERSION); + if (!version) + { + version = "(not specified)"; + } + + if (total == 0) + { + Log(LOG_LEVEL_VERBOSE, "Outcome of version '%s', no checks were scheduled", version); + return; + } + else + { + LogTotalCompliance(version, background_tasks); + } +} + +void FatalError(const EvalContext *ctx, char *s, ...) +{ + if (s) + { + va_list ap; + char buf[CF_BUFSIZE] = ""; + + va_start(ap, s); + vsnprintf(buf, CF_BUFSIZE - 1, s, ap); + va_end(ap); + Log(LOG_LEVEL_ERR, "Fatal CFEngine error: %s", buf); + } + + EndAudit(ctx, 0); + /* calling abort would bypass cleanup handlers and trigger subtle bugs */ + DoCleanupAndExit(EXIT_FAILURE); +} diff --git a/libpromises/audit.h b/libpromises/audit.h new file mode 100644 index 0000000000..4000a51e2a --- /dev/null +++ b/libpromises/audit.h @@ -0,0 +1,48 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_AUDIT_H +#define CFENGINE_AUDIT_H + +/* + * This module keeps track of amount and value of promises kept/repaired/not-kept + */ + +#include +#include +#include + +void BeginAudit(void); + +void UpdatePromiseCounters(PromiseResult status); + +void EndAudit(const EvalContext *ctx, int background_tasks); + +/* + * FatalError causes EndAudit, so don't call it from the low-memory or corrupted stack situations. + */ +void FatalError(const EvalContext *ctx, char *s, ...) FUNC_ATTR_NORETURN FUNC_ATTR_PRINTF(2, 3); + +#endif + diff --git a/libpromises/bootstrap.c b/libpromises/bootstrap.c new file mode 100644 index 0000000000..58e40fdccc --- /dev/null +++ b/libpromises/bootstrap.c @@ -0,0 +1,432 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include // PrintVersionBanner +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +Bootstrapping is a tricky sequence of fragile events. We need to map shakey/IP +and identify policy hub IP in a special order to bootstrap the license and agents. + +During commercial bootstrap: + + - InitGA (generic-agent) loads the public key + - The verifylicense function sets the policy hub but fails to verify license yet + as there is no key/IP binding + - Policy server gets set in workdir/state/am_policy_hub + - The agents gets run and start this all over again, but this time + the am_policy_hub is defined and caches the key/IP binding + - Now the license has a binding, resolves the policy hub's key and succeeds + +*/ + +#if defined(__CYGWIN__) || defined(__ANDROID__) + +bool BootstrapAllowed(void) +{ + return true; +} + +#elif !defined(__MINGW32__) + +bool BootstrapAllowed(void) +{ + return IsPrivileged(); +} + +#endif + + +/** + * @brief Sets both internal C variables as well as policy sys variables. + * + * Called at bootstrap and after reading policy_server.dat. + * Changes sys.policy_hub and sys.policy_hub_port. + * NULL is an acceptable value for new_policy_server. Could happen when an + * already bootstrapped server re-parses its policies, and the + * policy_server.dat file has been removed. Then this function will be called + * with NULL as new_policy_server, and cf-serverd will keep running even + * without a policy server set. + * + * @param ctx EvalContext is used to set related variables + * @param new_policy_server can be 'host:port', same as policy_server.dat + */ +void EvalContextSetPolicyServer(EvalContext *ctx, const char *new_policy_server) +{ + // Remove variables if undefined policy server: + if ( NULL_OR_EMPTY(new_policy_server) ) + { + EvalContextVariableRemoveSpecial( ctx, SPECIAL_SCOPE_SYS, + "policy_hub" ); + EvalContextVariableRemoveSpecial( ctx, SPECIAL_SCOPE_SYS, + "policy_hub_port" ); + return; + } + + PolicyServerSet(new_policy_server); + const char *ip = PolicyServerGetIP(); + + // Set the sys.policy_hub variable: + if ( ip != NULL ) + { + EvalContextVariablePutSpecial( ctx, SPECIAL_SCOPE_SYS, + "policy_hub", ip, + CF_DATA_TYPE_STRING, + "source=bootstrap" ); + } + else + { + EvalContextVariableRemoveSpecial( ctx, SPECIAL_SCOPE_SYS, + "policy_hub" ); + } + + // Set the sys.policy_hub_port variable: + if (PolicyServerGetPort() != NULL) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, + "policy_hub_port", PolicyServerGetPort(), + CF_DATA_TYPE_STRING, + "source=bootstrap" ); + } + else // Default value (CFENGINE_PORT_STR = "5308") is set + { + EvalContextVariablePutSpecial( ctx, SPECIAL_SCOPE_SYS, + "policy_hub_port", + CFENGINE_PORT_STR, + CF_DATA_TYPE_STRING, + "source=bootstrap" ); + } +} + +//******************************************************************* +// POLICY SERVER FILE FUNCTIONS: +//******************************************************************* + +/** + * @brief Reads the policy_server.dat and sets the internal variables. + * @param[in] ctx EvalContext used by EvalContextSetPolicyServer() + * @param[in] workdir the directory of policy_server.dat usually GetWorkDir() + */ + void EvalContextSetPolicyServerFromFile(EvalContext *ctx, const char *workdir) + { + char *contents = PolicyServerReadFile(workdir); + EvalContextSetPolicyServer(ctx, contents); + free(contents); + } + + + +//******************************************************************* +// POLICY HUB FUNCTIONS: +//******************************************************************* + +/** + * @brief Updates sys.last_policy_update variable from $(sys.masterdir)/cf_promises_validated + * @param ctx EvalContext to put variable into + */ +void UpdateLastPolicyUpdateTime(EvalContext *ctx) +{ + // Get the timestamp on policy update + struct stat sb; + { + char cf_promises_validated_filename[CF_MAXVARSIZE]; + snprintf(cf_promises_validated_filename, CF_MAXVARSIZE, "%s/cf_promises_validated", GetMasterDir()); + MapName(cf_promises_validated_filename); + + if ((stat(cf_promises_validated_filename, &sb)) != 0) + { + return; + } + } + + char timebuf[26] = { 0 }; + cf_strtimestamp_local(sb.st_mtime, timebuf); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "last_policy_update", timebuf, CF_DATA_TYPE_STRING, "source=agent"); +} + +/** + * @return True if the file STATEDIR/am_policy_hub exists + */ + bool GetAmPolicyHub(void) + { + char path[CF_BUFSIZE] = { 0 }; + snprintf(path, sizeof(path), "%s/am_policy_hub", GetStateDir()); + MapName(path); + + struct stat sb; + return stat(path, &sb) == 0; + } + +/** + * @brief Set the STATEDIR/am_policy_hub marker file. + * @param am_policy_hub If true, create marker file. If false, delete it. + * @return True if successful + */ +static char *AmPolicyHubFilename(void) +{ + return StringFormat("%s%cam_policy_hub", GetStateDir(), FILE_SEPARATOR); +} + +bool WriteAmPolicyHubFile(bool am_policy_hub) +{ + char *filename = AmPolicyHubFilename(); + if (am_policy_hub) + { + if (!GetAmPolicyHub()) + { + if (creat(filename, 0600) == -1) + { + Log(LOG_LEVEL_ERR, "Error writing marker file '%s'", filename); + free(filename); + return false; + } + } + } + else + { + if (GetAmPolicyHub()) + { + if (unlink(filename) != 0) + { + Log(LOG_LEVEL_ERR, "Error removing marker file '%s'", filename); + free(filename); + return false; + } + } + } + free(filename); + return true; +} + + +//******************************************************************* +// FAILSAFE FUNCTIONS: +//******************************************************************* + +/** + * @brief Write the builtin failsafe policy to the default location + * @return True if successful + */ +bool WriteBuiltinFailsafePolicy(const char *inputdir) +{ + char failsafe_path[CF_BUFSIZE]; + snprintf(failsafe_path, CF_BUFSIZE - 1, "%s/failsafe.cf", inputdir); + MapName(failsafe_path); + + return WriteBuiltinFailsafePolicyToPath(failsafe_path); +} + +/** + * @brief Exposed for testing. Use WriteBuiltinFailsafePolicy. + */ +bool WriteBuiltinFailsafePolicyToPath(const char *filename) +{ + // The bootstrap.inc file is generated by "make bootstrap-inc" + const char *bootstrap_content = +#include "bootstrap.inc" +; + + Log(LOG_LEVEL_INFO, "Writing built-in failsafe policy to '%s'", filename); + + FILE *fout = safe_fopen(filename, "w"); + if (!fout) + { + Log(LOG_LEVEL_ERR, "Unable to write failsafe to '%s' (fopen: %s)", filename, GetErrorStr()); + return false; + } + + fputs(bootstrap_content, fout); + fclose(fout); + + return true; +} + + +//******************************************************************* +// POLICY FILE FUNCTIONS: +//******************************************************************* + +/** + * @brief Removes all files in $(sys.inputdir) + * @param inputdir + * @return True if successful + */ +bool RemoveAllExistingPolicyInInputs(const char *inputs_path) +{ + Log(LOG_LEVEL_INFO, "Removing all files in '%s'", inputs_path); + + struct stat sb; + if (stat(inputs_path, &sb) == -1) + { + if (errno == ENOENT) + { + return true; + } + else + { + Log(LOG_LEVEL_ERR, "Could not stat inputs directory at '%s'. (stat: %s)", inputs_path, GetErrorStr()); + return false; + } + } + + if (!S_ISDIR(sb.st_mode)) + { + Log(LOG_LEVEL_ERR, "Inputs path exists at '%s', but it is not a directory", inputs_path); + return false; + } + + return DeleteDirectoryTree(inputs_path); +} + +/** + * @return True if the file $(sys.masterdir)/promises.cf exists + */ +bool MasterfileExists(const char *masterdir) +{ + char filename[CF_BUFSIZE] = { 0 }; + snprintf(filename, sizeof(filename), "%s/promises.cf", masterdir); + MapName(filename); + + struct stat sb; + if (stat(filename, &sb) == -1) + { + if (errno == ENOENT) + { + return false; + } + else + { + Log(LOG_LEVEL_ERR, "Could not stat file '%s'. (stat: %s)", filename, GetErrorStr()); + return false; + } + } + + if (!S_ISREG(sb.st_mode)) + { + Log(LOG_LEVEL_ERR, "Path exists at '%s', but it is not a regular file", filename); + return false; + } + + return true; +} + +static char *BootstrapIDFilename(const char *workdir) +{ + assert(workdir != NULL); + return StringFormat("%s%cbootstrap_id.dat", workdir, FILE_SEPARATOR); +} + +char *CreateBootstrapIDFile(const char *workdir) +{ + assert(workdir != NULL); + char *filename = BootstrapIDFilename(workdir); + + FILE *file = safe_fopen_create_perms(filename, "w", CF_PERMS_DEFAULT); + if (file == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to write bootstrap id file '%s' (fopen: %s)", filename, GetErrorStr()); + free(filename); + return NULL; + } + CryptoInitialize(); + #define RANDOM_BYTES 240 / 8 // 240 avoids padding (divisible by 6) + #define BASE_64_LENGTH_NO_PADDING (4 * (RANDOM_BYTES / 3)) + unsigned char buf[RANDOM_BYTES]; + RAND_bytes(buf, RANDOM_BYTES); + char *b64_id = StringEncodeBase64(buf, RANDOM_BYTES); + fprintf(file, "%s\n", b64_id); + fclose(file); + + free(filename); + return b64_id; +} + +char *ReadBootstrapIDFile(const char *workdir) +{ + assert(workdir != NULL); + + char *const path = BootstrapIDFilename(workdir); + Writer *writer = FileRead(path, BASE_64_LENGTH_NO_PADDING + 1, NULL); + if (writer == NULL) + { + // Not having a bootstrap id file is considered normal + Log(LOG_LEVEL_DEBUG, + "Could not read bootstrap ID from file: '%s'", + path); + free(path); + return NULL; + } + char *data = StringWriterClose(writer); + + size_t data_length = strlen(data); + assert(data_length == BASE_64_LENGTH_NO_PADDING + 1); + assert(data[data_length - 1] == '\n'); + if (data_length != BASE_64_LENGTH_NO_PADDING + 1) + { + Log(LOG_LEVEL_ERR, "'%s' contains invalid data: '%s'", path, data); + free(path); + free(data); + return NULL; + } + + data[data_length - 1] = '\0'; + + Log(LOG_LEVEL_VERBOSE, + "Successfully read bootstrap ID '%s' from file '%s'", + data, + path); + free(path); + return data; +} + +void EvalContextSetBootstrapID(EvalContext *ctx, char *bootstrap_id) +{ + EvalContextVariablePutSpecial( + ctx, + SPECIAL_SCOPE_SYS, + "bootstrap_id", + bootstrap_id, + CF_DATA_TYPE_STRING, + "source=bootstrap"); +} diff --git a/libpromises/bootstrap.h b/libpromises/bootstrap.h new file mode 100644 index 0000000000..3d3d4628c9 --- /dev/null +++ b/libpromises/bootstrap.h @@ -0,0 +1,52 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_BOOTSTRAP_H +#define CFENGINE_BOOTSTRAP_H + +#include + +// EVALCONTEXT POLICY SERVER: +void EvalContextSetPolicyServer(EvalContext *ctx, const char *new_policy_server); +void EvalContextSetPolicyServerFromFile(EvalContext *ctx, const char *workdir); + +// POLICY HUB FUNCTIONS: +void UpdateLastPolicyUpdateTime(EvalContext *ctx); +bool GetAmPolicyHub(void); +bool WriteAmPolicyHubFile(bool am_policy_hub); + +// FAILSAFE FUNCTIONS: +bool WriteBuiltinFailsafePolicy(const char *workdir); +bool WriteBuiltinFailsafePolicyToPath(const char *filename); + +// POLICY FILE FUNCTIONS: +bool RemoveAllExistingPolicyInInputs(const char *inputdir); +bool MasterfileExists(const char *masterdir); + +// BOOTSTRAP ID FUNCTIONS: +char *CreateBootstrapIDFile(const char *workdir); +char *ReadBootstrapIDFile(const char *workdir); +void EvalContextSetBootstrapID(EvalContext *ctx, char *bootstrap_id); + +#endif diff --git a/libpromises/cf-windows-functions.h b/libpromises/cf-windows-functions.h new file mode 100644 index 0000000000..ae8c9884ae --- /dev/null +++ b/libpromises/cf-windows-functions.h @@ -0,0 +1,98 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_WINDOWS_FUNCTIONS_H +#define CFENGINE_WINDOWS_FUNCTIONS_H + +#include + +#ifdef __MINGW32__ +/* win_api.c */ + +int NovaWin_chmod(const char *path, mode_t mode); + +/* win_file.c */ + +int NovaWin_rename(const char *oldpath, const char *newpath); +bool NovaWin_FileExists(const char *fileName); +bool NovaWin_IsDir(char *fileName); +int NovaWin_TakeFileOwnership(char *path); +int NovaWin_SetFileOwnership(char *path, SID *sid); +off_t NovaWin_GetDiskUsage(char *file, CfSize type); + +/* win_log.c */ + +void OpenLog(int facility); +void CloseLog(void); + +void LogToSystemLog(const char *msg, LogLevel level); + +/* win_proc.c */ + +int NovaWin_IsProcessRunning(pid_t pid); +int NovaWin_GetCurrentProcessOwner(SID *sid, int sidSz); +int NovaWin_SetTokenPrivilege(HANDLE token, char *privilegeName, int enablePriv); + +/* win_ps.c */ + +int NovaWin_GetProcessSnapshot(Item **procdata); +int GatherProcessUsers(Item **userList, int *userListSz, int *numRootProcs, int *numOtherProcs); + +/* win_service_exec.c */ + +void NovaWin_StartExecService(void); + +/* win_sysinfo.c */ + +int NovaWin_GetWinDir(char *winDir, int winDirSz); +int NovaWin_GetSysDir(char *sysDir, int sysDirSz); +int NovaWin_GetProgDir(char *progDir, int progDirSz); +int NovaWin_GetEnv(char *varName, char *varContents, int varContentsSz); + +/* win_user.c */ + +int NovaWin_UserNameToSid(char *userName, SID *sid, DWORD sidSz, int shouldExist); +int NovaWin_GroupNameToSid(char *groupName, SID *sid, DWORD sidSz, int shouldExist); +int NovaWin_NameToSid(char *name, SID *sid, DWORD sidSz); +int NovaWin_SidToName(SID *sid, char *name, int nameSz); +int NovaWin_StringToSid(char *stringSid, SID *sid, int sidSz); + +FnCallResult FnCallUserExists(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *finalargs); +FnCallResult FnCallGroupExists(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *finalargs); + +/* win_wmi.c */ + +int NovaWin_PackageListInstalledFromAPI(EvalContext *ctx, PackageItem ** pkgList, const Attributes *a, Promise *pp); + +/* win_execd_pipe.c */ + +bool IsReadReady(int fd, int timeout_sec); + +/* win_common.c */ + +void InitializeWindows(void); + +#endif /* __MINGW32__ */ + +#endif // CFENGINE_WINDOWS_FUNCTIONS_H diff --git a/libpromises/cf3.defs.h b/libpromises/cf3.defs.h new file mode 100644 index 0000000000..6294c92868 --- /dev/null +++ b/libpromises/cf3.defs.h @@ -0,0 +1,1718 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CF3_DEFS_H +#define CFENGINE_CF3_DEFS_H + +/* ALWAYS INCLUDE EITHER THIS FILE OR platform.h FIRST */ + + +#include + +#include +#include /* HashMethod */ +#include +#include + +#include /* CF_MAXVARSIZE, CF_BUFSIZE etc */ +#include /* ProtocolVersion, etc */ +#include /* xsnprintf, ProgrammingError etc */ + +/*******************************************************************/ +/* Undef platform specific defines that pollute our namespace */ +/*******************************************************************/ + +#ifdef interface +#undef interface +#endif + +/*******************************************************************/ +/* Various defines */ +/*******************************************************************/ + +#define CF_MAXFRAGMENT 19 /* abbreviate long promise names to 2*MAXFRAGMENT+3 */ +#define CF_NONCELEN (CF_BUFSIZE/16) +#define CF_MAXLINKSIZE 256 +#define CF_PROCCOLS 16 +#define CF_MACROALPHABET 61 /* a-z, A-Z plus a bit */ +#define CF_ALPHABETSIZE 256 +#define CF_SAMEMODE 7777 +/* CF_SAME_OWNER/GROUP should be -1; chown(-1) doesn't change ownership. */ +#define CF_SAME_OWNER ((uid_t)-1) +#define CF_UNKNOWN_OWNER ((uid_t)-2) +#define CF_SAME_GROUP ((gid_t)-1) +#define CF_UNKNOWN_GROUP ((gid_t)-2) +#define CF_INFINITY ((int)999999999) +#define CF_MONDAY_MORNING 345600 + +#define MINUTES_PER_HOUR 60 +#define SECONDS_PER_MINUTE 60 +#define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE) +#define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR) +#define SECONDS_PER_WEEK (7 * SECONDS_PER_DAY) +#define SECONDS_PER_YEAR (365 * SECONDS_PER_DAY) + +/* Long-term monitoring constants */ + +#define HOURS_PER_SHIFT 6 +#define SECONDS_PER_SHIFT (HOURS_PER_SHIFT * SECONDS_PER_HOUR) +#define SHIFTS_PER_DAY 4 +#define SHIFTS_PER_WEEK (4*7) + +#define MAX_MONTH_NAME 9 + +#define MAX_DIGEST_BYTES (512 / 8) /* SHA-512 */ +#define MAX_DIGEST_HEX (MAX_DIGEST_BYTES * 2) + + +/*******************************************************************/ + +#define CF_FILECHANGE "file_change.log" +#define CF_FILECHANGE_NEW "file_changes.log" +#define CF_PROMISE_LOG "promise_summary.log" + +#define CF_ENV_FILE "env_data" + +#define CF_DB_REPAIR_TRIGGER "db_repair_required" + +#define CF_SAVED ".cfsaved" +#define CF_EDITED ".cfedited" +#define CF_NEW ".cfnew" +#define CFD_TERMINATOR "---cfXen/gine/cfXen/gine---" +#define CFD_TRUE "CFD_TRUE" +#define CFD_FALSE "CFD_FALSE" +#define CFD_FALSE_SIZE sizeof(CFD_FALSE) // size of CFD_FALSE including terminator +#define CF_ANYCLASS "any" +#define CF_SMALL_OFFSET 2 + +#define CF_NS ':' // namespace character separator + +/* Mangled namespace and scope characters, in order for the iteration engine + * to VariablePut() to THIS scope single elements of namespaced iterables + * (slists/containers). See expand.c and iteration.c. */ +#define CF_MANGLED_NS '*' +#define CF_MANGLED_SCOPE '#' + +/*****************************************************************************/ + +/* Auditing key */ + +typedef enum +{ // Logging of outcomes in cf-agent.c: + PROMISE_RESULT_SKIPPED = 's', // + PROMISE_RESULT_NOOP = 'n', // Kept + PROMISE_RESULT_CHANGE = 'c', // Repaired + PROMISE_RESULT_WARN = 'w', // Not kept + PROMISE_RESULT_FAIL = 'f', // Not kept + PROMISE_RESULT_DENIED = 'd', // Not kept + PROMISE_RESULT_TIMEOUT = 't', // Timed out + PROMISE_RESULT_INTERRUPTED = 'i' // Not kept +} PromiseResult; + +/*****************************************************************************/ + +#define CF_FAILEDSTR "BAD: Unspecified server refusal (see verbose server output)" +#define CF_CHANGEDSTR1 "BAD: File changed " /* Split this so it cannot be recognized */ +#define CF_CHANGEDSTR2 "while copying" + +#define CF_START_DOMAIN "undefined.domain" + +#define CF_GRAINS 64 +#define CF_NETATTR 7 /* icmp udp dns tcpsyn tcpfin tcpack */ +#define CF_MEASURE_INTERVAL (5.0*60.0) +#define CF_SHIFT_INTERVAL (6*3600) + +#define CF_OBSERVABLES 100 + +/* Special exit codes */ +#define EC_EVAL_ABORTED 6 /* like SIGABRT, but signal exit codes are 120+SIG */ + +typedef struct +{ + char *name; + char *description; + char *units; + double expected_minimum; + double expected_maximum; + bool consolidable; +} MonitoringSlot; + +enum observables +{ + ob_users, + ob_rootprocs, + ob_otherprocs, + ob_diskfree, + ob_loadavg, + ob_netbiosns_in, + ob_netbiosns_out, + ob_netbiosdgm_in, + ob_netbiosdgm_out, + ob_netbiosssn_in, + ob_netbiosssn_out, + ob_imap_in, + ob_imap_out, + ob_cfengine_in, + ob_cfengine_out, + ob_nfsd_in, + ob_nfsd_out, + ob_smtp_in, + ob_smtp_out, + ob_www_in, + ob_www_out, + ob_ftp_in, + ob_ftp_out, + ob_ssh_in, + ob_ssh_out, + ob_wwws_in, + ob_wwws_out, + ob_icmp_in, + ob_icmp_out, + ob_udp_in, + ob_udp_out, + ob_dns_in, + ob_dns_out, + ob_tcpsyn_in, + ob_tcpsyn_out, + ob_tcpack_in, + ob_tcpack_out, + ob_tcpfin_in, + ob_tcpfin_out, + ob_tcpmisc_in, + ob_tcpmisc_out, + ob_webaccess, + ob_weberrors, + ob_syslog, + ob_messages, + ob_temp0, + ob_temp1, + ob_temp2, + ob_temp3, + ob_cpuall, + ob_cpu0, + ob_cpu1, + ob_cpu2, + ob_cpu3, + ob_microsoft_ds_in, + ob_microsoft_ds_out, + ob_www_alt_in, + ob_www_alt_out, + ob_imaps_in, + ob_imaps_out, + ob_ldap_in, + ob_ldap_out, + ob_ldaps_in, + ob_ldaps_out, + ob_mongo_in, + ob_mongo_out, + ob_mysql_in, + ob_mysql_out, + ob_postgresql_in, + ob_postgresql_out, + ob_ipp_in, + ob_ipp_out, + ob_spare +}; + +#include /* QPoint */ + +typedef struct +{ + time_t t; + QPoint Q; +} Event; + +typedef struct +{ + time_t last_seen; + QPoint Q[CF_OBSERVABLES]; +} Averages; + + +/******************************************************************/ + +/** Data for an individual promise (lock), as it's stored in the lock DB + * (/var/cfengine/state/cf_lock.lmdb). This is the value, the key is an MD5 + * hash of various strings identifying the promise. + * + * Most C code doesn't use this struct directly, instead AcquireLock() is called + * which uses functions in locks.c to read the LockData from LMDB and create a + * new CfLock struct. + * + * Please ensure the padding in this struct is initialized to 0 + * LockData lock = { 0 }; // Will zero padding as well as members + * lock.pid = [...] + */ +typedef struct +{ + pid_t pid; // 4 bytes + // 4 bytes padding + time_t time; // 8 bytes + time_t process_start_time; // 8 bytes +} LockData; + +/*****************************************************************************/ + +#ifdef __MINGW32__ +# define NULLFILE "nul" +# define EXEC_SUFFIX ".exe" +#else +# define NULLFILE "/dev/null" +# define EXEC_SUFFIX "" +#endif /* !__MINGW32__ */ + +#define CF_WORDSIZE 8 /* Number of bytes in a word */ + +/*******************************************************************/ + +typedef struct Item_ Item; + +/*******************************************************************/ + +typedef enum +{ + CF_SIZE_ABS, + CF_SIZE_PERCENT +} CfSize; + +/*******************************************************************/ + +typedef enum +{ + CONTEXT_STATE_POLICY_RESET, /* Policy when trying to add already defined persistent states */ + CONTEXT_STATE_POLICY_PRESERVE +} PersistentClassPolicy; + +/*******************************************************************/ + +typedef struct UidList_ UidList; + +// TODO: why do UIDs have their own list type? Clean up. +struct UidList_ +{ +#ifdef __MINGW32__ // TODO: remove uid for NT ? + char sid[CF_MAXSIDSIZE]; /* Invalid sid indicates unset */ +#endif /* __MINGW32__ */ + uid_t uid; + char *uidname; /* when uid is -2 */ + UidList *next; +}; + +/*******************************************************************/ + +typedef struct GidList_ GidList; + +// TODO: why do UIDs have their own list type? Clean up. +struct GidList_ +{ + gid_t gid; + char *gidname; /* when gid is -2 */ + GidList *next; +}; + +/*************************************************************************/ +/* Fundamental (meta) types */ +/*************************************************************************/ + +#define CF_UNDEFINED -1 +#define CF_NOINT -678L +#define CF_UNDEFINED_ITEM (void *)0x1234 + +#define DEFAULTMODE ((mode_t)0700) + +#define CF_DONEPASSES 4 + +#define CFPULSETIME 60 + +/*************************************************************************/ +/* Parsing and syntax tree structures */ +/*************************************************************************/ + +extern const int CF3_MODULES; + +/*************************************************************************/ + +typedef struct Policy_ Policy; +typedef struct Bundle_ Bundle; +typedef struct Body_ Body; +typedef struct Promise_ Promise; +typedef struct BundleSection_ BundleSection; +typedef struct FnCall_ FnCall; + +/*************************************************************************/ +/* Abstract datatypes */ +/*************************************************************************/ + +typedef enum +{ + CF_DATA_TYPE_STRING, + CF_DATA_TYPE_INT, + CF_DATA_TYPE_REAL, + CF_DATA_TYPE_STRING_LIST, + CF_DATA_TYPE_INT_LIST, + CF_DATA_TYPE_REAL_LIST, + CF_DATA_TYPE_OPTION, + CF_DATA_TYPE_OPTION_LIST, + CF_DATA_TYPE_BODY, + CF_DATA_TYPE_BUNDLE, + CF_DATA_TYPE_CONTEXT, + CF_DATA_TYPE_CONTEXT_LIST, + CF_DATA_TYPE_INT_RANGE, + CF_DATA_TYPE_REAL_RANGE, + CF_DATA_TYPE_COUNTER, + CF_DATA_TYPE_CONTAINER, + CF_DATA_TYPE_NONE +} DataType; + +/*************************************************************************/ + +#define CF_COMMONC "common" +#define CF_AGENTC "agent" +#define CF_SERVERC "server" +#define CF_MONITORC "monitor" +#define CF_EXECC "executor" +#define CF_RUNC "runagent" +#define CF_KEYGEN "keygenerator" +#define CF_HUBC "hub" + +typedef enum +{ + AGENT_TYPE_COMMON, + AGENT_TYPE_AGENT, + AGENT_TYPE_SERVER, + AGENT_TYPE_MONITOR, + AGENT_TYPE_EXECUTOR, + AGENT_TYPE_RUNAGENT, + AGENT_TYPE_KEYGEN, + AGENT_TYPE_HUB, + AGENT_TYPE_NOAGENT +} AgentType; + +/*************************************************************************/ + +typedef enum +{ + COMMON_CONTROL_BUNDLESEQUENCE, + COMMON_CONTROL_GOALPATTERNS, + COMMON_CONTROL_IGNORE_MISSING_BUNDLES, + COMMON_CONTROL_IGNORE_MISSING_INPUTS, + COMMON_CONTROL_INPUTS, + COMMON_CONTROL_VERSION, + COMMON_CONTROL_LASTSEEN_EXPIRE_AFTER, + COMMON_CONTROL_OUTPUT_PREFIX, + COMMON_CONTROL_DOMAIN, + COMMON_CONTROL_REQUIRE_COMMENTS, + COMMON_CONTROL_LICENSES, + COMMON_CONTROL_SITE_CLASSES, + COMMON_CONTROL_SYSLOG_HOST, + COMMON_CONTROL_SYSLOG_PORT, + COMMON_CONTROL_SYSTEM_LOG_LEVEL, + COMMON_CONTROL_FIPS_MODE, + COMMON_CONTROL_BWLIMIT, + COMMON_CONTROL_CACHE_SYSTEM_FUNCTIONS, + COMMON_CONTROL_PROTOCOL_VERSION, + COMMON_CONTROL_TLS_CIPHERS, + COMMON_CONTROL_TLS_MIN_VERSION, + COMMON_CONTROL_PACKAGE_INVENTORY, + COMMON_CONTROL_PACKAGE_MODULE, + COMMON_CONTROL_MAX +} CommonControl; + +/*************************************************************************/ + +typedef enum +{ + AGENT_CONTROL_ABORTCLASSES, + AGENT_CONTROL_ABORTBUNDLECLASSES, + AGENT_CONTROL_ADDCLASSES, + AGENT_CONTROL_AGENTACCESS, + AGENT_CONTROL_AGENTFACILITY, + AGENT_CONTROL_ALLCLASSESREPORT, + AGENT_CONTROL_ALWAYSVALIDATE, + AGENT_CONTROL_AUDITING, + AGENT_CONTROL_BINARYPADDINGCHAR, + AGENT_CONTROL_BINDTOINTERFACE, + AGENT_CONTROL_HASHUPDATES, + AGENT_CONTROL_CHILDLIBPATH, + AGENT_CONTROL_CHECKSUM_ALERT_TIME, + AGENT_CONTROL_DEFAULTCOPYTYPE, + AGENT_CONTROL_DRYRUN, + AGENT_CONTROL_EDITBINARYFILESIZE, + AGENT_CONTROL_EDITFILESIZE, + AGENT_CONTROL_ENVIRONMENT, + AGENT_CONTROL_EXCLAMATION, + AGENT_CONTROL_EXPIREAFTER, + AGENT_CONTROL_FSINGLECOPY, + AGENT_CONTROL_FAUTODEFINE, + AGENT_CONTROL_HOSTNAMEKEYS, + AGENT_CONTROL_IFELAPSED, + AGENT_CONTROL_INFORM, + AGENT_CONTROL_INTERMITTENCY, + AGENT_CONTROL_MAX_CHILDREN, + AGENT_CONTROL_MAXCONNECTIONS, + AGENT_CONTROL_MOUNTFILESYSTEMS, + AGENT_CONTROL_NONALPHANUMFILES, + AGENT_CONTROL_REPCHAR, + AGENT_CONTROL_REFRESH_PROCESSES, + AGENT_CONTROL_REPOSITORY, + AGENT_CONTROL_SECUREINPUT, + AGENT_CONTROL_SENSIBLECOUNT, + AGENT_CONTROL_SENSIBLESIZE, + AGENT_CONTROL_SKIPIDENTIFY, + AGENT_CONTROL_SUSPICIOUSNAMES, + AGENT_CONTROL_SYSLOG, + AGENT_CONTROL_TRACK_VALUE, + AGENT_CONTROL_TIMEZONE, + AGENT_CONTROL_TIMEOUT, + AGENT_CONTROL_VERBOSE, + AGENT_CONTROL_REPORTCLASSLOG, + AGENT_CONTROL_SELECT_END_MATCH_EOF, + AGENT_CONTROL_COPYFROM_RESTRICT_KEYS, + AGENT_CONTROL_NONE +} AgentControl; + +/*************************************************************************/ + +typedef enum +{ + EXEC_CONTROL_SPLAYTIME, + EXEC_CONTROL_MAILFROM, + EXEC_CONTROL_MAILTO, + EXEC_CONTROL_MAILSUBJECT, + EXEC_CONTROL_SMTPSERVER, + EXEC_CONTROL_MAILMAXLINES, + EXEC_CONTROL_MAILFILTER_INCLUDE, + EXEC_CONTROL_MAILFILTER_EXCLUDE, + EXEC_CONTROL_SCHEDULE, + EXEC_CONTROL_EXECUTORFACILITY, + EXEC_CONTROL_EXECCOMMAND, + EXEC_CONTROL_AGENT_EXPIREAFTER, + EXEC_CONTROL_RUNAGENT_ALLOW_USERS, + EXEC_CONTROL_NONE +} ExecControl; + +typedef enum +{ + EDIT_ORDER_BEFORE, + EDIT_ORDER_AFTER +} EditOrder; + +/*************************************************************************/ + +typedef enum +{ + TYPE_SEQUENCE_META, + TYPE_SEQUENCE_VARS, + TYPE_SEQUENCE_DEFAULTS, + TYPE_SEQUENCE_CONTEXTS, + TYPE_SEQUENCE_USERS, + TYPE_SEQUENCE_FILES, + TYPE_SEQUENCE_PACKAGES, + TYPE_SEQUENCE_ENVIRONMENTS, + TYPE_SEQUENCE_METHODS, + TYPE_SEQUENCE_PROCESSES, + TYPE_SEQUENCE_SERVICES, + TYPE_SEQUENCE_COMMANDS, + TYPE_SEQUENCE_STORAGE, + TYPE_SEQUENCE_DATABASES, + TYPE_SEQUENCE_REPORTS, + TYPE_SEQUENCE_NONE +} TypeSequence; + +/*************************************************************************/ +/* Syntax module range/pattern constants for type validation */ +/*************************************************************************/ + +#define CF_BUNDLE (void*)1234 /* any non-null value, not used */ + +/* Initial values for max=low, min=high when determining a range, for + * which we'll revise max upwards and in downwards if given meaningful + * bounds for them. Quite why they're these values, rather than 9999 + * (longest string of 9s to fit in an int16) or 999999999 (similar for + * int32) remains a mystery. */ +#define CF_HIGHINIT 999999L +#define CF_LOWINIT -999999L + +#define CF_BOOL "true,false,yes,no,on,off" +#define CF_LINKRANGE "symlink,hardlink,relative,absolute" +#define CF_TIMERANGE "0,2147483647" /* i.e. "0,0x7fffffff" */ + +/* Syntax checker accepts absurdly big numbers for backwards + * compatibility. WARNING: internally they are stored as longs, possibly + * being truncated to LONG_MAX within IntFromString(). */ +#define CF_VALRANGE "0,99999999999" +#define CF_INTRANGE "-99999999999,99999999999" + +#define CF_INTLISTRANGE "[-0-9_$(){}\\[\\].]+" +#define CF_REALRANGE "-9.99999E100,9.99999E100" +#define CF_CHARRANGE "^.$" + +#define CF_MODERANGE "[0-7augorwxst,+-=]+" +#define CF_BSDFLAGRANGE "[+-]*[(arch|archived|nodump|opaque|sappnd|sappend|schg|schange|simmutable|sunlnk|sunlink|uappnd|uappend|uchg|uchange|uimmutable|uunlnk|uunlink)]+" +#define CF_CLASSRANGE "[a-zA-Z0-9_!&@@$|.()\\[\\]{}:]+" +#define CF_IDRANGE "[a-zA-Z0-9_$(){}\\[\\].:]+" +#define CF_USERRANGE "[a-zA-Z0-9_$.-]+" +#define CF_IPRANGE "[a-zA-Z0-9_$(){}.:-]+" +#define CF_FNCALLRANGE "[a-zA-Z0-9_(){}.$@]+" +#define CF_NAKEDLRANGE "@[(][a-zA-Z0-9_$(){}\\[\\].:]+[)]" +#define CF_ANYSTRING ".*" + +#define CF_KEYSTRING "^(SHA|MD5)=[0123456789abcdef]*$" + + +#ifndef __MINGW32__ +# define CF_ABSPATHRANGE "\"?(/.*)" +#else +// can start with e.g. c:\... or "c:\... | unix (for Cygwin-style paths) +# define CF_ABSPATHRANGE "\"?(([a-zA-Z]:\\\\.*)|(/.*))" +#endif + +/* Any non-empty string can be an absolute path under Unix */ +#define CF_PATHRANGE ".+" + +// Put this here now for caching efficiency + +#define SOFTWARE_PACKAGES_CACHE "software_packages.csv" +#define SOFTWARE_PATCHES_CACHE "software_patches_avail.csv" + +#define PACKAGES_CONTEXT "cf_pack_context" +#define PACKAGES_CONTEXT_ANYVER "cf_pack_context_anyver" + +/*************************************************************************/ + +typedef struct EvalContext_ EvalContext; + +typedef enum +{ + RVAL_TYPE_SCALAR = 's', + RVAL_TYPE_LIST = 'l', + RVAL_TYPE_FNCALL = 'f', + RVAL_TYPE_CONTAINER = 'c', + RVAL_TYPE_NOPROMISEE = 'X' // TODO: must be another hack +} RvalType; + +typedef struct +{ + void *item; + RvalType type; +} Rval; + +typedef struct Rlist_ Rlist; + +typedef struct ConstraintSyntax_ ConstraintSyntax; +typedef struct BodySyntax_ BodySyntax; + +/* + * Promise types or bodies may optionally provide parse-tree check function, called after + * parsing to do a preliminary syntax/semantic checking of unexpanded promises. + * + * This check function should populate #errors sequence with errors it finds and + * return false in case it has found at least one error. + * + * If the check function has not found any errors, it should return true. + */ +typedef bool (*PromiseCheckFn)(const Promise *pp, Seq *errors); +typedef bool (*BodyCheckFn)(const Body *body, Seq *errors); + +typedef enum +{ + SYNTAX_STATUS_NORMAL, + SYNTAX_STATUS_DEPRECATED, + SYNTAX_STATUS_REMOVED, + SYNTAX_STATUS_CUSTOM +} SyntaxStatus; + +typedef enum +{ + FNCALL_CATEGORY_SYSTEM, + FNCALL_CATEGORY_FILES, + FNCALL_CATEGORY_IO, + FNCALL_CATEGORY_COMM, + FNCALL_CATEGORY_DATA, + FNCALL_CATEGORY_UTILS, + FNCALL_CATEGORY_INTERNAL +} FnCallCategory; + +struct ConstraintSyntax_ +{ + const char *lval; + const DataType dtype; + union + { + const char *validation_string; + const BodySyntax *body_type_syntax; + } range; + const char *description; + SyntaxStatus status; +}; + +struct BodySyntax_ +{ + const char *body_type; + const ConstraintSyntax *constraints; + BodyCheckFn check_body; + SyntaxStatus status; +}; + +typedef struct +{ + const char *bundle_type; + const char *promise_type; + const ConstraintSyntax *constraints; + const PromiseCheckFn check_promise; + SyntaxStatus status; +} PromiseTypeSyntax; + +/*************************************************************************/ + +typedef struct Constraint_ Constraint; + +typedef enum +{ + INTERVAL_HOURLY, + INTERVAL_DAILY, + INTERVAL_NONE +} Interval; + +typedef enum +{ + FILE_COMPARATOR_ATIME, + FILE_COMPARATOR_MTIME, + FILE_COMPARATOR_CTIME, + FILE_COMPARATOR_CHECKSUM, + FILE_COMPARATOR_HASH, + FILE_COMPARATOR_BINARY, + FILE_COMPARATOR_EXISTS, + FILE_COMPARATOR_NONE +} FileComparator; + +typedef enum +{ + FILE_LINK_TYPE_SYMLINK, + FILE_LINK_TYPE_HARDLINK, + FILE_LINK_TYPE_RELATIVE, + FILE_LINK_TYPE_ABSOLUTE, + FILE_LINK_TYPE_NONE +} FileLinkType; + +enum cfopaction +{ + cfa_fix, + cfa_warn, +}; + +typedef enum +{ + BACKUP_OPTION_BACKUP, + BACKUP_OPTION_NO_BACKUP, + BACKUP_OPTION_TIMESTAMP, + BACKUP_OPTION_ROTATE, + BACKUP_OPTION_REPOSITORY_STORE /* for internal use only */ +} BackupOption; + +enum cfnofile +{ + cfa_force, + cfa_delete, + cfa_skip +}; + +enum cflinkchildren +{ + cfa_override, + cfa_onlynonexisting +}; + +typedef enum +{ + FILE_CHANGE_REPORT_NONE, + FILE_CHANGE_REPORT_CONTENT_CHANGE, + FILE_CHANGE_REPORT_STATS_CHANGE, + FILE_CHANGE_REPORT_ALL +} FileChangeReport; + +typedef enum +{ + PACKAGE_ACTION_ADD, + PACKAGE_ACTION_DELETE, + PACKAGE_ACTION_REINSTALL, + PACKAGE_ACTION_UPDATE, + PACKAGE_ACTION_ADDUPDATE, + PACKAGE_ACTION_PATCH, + PACKAGE_ACTION_VERIFY, + PACKAGE_ACTION_NONE +} PackageAction; + +typedef enum +{ + NEW_PACKAGE_ACTION_ABSENT, + NEW_PACKAGE_ACTION_PRESENT, + NEW_PACKAGE_ACTION_NONE +} NewPackageAction; + +typedef enum +{ + PACKAGE_VERSION_COMPARATOR_EQ, + PACKAGE_VERSION_COMPARATOR_NEQ, + PACKAGE_VERSION_COMPARATOR_GT, + PACKAGE_VERSION_COMPARATOR_LT, + PACKAGE_VERSION_COMPARATOR_GE, + PACKAGE_VERSION_COMPARATOR_LE, + PACKAGE_VERSION_COMPARATOR_NONE +} PackageVersionComparator; + +typedef enum +{ + PACKAGE_ACTION_POLICY_INDIVIDUAL, + PACKAGE_ACTION_POLICY_BULK, + PACKAGE_ACTION_POLICY_NONE +} PackageActionPolicy; + +typedef enum +{ + PROMISE_STATE_REPAIRED = 'r', + PROMISE_STATE_NOTKEPT = 'n', + PROMISE_STATE_KEPT = 'c', + PROMISE_STATE_ANY = 'x' +} PromiseState; + +/************************************************************************************/ + +typedef enum +{ + LAST_SEEN_DIRECTION_INCOMING = '-', + LAST_SEEN_DIRECTION_OUTGOING = '+' +} LastSeenDirection; + +/************************************************************************************/ + +typedef enum +{ + ACL_METHOD_APPEND, + ACL_METHOD_OVERWRITE, + ACL_METHOD_NONE +} AclMethod; + +typedef enum +{ + ACL_TYPE_GENERIC, + ACL_TYPE_POSIX, + ACL_TYPE_NTFS_, + ACL_TYPE_NONE +} AclType; + +typedef enum +{ + ACL_DEFAULT_NO_CHANGE, + ACL_DEFAULT_SPECIFY, + ACL_DEFAULT_ACCESS, + ACL_DEFAULT_CLEAR, + ACL_DEFAULT_NONE +} AclDefault; + +typedef enum +{ + ACL_INHERIT_FALSE, + ACL_INHERIT_TRUE, + ACL_INHERIT_NOCHANGE +} AclInherit; + +typedef struct +{ + AclMethod acl_method; + AclType acl_type; + AclDefault acl_default; + Rlist *acl_entries; + Rlist *acl_default_entries; + /* Only used on Windows */ + AclInherit acl_inherit; +} Acl; + +typedef enum +{ + INHERIT_ACCESS_ONLY, + INHERIT_DEFAULT_ONLY, + INHERIT_ACCESS_AND_DEFAULT +} +inherit_t; + +typedef enum +{ + INSERT_MATCH_TYPE_IGNORE_LEADING, + INSERT_MATCH_TYPE_IGNORE_TRAILING, + INSERT_MATCH_TYPE_IGNORE_EMBEDDED, + INSERT_MATCH_TYPE_EXACT +} InsertMatchType; + +/*************************************************************************/ +/* Runtime constraint structures */ +/*************************************************************************/ + +#define OVECCOUNT 30 + +/*******************************************************************/ + +typedef struct +{ + char *last; + char *lock; + bool is_dummy; +} CfLock; + +/*************************************************************************/ + +typedef struct +{ + char *host; + char *source; + char *mounton; + char *options; + int unmount; +} Mount; + +/*************************************************************************/ + +typedef struct +{ + int travlinks; + int rmdeadlinks; + int depth; + int xdev; + int include_basedir; + Rlist *include_dirs; + Rlist *exclude_dirs; +} DirectoryRecursion; + +/*************************************************************************/ + +typedef struct +{ + enum cfopaction action; + int ifelapsed; + int expireafter; + int background; + char *log_string; + char *log_kept; + char *log_repaired; + char *log_failed; + int log_priority; + char *measure_id; + int audit; + LogLevel report_level; + LogLevel log_level; +} TransactionContext; + +/*************************************************************************/ + +typedef enum +{ + CONTEXT_SCOPE_NAMESPACE, + CONTEXT_SCOPE_BUNDLE, + CONTEXT_SCOPE_NONE +} ContextScope; + +typedef struct +{ + ContextScope scope; + Rlist *change; + Rlist *failure; + Rlist *denied; + Rlist *timeout; + Rlist *kept; + int persist; + PersistentClassPolicy timer; + Rlist *del_change; + Rlist *del_kept; + Rlist *del_notkept; + Rlist *retcode_kept; + Rlist *retcode_repaired; + Rlist *retcode_failed; +} DefineClasses; + +/*************************************************************************/ +/* SQL Database connectors */ +/*************************************************************************/ + +typedef enum +{ + DATABASE_TYPE_MYSQL, + DATABASE_TYPE_POSTGRES, + DATABASE_TYPE_NONE +} DatabaseType; + +/*************************************************************************/ +/* Package promises */ +/*************************************************************************/ + +typedef struct PackageItem_ PackageItem; +typedef struct PackageManager_ PackageManager; + +struct PackageManager_ +{ + char *manager; + PackageAction action; + PackageActionPolicy policy; + PackageItem *pack_list; + PackageItem *patch_list; + PackageItem *patch_avail; + PackageManager *next; +}; + +/*************************************************************************/ + +struct PackageItem_ +{ + char *name; + char *version; + char *arch; + Promise *pp; + PackageItem *next; +}; + + +typedef struct +{ + unsigned int expires; + PersistentClassPolicy policy; + char tags[]; // variable length, must be zero terminated +} PersistentClassInfo; + +/*************************************************************************/ + +typedef struct +{ + mode_t plus; + mode_t minus; + UidList *owners; + GidList *groups; + char *findertype; + u_long plus_flags; /* for *BSD chflags */ + u_long minus_flags; /* for *BSD chflags */ + int rxdirs; +} FilePerms; + +/*************************************************************************/ + +typedef struct +{ + Rlist *name; + Rlist *path; + Rlist *perms; + Rlist *bsdflags; + Rlist *owners; + Rlist *groups; + long max_size; + long min_size; + time_t max_ctime; + time_t min_ctime; + time_t max_mtime; + time_t min_mtime; + time_t max_atime; + time_t min_atime; + char *exec_regex; + char *exec_program; + Rlist *filetypes; + Rlist *issymlinkto; + char *result; +} FileSelect; + +/*************************************************************************/ + +typedef struct +{ + enum + { + TIDY_LINK_DELETE, + TIDY_LINK_KEEP + } dirlinks; + int rmdirs; +} FileDelete; + +/*************************************************************************/ + +typedef struct +{ + char *newname; + char *disable_suffix; + int disable; + int rotate; + mode_t plus; + mode_t minus; +} FileRename; + +/*************************************************************************/ + +typedef struct +{ + HashMethod hash; + FileChangeReport report_changes; + int report_diffs; + int update; +} FileChange; + +/*************************************************************************/ + +typedef struct +{ + char *source; + FileLinkType link_type; + Rlist *copy_patterns; + enum cfnofile when_no_file; + enum cflinkchildren when_linking_children; + int link_children; +} FileLink; + +/*************************************************************************/ + +typedef enum +{ + SHELL_TYPE_NONE, + SHELL_TYPE_USE, + SHELL_TYPE_POWERSHELL +} ShellType; + +typedef struct +{ + ShellType shelltype; + mode_t umask; + uid_t owner; + gid_t group; + char *chdir; + char *chroot; + int preview; + bool nooutput; + int timeout; +} ExecContain; + +/*************************************************************************/ + +typedef struct +{ + long min_range; + long max_range; + Rlist *in_range_define; + Rlist *out_of_range_define; +} ProcessCount; + +/*************************************************************************/ + +typedef struct +{ + Rlist *owner; + long min_pid; + long max_pid; + long min_ppid; + long max_ppid; + long min_pgid; + long max_pgid; + long min_rsize; + long max_rsize; + long min_vsize; + long max_vsize; + time_t min_ttime; + time_t max_ttime; + time_t min_stime; + time_t max_stime; + long min_pri; + long max_pri; + long min_thread; + long max_thread; + char *status; + char *command; + char *tty; + char *process_result; +} ProcessSelect; + +#define PROCESS_SELECT_INIT { \ + .owner = NULL, \ + .min_pid = CF_NOINT, \ + .max_pid = CF_NOINT, \ + .min_ppid = CF_NOINT, \ + .max_ppid = CF_NOINT, \ + .min_pgid = CF_NOINT, \ + .max_pgid = CF_NOINT, \ + .min_rsize = CF_NOINT, \ + .max_rsize = CF_NOINT, \ + .min_vsize = CF_NOINT, \ + .max_vsize = CF_NOINT, \ + .min_ttime = CF_NOINT, \ + .max_ttime = CF_NOINT, \ + .min_stime = CF_NOINT, \ + .max_stime = CF_NOINT, \ + .min_pri = CF_NOINT, \ + .max_pri = CF_NOINT, \ + .min_thread = CF_NOINT, \ + .max_thread = CF_NOINT, \ + .status = NULL, \ + .command = NULL, \ + .tty = NULL, \ + .process_result = NULL, \ +} + +/*************************************************************************/ + +typedef struct +{ + Constraint *expression; + ContextScope scope; + int nconstraints; + int persistent; +} ContextConstraint; + +/*************************************************************************/ + +typedef struct +{ + BackupOption backup; + int empty_before_use; + int maxfilesize; + int joinlines; + int rotate; + int inherit; +} EditDefaults; + +/*************************************************************************/ + +typedef struct +{ + Rlist *startwith_from_list; + Rlist *not_startwith_from_list; + Rlist *match_from_list; + Rlist *not_match_from_list; + Rlist *contains_from_list; + Rlist *not_contains_from_list; +} LineSelect; + +typedef struct +{ + char *build_xpath; + char *select_xpath; + char *attribute_value; + int havebuildxpath; + int haveselectxpath; + int haveattributevalue; +} EditXml; + +typedef struct +{ + char *line_matching; + EditOrder before_after; + char *first_last; +} EditLocation; + +typedef struct +{ + char *select_start; + char *select_end; + int include_start; + int include_end; + bool select_end_match_eof; +} EditRegion; + +typedef struct +{ + char *column_separator; + int select_column; + char value_separator; + char *column_value; + char *column_operation; + int extend_columns; + int blanks_ok; +} EditColumn; + +typedef struct +{ + char *replace_value; + char *occurrences; +} EditReplace; + +/*************************************************************************/ + +typedef struct +{ + char *mount_type; + char *mount_source; + char *mount_server; + Rlist *mount_options; + int editfstab; + int unmount; +} StorageMount; + +typedef struct +{ + int check_foreign; + long freespace; + int sensible_size; + int sensible_count; + int scan_arrivals; +} StorageVolume; + +/*************************************************************************/ + +typedef struct +{ + int haveprintfile; + int havelastseen; + int lastseen; + char *result; + double intermittency; + char *friend_pattern; + char *filename; + char *to_file; + int numlines; + Rlist *showstate; +} Report; + +/*************************************************************************/ + +typedef struct +{ + PackageAction package_policy; + char *package_version; + Rlist *package_architectures; + PackageVersionComparator package_select; + PackageActionPolicy package_changes; + Rlist *package_file_repositories; + + char *package_default_arch_command; + + char *package_list_command; + char *package_list_version_regex; + char *package_list_name_regex; + char *package_list_arch_regex; + char *package_patch_list_command; + + char *package_patch_version_regex; + char *package_patch_name_regex; + char *package_patch_arch_regex; + char *package_patch_installed_regex; + + char *package_list_update_command; + int package_list_update_ifelapsed; + + char *package_version_regex; + char *package_name_regex; + char *package_arch_regex; + char *package_installed_regex; + + char *package_add_command; + char *package_delete_command; + char *package_update_command; + char *package_patch_command; + char *package_verify_command; + char *package_noverify_regex; + char *package_name_convention; + char *package_delete_convention; + + bool package_commands_useshell; + + char *package_multiline_start; + + char *package_version_less_command; + char *package_version_equal_command; + + int package_noverify_returncode; + + bool has_package_method; + bool is_empty; +} Packages; + +/*************************************************************************/ + +typedef struct +{ + char *name; + int updates_ifelapsed; + int installed_ifelapsed; + Rlist *options; + char *interpreter; + char *module_path; +} PackageModuleBody; + + +typedef struct +{ + Rlist *control_package_inventory; /* list of all inventory used package managers + * names taken from common control */ + char *control_package_module; /* policy default package manager name */ + Seq *package_modules_bodies; /* list of all discovered in policy PackageManagerBody + * bodies taken from common control */ +} PackagePromiseContext; + + +typedef struct +{ + NewPackageAction package_policy; + PackageModuleBody *module_body; + Rlist *package_inventory; + char *package_version; + char *package_architecture; + Rlist *package_options; + + bool is_empty; +} NewPackages; + +/*************************************************************************/ + +typedef enum +{ + MEASURE_POLICY_AVERAGE, + MEASURE_POLICY_SUM, + MEASURE_POLICY_FIRST, + MEASURE_POLICY_LAST, + MEASURE_POLICY_NONE +} MeasurePolicy; + +typedef struct +{ + char *stream_type; + DataType data_type; + MeasurePolicy policy; + char *history_type; + char *select_line_matching; + int select_line_number; + char *extraction_regex; + char *units; + int growing; +} Measurement; + +typedef struct +{ + char *db_server_owner; + char *db_server_password; + char *db_server_host; + char *db_connect_db; + DatabaseType db_server_type; + char *server; + char *type; + char *operation; + Rlist *columns; + Rlist *rows; + Rlist *exclude; +} Database; + +/*************************************************************************/ +typedef enum +{ + USER_STATE_PRESENT, + USER_STATE_ABSENT, + USER_STATE_LOCKED, + USER_STATE_NONE +} UserState; + +typedef enum +{ + PASSWORD_FORMAT_PLAINTEXT, + PASSWORD_FORMAT_HASH, + PASSWORD_FORMAT_NONE +} PasswordFormat; + +typedef struct +{ + UserState policy; + char *uid; + PasswordFormat password_format; + char *password; + char *description; + char *group_primary; + Rlist *groups_secondary; + bool groups_secondary_given; + char *home_dir; + char *shell; +} User; + +/*************************************************************************/ + +typedef struct +{ + Rlist *service_depend; + char *service_type; + char *service_args; + char *service_policy; + char *service_autostart_policy; + char *service_depend_chain; +} Services; + +/*************************************************************************/ + +typedef enum +{ + ENVIRONMENT_STATE_CREATE, + ENVIRONMENT_STATE_DELETE, + ENVIRONMENT_STATE_RUNNING, + ENVIRONMENT_STATE_SUSPENDED, + ENVIRONMENT_STATE_DOWN, + ENVIRONMENT_STATE_NONE +} EnvironmentState; + +typedef struct +{ + int cpus; + int memory; + int disk; + char *baseline; + char *spec; + Rlist *addresses; + char *name; + char *host; + char *type; + EnvironmentState state; +} Environments; + +/* This is huge, but the simplification of logic is huge too + so we leave it to the compiler to optimize */ + +#include + +typedef struct +{ + const char *source; + const char *port; /* port or service name */ + char *destination; + FileComparator compare; + FileLinkType link_type; + Rlist *servers; + Rlist *link_instead; + Rlist *copy_links; + BackupOption backup; + int stealth; + int preserve; + int collapse; /* collapse_destination_dir */ + int check_root; + int type_check; + int force_update; + int force_ipv4; + size_t min_size; /* Safety margin not search criterion */ + size_t max_size; + int trustkey; + int encrypt; + int verify; + int purge; + short timeout; + ProtocolVersion protocol_version; + bool missing_ok; +} FileCopy; + +typedef struct +{ + FileSelect select; + FilePerms perms; + FileCopy copy; + FileDelete delete; + char *content; + FileRename rename; + FileChange change; + FileLink link; + EditDefaults edits; + Packages packages; + NewPackages new_packages; + ContextConstraint context; + Measurement measure; + Acl acl; + Database database; + Services service; + User users; + Environments env; + char *transformer; + char *pathtype; + char *file_type; + char *repository; + char *edit_template; + char *edit_template_string; + char *template_method; + JsonElement *template_data; + int touch; + int create; + int move_obstructions; + int inherit; + + DirectoryRecursion recursion; + TransactionContext transaction; + DefineClasses classes; + + ExecContain contain; + char *args; + Rlist *arglist; + int module; + bool inform; + + Rlist *signals; + char *process_stop; + char *restart_class; + ProcessCount process_count; + ProcessSelect process_select; + + Report report; + StorageMount mount; + StorageVolume volume; + + int havedepthsearch; + int haveselect; + int haverename; + int havedelete; + int haveperms; + int havechange; + int havecopy; + int havelink; + int haveeditline; + int haveeditxml; + int haveedit; + int havecontain; + int haveclasses; + int havetrans; + int haveprocess_count; + int havemount; + int havevolume; + int havebundle; + int havepackages; + + /* editline */ + + EditRegion region; + EditLocation location; + EditColumn column; + EditReplace replace; + EditXml xml; + int haveregion; + int havelocation; + int havecolumn; + int havereplace; + int haveinsertselect; + int havedeleteselect; + LineSelect line_select; + char *sourcetype; + int expandvars; + int not_matching; + Rlist *insert_match; +} Attributes; + +#define ZeroAttributes {\ + .select = {0},\ + .perms = {0},\ + .copy = {0},\ + .delete = {0},\ + .content = NULL,\ + .rename = {0},\ + .change = {0},\ + .link = {0},\ + .edits = {0},\ + .packages = {0},\ + .new_packages = {0},\ + .context = {0},\ + .measure = {0},\ + .acl = {0},\ + .database = {0},\ + .service = {0},\ + .users = {0},\ + .env = {0},\ + .transformer = NULL,\ + .pathtype = NULL,\ + .file_type = NULL,\ + .repository = NULL,\ + .edit_template = NULL,\ + .edit_template_string = NULL,\ + .template_method = NULL,\ + .template_data = NULL,\ + .touch = 0,\ + .create = 0,\ + .move_obstructions = 0,\ + .inherit = 0,\ + .recursion = { 0 },\ + .transaction = { 0 },\ + .classes = { 0 },\ + .contain = { 0 },\ + .args = NULL,\ + .arglist = NULL,\ + .module = 0,\ + .inform = false,\ + .signals = NULL,\ + .process_stop = NULL,\ + .restart_class = NULL,\ + .process_count = { 0 },\ + .process_select = { 0 },\ + .report = { 0 },\ + .mount = { 0 },\ + .volume = { 0 },\ + .havedepthsearch = 0,\ + .haveselect = 0,\ + .haverename = 0,\ + .havedelete = 0,\ + .haveperms = 0,\ + .havechange = 0,\ + .havecopy = 0,\ + .havelink = 0,\ + .haveeditline = 0,\ + .haveeditxml = 0,\ + .haveedit = 0,\ + .havecontain = 0,\ + .haveclasses = 0,\ + .havetrans = 0,\ + .haveprocess_count = 0,\ + .havemount = 0,\ + .havevolume = 0,\ + .havebundle = 0,\ + .havepackages = 0,\ + .region = { 0 },\ + .location = { 0 },\ + .column = { 0 },\ + .replace = { 0 },\ + .xml = { 0 },\ + .haveregion = 0,\ + .havelocation = 0,\ + .havecolumn = 0,\ + .havereplace = 0,\ + .haveinsertselect = 0,\ + .havedeleteselect = 0,\ + .line_select = { 0 },\ + .sourcetype = NULL,\ + .expandvars = 0,\ + .not_matching = 0,\ + .insert_match = NULL\ +} + +/*************************************************************************/ +/* common macros */ +/*************************************************************************/ + +#include +#include +#include +#include +#include +#include + +extern const ConstraintSyntax CF_COMMON_BODIES[]; +extern const ConstraintSyntax CF_VARBODY[]; +extern const PromiseTypeSyntax *const CF_ALL_PROMISE_TYPES[]; +extern const ConstraintSyntax CFG_CONTROLBODY[]; +extern const BodySyntax CONTROL_BODIES[]; +extern const ConstraintSyntax CFH_CONTROLBODY[]; +extern const PromiseTypeSyntax CF_COMMON_PROMISE_TYPES[]; +extern const ConstraintSyntax CF_CLASSBODY[]; +extern const ConstraintSyntax CFA_CONTROLBODY[]; +extern const ConstraintSyntax CFEX_CONTROLBODY[]; + +typedef struct ServerConnectionState_ ServerConnectionState; + +#endif diff --git a/libpromises/cf3.extern.h b/libpromises/cf3.extern.h new file mode 100644 index 0000000000..d8d93f5341 --- /dev/null +++ b/libpromises/cf3.extern.h @@ -0,0 +1,90 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CF3_EXTERN_H +#define CFENGINE_CF3_EXTERN_H + + +#include /* CF_MAX_IP_LEN */ +#include /* CF_MAXVARSIZE,CF_OBSERVABLES */ + + +/* See variables in cf3globals.c and syntax.c */ + +extern pid_t ALARM_PID; +extern RSA *PRIVKEY, *PUBKEY; + +extern char BINDINTERFACE[CF_MAXVARSIZE]; +extern time_t CONNTIMEOUT; + +extern time_t CFSTARTTIME; + +extern struct utsname VSYSNAME; +extern char VIPADDRESS[CF_MAX_IP_LEN]; +extern char VPREFIX[1024]; + +extern char VFQNAME[CF_MAXVARSIZE]; +extern char VDOMAIN[CF_MAXVARSIZE / 2]; +extern char VUQNAME[CF_MAXVARSIZE / 2]; + +typedef enum EvalMode { + EVAL_MODE_NORMAL = 0, /* needs to be 'false' to work for DONTDO below */ + EVAL_MODE_DRY_RUN = 1, + EVAL_MODE_SIMULATE_DIFF = 2, + EVAL_MODE_SIMULATE_MANIFEST = 3, + EVAL_MODE_SIMULATE_MANIFEST_FULL = 4, +} EvalMode; + +extern EvalMode EVAL_MODE; + +/* This is only for backwards compatibility with code that uses + * 'if (!DONTDO)'. */ +#define DONTDO ((bool) EVAL_MODE) + +extern bool MINUSF; + +extern int EDITFILESIZE; +extern int VIFELAPSED; +extern int VEXPIREAFTER; + +extern const char *const OBSERVABLES[CF_OBSERVABLES][2]; + +extern bool FIPS_MODE; +extern HashMethod CF_DEFAULT_DIGEST; +extern int CF_DEFAULT_DIGEST_LEN; + +extern int CF_PERSISTENCE; + +extern const char *const CF_AGENTTYPES[]; + +extern int CFA_MAXTHREADS; +extern AgentType THIS_AGENT_TYPE; +extern long LASTSEENEXPIREAFTER; +extern const char *DEFAULT_COPYTYPE; + +extern const char *const DAY_TEXT[]; +extern const char *const MONTH_TEXT[]; +extern const char *const SHIFT_TEXT[]; + +#endif diff --git a/libpromises/cf3globals.c b/libpromises/cf3globals.c new file mode 100644 index 0000000000..745cfc50a1 --- /dev/null +++ b/libpromises/cf3globals.c @@ -0,0 +1,156 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +/*****************************************************************************/ +/* flags */ +/*****************************************************************************/ + + +/*****************************************************************************/ +/* operational state */ +/*****************************************************************************/ + +bool FIPS_MODE = false; /* GLOBAL_P */ + +struct utsname VSYSNAME; /* GLOBAL_E, initialized later */ + +int CFA_MAXTHREADS = 10; /* GLOBAL_P */ +int CF_PERSISTENCE = 10; /* GLOBAL_P */ + +AgentType THIS_AGENT_TYPE; /* GLOBAL_C, initialized later */ + +/*****************************************************************************/ +/* Internal data structures */ +/*****************************************************************************/ + +long LASTSEENEXPIREAFTER = SECONDS_PER_WEEK; /* GLOBAL_P */ + +/*****************************************************************************/ +/* Compatibility infrastructure */ +/*****************************************************************************/ + +/* The mode policy is evaluated in (normal, dry-run, audit,...) */ +EvalMode EVAL_MODE = EVAL_MODE_NORMAL; + +/* NB! Check use before changing sizes */ +// Note: These were previously all CF_MAXVARSIZE = 1024 size +// However, to avoid problematic truncation, we changed the last 2 to 512, +// thus they will fit into VFQNAME ("%s.%s"). +// This RFC indicates that DNS only supports up to 255 bytes, anyway: +// https://tools.ietf.org/html/rfc2181#section-11 +char VFQNAME[CF_MAXVARSIZE] = ""; /* GLOBAL_E GLOBAL_P */ +char VUQNAME[CF_MAXVARSIZE / 2] = ""; /* GLOBAL_E */ +char VDOMAIN[CF_MAXVARSIZE / 2] = ""; /* GLOBAL_E GLOBAL_P */ + +/* + Default value for copytype attribute. Loaded by cf-agent from body control +*/ +const char *DEFAULT_COPYTYPE = NULL; /* GLOBAL_P */ + +/* + Keys for the agent. Loaded by LoadSecretKeys. + + Used in network protocol and leaked to language. +*/ +RSA *PRIVKEY = NULL, *PUBKEY = NULL; /* GLOBAL_X */ + +/* + First IP address discovered by DetectEnvironment (hence reloaded every policy + change). + + Used somewhere in cf-execd, superficially in old-style protocol handshake and + sporadically in other situations. +*/ +char VIPADDRESS[CF_MAX_IP_LEN] = ""; /* GLOBAL_E */ + +/* + Edition-time constant (MD5 for community, something else for Enterprise) + + Used as a default hash everywhere (not only in network protocol) +*/ +HashMethod CF_DEFAULT_DIGEST; /* GLOBAL_C, initialized later */ +int CF_DEFAULT_DIGEST_LEN; /* GLOBAL_C, initialized later */ + +/* + Holds the "now" time captured at the moment of policy (re)load. + + TODO: This variable should be internal to timeout.c, not exposed. + It should only be set by SetStartTime() and read by GetStartTime(). + + Utilized everywhere "now" start time is needed +*/ +time_t CFSTARTTIME; /* GLOBAL_E, initialized later */ + +/* + Set in cf-agent/cf-runagent (from control body). + + Used as a timeout for socket operations in network code. +*/ +time_t CONNTIMEOUT = 30; /* seconds */ /* GLOBAL_A GLOBAL_P */ + +/* + Internal detail of timeout operations. Due to historical reasons + is defined here, not in libpromises/timeout.c + */ +pid_t ALARM_PID = -1; /* GLOBAL_X */ + +/* + Set in cf-agent (from control body). + + Used as a default value for maxfilesize attribute in policy +*/ +int EDITFILESIZE = 100000; /* GLOBAL_P */ + +/* + Set in cf-agent (from control body) and GenericAgentInitialize. + + Used as a default value for ifelapsed attribute in policy. +*/ +int VIFELAPSED = 1; /* GLOBAL_P */ + +/* + Set in cf-agent (from control body) and GenericAgentInitialize. + + Used as a default value for expireafter attribute in policy. +*/ +int VEXPIREAFTER = 120; /* GLOBAL_P */ + +/* + Set in cf-agent/cf-serverd (from control body). + + Utilized in server/client code to bind sockets. +*/ +char BINDINTERFACE[CF_MAXVARSIZE]; /* GLOBAL_P */ + +/* + Set in cf-*.c:CheckOpts and GenericAgentConfigParseArguments. + + Utilized in generic_agent.c for + - cf_promises_validated filename + - GenericAgentCheckPolicy + - GenericAgentLoadPolicy (ReadPolicyValidatedFile) +*/ +bool MINUSF = false; /* GLOBAL_A */ diff --git a/libpromises/cf3lex.l b/libpromises/cf3lex.l new file mode 100644 index 0000000000..bcbc9313b0 --- /dev/null +++ b/libpromises/cf3lex.l @@ -0,0 +1,784 @@ +%top{ +/* + Copyright 2021 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/*******************************************************************/ +/* */ +/* LEXER for cfengine 3 */ +/* */ +/*******************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +} + +%{ +/* yyinput/input are generated and unused */ + +#if defined(__GNUC__) +# define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +# if GCC_VERSION >= 40200 +# pragma GCC diagnostic ignored "-Wunused-function" +# endif +#endif + +#undef malloc +#undef realloc +#define malloc xmalloc +#define realloc xrealloc + +//#define ParserDebug if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) printf +#define ParserDebug(...) ((void) 0) +#define P PARSER_STATE + +static Regex *context_expression_whitespace_rx = NULL; + +static int DeEscapeQuotedString(const char *from, char *to); + +int yywrap(void) +{ +return 1; +} + +static void yyuseraction() +{ +P.offsets.current += yyleng; +} + +#define YY_USER_ACTION yyuseraction(); + +// Do not use lex - flex only + +%} + +%x if_ignore_state + +space [ \t]+ + +newline ([\n]|[\xd][\xa]) + +eat_line ([\n]|[\xd][\xa]).* + +comment #[^\n]* + +macro_if_minimum_version @if\ minimum_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\) +macro_if_maximum_version @if\ maximum_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\) +macro_if_before_version @if\ before_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\) +macro_if_at_version @if\ at_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\) +macro_if_after_version @if\ after_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\) +macro_if_between_versions @if\ between_versions\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\ *,\ *[0-9]{1,5}(\.[0-9]{1,5}){0,2}\) +macro_if_feature @if\ feature\([a-zA-Z0-9_]+\) +macro_else @else +macro_endif @endif +macro_line [^@\n\xd\xa].* + +bundle bundle + +body body + +promise promise + +nakedvar [$@][(][a-zA-Z0-9_\[\]\200-\377.:]+[)]|[$@][{][a-zA-Z0-9_\[\]\200-\377.:]+[}]|[$@][(][a-zA-Z0-9_\200-\377.:]+[\[][a-zA-Z0-9_$(){}\200-\377.:]+[\]]+[)]|[$@][{][a-zA-Z0-9_\200-\377.:]+[\[][a-zA-Z0-9_$(){}\200-\377.:]+[\]]+[}] + +identifier [a-zA-Z0-9_\200-\377]+ + +symbol [a-zA-Z0-9_\200-\377]+[:][a-zA-Z0-9_\200-\377]+ + +fat_arrow => + +thin_arrow -> + +/* + * Three types of quoted strings: + * + * - string in double quotes, starts with double quote, runs until another + * double quote, \" masks the double quote. + * - string in single quotes, starts with single quote, runs until another + * single quote, \' masks the single quote. + * - string in backquotes, starts with backquote, runs until another backquote. + * + * The same rule formatted for the better readability: + * + * := \" \" | \' \' | ` ` + * = * + * = \\ | [^"\\] + * = * + * = \\ | [^'\\] + * = * + * = [^`] + * = . | \n + * + */ + +qstring \"((\\(.|\n))|[^"\\])*\"|\'((\\(.|\n))|[^'\\])*\'|`[^`]*` + +class [.|&!()a-zA-Z0-9_\200-\377:][\t .|&!()a-zA-Z0-9_\200-\377:]*:: +varclass (\"[^"\0]*\"|\'[^'\0]*\'):: + +promise_guard [a-zA-Z_]+: + +%% +{eat_line} { + free(P.current_line); + P.current_line = xstrdup(yytext+1); + ParserDebug("L:line %s\n", P.current_line); + P.line_no++; + P.line_pos = 1; + // push back on stack and skip first char + yyless(1); + } + +{macro_if_minimum_version} { + if ( P.line_pos != 1 ) + { + yyerror("fatal: macro @if must be at beginning of the line."); + return 0; + } + if (P.if_depth > 0) + { + yyerror("fatal: nested @if macros are not allowed"); + return 0; + } + + P.if_depth++; + + const char* minimum = yytext+20; + ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, minimum); + + VersionComparison result = CompareVersion(Version(), minimum); + if (result == VERSION_GREATER || result == VERSION_EQUAL) + { + ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos); + } + else if (result == VERSION_SMALLER) + { + ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos); + BEGIN(if_ignore_state); + } + else + { + assert(result == VERSION_ERROR); + yyerror("fatal: macro @if requested an unparseable version"); + } + } + +{macro_if_maximum_version} { + if ( P.line_pos != 1 ) + { + yyerror("fatal: macro @if must be at beginning of the line."); + return 0; + } + if (P.if_depth > 0) + { + yyerror("fatal: nested @if macros are not allowed"); + return 0; + } + + P.if_depth++; + + const char* maximum = yytext+20; + ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, maximum); + + VersionComparison result = CompareVersion(Version(), maximum); + if (result == VERSION_SMALLER || result == VERSION_EQUAL) + { + ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos); + } + else if (result == VERSION_GREATER) + { + ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos); + BEGIN(if_ignore_state); + } + else + { + assert(result == VERSION_ERROR); + yyerror("fatal: macro @if requested an unparseable version"); + } + } + +{macro_if_before_version} { + if ( P.line_pos != 1 ) + { + yyerror("fatal: macro @if must be at beginning of the line."); + return 0; + } + if (P.if_depth > 0) + { + yyerror("fatal: nested @if macros are not allowed"); + return 0; + } + + P.if_depth++; + + const char* target = yytext + strlen("@if before_version("); + ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target); + + VersionComparison result = CompareVersion(Version(), target); + if (result == VERSION_SMALLER) + { + ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos); + } + else if (result == VERSION_GREATER || result == VERSION_EQUAL) + { + ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos); + BEGIN(if_ignore_state); + } + else + { + assert(result == VERSION_ERROR); + yyerror("fatal: macro @if requested an unparseable version"); + } + } + +{macro_if_at_version} { + if ( P.line_pos != 1 ) + { + yyerror("fatal: macro @if must be at beginning of the line."); + return 0; + } + if (P.if_depth > 0) + { + yyerror("fatal: nested @if macros are not allowed"); + return 0; + } + + P.if_depth++; + + const char* target = yytext + strlen("@if at_version("); + ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target); + + VersionComparison result = CompareVersion(Version(), target); + if (result == VERSION_EQUAL) + { + ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos); + } + else if (result == VERSION_GREATER || result == VERSION_SMALLER) + { + ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos); + BEGIN(if_ignore_state); + } + else + { + assert(result == VERSION_ERROR); + yyerror("fatal: macro @if requested an unparseable version"); + } + } + +{macro_if_after_version} { + if ( P.line_pos != 1 ) + { + yyerror("fatal: macro @if must be at beginning of the line."); + return 0; + } + if (P.if_depth > 0) + { + yyerror("fatal: nested @if macros are not allowed"); + return 0; + } + + P.if_depth++; + + const char* target = yytext + strlen("@if after_version("); + ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target); + + VersionComparison result = CompareVersion(Version(), target); + if (result == VERSION_GREATER) + { + ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos); + } + else if (result == VERSION_EQUAL || result == VERSION_SMALLER) + { + ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos); + BEGIN(if_ignore_state); + } + else + { + assert(result == VERSION_ERROR); + yyerror("fatal: macro @if requested an unparseable version"); + } + } + +{macro_if_between_versions} { + if ( P.line_pos != 1 ) + { + yyerror("fatal: macro @if must be at beginning of the line."); + return 0; + } + if (P.if_depth > 0) + { + yyerror("fatal: nested @if macros are not allowed"); + return 0; + } + + P.if_depth++; + + const char* from = yytext + strlen("@if between_versions("); + + const char *to = strchr(from, ','); + to += 1; + while (*to == ' ') + { + to += 1; + } + + ParserDebug("\tL:macro @if %d:between_versions(%s\n", P.line_pos, from); + + VersionComparison a = CompareVersion(Version(), from); + VersionComparison b = CompareVersion(Version(), to); + if ((a == VERSION_EQUAL || a == VERSION_GREATER) + && (b == VERSION_EQUAL || b == VERSION_SMALLER)) + { + ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos); + } + else + { + ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos); + BEGIN(if_ignore_state); + } + } + +{macro_if_feature} { + if ( P.line_pos != 1 ) + { + yyerror("fatal: macro @if must be at beginning of the line."); + return 0; + } + + char* feature_text = yytext+12; + // remove trailing ')' + feature_text[strlen(feature_text)-1] = 0; + ParserDebug("\tL:macro @if %d:feature=%s\n", P.line_pos, feature_text); + { + if (P.if_depth > 0) + { + yyerror("fatal: nested @if macros are not allowed"); + return 0; + } + + P.if_depth++; + + if (KnownFeature(feature_text)) + { + ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos); + } + else + { + /* ignore to the next @endif */ + ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos); + BEGIN(if_ignore_state); + } + } + } + +{macro_endif} { + if ( P.line_pos != 1 ) + { + yyerror("fatal: macro @endif must be at beginning of the line."); + return 0; + } + + ParserDebug("\tL:macro @endif %d\n", P.line_pos); + BEGIN(INITIAL); + if (P.if_depth <= 0) + { + yyerror("fatal: @endif macros without a matching @if are not allowed"); + return 0; + } + P.if_depth--; + } + + +{macro_line} { + ParserDebug("\tL:inside macro @if, ignoring line text:\"%s\"\n", yytext); + } + +{macro_else} { + ParserDebug("\tL:macro @else, will no longer ignore lines\n", P.line_pos); + BEGIN(INITIAL); + } +{macro_else} { + if (P.if_depth <= 0) + { + yyerror("fatal: @else macro without a matching @if are not allowed"); + return 0; + } + ParserDebug("\tL:macro @else, will now ignore lines\n", P.line_pos); + BEGIN(if_ignore_state); + } + +{macro_endif} { + ParserDebug("\tL:macro @endif %d\n", P.line_pos); + BEGIN(INITIAL); + if (P.if_depth <= 0) + { + yyerror("fatal: @endif macros without a matching @if are not allowed"); + return 0; + } + P.if_depth--; + } + +{newline} { + } + +. { + /* eat up al unknown chars when line starts with @*/ + ParserDebug("\tL:inside macro @if, ignoring char text:\"%s\"\n", yytext); + } + +{bundle} { + /* Note this has to come before "id" since it is a subset of id */ + + if (P.currentclasses != NULL) + { + free(P.currentclasses); + P.currentclasses = NULL; + } + + if (P.currentvarclasses != NULL) + { + free(P.currentvarclasses); + P.currentvarclasses = NULL; + } + + P.line_pos += yyleng; + ParserDebug("\tL:bundle %d\n", P.line_pos); + return BUNDLE; + } + +{body} { + /* Note this has to come before "id" since it is a subset of id */ + + if (P.currentclasses != NULL) + { + free(P.currentclasses); + P.currentclasses = NULL; + } + + if (P.currentvarclasses != NULL) + { + free(P.currentvarclasses); + P.currentvarclasses = NULL; + } + + P.line_pos += yyleng; + ParserDebug("\tL:body %d\n", P.line_pos); + return BODY; + } + +{promise} { + /* Note this has to come before "id" since it is a subset of id */ + + if (P.currentclasses != NULL) + { + free(P.currentclasses); + P.currentclasses = NULL; + } + + if (P.currentvarclasses != NULL) + { + free(P.currentvarclasses); + P.currentvarclasses = NULL; + } + + P.line_pos += yyleng; + ParserDebug("\tL:promise %d\n", P.line_pos); + return PROMISE; + } + +{identifier} { + P.offsets.last_id = P.offsets.current - yyleng; + P.line_pos += yyleng; + ParserDebug("\tL:id %s %d\n", yytext, P.line_pos); + if (yyleng > CF_MAXVARSIZE-1) + { + yyerror("identifier too long"); + } + strncpy(P.currentid, yytext, CF_MAXVARSIZE - 1); + return IDENTIFIER; + } + + +{symbol} { + P.offsets.last_id = P.offsets.current - yyleng; + P.line_pos += yyleng; + ParserDebug("\tL:symbol %s %d\n", yytext, P.line_pos); + if (yyleng > CF_MAXVARSIZE-1) + { + yyerror("qualified identifier too long"); + } + strncpy(P.currentid, yytext, CF_MAXVARSIZE - 1); + return IDENTIFIER; + } + + +{fat_arrow} { + P.line_pos += yyleng; + ParserDebug("\tL:assign %d\n", P.line_pos); + return FAT_ARROW; + } + +{thin_arrow} { + P.line_pos += yyleng; + ParserDebug("\tL:arrow %d\n", P.line_pos); + return THIN_ARROW; + } + +{varclass} { + char *tmp = NULL; + + P.line_pos += yyleng; + ParserDebug("\tL:varclass %s %d\n", yytext, P.line_pos); + + tmp = xstrdup(yytext+1); // remove leading quote + tmp[yyleng-4] = '\0'; // remove tail quote plus :: + + if (P.currentclasses != NULL) + { + free(P.currentclasses); + P.currentclasses = NULL; + } + + if (P.currentvarclasses != NULL) + { + free(P.currentvarclasses); + P.currentvarclasses = NULL; + } + + P.currentvarclasses = xstrdup(tmp); + + free(tmp); + return CLASS_GUARD; + } + +{class} { + char *tmp = NULL; + + P.line_pos += yyleng; + ParserDebug("\tL:class %s %d\n", yytext, P.line_pos); + if (context_expression_whitespace_rx == NULL) + { + context_expression_whitespace_rx = CompileRegex(CFENGINE_REGEX_WHITESPACE_IN_CONTEXTS); + } + + if (context_expression_whitespace_rx == NULL) + { + yyerror("The context expression whitespace regular expression could not be compiled, aborting."); + } + + if (StringMatchFullWithPrecompiledRegex(context_expression_whitespace_rx, yytext)) + { + yyerror("class names can't be separated by whitespace without an intervening operator"); + } + + tmp = xstrdup(yytext); + tmp[yyleng-2] = '\0'; + + if (P.currentclasses != NULL) + { + free(P.currentclasses); + P.currentclasses = NULL; + } + + if (P.currentvarclasses != NULL) + { + free(P.currentvarclasses); + P.currentvarclasses = NULL; + } + + P.currentclasses = xstrdup(tmp); + + free(tmp); + return CLASS_GUARD; + } + +{promise_guard} { + char *tmp = NULL; + + P.line_pos += yyleng; + ParserDebug("\tL:promise_guard %s %d\n", yytext, P.line_pos); + P.offsets.last_promise_guard_id = P.offsets.current - yyleng; + + tmp = xstrdup(yytext); + assert(tmp[yyleng - 1] == ':'); + tmp[yyleng - 1] = '\0'; // Exclude trailing colon in promise guard + assert(strlen(tmp) > 0); + assert(tmp[strlen(tmp) - 1] != ':'); + + strncpy(P.currenttype, tmp, CF_MAXVARSIZE - 1); + + if (P.currentclasses != NULL) + { + free(P.currentclasses); + P.currentclasses = NULL; + } + + if (P.currentvarclasses != NULL) + { + free(P.currentvarclasses); + P.currentvarclasses = NULL; + } + + free(tmp); + return PROMISE_GUARD; + } + +{qstring} { + char *tmp = NULL; + int less = 0; + + P.offsets.last_string = P.offsets.current - yyleng; + P.line_pos += yyleng; + ParserDebug("\tL:qstring %s %d\n", yytext, P.line_pos); + + for (char *c = yytext; *c; ++c) + { + if (*c == '\n') + { + P.line_no++; + } + } + + tmp = xmalloc(yyleng + 1); + + if ((less = DeEscapeQuotedString(yytext,tmp)) > 0) + { + yyless(less); + P.offsets.current -= less; + } + + if (P.currentstring) + { + free(P.currentstring); + } + + P.currentstring = xstrdup(tmp); + + free(tmp); + return QUOTED_STRING; + } + + +{nakedvar} { + P.line_pos += yyleng; + ParserDebug("\tL: %s %d\n", yytext, P.line_pos); + if (P.currentstring) + { + free(P.currentstring); + } + P.currentstring = xstrdup(yytext); + return NAKEDVAR; + } + +{space}+ { + P.line_pos += yyleng; + } + +{comment} { + } + + +. { + P.line_pos++; + return yytext[0]; + } + +<> { + if (P.if_depth > 0) + { + yyerror("EOF seen while @if was waiting for @endif in ignore_state"); + return 0; + } + } + +<> { + if (P.if_depth > 0) + { + yyerror("EOF seen while @if was waiting for @endif without ignore_state"); + } + return 0; // loops forever without this + } + +%% + +static int DeEscapeQuotedString(const char *from, char *to) +{ + char *cp; + const char *sp; + char start = *from; + int len = strlen(from); + + if (len == 0) + { + return 0; + } + + for (sp = from + 1, cp = to; (sp - from) < len; sp++, cp++) + { + if ((*sp == start)) + { + *(cp) = '\0'; + + if (*(sp + 1) != '\0') + { + return (2 + (sp - from)); + } + + return 0; + } + + if (*sp == '\\') + { + switch (*(sp + 1)) + { + case '\n': + sp += 2; + break; + + case ' ': + break; + + case '\\': + case '\"': + case '\'': + sp++; + break; + } + } + + *cp = *sp; + } + + yyerror("Runaway string"); + *(cp) = '\0'; + return 0; +} + + +/* EOF */ diff --git a/libpromises/cf3parse.y b/libpromises/cf3parse.y new file mode 100644 index 0000000000..047843fa68 --- /dev/null +++ b/libpromises/cf3parse.y @@ -0,0 +1,903 @@ +/* + Copyright 2021 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +%{ +#include +%} + +%token IDENTIFIER QUOTED_STRING CLASS_GUARD PROMISE_GUARD BUNDLE BODY PROMISE FAT_ARROW THIN_ARROW NAKEDVAR +%expect 1 + +%% + +specification: /* empty */ + | blocks + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +blocks: block + | blocks block; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +block: bundle + | body + | promise + | error + { + ParseError("Expected 'bundle' or 'body' keyword, wrong input '%s'", yytext); + YYABORT; + } + +bundle: BUNDLE bundletype bundleid arglist bundlebody + +body: BODY bodytype bodyid arglist bodybody + +promise: PROMISE promisecomponent promiseid arglist bodybody + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bundletype: bundletype_values + { + ParserBeginBlock(PARSER_BLOCK_BUNDLE); + } + +bundletype_values: typeid + { + /* FIXME: We keep it here, because we skip unknown + * promise bundles. Ought to be moved to + * after-parsing step once we know how to deal with + * it */ + + if (!BundleTypeCheck(P.blocktype)) + { + ParseError("Unknown bundle type '%s'", P.blocktype); + INSTALL_SKIP = true; + } + } + | error + { + yyclearin; + ParseError("Expected bundle type, wrong input '%s'", yytext); + INSTALL_SKIP = true; + } + +bundleid: bundleid_values + { + ParserDebug("\tP:bundle:%s:%s\n", P.blocktype, P.blockid); + CURRENT_BLOCKID_LINE = P.line_no; + } + +bundleid_values: symbol + | error + { + yyclearin; + ParseError("Expected bundle identifier, wrong input '%s'", yytext); + INSTALL_SKIP = true; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bodytype: bodytype_values + { + ParserBeginBlock(PARSER_BLOCK_BODY); + } + +bodytype_values: typeid + { + if (!BodySyntaxGet(PARSER_BLOCK_BODY, P.blocktype)) + { + ParseError("Unknown body type '%s'", P.blocktype); + } + } + | error + { + yyclearin; + ParseError("Expected body type, wrong input '%s'", yytext); + } + +bodyid: bodyid_values + { + ParserDebug("\tP:body:%s:%s\n", P.blocktype, P.blockid); + CURRENT_BLOCKID_LINE = P.line_no; + } + +bodyid_values: symbol + | error + { + yyclearin; + ParseError("Expected body identifier, wrong input '%s'", yytext); + INSTALL_SKIP = true; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +promisecomponent: promisecomponent_values + { + ParserBeginBlock(PARSER_BLOCK_PROMISE); + } + +promisecomponent_values: typeid + { + if (!StringEqual(P.blocktype, "agent")) + { + ParseError("Custom promises only supported for 'agent', not '%s'", P.blocktype); + } + } + | error + { + yyclearin; + ParseError("Expected 'agent', got '%s'", yytext); + } + +promiseid: promiseid_values + { + if (IsBuiltInPromiseType(P.blockid)) + { + ParseError("'%s' promises are built in and cannot be custom", yytext); + } + ParserDebug("\tP:promise:%s:%s\n", P.blocktype, P.blockid); + CURRENT_BLOCKID_LINE = P.line_no; + } + +promiseid_values: symbol + | error + { + yyclearin; + ParseError("Expected promise type identifier, wrong input '%s'", yytext); + INSTALL_SKIP = true; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typeid: IDENTIFIER + { + strncpy(P.blocktype,P.currentid,CF_MAXVARSIZE); + + RlistDestroy(P.useargs); + P.useargs = NULL; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +symbol: IDENTIFIER + { + strncpy(P.blockid,P.currentid,CF_MAXVARSIZE); + P.offsets.last_block_id = P.offsets.last_id; + }; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +arglist: /* Empty */ + | arglist_begin aitems arglist_end + | arglist_begin arglist_end + | arglist_begin error + { + yyclearin; + ParseError("Error in bundle parameter list, expected ')', wrong input '%s'", yytext); + } + +arglist_begin: '(' + { + ParserDebug("P:%s:%s:%s arglist begin:%s\n", ParserBlockString(P.block),P.blocktype,P.blockid, yytext); + } + +arglist_end: ')' + { + ParserDebug("P:%s:%s:%s arglist end:%s\n", ParserBlockString(P.block),P.blocktype,P.blockid, yytext); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +aitems: aitem + | aitem ',' + | aitem ',' aitems + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +aitem: IDENTIFIER /* recipient of argument is never a literal */ + { + ParserDebug("P:%s:%s:%s arg id: %s\n", ParserBlockString(P.block),P.blocktype,P.blockid, P.currentid); + RlistAppendScalar(&(P.useargs),P.currentid); + } + | error + { + yyclearin; + ParseError("Expected identifier, wrong input '%s'", yytext); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bundlebody: body_begin + { + ParserBeginBundleBody(); + } + + bundle_decl + + '}' + { + INSTALL_SKIP = false; + ParserEndCurrentBlock(); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +body_begin: '{' + { + ParserDebug("P:%s:%s:%s begin body open\n", ParserBlockString(P.block),P.blocktype,P.blockid); + } + | error + { + ParseError("Expected body open '{', wrong input '%s'", yytext); + } + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bundle_decl: /* empty */ + | bundle_statements + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bundle_statements: bundle_statement + | bundle_statements bundle_statement + | error + { + INSTALL_SKIP = true; + ParseError("Expected promise type, got '%s'", yytext); + ParserDebug("P:promise_type:error yychar = %d, %c, yyempty = %d\n", yychar, yychar, YYEMPTY); + yyclearin; + } + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bundle_statement: promise_guard classpromises_decl + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +promise_guard: PROMISE_GUARD /* BUNDLE ONLY */ + { + ParserHandlePromiseGuard(); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +classpromises_decl: /* empty */ + | classpromises + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +classpromises: classpromise + | classpromises classpromise + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +classpromise: class + | promise_decl + { + ParserCheckPromiseLine(); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +promise_decl: promise_line ';' + | promiser error + { + /* + * Based on yychar display right error message + */ + ParserDebug("P:promiser:error yychar = %d\n", yychar); + if (yychar =='-' || yychar == '>') + { + ParseError("Expected '->', got '%s'", yytext); + } + else if (yychar == IDENTIFIER) + { + ParseError("Expected attribute, got '%s'", yytext); + } + else if (yychar == ',') + { + ParseError("Expected attribute, got '%s' (comma after promiser is not allowed since 3.5.0)", yytext); + } + else + { + ParseError("Expected ';', got '%s'", yytext); + } + yyclearin; + } + +promise_line: promise_with_promisee + | promise_without_promisee + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +promise_with_promisee: promiser + + promisee_arrow + + rval + { + if (!INSTALL_SKIP) + { + if (!P.currentstype) + { + ParseError("Missing promise type declaration"); + } + + P.currentpromise = BundleSectionAppendPromise(P.currentstype, P.promiser, + RvalCopy(P.rval), + P.currentclasses ? P.currentclasses : "any", + P.currentvarclasses); + P.currentpromise->offset.line = CURRENT_PROMISER_LINE; + P.currentpromise->offset.start = P.offsets.last_string; + P.currentpromise->offset.context = P.offsets.last_class_id; + } + else + { + P.currentpromise = NULL; + } + } + + promise_decl_constraints + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +promise_without_promisee: promiser + { + + if (!INSTALL_SKIP) + { + if (!P.currentstype) + { + ParseError("Missing promise type declaration"); + } + + P.currentpromise = BundleSectionAppendPromise(P.currentstype, P.promiser, + (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, + P.currentclasses ? P.currentclasses : "any", + P.currentvarclasses); + P.currentpromise->offset.line = CURRENT_PROMISER_LINE; + P.currentpromise->offset.start = P.offsets.last_string; + P.currentpromise->offset.context = P.offsets.last_class_id; + } + else + { + P.currentpromise = NULL; + } + } + + promise_decl_constraints + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +promiser: QUOTED_STRING + { + if (P.promiser) + { + free(P.promiser); + } + P.promiser = P.currentstring; + P.currentstring = NULL; + CURRENT_PROMISER_LINE = P.line_no; + ParserDebug("\tP:%s:%s:%s:%s:%s promiser = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currenttype, P.currentclasses ? P.currentclasses : "any", P.promiser); + } + | error + { + INSTALL_SKIP = true; + ParserDebug("P:promiser:qstring::error yychar = %d\n", yychar); + + if (yychar == BUNDLE || yychar == BODY) + { + ParseError("Expected '}', got '%s'", yytext); + /* + YYABORT; + */ + } + else + { + ParseError("Expected promiser string, got '%s'", yytext); + } + + yyclearin; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +promise_decl_constraints: /* empty */ + | constraints_decl + | constraints_decl error + { + /* + * Based on next token id display right error message + */ + ParserDebug("P:constraints_decl:error yychar = %d\n", yychar); + if ( yychar == IDENTIFIER ) + { + ParseError("Check previous line, Expected ',', got '%s'", yytext); + } + else + { + ParseError("Check previous line, Expected ';', got '%s'", yytext); + } + yyclearin; + + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +constraints_decl: constraints + { + /* Don't free these */ + strcpy(P.currentid,""); + RlistDestroy(P.currentRlist); + P.currentRlist = NULL; + free(P.promiser); + if (P.currentstring) + { + free(P.currentstring); + } + P.currentstring = NULL; + P.promiser = NULL; + P.promisee = NULL; + /* reset argptrs etc*/ + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +constraints: constraint /* BUNDLE ONLY */ + | constraints ',' constraint + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +constraint: constraint_id /* BUNDLE ONLY */ + assign_arrow + rval + { + ParserHandleBundlePromiseRval(); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +constraint_id: IDENTIFIER /* BUNDLE ONLY */ + { + ParserDebug("\tP:%s:%s:%s:%s:%s:%s attribute = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currenttype, P.currentclasses ? P.currentclasses : "any", P.promiser, P.currentid); + + const PromiseTypeSyntax *promise_type_syntax = PromiseTypeSyntaxGet(P.blocktype, P.currenttype); + if (promise_type_syntax == NULL) + { + // This promise type might be defined in another Policy object. + // There is no way to distinguish a custom promise type + // from a wrong (misspelled) promise type while parsing + // since the Policy objects will be merged later. + } + else if (!PromiseTypeSyntaxGetConstraintSyntax(promise_type_syntax, P.currentid)) + { + // Built in promise type with bad attribute + ParseError("Unknown attribute '%s' for promise type '%s' in bundle with type '%s'", P.currentid, P.currenttype, P.blocktype); + INSTALL_SKIP = true; + } + + strncpy(P.lval,P.currentid,CF_MAXVARSIZE); + RlistDestroy(P.currentRlist); + P.currentRlist = NULL; + } + | error + { + ParseError("Expected attribute, got '%s'\n", yytext); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bodybody: body_begin + { + ParserBeginBlockBody(); + } + + bodybody_inner + + '}' + { + ParserEndCurrentBlock(); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bodybody_inner: /* empty */ + | bodyattribs + +bodyattribs: bodyattrib /* BODY/PROMISE ONLY */ + | bodyattribs bodyattrib + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +bodyattrib: class + | selection_line + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +selection_line: selection ';' + | selection error + { + ParseError("Expected ';' check previous statement, got '%s'", yytext); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +selection: selection_id /* BODY/PROMISE ONLY */ + assign_arrow + rval + { + ParserHandleBlockAttributeRval(); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +selection_id: IDENTIFIER + { + ParserDebug("\tP:%s:%s:%s:%s attribute = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.currentid); + + if (!INSTALL_SKIP) + { + const BodySyntax *body_syntax = BodySyntaxGet(P.block, P.currentbody->type); + + if (!body_syntax || (body_syntax->status != SYNTAX_STATUS_CUSTOM && + !BodySyntaxGetConstraintSyntax(body_syntax->constraints, P.currentid))) + { + ParseError( + "Unknown attribute '%s' for '%s %s %s'", + P.currentid, // attribute name (lval) + ParserBlockString(P.block), // body (block type) + P.currentbody->type, // file (body type) + P.blockid); // control (body name) + INSTALL_SKIP = true; + } + + strncpy(P.lval,P.currentid,CF_MAXVARSIZE); + } + RlistDestroy(P.currentRlist); + P.currentRlist = NULL; + } + | error + { + ParserDebug("P:selection_id:idsyntax:error yychar = %d\n", yychar); + + if ( yychar == BUNDLE || yychar == BODY ) + { + ParseError("Expected '}', got '%s'", yytext); + /* + YYABORT; + */ + } + else + { + ParseError("Expected attribute, got '%s'", yytext); + } + + yyclearin; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +assign_arrow: FAT_ARROW + { + ParserDebug("\tP:=>\n"); + } + | error + { + yyclearin; + ParseError("Expected '=>', got '%s'", yytext); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +promisee_arrow: THIN_ARROW + { + ParserDebug("\tP:->\n"); + } + /* else we display the wrong error + | error + { + yyclearin; + ParseError("Expected '->', got '%s'", yytext); + } + */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +class: CLASS_GUARD + { + P.offsets.last_class_id = P.offsets.current - strlen(P.currentclasses ? P.currentclasses : P.currentvarclasses) - 2; + ParserDebug("\tP:%s:%s:%s:%s %s = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currenttype, P.currentclasses ? "class": "varclass", yytext); + + if (P.currentclasses != NULL) + { + char *literal = xstrdup(P.currentclasses); + + ValidateClassLiteral(literal); + + free(literal); + } + } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +rval: IDENTIFIER + { + ParserDebug("\tP:%s:%s:%s:%s id rval, %s = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.lval, P.currentid); + RvalDestroy(P.rval); + P.rval = (Rval) { xstrdup(P.currentid), RVAL_TYPE_SCALAR }; + P.references_body = true; + } + | QUOTED_STRING + { + ParserDebug("\tP:%s:%s:%s:%s qstring rval, %s = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.lval, P.currentstring); + RvalDestroy(P.rval); + P.rval = (Rval) { P.currentstring, RVAL_TYPE_SCALAR }; + + P.currentstring = NULL; + P.references_body = false; + + if (P.currentpromise) + { + if (LvalWantsBody(P.currentpromise->parent_section->promise_type, P.lval)) + { + yyerror("An rvalue is quoted, but we expect an unquoted body identifier"); + } + } + } + | NAKEDVAR + { + ParserDebug("\tP:%s:%s:%s:%s nakedvar rval, %s = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.lval, P.currentstring); + RvalDestroy(P.rval); + P.rval = (Rval) { P.currentstring, RVAL_TYPE_SCALAR }; + + P.currentstring = NULL; + P.references_body = false; + } + | list + { + ParserDebug("\tP:%s:%s:%s:%s install list = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.lval); + RvalDestroy(P.rval); + P.rval = (Rval) { RlistCopy(P.currentRlist), RVAL_TYPE_LIST }; + RlistDestroy(P.currentRlist); + P.currentRlist = NULL; + P.references_body = false; + } + | usefunction + { + RvalDestroy(P.rval); + P.rval = (Rval) { P.currentfncall[P.arg_nesting+1], RVAL_TYPE_FNCALL }; + P.currentfncall[P.arg_nesting+1] = NULL; + P.references_body = false; + } + + | error + { + yyclearin; + ParseError("Invalid r-value type '%s'", yytext); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +list: '{' '}' + | '{' litems '}' + | '{' litems ',' '}' + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +litems: + litem + | litems ',' litem + | litem error + { + ParserDebug("P:rval:list:error yychar = %d\n", yychar); + if ( yychar ==';' ) + { + ParseError("Expected '}', wrong input '%s'", yytext); + } + else if ( yychar == FAT_ARROW ) + { + ParseError("Check list statement previous line," + " Expected '}', wrong input '%s'", + yytext); + } + else + { + ParseError("Expected ',', wrong input '%s'", yytext); + } + yyclearin; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +litem: IDENTIFIER + { + ParserDebug("\tP:%s:%s:%s:%s list append: " + "id = %s\n", + ParserBlockString(P.block), P.blocktype, P.blockid, + (P.currentclasses ? + P.currentclasses : "any"), + P.currentid); + RlistAppendScalar((Rlist **) &P.currentRlist, + P.currentid); + } + + | QUOTED_STRING + { + ParserDebug("\tP:%s:%s:%s:%s list append: " + "qstring = %s\n", + ParserBlockString(P.block), P.blocktype, P.blockid, + (P.currentclasses ? + P.currentclasses : "any"), + P.currentstring); + + ParserHandleQuotedListItem(); + } + + | NAKEDVAR + { + ParserDebug("\tP:%s:%s:%s:%s list append: nakedvar = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.currentstring); + RlistAppendScalar((Rlist **)&P.currentRlist,(void *)P.currentstring); + free(P.currentstring); + P.currentstring = NULL; + } + + | usefunction + { + RlistAppend(&P.currentRlist, P.currentfncall[P.arg_nesting+1], RVAL_TYPE_FNCALL); + FnCallDestroy(P.currentfncall[P.arg_nesting+1]); + P.currentfncall[P.arg_nesting+1] = NULL; + } + + | error + { + yyclearin; + ParseError("Invalid input for a list item, got '%s'", yytext); + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +functionid: IDENTIFIER + { + ParserDebug("\tP:%s:%s:%s:%s function id = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.currentid); + } + | NAKEDVAR + { + ParserDebug("\tP:%s:%s:%s:%s function nakedvar = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.currentstring); + strncpy(P.currentid, P.currentstring, CF_MAXVARSIZE - 1); // Make a var look like an ID + free(P.currentstring); + P.currentstring = NULL; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +usefunction: functionid givearglist + { + ParserDebug("\tP:%s:%s:%s:%s Finished with function, now at level %d\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.arg_nesting); + }; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +givearglist: '(' + { + if (++P.arg_nesting >= CF_MAX_NESTING) + { + fatal_yyerror("Nesting of functions is deeper than recommended"); + } + P.currentfnid[P.arg_nesting] = xstrdup(P.currentid); + ParserDebug("\tP:%s:%s:%s begin givearglist for function %s, level %d\n", ParserBlockString(P.block),P.blocktype,P.blockid, P.currentfnid[P.arg_nesting], P.arg_nesting ); + } + + gaitems + + ')' + { + ParserDebug("\tP:%s:%s:%s end givearglist for function %s, level %d\n", ParserBlockString(P.block),P.blocktype,P.blockid, P.currentfnid[P.arg_nesting], P.arg_nesting ); + P.currentfncall[P.arg_nesting] = FnCallNew(P.currentfnid[P.arg_nesting], P.giveargs[P.arg_nesting]); + P.giveargs[P.arg_nesting] = NULL; + strcpy(P.currentid,""); + free(P.currentfnid[P.arg_nesting]); + P.currentfnid[P.arg_nesting] = NULL; + P.arg_nesting--; + } + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +gaitems: /* empty */ + | gaitem + | gaitems ',' gaitem + | gaitem error + { + ParseError("Expected ',', wrong input '%s'", yytext); + yyclearin; + } + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +gaitem: IDENTIFIER + { + ParserDebug("\tP:%s:%s:%s:%s function %s, id arg = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.currentfnid[P.arg_nesting], P.currentid); + /* currently inside a use function */ + RlistAppendScalar(&P.giveargs[P.arg_nesting],P.currentid); + } + + | QUOTED_STRING + { + /* currently inside a use function */ + ParserDebug("\tP:%s:%s:%s:%s function %s, qstring arg = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.currentfnid[P.arg_nesting], P.currentstring); + RlistAppendScalar(&P.giveargs[P.arg_nesting],P.currentstring); + free(P.currentstring); + P.currentstring = NULL; + } + + | NAKEDVAR + { + /* currently inside a use function */ + ParserDebug("\tP:%s:%s:%s:%s function %s, nakedvar arg = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.currentfnid[P.arg_nesting], P.currentstring); + RlistAppendScalar(&P.giveargs[P.arg_nesting],P.currentstring); + free(P.currentstring); + P.currentstring = NULL; + } + + | usefunction + { + /* Careful about recursion */ + ParserDebug("\tP:%s:%s:%s:%s function %s, nakedvar arg = %s\n", ParserBlockString(P.block), P.blocktype, P.blockid, P.currentclasses ? P.currentclasses : "any", P.currentfnid[P.arg_nesting], P.currentstring); + RlistAppend(&P.giveargs[P.arg_nesting], P.currentfncall[P.arg_nesting+1], RVAL_TYPE_FNCALL); + RvalDestroy((Rval) { P.currentfncall[P.arg_nesting+1], RVAL_TYPE_FNCALL }); + P.currentfncall[P.arg_nesting+1] = NULL; + } + + | error + { + ParserDebug("P:rval:function:gaitem:error yychar = %d\n", yychar); + if (yychar == ';') + { + ParseError("Expected ')', wrong input '%s'", yytext); + } + else if (yychar == FAT_ARROW ) + { + ParseError("Check function statement previous line, Expected ')', wrong input '%s'", yytext); + } + else + { + ParseError("Invalid function argument, wrong input '%s'", yytext); + } + yyclearin; + } + +%% diff --git a/libpromises/cf3parse_logic.h b/libpromises/cf3parse_logic.h new file mode 100644 index 0000000000..39c1bec242 --- /dev/null +++ b/libpromises/cf3parse_logic.h @@ -0,0 +1,1123 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* + This file is an attempt to clean up cf3parse.y, moving as much + C code (logic) as possible out of it, so the actual grammar + is more readable. It should only be included from cf3parse.y (!). + + The advantages of moving the C code out of the grammar are: + * Separate overall grammar from noisy details + * Less crazy indentation + * Better editor support for auto complete / syntax highlighting +*/ + +#ifndef CF3_PARSE_LOGIC_H +#define CF3_PARSE_LOGIC_H + +#include "cf3.defs.h" +#include "parser.h" +#include "parser_helpers.h" +#include "parser_state.h" + +#include "logging.h" +#include "fncall.h" +#include "rlist.h" +#include "item_lib.h" +#include "policy.h" +#include "mod_files.h" +#include "string_lib.h" +#include "logic_expressions.h" +#include "json-yaml.h" +#include "cleanup.h" + +// FIX: remove +#include "syntax.h" + +#include + +int yylex(void); +extern char *yytext; + +static bool RelevantBundle(const char *agent, const char *blocktype); +static bool LvalWantsBody(char *stype, char *lval); +static SyntaxTypeMatch CheckSelection( + ParserBlock block, + const char *type, + const char *name, + const char *lval, + Rval rval); +static SyntaxTypeMatch CheckConstraint( + const char *type, + const char *lval, + Rval rval, + const PromiseTypeSyntax *ss); +static void fatal_yyerror(const char *s); + +static void ParseErrorColumnOffset(int column_offset, const char *s, ...) + FUNC_ATTR_PRINTF(2, 3); +static void ParseError(const char *s, ...) FUNC_ATTR_PRINTF(1, 2); +static void ParseWarning(unsigned int warning, const char *s, ...) + FUNC_ATTR_PRINTF(2, 3); + +static void ValidateClassLiteral(const char *class_literal); + +static bool INSTALL_SKIP = false; +static size_t CURRENT_BLOCKID_LINE = 0; +static size_t CURRENT_PROMISER_LINE = 0; + +#define YYMALLOC xmalloc +#define P PARSER_STATE + +#define ParserDebug(...) LogDebug(LOG_MOD_PARSER, __VA_ARGS__) + + +/*****************************************************************/ + +static void ParseErrorVColumnOffset( + int column_offset, const char *s, va_list ap) +{ + char *errmsg = StringVFormat(s, ap); + fprintf( + stderr, + "%s:%d:%d: error: %s\n", + P.filename, + P.line_no, + P.line_pos + column_offset, + errmsg); + free(errmsg); + + P.error_count++; + + /* Current line is not set when syntax error in first line */ + if (P.current_line) + { + fprintf(stderr, "%s\n", P.current_line); + fprintf(stderr, "%*s\n", P.line_pos + column_offset, "^"); + } + + if (P.error_count > 12) + { + fprintf(stderr, "Too many errors\n"); + DoCleanupAndExit(EXIT_FAILURE); + } +} + +static void ParseErrorColumnOffset(int column_offset, const char *s, ...) +{ + va_list ap; + va_start(ap, s); + ParseErrorVColumnOffset(column_offset, s, ap); + va_end(ap); +} + +static void ParseErrorV(const char *s, va_list ap) +{ + ParseErrorVColumnOffset(0, s, ap); +} + +static void ParseError(const char *s, ...) +{ + va_list ap; + va_start(ap, s); + ParseErrorV(s, ap); + va_end(ap); +} + +static void ParseWarningV(unsigned int warning, const char *s, va_list ap) +{ + if (((P.warnings | P.warnings_error) & warning) == 0) + { + return; + } + + char *errmsg = StringVFormat(s, ap); + const char *warning_str = ParserWarningToString(warning); + + fprintf( + stderr, + "%s:%d:%d: warning: %s [-W%s]\n", + P.filename, + P.line_no, + P.line_pos, + errmsg, + warning_str); + fprintf(stderr, "%s\n", P.current_line); + fprintf(stderr, "%*s\n", P.line_pos, "^"); + + free(errmsg); + + P.warning_count++; + + if ((P.warnings_error & warning) != 0) + { + P.error_count++; + } + + if (P.error_count > 12) + { + fprintf(stderr, "Too many errors\n"); + DoCleanupAndExit(EXIT_FAILURE); + } +} + +static void ParseWarning(unsigned int warning, const char *s, ...) +{ + va_list ap; + va_start(ap, s); + ParseWarningV(warning, s, ap); + va_end(ap); +} + +void yyerror(const char *str) +{ + ParseError("%s", str); +} + +static void fatal_yyerror(const char *s) +{ + char *sp = yytext; + /* Skip quotation mark */ + if (sp && *sp == '\"' && sp[1]) + { + sp++; + } + + fprintf( + stderr, + "%s: %d,%d: Fatal error during parsing: %s, near token \'%.20s\'\n", + P.filename, + P.line_no, + P.line_pos, + s, + sp ? sp : "NULL"); + DoCleanupAndExit(EXIT_FAILURE); +} + +static bool RelevantBundle(const char *agent, const char *blocktype) +{ + if ((strcmp(agent, CF_AGENTTYPES[AGENT_TYPE_COMMON]) == 0) + || (strcmp(CF_COMMONC, blocktype) == 0)) + { + return true; + } + + /* Here are some additional bundle types handled by cfAgent */ + + Item *ip = SplitString("edit_line,edit_xml", ','); + + if (strcmp(agent, CF_AGENTTYPES[AGENT_TYPE_AGENT]) == 0) + { + if (IsItemIn(ip, blocktype)) + { + DeleteItemList(ip); + return true; + } + } + + DeleteItemList(ip); + return false; +} + +static bool LvalWantsBody(char *stype, char *lval) +{ + for (int i = 0; i < CF3_MODULES; i++) + { + const PromiseTypeSyntax *promise_type_syntax = CF_ALL_PROMISE_TYPES[i]; + if (!promise_type_syntax) + { + continue; + } + + for (int j = 0; promise_type_syntax[j].promise_type != NULL; j++) + { + const ConstraintSyntax *bs = promise_type_syntax[j].constraints; + if (!bs) + { + continue; + } + + if (strcmp(promise_type_syntax[j].promise_type, stype) != 0) + { + continue; + } + + for (int l = 0; bs[l].lval != NULL; l++) + { + if (strcmp(bs[l].lval, lval) == 0) + { + if (bs[l].dtype == CF_DATA_TYPE_BODY) + { + return true; + } + else + { + return false; + } + } + } + } + } + + return false; +} + +static SyntaxTypeMatch CheckSelection( + ParserBlock block, + const char *type, + const char *name, + const char *lval, + Rval rval) +{ + if (block == PARSER_BLOCK_PROMISE) + { + const BodySyntax *body_syntax = BodySyntaxGet(block, type); + const ConstraintSyntax *constraints = body_syntax->constraints; + for (int i = 0; constraints[i].lval != NULL; i++) + { + if (StringEqual(lval, constraints[i].lval)) + { + return CheckConstraintTypeMatch( + lval, + rval, + constraints[i].dtype, + constraints[i].range.validation_string, + 0); + } + } + // Parser should ensure that lval is a valid + // attribute, and so we should never get here + debug_abort_if_reached(); + } + + assert(block == PARSER_BLOCK_BODY); + + // Check internal control bodies etc + if (strcmp("control", name) == 0) + { + for (int i = 0; CONTROL_BODIES[i].body_type != NULL; i++) + { + if (strcmp(type, CONTROL_BODIES[i].body_type) == 0) + { + const ConstraintSyntax *bs = CONTROL_BODIES[i].constraints; + + for (int l = 0; bs[l].lval != NULL; l++) + { + if (strcmp(lval, bs[l].lval) == 0) + { + if (bs[l].dtype == CF_DATA_TYPE_BODY) + { + return SYNTAX_TYPE_MATCH_OK; + } + else if (bs[l].dtype == CF_DATA_TYPE_BUNDLE) + { + return SYNTAX_TYPE_MATCH_OK; + } + else + { + return CheckConstraintTypeMatch( + lval, + rval, + bs[l].dtype, + bs[l].range.validation_string, + 0); + } + } + } + } + } + } + + // Now check the functional modules - extra level of indirection + for (int i = 0; i < CF3_MODULES; i++) + { + const PromiseTypeSyntax *promise_type_syntax = CF_ALL_PROMISE_TYPES[i]; + if (!promise_type_syntax) + { + continue; + } + + for (int j = 0; promise_type_syntax[j].promise_type != NULL; j++) + { + const ConstraintSyntax *bs = promise_type_syntax[j].constraints; + + if (!bs) + { + continue; + } + + for (int l = 0; bs[l].lval != NULL; l++) + { + if (bs[l].dtype == CF_DATA_TYPE_BODY) + { + const ConstraintSyntax *bs2 = + bs[l].range.body_type_syntax->constraints; + + if (bs2 == NULL || bs2 == (void *) CF_BUNDLE) + { + continue; + } + + for (int k = 0; bs2[k].dtype != CF_DATA_TYPE_NONE; k++) + { + /* Either module defined or common */ + + if (strcmp(promise_type_syntax[j].promise_type, type) + == 0 + && strcmp(promise_type_syntax[j].promise_type, "*") + != 0) + { + char output[CF_BUFSIZE]; + snprintf( + output, + CF_BUFSIZE, + "lval %s belongs to promise type '%s': but this is '%s'\n", + lval, + promise_type_syntax[j].promise_type, + type); + yyerror(output); + return SYNTAX_TYPE_MATCH_OK; + } + + if (strcmp(lval, bs2[k].lval) == 0) + { + /* Body definitions will be checked later. */ + if (bs2[k].dtype != CF_DATA_TYPE_BODY) + { + return CheckConstraintTypeMatch( + lval, + rval, + bs2[k].dtype, + bs2[k].range.validation_string, + 0); + } + else + { + return SYNTAX_TYPE_MATCH_OK; + } + } + } + } + } + } + } + + char output[CF_BUFSIZE]; + snprintf( + output, + CF_BUFSIZE, + "Constraint lvalue \"%s\" is not allowed in \'%s\' constraint body", + lval, + type); + yyerror(output); + + return SYNTAX_TYPE_MATCH_OK; // TODO: OK? +} + +static SyntaxTypeMatch CheckConstraint( + const char *type, + const char *lval, + Rval rval, + const PromiseTypeSyntax *promise_type_syntax) +{ + assert(promise_type_syntax); + + if (promise_type_syntax->promise_type != NULL) /* In a bundle */ + { + if (strcmp(promise_type_syntax->promise_type, type) == 0) + { + const ConstraintSyntax *bs = promise_type_syntax->constraints; + + for (int l = 0; bs[l].lval != NULL; l++) + { + if (strcmp(lval, bs[l].lval) == 0) + { + /* If we get here we have found the lval and it is valid + for this promise_type */ + + /* For bodies and bundles definitions can be elsewhere, so + they are checked in PolicyCheckRunnable(). */ + if (bs[l].dtype != CF_DATA_TYPE_BODY + && bs[l].dtype != CF_DATA_TYPE_BUNDLE) + { + return CheckConstraintTypeMatch( + lval, + rval, + bs[l].dtype, + bs[l].range.validation_string, + 0); + } + } + } + } + } + + return SYNTAX_TYPE_MATCH_OK; +} + +static void ValidateClassLiteral(const char *class_literal) +{ + ParseResult res = ParseExpression(class_literal, 0, strlen(class_literal)); + + if (!res.result) + { + ParseErrorColumnOffset( + res.position - strlen(class_literal), + "Syntax error in context string"); + } + + FreeExpression(res.result); +} + +static inline void ParserEndCurrentBlock() +{ + P.offsets.last_id = -1; + P.offsets.last_string = -1; + P.offsets.last_class_id = -1; + if (P.block != PARSER_BLOCK_BUNDLE && P.currentbody != NULL) + { + P.currentbody->offset.end = P.offsets.current; + } + + if (P.block == PARSER_BLOCK_BUNDLE && P.currentbundle != NULL) + { + P.currentbundle->offset.end = P.offsets.current; + } +} + +// This part of the parser is interesting, to say the least. +// While parsing, we try to, opportunistically, do a bunch of +// transformations on the right-hand side values (rval) of +// promise attributes. For example converting some patterns +// to function calls: +// @(x) +// mergedata(x) +// data => "{}" +// data => parsejson("{}") +// +// In some cases, we even try to evaluate the function call, +// like parsejson, and if it succeeds (if there are no +// unresolved variables) we just insert the resulting data +// container directly. + +static inline void MagicRvalTransformations( + const PromiseTypeSyntax *promise_type_syntax) +{ + const char *item = P.rval.item; + // convert @(x) to mergedata(x) + if (P.rval.type == RVAL_TYPE_SCALAR + && (strcmp(P.lval, "data") == 0 + || strcmp(P.lval, "template_data") == 0) + && strlen(item) > 3 && item[0] == '@' + && (item[1] == '(' || item[1] == '{')) + { + Rlist *synthetic_args = NULL; + char *tmp = xstrndup(P.rval.item + 2, strlen(P.rval.item) - 3); + RlistAppendScalar(&synthetic_args, tmp); + free(tmp); + RvalDestroy(P.rval); + + P.rval = + (Rval){FnCallNew("mergedata", synthetic_args), RVAL_TYPE_FNCALL}; + } + // convert 'json or yaml' to direct container or parsejson(x) + // or parseyaml(x) + else if ( + P.rval.type == RVAL_TYPE_SCALAR + && (strcmp(P.lval, "data") == 0 + || strcmp(P.lval, "template_data") == 0)) + { + JsonElement *json = NULL; + JsonParseError res; + bool json_parse_attempted = false; + Buffer *copy = BufferNewFrom(P.rval.item, strlen(P.rval.item)); + + const char *fname = NULL; + if (strlen(P.rval.item) > 3 && strncmp("---", P.rval.item, 3) == 0) + { + fname = "parseyaml"; + + // look for unexpanded variables + if (strstr(P.rval.item, "$(") == NULL + && strstr(P.rval.item, "${") == NULL) + { + const char *copy_data = BufferData(copy); + res = JsonParseYamlString(©_data, &json); + json_parse_attempted = true; + } + } + else + { + fname = "parsejson"; + // look for unexpanded variables + if (strstr(P.rval.item, "$(") == NULL + && strstr(P.rval.item, "${") == NULL) + { + const char *copy_data = BufferData(copy); + res = JsonParse(©_data, &json); + json_parse_attempted = true; + } + } + + BufferDestroy(copy); + + if (json_parse_attempted && res != JSON_PARSE_OK) + { + // Parsing failed, insert fncall so it can be retried + // during evaluation + } + else if ( + json != NULL + && JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + // Parsing failed, insert fncall so it can be retried + // during evaluation + JsonDestroy(json); + json = NULL; + } + + if (fname != NULL) + { + if (json == NULL) + { + Rlist *synthetic_args = NULL; + RlistAppendScalar(&synthetic_args, P.rval.item); + RvalDestroy(P.rval); + + P.rval = + (Rval){FnCallNew(fname, synthetic_args), RVAL_TYPE_FNCALL}; + } + else + { + RvalDestroy(P.rval); + P.rval = (Rval){json, RVAL_TYPE_CONTAINER}; + } + } + } + + if (promise_type_syntax != NULL) // NULL for custom promise types + { + SyntaxTypeMatch err = CheckConstraint( + P.currenttype, P.lval, P.rval, promise_type_syntax); + if (err != SYNTAX_TYPE_MATCH_OK + && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + yyerror(SyntaxTypeMatchToString(err)); + } + } + + if (P.rval.type == RVAL_TYPE_SCALAR + && (strcmp(P.lval, "ifvarclass") == 0 || strcmp(P.lval, "if") == 0)) + { + ValidateClassLiteral(P.rval.item); + } +} + +static inline void ParserAppendCurrentConstraint() +{ + Constraint *cp = PromiseAppendConstraint( + P.currentpromise, P.lval, RvalCopy(P.rval), P.references_body); + cp->offset.line = P.line_no; + cp->offset.start = P.offsets.last_id; + cp->offset.end = P.offsets.current; + cp->offset.context = P.offsets.last_class_id; + P.currentstype->offset.end = P.offsets.current; +} + +// This function is called for every rval (right hand side value) of every +// promise while parsing. The reason why it is so big is because it does a lot +// of transformation, for example transforming strings into function calls like +// parsejson, and then attempts to resolve those function calls. +static inline void ParserHandleBundlePromiseRval() +{ + if (INSTALL_SKIP) + { + RvalDestroy(P.rval); + P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE); + return; + } + + if (PolicyHasCustomPromiseType(P.policy, P.currenttype)) + { + // Definitely custom promise type, just add the constraint and move on + MagicRvalTransformations(NULL); + ParserAppendCurrentConstraint(); + goto cleanup; + } + + const PromiseTypeSyntax *promise_type_syntax = + PromiseTypeSyntaxGet(P.blocktype, P.currenttype); + const ConstraintSyntax *constraint_syntax = (promise_type_syntax != NULL) + ? PromiseTypeSyntaxGetConstraintSyntax(promise_type_syntax, P.lval) + : NULL; + + if (promise_type_syntax == NULL) + { + // Assume custom promise type, but defined in another policy file + MagicRvalTransformations(NULL); + ParserAppendCurrentConstraint(); + goto cleanup; + } + else if (constraint_syntax == NULL) + { + ParseError( + "Unknown constraint '%s' in promise type '%s'", + P.lval, + promise_type_syntax->promise_type); + } + else + { + switch (constraint_syntax->status) + { + case SYNTAX_STATUS_DEPRECATED: + ParseWarning( + PARSER_WARNING_DEPRECATED, + "Deprecated constraint '%s' in promise type '%s'", + constraint_syntax->lval, + promise_type_syntax->promise_type); + // fall through + case SYNTAX_STATUS_CUSTOM: + // fall through + case SYNTAX_STATUS_NORMAL: + { + MagicRvalTransformations(promise_type_syntax); + ParserAppendCurrentConstraint(); + } + break; + case SYNTAX_STATUS_REMOVED: + ParseWarning( + PARSER_WARNING_REMOVED, + "Removed constraint '%s' in promise type '%s'", + constraint_syntax->lval, + promise_type_syntax->promise_type); + break; + } + } + +cleanup: + + RvalDestroy(P.rval); + P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE); + strcpy(P.lval, "no lval"); + RlistDestroy(P.currentRlist); + P.currentRlist = NULL; +} + +static inline void ParserBeginBlock(ParserBlock b) +{ + ParserDebug("P:%s:%s\n", ParserBlockString(b), P.blocktype); + P.block = b; + + if (b == PARSER_BLOCK_BUNDLE) + { + RvalDestroy(P.rval); + P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE); + } + + RlistDestroy(P.currentRlist); + P.currentRlist = NULL; + + if (P.currentstring) + { + free(P.currentstring); + } + P.currentstring = NULL; + + strcpy(P.blockid, ""); +} + +// The promise "guard" is a promise type followed by a single colon, +// found in bundles. It is called guard because it resembles the +// class guards, and all other names I could think of were confusing. +// (It doesn't really "guard" anything). +static inline void ParserHandlePromiseGuard() +{ + ParserDebug( + "\tP:%s:%s:%s promise_type = %s\n", + ParserBlockString(P.block), + P.blocktype, + P.blockid, + P.currenttype); + + const PromiseTypeSyntax *promise_type_syntax = + PromiseTypeSyntaxGet(P.blocktype, P.currenttype); + + if (promise_type_syntax) + { + switch (promise_type_syntax->status) + { + case SYNTAX_STATUS_DEPRECATED: + ParseWarning( + PARSER_WARNING_DEPRECATED, + "Deprecated promise type '%s' in bundle type '%s'", + promise_type_syntax->promise_type, + promise_type_syntax->bundle_type); + // fall through + case SYNTAX_STATUS_CUSTOM: + // fall through + case SYNTAX_STATUS_NORMAL: + if (P.block == PARSER_BLOCK_BUNDLE) + { + if (!INSTALL_SKIP) + { + P.currentstype = + BundleAppendSection(P.currentbundle, P.currenttype); + P.currentstype->offset.line = P.line_no; + P.currentstype->offset.start = + P.offsets.last_promise_guard_id; + } + else + { + P.currentstype = NULL; + } + } + break; + case SYNTAX_STATUS_REMOVED: + ParseWarning( + PARSER_WARNING_REMOVED, + "Removed promise type '%s' in bundle type '%s'", + promise_type_syntax->promise_type, + promise_type_syntax->bundle_type); + INSTALL_SKIP = true; + break; + } + } + else + { + // Unrecognized promise type, assume it is custom + // no way to know while parsing, let's check later: + if (!INSTALL_SKIP) + { + P.currentstype = + BundleAppendSection(P.currentbundle, P.currenttype); + P.currentstype->offset.line = P.line_no; + P.currentstype->offset.start = P.offsets.last_promise_guard_id; + } + else + { + P.currentstype = NULL; + } + } +} + +// Called at the beginning of the body of the block, i.e. the opening '{' +static inline void ParserBeginBlockBody() +{ + const BodySyntax *body_syntax = BodySyntaxGet(P.block, P.blocktype); + + if (P.block == PARSER_BLOCK_PROMISE && body_syntax != NULL) + { + P.currentbody = PolicyAppendPromiseBlock( + P.policy, + P.current_namespace, + P.blockid, + P.blocktype, + P.useargs, + P.filename); + P.currentbody->offset.line = CURRENT_BLOCKID_LINE; + P.currentbody->offset.start = P.offsets.last_block_id; + } + else if (body_syntax) + { + INSTALL_SKIP = false; + + switch (body_syntax->status) + { + case SYNTAX_STATUS_DEPRECATED: + ParseWarning( + PARSER_WARNING_DEPRECATED, + "Deprecated body '%s' of type '%s'", + P.blockid, + body_syntax->body_type); + // fall through + case SYNTAX_STATUS_CUSTOM: + // fall through + case SYNTAX_STATUS_NORMAL: + P.currentbody = PolicyAppendBody( + P.policy, + P.current_namespace, + P.blockid, + P.blocktype, + P.useargs, + P.filename, + body_syntax->status == SYNTAX_STATUS_CUSTOM); + P.currentbody->offset.line = CURRENT_BLOCKID_LINE; + P.currentbody->offset.start = P.offsets.last_block_id; + break; + + case SYNTAX_STATUS_REMOVED: + ParseWarning( + PARSER_WARNING_REMOVED, + "Removed body '%s' of type '%s'", + P.blockid, + body_syntax->body_type); + INSTALL_SKIP = true; + break; + } + } + else + { + ParseError("Invalid body type '%s'", P.blocktype); + INSTALL_SKIP = true; + } + + RlistDestroy(P.useargs); + P.useargs = NULL; + + strcpy(P.currentid, ""); +} + +// Called for every Rval (Right hand side value) of attributes +// in body blocks +static inline void ParserHandleBlockAttributeRval() +{ + assert(P.block == PARSER_BLOCK_BODY || P.block == PARSER_BLOCK_PROMISE); + + if (!INSTALL_SKIP) + { + const BodySyntax *body_syntax = BodySyntaxGet(P.block, P.blocktype); + assert(body_syntax != NULL); + + ConstraintSyntax *constraint_syntax; + if (body_syntax->status == SYNTAX_STATUS_CUSTOM) + { + constraint_syntax = xmalloc(sizeof(ConstraintSyntax)); + constraint_syntax->status = SYNTAX_STATUS_CUSTOM; + } + else + { + constraint_syntax = (ConstraintSyntax *) + BodySyntaxGetConstraintSyntax(body_syntax->constraints, + P.lval); + } + + if (constraint_syntax) + { + switch (constraint_syntax->status) + { + case SYNTAX_STATUS_DEPRECATED: + ParseWarning( + PARSER_WARNING_DEPRECATED, + "Deprecated constraint '%s' in body type '%s'", + constraint_syntax->lval, + body_syntax->body_type); + // fall through + case SYNTAX_STATUS_NORMAL: + { + SyntaxTypeMatch err = CheckSelection( + P.block, P.blocktype, P.blockid, P.lval, P.rval); + if (err != SYNTAX_TYPE_MATCH_OK + && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + yyerror(SyntaxTypeMatchToString(err)); + } + + if (P.rval.type == RVAL_TYPE_SCALAR + && (strcmp(P.lval, "ifvarclass") == 0 + || strcmp(P.lval, "if") == 0)) + { + ValidateClassLiteral(P.rval.item); + } + } + // fall through + case SYNTAX_STATUS_CUSTOM: + { + Constraint *cp = NULL; + if (P.currentclasses == NULL) + { + cp = BodyAppendConstraint( + P.currentbody, + P.lval, + RvalCopy(P.rval), + "any", + P.references_body); + } + else + { + cp = BodyAppendConstraint( + P.currentbody, + P.lval, + RvalCopy(P.rval), + P.currentclasses, + P.references_body); + } + + if (P.currentvarclasses != NULL) + { + ParseError( + "Body attributes can't be put under a variable class '%s'", + P.currentvarclasses); + } + + cp->offset.line = P.line_no; + cp->offset.start = P.offsets.last_id; + cp->offset.end = P.offsets.current; + cp->offset.context = P.offsets.last_class_id; + break; + } + case SYNTAX_STATUS_REMOVED: + ParseWarning( + PARSER_WARNING_REMOVED, + "Removed constraint '%s' in promise type '%s'", + constraint_syntax->lval, + body_syntax->body_type); + break; + } + } + + if (body_syntax->status == SYNTAX_STATUS_CUSTOM) + { + free(constraint_syntax); + } + } + else + { + RvalDestroy(P.rval); + P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE); + } + + if (strcmp(P.blockid, "control") == 0 && strcmp(P.blocktype, "file") == 0) + { + if (strcmp(P.lval, "namespace") == 0) + { + if (P.rval.type != RVAL_TYPE_SCALAR) + { + yyerror("namespace must be a constant scalar string"); + } + else + { + free(P.current_namespace); + P.current_namespace = xstrdup(P.rval.item); + } + } + } + + RvalDestroy(P.rval); + P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE); +} + +static inline void ParserBeginBundleBody() +{ + assert(P.block == PARSER_BLOCK_BUNDLE); + + if (RelevantBundle(CF_AGENTTYPES[P.agent_type], P.blocktype)) + { + INSTALL_SKIP = false; + } + else if (strcmp(CF_AGENTTYPES[P.agent_type], P.blocktype) != 0) + { + INSTALL_SKIP = true; + } + + if (!INSTALL_SKIP) + { + P.currentbundle = PolicyAppendBundle( + P.policy, + P.current_namespace, + P.blockid, + P.blocktype, + P.useargs, + P.filename); + P.currentbundle->offset.line = CURRENT_BLOCKID_LINE; + P.currentbundle->offset.start = P.offsets.last_block_id; + } + else + { + P.currentbundle = NULL; + } + + RlistDestroy(P.useargs); + P.useargs = NULL; +} + +static inline void ParserHandleQuotedListItem() +{ + RlistAppendScalar((Rlist **) &P.currentRlist, + (void *) P.currentstring); + FREE_AND_NULL(P.currentstring); +} + +/** + * A sanity check that prints a warning if there is a promise without any + * actions (i.e., the promise is a no-op). This check is naive and assumes + * promises containing any non-common attributes perform actions. It also has + * exceptions for promises that perform actions without any attributes. We can + * expect false negatives. However, there should not be any false positives. The + * motivation for this check is to aid policy writers in detecting semantic + * errors early. + */ +static inline void ParserCheckPromiseLine() +{ + if (P.currentpromise == NULL) + { + return; + } + + const char *const promise_type = P.currenttype; + if (!IsBuiltInPromiseType(promise_type)) + { + // We leave sanity checking to the custom promise module. + return; + } + + // The following promise types does not require any actions + static const char *const exceptions[] = { + "classes", "commands", "methods", "reports", "insert_lines", + "delete_lines", "build_xpath", "insert_tree" }; + static const size_t num_exceptions = sizeof(exceptions) / sizeof(exceptions[0]); + + if (IsStringInArray(promise_type, exceptions, num_exceptions)) + { + // This promise type does not require any action attributes. + return; + } + + // We don't consider common attributes an actions. + static const char *const common_attrs[] = { + "action", "classes", "comment", "depends_on", + "handle", "if", "meta", "with" }; + + const Seq *const constraints = P.currentpromise->conlist; + const size_t num_constraints = SeqLength(constraints); + for (size_t i = 0; i < num_constraints; i++) + { + const Constraint *const constraint = SeqAt(constraints, i); + if (!IsStringInArray(constraint->lval, common_attrs, + sizeof(common_attrs) / sizeof(common_attrs[0]))) + { + // Not in common attributes, we assume it is an action + return; + } + } + + const char *const promiser = P.currentpromise->promiser; + const char *const file = P.filename; + const char *const bundle = P.currentbundle->name; + size_t line = P.currentpromise->offset.line; + ParseWarning(PARSER_WARNING_SANITY_CHECK, + "No action requested for %s promise with promiser '%s' in %s:%s close to line %zu", + promise_type, promiser, file, bundle, line); +} + +#endif // CF3_PARSE_LOGIC_H diff --git a/libpromises/changes_chroot.c b/libpromises/changes_chroot.c new file mode 100644 index 0000000000..0d27c0c239 --- /dev/null +++ b/libpromises/changes_chroot.c @@ -0,0 +1,310 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include /* xstrdup() */ +#include /* DirOpen(), DirRead(), DirClose() */ +#include /* ToChangesChroot() */ +#include /* FILE_SEPARATOR */ +#include /* MakeParentDirectory() */ +#include /* CopyRegularFileDisk(), CopyFilePermissionsDisk() */ +#include /* IsAbsPath(), JoinPaths() */ +#include /* ExpandLinks() */ +#include /* StringEqual() */ +#include /* WriteLenPrefixedString() */ +#include /* FileWriter(), Writer */ +#include /* CsvWriter */ + +#include + + +static inline const char *GetLastFileSeparator(const char *path, const char *end) +{ + const char *cp = end; + for (; (cp > path) && (*cp != FILE_SEPARATOR); cp--); + return cp; +} + +static char *GetFirstNonExistingParentDir(const char *path, char buf[PATH_MAX]) +{ + /* Get rid of the trailing file separator (if any). */ + size_t path_len = strlen(path); + if (path[path_len - 1] == FILE_SEPARATOR) + { + strncpy(buf, path, PATH_MAX - 1); + buf[path_len] = '\0'; + } + else + { + strncpy(buf, path, PATH_MAX - 1); + } + + char *last_sep = (char *) GetLastFileSeparator(buf, buf + path_len); + while (last_sep != buf) + { + *last_sep = '\0'; + if (access(buf, F_OK) == 0) + { + *last_sep = FILE_SEPARATOR; + *(last_sep + 1) = '\0'; + return buf; + } + last_sep = (char *) GetLastFileSeparator(buf, last_sep - 1); + } + *last_sep = '\0'; + return buf; +} + +static bool MirrorDirTreePermsToChroot(const char *path) +{ + const char *chrooted = ToChangesChroot(path); + + if (!CopyFilePermissionsDisk(path, chrooted)) + { + return false; + } + + size_t path_len = strlen(path); + char path_copy[path_len + 1]; + strcpy(path_copy, path); + + const char *const path_copy_end = path_copy + (path_len - 1); + const char *const chrooted_end = chrooted + strlen(chrooted) - 1; + + char *last_sep = (char *) GetLastFileSeparator(path_copy, path_copy_end); + while (last_sep != path_copy) + { + char *last_sep_chrooted = (char *) chrooted_end - (path_copy_end - last_sep); + *last_sep = '\0'; + *last_sep_chrooted = '\0'; + if (!CopyFilePermissionsDisk(path_copy, chrooted)) + { + return false; + } + *last_sep = FILE_SEPARATOR; + *last_sep_chrooted = FILE_SEPARATOR; + last_sep = (char *) GetLastFileSeparator(path_copy, last_sep - 1); + } + return true; +} + +#ifndef __MINGW32__ +/** + * Mirror the symlink #path to #chrooted_path together with its target, then + * mirror the target if it is a symlink too,..., recursively. + */ +static void ChrootSymlinkDeep(const char *path, const char *chrooted_path, struct stat *sb) +{ + assert(sb != NULL); + + size_t target_size = (sb->st_size != 0 ? sb->st_size + 1 : PATH_MAX); + char target[target_size]; + ssize_t ret = readlink(path, target, target_size); + if (ret == -1) + { + /* Should never happen, but nothing to do here if it does. */ + return; + } + target[ret] = '\0'; + + if (IsAbsPath(target)) + { + const char *chrooted_target = ToChangesChroot(target); + if (symlink(chrooted_target, chrooted_path) != 0) + { + /* Should never happen, but nothing to do here if it does. */ + return; + } + else + { + PrepareChangesChroot(target); + } + } + else + { + if (symlink(target, chrooted_path) != 0) + { + /* Should never happen, but nothing to do here if it does. */ + return; + } + else + { + char expanded_target[PATH_MAX]; + if (!ExpandLinks(expanded_target, path, 0, 1)) + { + /* Should never happen, but nothing to do here if it does. */ + return; + } + PrepareChangesChroot(expanded_target); + } + } +} +#endif /* __MINGW32__ */ + +void PrepareChangesChroot(const char *path) +{ + struct stat sb; + + /* We need to create a copy because ToChangesChroot() returns a pointer to + * its internal buffer which gets overwritten by later calls of the + * function. */ + char *chrooted = xstrdup(ToChangesChroot(path)); + if (lstat(chrooted, &sb) != -1) + { + /* chrooted 'path' already exists, we are done */ + free(chrooted); + return; + } + + { + char first_nonexisting_parent[PATH_MAX]; + GetFirstNonExistingParentDir(path, first_nonexisting_parent); + MakeParentDirectory(first_nonexisting_parent, true, NULL); + MirrorDirTreePermsToChroot(first_nonexisting_parent); + } + + if (lstat(path, &sb) == -1) + { + /* 'path' doesn't exist, nothing to do here */ + return; + } + +#ifndef __MINGW32__ + if (S_ISLNK(sb.st_mode)) + { + ChrootSymlinkDeep(path, chrooted, &sb); + } + else +#endif /* __MINGW32__ */ + if (S_ISDIR(sb.st_mode)) + { + mkdir(chrooted, sb.st_mode); + Dir *dir = DirOpen(path); + if (dir == NULL) + { + /* Should never happen, but nothing to do here if it does. */ + free(chrooted); + return; + } + + for (const struct dirent *entry = DirRead(dir); entry != NULL; entry = DirRead(dir)) + { + if (StringEqual(entry->d_name, ".") || StringEqual(entry->d_name, "..")) + { + continue; + } + char entry_path[PATH_MAX]; + strcpy(entry_path, path); + JoinPaths(entry_path, PATH_MAX, entry->d_name); + PrepareChangesChroot(entry_path); + } + DirClose(dir); + } + else + { + /* TODO: sockets, pipes, devices,... ? */ + CopyRegularFileDisk(path, chrooted); + } + CopyFilePermissionsDisk(path, chrooted); + + free(chrooted); +} + +bool RecordFileChangedInChroot(const char *path) +{ + FILE *chroot_changes = safe_fopen(ToChangesChroot(CHROOT_CHANGES_LIST_FILE), "a"); + Writer *writer = FileWriter(chroot_changes); + + bool ret = WriteLenPrefixedString(writer, path); + + WriterClose(writer); + return ret; +} + +bool RecordFileRenamedInChroot(const char *old_name, const char *new_name) +{ + FILE *chroot_renames = safe_fopen(ToChangesChroot(CHROOT_RENAMES_LIST_FILE), "a"); + Writer *writer = FileWriter(chroot_renames); + + bool ret = WriteLenPrefixedString(writer, old_name); + ret = (ret && WriteLenPrefixedString(writer, new_name)); + + WriterClose(writer); + return ret; +} + +bool RecordFileEvaluatedInChroot(const char *path) +{ + FILE *chroot_changes = safe_fopen(ToChangesChroot(CHROOT_KEPT_LIST_FILE), "a"); + Writer *writer = FileWriter(chroot_changes); + + bool ret = WriteLenPrefixedString(writer, path); + + WriterClose(writer); + return ret; +} + +bool RecordPkgOperationInChroot(const char *op, const char *name, const char *arch, const char *version) +{ + assert(op != NULL); + assert(name != NULL); + /* the rest is optional */ + + FILE *chroot_pkgs_ops = safe_fopen(ToChangesChroot(CHROOT_PKGS_OPS_FILE), "a"); + if (chroot_pkgs_ops == NULL) + { + Log(LOG_LEVEL_ERR, "Failed to open package operations record file '%s'", + CHROOT_PKGS_OPS_FILE); + return false; + } + + Writer *writer = FileWriter(chroot_pkgs_ops); + if (writer == NULL) + { + Log(LOG_LEVEL_ERR, "Failed to create a writer for package operations record file '%s'", + CHROOT_PKGS_OPS_FILE); + fclose(chroot_pkgs_ops); + return false; + } + + CsvWriter *csv_writer = CsvWriterOpen(writer); + if (csv_writer == NULL) + { + Log(LOG_LEVEL_ERR, "Failed to create a CSV writer for package operations record file '%s'", + CHROOT_PKGS_OPS_FILE); + WriterClose(writer); + return false; + } + + CsvWriterField(csv_writer, op); + CsvWriterField(csv_writer, name); + CsvWriterField(csv_writer, NULL_TO_EMPTY_STRING(arch)); + CsvWriterField(csv_writer, NULL_TO_EMPTY_STRING(version)); + + CsvWriterNewRecord(csv_writer); + CsvWriterClose(csv_writer); + WriterClose(writer); + + return true; +} diff --git a/libpromises/changes_chroot.h b/libpromises/changes_chroot.h new file mode 100644 index 0000000000..e17b25763a --- /dev/null +++ b/libpromises/changes_chroot.h @@ -0,0 +1,39 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CHANGES_CHROOT_H +#define CFENGINE_CHANGES_CHROOT_H + +#define CHROOT_CHANGES_LIST_FILE "/changed_files" +#define CHROOT_RENAMES_LIST_FILE "/renamed_files" +#define CHROOT_KEPT_LIST_FILE "/kept_files" +#define CHROOT_PKGS_OPS_FILE "/pkgs_ops" + +void PrepareChangesChroot(const char *path); +bool RecordFileChangedInChroot(const char *path); +bool RecordFileRenamedInChroot(const char *old_name, const char *new_name); +bool RecordFileEvaluatedInChroot(const char *path); +bool RecordPkgOperationInChroot(const char *op, const char *name, const char *arch, const char *version); + +#endif /* CFENGINE_CHANGES_CHROOT_H */ diff --git a/libpromises/chflags.c b/libpromises/chflags.c new file mode 100644 index 0000000000..e5f212d46a --- /dev/null +++ b/libpromises/chflags.c @@ -0,0 +1,181 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* BSD flags */ + +#include + +typedef struct +{ + char *name; + u_long bits; +} BSDFlag; + +static const BSDFlag CF_BSDFLAGS[] = +{ + {"arch", (u_long) SF_ARCHIVED}, + {"archived", (u_long) SF_ARCHIVED}, + {"nodump", (u_long) UF_NODUMP}, + {"opaque", (u_long) UF_OPAQUE}, + {"sappnd", (u_long) SF_APPEND}, + {"sappend", (u_long) SF_APPEND}, + {"schg", (u_long) SF_IMMUTABLE}, + {"schange", (u_long) SF_IMMUTABLE}, + {"simmutable", (u_long) SF_IMMUTABLE}, + {"sunlnk", (u_long) SF_NOUNLINK}, + {"sunlink", (u_long) SF_NOUNLINK}, + {"uappnd", (u_long) UF_APPEND}, + {"uappend", (u_long) UF_APPEND}, + {"uchg", (u_long) UF_IMMUTABLE}, + {"uchange", (u_long) UF_IMMUTABLE}, + {"uimmutable", (u_long) UF_IMMUTABLE}, + {"uunlnk", (u_long) UF_NOUNLINK}, + {"uunlink", (u_long) UF_NOUNLINK}, + {NULL, (u_long) 0} +}; + +/***************************************************************/ + +static u_long ConvertBSDBits(const char *s); + +/***************************************************************/ + +bool ParseFlagString(Rlist *bitlist, u_long *plusmask, u_long *minusmask) +{ + // FIXME: ALWAYS returns true + if (bitlist == NULL) + { + return true; + } + + *plusmask = 0; + *minusmask = 0; + + for (const Rlist *rp = bitlist; rp != NULL; rp = rp->next) + { + const char *flag = RlistScalarValue(rp); + char op = *RlistScalarValue(rp); + + switch (op) + { + case '-': + *minusmask |= ConvertBSDBits(flag + 1); + break; + + case '+': + *plusmask |= ConvertBSDBits(flag + 1); + break; + + default: + *plusmask |= ConvertBSDBits(flag); + break; + + } + } + + Log(LOG_LEVEL_DEBUG, "ParseFlagString: [PLUS = %lo] [MINUS = %lo]", *plusmask, *minusmask); + return true; +} + +/***************************************************************/ + +static u_long ConvertBSDBits(const char *s) +{ + int i; + + for (i = 0; CF_BSDFLAGS[i].name != NULL; i++) + { + if (strcmp(s, CF_BSDFLAGS[i].name) == 0) + { + return CF_BSDFLAGS[i].bits; + } + } + + return 0; +} + +/* +CHFLAGS(1) FreeBSD General Commands Manual CHFLAGS(1) + +NAME + chflags - change file flags + +SYNOPSIS + chflags [-R [-H | -L | -P]] flags file ... + +DESCRIPTION + The chflags utility modifies the file flags of the listed files as speci- + fied by the flags operand. + + The options are as follows: + + -H If the -R option is specified, symbolic links on the command line + are followed. (Symbolic links encountered in the tree traversal + are not followed.) + + -L If the -R option is specified, all symbolic links are followed. + + -P If the -R option is specified, no symbolic links are followed. + + -R Change the file flags for the file hierarchies rooted in the + files instead of just the files themselves. + + Flags are a comma separated list of keywords. The following keywords are + currently defined: + + arch set the archived flag (super-user only) + dump set the dump flag + sappnd set the system append-only flag (super-user only) + schg set the system immutable flag (super-user only) + sunlnk set the system undeletable flag (super-user only) + uappnd set the user append-only flag (owner or super-user only) + uchg set the user immutable flag (owner or super-user only) + uunlnk set the user undeletable flag (owner or super-user only) + archived, sappend, schange, simmutable, uappend, uchange, uimmutable, + sunlink, uunlink + aliases for the above + + Putting the letters ``no'' before an option causes the flag to be turned + off. For example: + + nodump the file should never be dumped + + Symbolic links do not have flags, so unless the -H or -L option is set, + chflags on a symbolic link always succeeds and has no effect. The -H, -L + and -P options are ignored unless the -R option is specified. In addi- + tion, these options override each other and the command's actions are de- + termined by the last one specified. + + You can use "ls -lo" to see the flags of existing files. + + The chflags utility exits 0 on success, and >0 if an error occurs. + +SEE ALSO + ls(1), chflags(2), stat(2), fts(3), symlink(7) + +HISTORY + The chflags command first appeared in 4.4BSD. + +BSD March 31, 1994 1 +*/ diff --git a/libpromises/chflags.h b/libpromises/chflags.h new file mode 100644 index 0000000000..26a1b8d0fe --- /dev/null +++ b/libpromises/chflags.h @@ -0,0 +1,35 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CHFLAGS_H +#define CFENGINE_CHFLAGS_H + +/* BSD flags */ + +#include +#include + +bool ParseFlagString(Rlist *flags, u_long *plusmask, u_long *minusmask); + +#endif diff --git a/libpromises/class.c b/libpromises/class.c new file mode 100644 index 0000000000..01bf7af510 --- /dev/null +++ b/libpromises/class.c @@ -0,0 +1,349 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#include + +#include +#include +#include /* String*() */ +#include /* CompileRegex,StringMatchFullWithPrecompiledRegex */ +#include + + +static void ClassDestroy(Class *cls); /* forward declaration */ + +static void ClassDestroy_untyped(void *p) +{ + ClassDestroy(p); +} + + +/** + Define ClassMap. + Key: a string which is always the fully qualified class name, + for example "default:127_0_0_1" +*/ + +TYPED_MAP_DECLARE(Class, char *, Class *) + +TYPED_MAP_DEFINE(Class, char *, Class *, + StringHash_untyped, + StringEqual_untyped, + free, + ClassDestroy_untyped) + +struct ClassTable_ +{ + ClassMap *classes; +}; + +struct ClassTableIterator_ +{ + MapIterator iter; + char *ns; + bool is_hard; + bool is_soft; +}; + + +static void ClassInit(Class *cls, + const char *ns, const char *name, + bool is_soft, ContextScope scope, StringSet *tags, + const char *comment) +{ + assert(cls != NULL); + + if (ns == NULL || strcmp(ns, "default") == 0) + { + cls->ns = NULL; + } + else + { + cls->ns = xstrdup(ns); + } + + cls->name = xstrdup(name); + CanonifyNameInPlace(cls->name); + + cls->is_soft = is_soft; + cls->scope = scope; + cls->tags = (tags ? tags : StringSetNew()); + if (!is_soft && !StringSetContains(cls->tags, "hardclass")) + { + StringSetAdd(cls->tags, xstrdup("hardclass")); + } + cls->comment = SafeStringDuplicate(comment); +} + +static void ClassDestroySoft(Class *cls) +{ + if (cls != NULL) + { + free(cls->ns); + free(cls->name); + StringSetDestroy(cls->tags); + free(cls->comment); + } +} + +static void ClassDestroy(Class *cls) +{ + if (cls) + { + ClassDestroySoft(cls); + free(cls); + } +} + +ClassTable *ClassTableNew(void) +{ + ClassTable *table = xmalloc(sizeof(*table)); + + table->classes = ClassMapNew(); + + return table; +} + +void ClassTableDestroy(ClassTable *table) +{ + if (table) + { + ClassMapDestroy(table->classes); + free(table); + } +} + +bool ClassTablePut(ClassTable *table, + const char *ns, const char *name, + bool is_soft, ContextScope scope, StringSet *tags, const char *comment) +{ + assert(name); + assert(is_soft || (!ns || strcmp("default", ns) == 0)); // hard classes should have default namespace + assert(is_soft || scope == CONTEXT_SCOPE_NAMESPACE); // hard classes cannot be local + + if (ns == NULL) + { + ns = "default"; + } + + Class *cls = xmalloc(sizeof(*cls)); + ClassInit(cls, ns, name, is_soft, scope, tags, comment); + + /* (cls->name != name) because canonification has happened. */ + char *fullname = StringConcatenate(3, ns, ":", cls->name); + + Log(LOG_LEVEL_DEBUG, "Setting %sclass: %s", + is_soft ? "" : "hard ", + fullname); + + return ClassMapInsert(table->classes, fullname, cls); +} + +Class *ClassTableGet(const ClassTable *table, const char *ns, const char *name) +{ + if (ns == NULL) + { + ns = "default"; + } + + char fullname[ strlen(ns) + 1 + strlen(name) + 1 ]; + xsnprintf(fullname, sizeof(fullname), "%s:%s", ns, name); + + return ClassMapGet(table->classes, fullname); +} + +Class *ClassTableMatch(const ClassTable *table, const char *regex) +{ + ClassTableIterator *it = ClassTableIteratorNew(table, NULL, true, true); + Class *cls = NULL; + + Regex *pattern = CompileRegex(regex); + if (pattern == NULL) + { + // TODO: perhaps pcre has can give more info on this error? + Log(LOG_LEVEL_ERR, "Unable to pcre compile regex '%s'", regex); + return NULL; + } + + while ((cls = ClassTableIteratorNext(it))) + { + bool matched; + if (cls->ns) + { + char *class_expr = ClassRefToString(cls->ns, cls->name); + matched = StringMatchFullWithPrecompiledRegex(pattern, class_expr); + free(class_expr); + } + else + { + matched = StringMatchFullWithPrecompiledRegex(pattern, cls->name); + } + + if (matched) + { + break; + } + } + + RegexDestroy(pattern); + + ClassTableIteratorDestroy(it); + return cls; +} + +bool ClassTableRemove(ClassTable *table, const char *ns, const char *name) +{ + if (ns == NULL) + { + ns = "default"; + } + + char fullname[ strlen(ns) + 1 + strlen(name) + 1 ]; + xsnprintf(fullname, sizeof(fullname), "%s:%s", ns, name); + + return ClassMapRemove(table->classes, fullname); +} + +bool ClassTableClear(ClassTable *table) +{ + bool has_classes = (ClassMapSize(table->classes) > 0); + ClassMapClear(table->classes); + return has_classes; +} + +ClassTableIterator *ClassTableIteratorNew(const ClassTable *table, + const char *ns, + bool is_hard, bool is_soft) +{ + ClassTableIterator *iter = xmalloc(sizeof(*iter)); + + iter->ns = ns ? xstrdup(ns) : NULL; + iter->iter = MapIteratorInit(table->classes->impl); + iter->is_soft = is_soft; + iter->is_hard = is_hard; + + return iter; +} + +Class *ClassTableIteratorNext(ClassTableIterator *iter) +{ + MapKeyValue *keyvalue; + + while ((keyvalue = MapIteratorNext(&iter->iter)) != NULL) + { + Class *cls = keyvalue->value; + + /* Make sure we never store "default" as namespace in the ClassTable, + * instead we have always ns==NULL in that case. */ + CF_ASSERT_FIX(cls->ns == NULL || + strcmp(cls->ns, "default") != 0, + (cls->ns = NULL), + "Class table contained \"default\" namespace," + " should never happen!"); + + const char *key_ns = cls->ns ? cls->ns : "default"; + + if (iter->ns && strcmp(key_ns, iter->ns) != 0) + { + continue; + } + + if (iter->is_soft && !iter->is_soft) + { + continue; + } + if (iter->is_hard && !iter->is_hard) + { + continue; + } + + return cls; + } + + return NULL; +} + +void ClassTableIteratorDestroy(ClassTableIterator *iter) +{ + if (iter) + { + free(iter->ns); + free(iter); + } +} + +ClassRef ClassRefParse(const char *expr) +{ + char *name_start = strchr(expr, ':'); + if (!name_start) + { + return (ClassRef) { .ns = NULL, .name = xstrdup(expr) }; + } + else + { + char *ns = NULL; + if ((name_start - expr) > 0) + { + ns = xstrndup(expr, name_start - expr); + } + else + { + // this would be invalid syntax + ns = xstrdup(""); + } + char *name = xstrdup(name_start + 1); + return (ClassRef) { .ns = ns, .name = name }; + } +} + +char *ClassRefToString(const char *ns, const char *name) +{ + assert(name != NULL); + + if (ns == NULL || + strcmp("default", ns) == 0) + { + return xstrdup(name); + } + else + { + return StringConcatenate(3, ns, ":", name); + } +} + +bool ClassRefIsQualified(ClassRef ref) +{ + return ref.ns != NULL; +} + +void ClassRefQualify(ClassRef *ref, const char *ns) +{ + free(ref->ns); + ref->ns = xstrdup(ns); +} + +void ClassRefDestroy(ClassRef ref) +{ + free(ref.ns); + free(ref.name); +} diff --git a/libpromises/class.h b/libpromises/class.h new file mode 100644 index 0000000000..67b1d8cb20 --- /dev/null +++ b/libpromises/class.h @@ -0,0 +1,72 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#ifndef CFENGINE_CLASS_H +#define CFENGINE_CLASS_H + +#include +#include + +typedef struct +{ + char *ns; /* NULL in case of default namespace */ + char *name; /* class name */ + + ContextScope scope; + bool is_soft; + StringSet *tags; + char *comment; +} Class; + + +typedef struct ClassTable_ ClassTable; +typedef struct ClassTableIterator_ ClassTableIterator; + +ClassTable *ClassTableNew(void); +void ClassTableDestroy(ClassTable *table); + +bool ClassTablePut(ClassTable *table, const char *ns, const char *name, bool is_soft, ContextScope scope, + StringSet *tags, const char *comment); +Class *ClassTableGet(const ClassTable *table, const char *ns, const char *name); +Class *ClassTableMatch(const ClassTable *table, const char *regex); +bool ClassTableRemove(ClassTable *table, const char *ns, const char *name); + +bool ClassTableClear(ClassTable *table); + +ClassTableIterator *ClassTableIteratorNew(const ClassTable *table, const char *ns, bool is_hard, bool is_soft); +Class *ClassTableIteratorNext(ClassTableIterator *iter); +void ClassTableIteratorDestroy(ClassTableIterator *iter); + +typedef struct +{ + char *ns; + char *name; +} ClassRef; + +ClassRef ClassRefParse(const char *expr); +char *ClassRefToString(const char *ns, const char *name); +bool ClassRefIsQualified(ClassRef ref); +void ClassRefQualify(ClassRef *ref, const char *ns); +void ClassRefDestroy(ClassRef ref); + +#endif diff --git a/libpromises/cmdb.c b/libpromises/cmdb.c new file mode 100644 index 0000000000..dd25fdc978 --- /dev/null +++ b/libpromises/cmdb.c @@ -0,0 +1,569 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include /* StringSet */ +#include +#include /* GetDataDir() */ +#include /* VarRef, StringContainsUnresolved() */ +#include /* EvalContext*() */ +#include /* JoinPaths() */ + +#include + +#define HOST_SPECIFIC_DATA_FILE "host_specific.json" +#define HOST_SPECIFIC_DATA_MAX_SIZE (5 * 1024 * 1024) /* maximum size of the host-specific.json file */ + +#define CMDB_NAMESPACE "data" +#define CMDB_VARIABLES_TAGS "tags" +#define CMDB_VARIABLES_DATA "value" +#define CMDB_CLASSES_TAGS "tags" +#define CMDB_CLASSES_CLASS_EXPRESSIONS "class_expressions" +#define CMDB_CLASSES_REGULAR_EXPRESSIONS "regular_expressions" +#define CMDB_COMMENT_KEY "comment" + +JsonElement *ReadJsonFile(const char *filename, LogLevel log_level, size_t size_max) +{ + assert(filename != NULL); + + JsonElement *doc = NULL; + JsonParseError err = JsonParseFile(filename, size_max, &doc); + + if (err == JSON_PARSE_ERROR_NO_SUCH_FILE) + { + Log(log_level, "Could not open JSON file %s", filename); + return NULL; + } + + if (err != JSON_PARSE_OK || + doc == NULL) + { + Log(log_level, "Could not parse JSON file %s: %s", filename, JsonParseErrorToString(err)); + } + + return doc; +} + +static bool CheckPrimitiveForUnexpandedVars(JsonElement *primitive, ARG_UNUSED void *data) +{ + assert(JsonGetElementType(primitive) == JSON_ELEMENT_TYPE_PRIMITIVE); + + /* Stop the iteration if a variable expression is found. */ + return (!StringContainsUnresolved(JsonPrimitiveGetAsString(primitive))); +} + +static bool CheckObjectForUnexpandedVars(JsonElement *object, ARG_UNUSED void *data) +{ + assert(JsonGetType(object) == JSON_TYPE_OBJECT); + + /* Stop the iteration if a variable expression is found among children + * keys. (elements inside the object are checked separately) */ + JsonIterator iter = JsonIteratorInit(object); + while (JsonIteratorHasMore(&iter)) + { + const char *key = JsonIteratorNextKey(&iter); + if (StringContainsUnresolved(key)) + { + return false; + } + } + return true; +} + +static VarRef *GetCMDBVariableRef(const char *key) +{ + VarRef *ref = VarRefParse(key); + if (ref->ns == NULL) + { + ref->ns = xstrdup(CMDB_NAMESPACE); + } + else + { + if (ref->scope == NULL) + { + Log(LOG_LEVEL_ERR, "Invalid variable specification in CMDB data: '%s'" + " (bundle name has to be specified if namespace is specified)", key); + VarRefDestroy(ref); + return NULL; + } + } + + if (ref->scope == NULL) + { + ref->scope = xstrdup("variables"); + } + return ref; +} + +static bool AddCMDBVariable(EvalContext *ctx, const char *key, const VarRef *ref, + JsonElement *data, StringSet *tags, const char *comment) +{ + assert(ctx != NULL); + assert(key != NULL); + assert(ref != NULL); + assert(data != NULL); + assert(tags != NULL); + + bool ret; + if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + char *value = JsonPrimitiveToString(data); + Log(LOG_LEVEL_VERBOSE, "Installing CMDB variable '%s:%s.%s=%s'", + ref->ns, ref->scope, key, value); + ret = EvalContextVariablePutTagsSetWithComment(ctx, ref, value, CF_DATA_TYPE_STRING, + tags, comment); + free(value); + } + else if ((JsonGetType(data) == JSON_TYPE_ARRAY) && + JsonArrayContainsOnlyPrimitives(data)) + { + // map to slist if the data only has primitives + Log(LOG_LEVEL_VERBOSE, "Installing CMDB slist variable '%s:%s.%s'", + ref->ns, ref->scope, key); + Rlist *data_rlist = RlistFromContainer(data); + ret = EvalContextVariablePutTagsSetWithComment(ctx, ref, + data_rlist, CF_DATA_TYPE_STRING_LIST, + tags, comment); + RlistDestroy(data_rlist); + } + else + { + // install as a data container + Log(LOG_LEVEL_VERBOSE, "Installing CMDB data container variable '%s:%s.%s'", + ref->ns, ref->scope, key); + ret = EvalContextVariablePutTagsSetWithComment(ctx, ref, + data, CF_DATA_TYPE_CONTAINER, + tags, comment); + } + if (!ret) + { + /* On success, EvalContextVariablePutTagsSet() consumes the tags set, + * otherwise, we shall destroy it. */ + StringSetDestroy(tags); + } + return ret; +} + +static bool ReadCMDBVars(EvalContext *ctx, JsonElement *vars) +{ + assert(vars != NULL); + + if (JsonGetType(vars) != JSON_TYPE_OBJECT) + { + Log(LOG_LEVEL_ERR, "Invalid 'vars' CMDB data, must be a JSON object"); + return false; + } + + if (!JsonWalk(vars, CheckObjectForUnexpandedVars, NULL, CheckPrimitiveForUnexpandedVars, NULL)) + { + Log(LOG_LEVEL_ERR, "Invalid 'vars' CMDB data, cannot contain variable references"); + return false; + } + + JsonIterator iter = JsonIteratorInit(vars); + while (JsonIteratorHasMore(&iter)) + { + const char *key = JsonIteratorNextKey(&iter); + JsonElement *data = JsonObjectGet(vars, key); + + VarRef *ref = GetCMDBVariableRef(key); + if (ref == NULL) + { + continue; + } + + StringSet *tags = StringSetNew(); + StringSetAdd(tags, xstrdup(CMDB_SOURCE_TAG)); + bool ret = AddCMDBVariable(ctx, key, ref, data, tags, NULL); + VarRefDestroy(ref); + if (!ret) + { + /* Details should have been logged already. */ + Log(LOG_LEVEL_ERR, "Failed to add CMDB variable '%s'", key); + } + } + return true; +} + +static StringSet *GetTagsFromJsonTags(const char *item_type, + const char *key, + const JsonElement *json_tags, + const char *default_tag) +{ + StringSet *tags = NULL; + if (JSON_NOT_NULL(json_tags)) + { + if ((JsonGetType(json_tags) != JSON_TYPE_ARRAY) || + (!JsonArrayContainsOnlyPrimitives((JsonElement*) json_tags))) + { + Log(LOG_LEVEL_ERR, + "Invalid json_tags information for %s '%s' in CMDB data:" + " must be a JSON array of strings", + item_type, key); + } + else + { + tags = JsonArrayToStringSet(json_tags); + if (tags == NULL) + { + Log(LOG_LEVEL_ERR, + "Invalid json_tags information %s '%s' in CMDB data:" + " must be a JSON array of strings", + item_type, key); + } + } + } + if (tags == NULL) + { + tags = StringSetNew(); + } + StringSetAdd(tags, xstrdup(default_tag)); + + return tags; +} + +static inline const char *GetCMDBComment(const char *item_type, const char *identifier, + const JsonElement *json_object) +{ + assert(JsonGetType(json_object) == JSON_TYPE_OBJECT); + + JsonElement *json_comment = JsonObjectGet(json_object, CMDB_COMMENT_KEY); + if (NULL_JSON(json_comment)) + { + return NULL; + } + + if (JsonGetType(json_comment) != JSON_TYPE_STRING) + { + Log(LOG_LEVEL_ERR, + "Invalid type of the 'comment' field for the '%s' %s in CMDB data, must be a string", + identifier, item_type); + return NULL; + } + + return JsonPrimitiveGetAsString(json_comment); +} + +/** Uses the new format allowing metadata (CFE-3633) */ +static bool ReadCMDBVariables(EvalContext *ctx, JsonElement *variables) +{ + assert(variables != NULL); + + if (JsonGetType(variables) != JSON_TYPE_OBJECT) + { + Log(LOG_LEVEL_ERR, "Invalid 'variables' CMDB data, must be a JSON object"); + return false; + } + + if (!JsonWalk(variables, CheckObjectForUnexpandedVars, NULL, CheckPrimitiveForUnexpandedVars, NULL)) + { + Log(LOG_LEVEL_ERR, "Invalid 'variables' CMDB data, cannot contain variable references"); + return false; + } + + JsonIterator iter = JsonIteratorInit(variables); + while (JsonIteratorHasMore(&iter)) + { + const char *key = JsonIteratorNextKey(&iter); + + VarRef *ref = GetCMDBVariableRef(key); + if (ref == NULL) + { + continue; + } + + JsonElement *const var_info = JsonObjectGet(variables, key); + + JsonElement *data; + StringSet *tags; + const char *comment = NULL; + + if (JsonGetType(var_info) == JSON_TYPE_OBJECT) + { + data = JsonObjectGet(var_info, CMDB_VARIABLES_DATA); + + if (data == NULL) + { + Log(LOG_LEVEL_ERR, "Missing value in '%s' variable specification in CMDB data (value field is required)", key); + VarRefDestroy(ref); + continue; + } + + JsonElement *json_tags = JsonObjectGet(var_info, CMDB_VARIABLES_TAGS); + tags = GetTagsFromJsonTags("variable", key, json_tags, CMDB_SOURCE_TAG); + comment = GetCMDBComment("variable", key, var_info); + } + else + { + // Just a bare value, like in "vars", no metadata + data = var_info; + tags = GetTagsFromJsonTags("variable", key, NULL, CMDB_SOURCE_TAG); + } + + assert(tags != NULL); + assert(data != NULL); + + bool ret = AddCMDBVariable(ctx, key, ref, data, tags, comment); + VarRefDestroy(ref); + if (!ret) + { + /* Details should have been logged already. */ + Log(LOG_LEVEL_ERR, "Failed to add CMDB variable '%s'", key); + } + } + return true; +} + +static bool AddCMDBClass(EvalContext *ctx, const char *key, StringSet *tags, const char *comment) +{ + assert(ctx != NULL); + assert(key != NULL); + assert(tags != NULL); + + bool ret; + Log(LOG_LEVEL_VERBOSE, "Installing CMDB class '%s'", key); + + if (strchr(key, ':') != NULL) + { + char *ns_class_name = xstrdup(key); + char *sep = strchr(ns_class_name, ':'); + *sep = '\0'; + key = sep + 1; + ret = EvalContextClassPutSoftNSTagsSetWithComment(ctx, ns_class_name, key, + CONTEXT_SCOPE_NAMESPACE, tags, comment); + free(ns_class_name); + } + else + { + ret = EvalContextClassPutSoftNSTagsSetWithComment(ctx, CMDB_NAMESPACE, key, + CONTEXT_SCOPE_NAMESPACE, tags, comment); + } + if (!ret) + { + /* On success, EvalContextClassPutSoftNSTagsSetWithComment() consumes + * the tags set, otherwise, we shall destroy it. */ + StringSetDestroy(tags); + } + + return ret; +} + +static bool ReadCMDBClasses(EvalContext *ctx, JsonElement *classes) +{ + assert(classes != NULL); + + if (JsonGetType(classes) != JSON_TYPE_OBJECT) + { + Log(LOG_LEVEL_ERR, "Invalid 'classes' CMDB data, must be a JSON object"); + return false; + } + + if (!JsonWalk(classes, CheckObjectForUnexpandedVars, NULL, CheckPrimitiveForUnexpandedVars, NULL)) + { + Log(LOG_LEVEL_ERR, "Invalid 'classes' CMDB data, cannot contain variable references"); + return false; + } + + JsonIterator iter = JsonIteratorInit(classes); + while (JsonIteratorHasMore(&iter)) + { + const char *key = JsonIteratorNextKey(&iter); + JsonElement *data = JsonObjectGet(classes, key); + if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + const char *expr = JsonPrimitiveGetAsString(data); + if (!StringEqual(expr, "any::")) + { + Log(LOG_LEVEL_ERR, + "Invalid class specification '%s' in CMDB data, only \"any::\" allowed", expr); + continue; + } + + StringSet *default_tags = StringSetNew(); + StringSetAdd(default_tags, xstrdup(CMDB_SOURCE_TAG)); + bool ret = AddCMDBClass(ctx, key, default_tags, NULL); + if (!ret) + { + /* It does not have to be and error, it could be a result of + * the class already existing. Anyways, details about + * potential errors should have been logged already. */ + Log(LOG_LEVEL_DEBUG, "Did not add CMDB class '%s'", key); + } + } + else if (JsonGetContainerType(data) == JSON_CONTAINER_TYPE_ARRAY && + JsonArrayContainsOnlyPrimitives(data)) + { + switch (JsonLength(data)) + { + case 0: + Log(LOG_LEVEL_ERR, + "Empty class specification '[]' in CMDB data not allowed, only '[\"any::\"]'"); + continue;; + case 1: + if (!StringEqual(JsonPrimitiveGetAsString(JsonArrayGet(data, 0)), "any::")) + { + Log(LOG_LEVEL_ERR, + "Invalid class specification '[\"%s\"]' in CMDB data, only '[\"any::\"]' allowed", + JsonPrimitiveGetAsString(JsonArrayGet(data, 0))); + continue; + } + // All good :) + break; + default: + { + Writer *const str = StringWriter(); + JsonWriteCompact(str, data); + Log(LOG_LEVEL_ERR, + "Too many elements in class specification '%s' in CMDB data, only '[\"any::\"]' allowed", + StringWriterData(str)); + WriterClose(str); + } + continue; + } + StringSet *default_tags = StringSetNew(); + StringSetAdd(default_tags, xstrdup(CMDB_SOURCE_TAG)); + bool ret = AddCMDBClass(ctx, key, default_tags, NULL); + if (!ret) + { + /* It does not have to be and error, it could be a result of + * the class already existing. Anyways, details about + * potential errors should have been logged already. */ + Log(LOG_LEVEL_DEBUG, "Did not add CMDB class '%s'", key); + } + } + else if (JsonGetContainerType(data) == JSON_CONTAINER_TYPE_OBJECT) + { + const JsonElement *class_exprs = JsonObjectGet(data, CMDB_CLASSES_CLASS_EXPRESSIONS); + const JsonElement *reg_exprs = JsonObjectGet(data, CMDB_CLASSES_REGULAR_EXPRESSIONS); + const JsonElement *json_tags = JsonObjectGet(data, CMDB_CLASSES_TAGS); + + if (JSON_NOT_NULL(class_exprs) && + (JsonGetType(class_exprs) != JSON_TYPE_ARRAY || + JsonLength(class_exprs) > 1 || + (JsonLength(class_exprs) == 1 && + !StringEqual(JsonPrimitiveGetAsString(JsonArrayGet(class_exprs, 0)), "any::")))) + { + Log(LOG_LEVEL_ERR, + "Invalid class expression rules for class '%s' in CMDB data," + " only '[]' or '[\"any::\"]' allowed", key); + continue; + } + if (JSON_NOT_NULL(reg_exprs) && + (JsonGetType(reg_exprs) != JSON_TYPE_ARRAY || + JsonLength(reg_exprs) > 1 || + (JsonLength(reg_exprs) == 1 && + !StringEqual(JsonPrimitiveGetAsString(JsonArrayGet(reg_exprs, 0)), "any")))) + { + Log(LOG_LEVEL_ERR, + "Invalid regular expression rules for class '%s' in CMDB data," + " only '[]' or '[\"any\"]' allowed", key); + continue; + } + + StringSet *tags = GetTagsFromJsonTags("class", key, json_tags, CMDB_SOURCE_TAG); + const char *comment = GetCMDBComment("class", key, data); + bool ret = AddCMDBClass(ctx, key, tags, comment); + if (!ret) + { + /* It does not have to be and error, it could be a result of + * the class already existing. Anyways, details about + * potential errors should have been logged already. */ + Log(LOG_LEVEL_DEBUG, "Did not add CMDB class '%s'", key); + } + } + else + { + Log(LOG_LEVEL_ERR, "Invalid CMDB class data for class '%s'", key); + } + } + return true; +} + +bool LoadCMDBData(EvalContext *ctx) +{ + char file_path[PATH_MAX] = {0}; + strncpy(file_path, GetDataDir(), sizeof(file_path) - 1); + JoinPaths(file_path, sizeof(file_path), HOST_SPECIFIC_DATA_FILE); + if (access(file_path, F_OK) != 0) + { + Log(LOG_LEVEL_VERBOSE, "No host-specific JSON data available at '%s'", file_path); + return true; /* not an error */ + } + if (access(file_path, R_OK) != 0) + { + Log(LOG_LEVEL_ERR, "Cannot read host-spefic JSON data from '%s'", + file_path); + return false; + } + + JsonElement *data = ReadJsonFile(file_path, LOG_LEVEL_ERR, HOST_SPECIFIC_DATA_MAX_SIZE); + if (data == NULL) + { + /* Details are logged by ReadJsonFile() */ + return false; + } + if (JsonGetType(data) != JSON_TYPE_OBJECT) + { + Log(LOG_LEVEL_ERR, "Invalid CMDB contents in '%s', must be a JSON object", file_path); + JsonDestroy(data); + return false; + } + + Log(LOG_LEVEL_VERBOSE, "Loaded CMDB data file '%s', installing contents", file_path); + + JsonIterator iter = JsonIteratorInit(data); + while (JsonIteratorHasMore(&iter)) + { + const char *key = JsonIteratorNextKey(&iter); + /* Only vars and classes allowed in CMDB data */ + if (!IsStrIn(key, (const char*[4]){"vars", "classes", "variables", NULL})) + { + Log(LOG_LEVEL_WARNING, "Invalid key '%s' in the CMDB data file '%s', skipping it", + key, file_path); + } + } + + bool success = true; + JsonElement *vars = JsonObjectGet(data, "vars"); + if (JSON_NOT_NULL(vars) && !ReadCMDBVars(ctx, vars)) + { + success = false; + } + /* Uses the new format allowing metadata (CFE-3633) */ + JsonElement *variables = JsonObjectGet(data, "variables"); + if (JSON_NOT_NULL(variables) && !ReadCMDBVariables(ctx, variables)) + { + success = false; + } + JsonElement *classes = JsonObjectGet(data, "classes"); + if (JSON_NOT_NULL(classes) && !ReadCMDBClasses(ctx, classes)) + { + success = false; + } + + JsonDestroy(data); + return success; +} diff --git a/libpromises/cmdb.h b/libpromises/cmdb.h new file mode 100644 index 0000000000..e9e34be78f --- /dev/null +++ b/libpromises/cmdb.h @@ -0,0 +1,36 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CMDB_H +#define CFENGINE_CMDB_H + +#include +#include + +#define CMDB_SOURCE_TAG "source=cmdb" + +JsonElement *ReadJsonFile(const char *filename, LogLevel log_level, size_t size_max); +bool LoadCMDBData(EvalContext *ctx); + +#endif /* CFENGINE_CMDB_H */ diff --git a/libpromises/constants.c b/libpromises/constants.c new file mode 100644 index 0000000000..f635c58871 --- /dev/null +++ b/libpromises/constants.c @@ -0,0 +1,181 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +const char *const DAY_TEXT[] = +{ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + NULL +}; + +const char *const MONTH_TEXT[] = +{ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + NULL +}; + +const char *const SHIFT_TEXT[] = +{ + "Night", + "Morning", + "Afternoon", + "Evening", + NULL +}; + +const char *const CF_AGENTTYPES[] = /* see enum cfagenttype */ +{ + CF_COMMONC, + CF_AGENTC, + CF_SERVERC, + CF_MONITORC, + CF_EXECC, + CF_RUNC, + CF_KEYGEN, + CF_HUBC, + "", +}; + +// Name and description pairs of all observed monitoring variables +const char *const OBSERVABLES[CF_OBSERVABLES][2] = +{ + {"users", "Users with active processes - including system users"}, + {"rootprocs", "Sum privileged system processes"}, + {"otherprocs", "Sum non-privileged process"}, + {"diskfree", "Free disk on / partition"}, + {"loadavg", "Kernel load average utilization (sum over cores)"}, + {"netbiosns_in", "netbios name lookups (in)"}, + {"netbiosns_out", "netbios name lookups (out)"}, + {"netbiosdgm_in", "netbios name datagrams (in)"}, + {"netbiosdgm_out", "netbios name datagrams (out)"}, + {"netbiosssn_in", "Samba/netbios name sessions (in)"}, + {"netbiosssn_out", "Samba/netbios name sessions (out)"}, + {"imap_in", "imap mail client sessions (in)"}, + {"imap_out", "imap mail client sessions (out)"}, + {"cfengine_in", "cfengine connections (in)"}, + {"cfengine_out", "cfengine connections (out)"}, + {"nfsd_in", "nfs connections (in)"}, + {"nfsd_out", "nfs connections (out)"}, + {"smtp_in", "smtp connections (in)"}, + {"smtp_out", "smtp connections (out)"}, + {"www_in", "www connections (in)"}, + {"www_out", "www connections (out)"}, + {"ftp_in", "ftp connections (in)"}, + {"ftp_out", "ftp connections (out)"}, + {"ssh_in", "ssh connections (in)"}, + {"ssh_out", "ssh connections (out)"}, + {"wwws_in", "wwws connections (in)"}, + {"wwws_out", "wwws connections (out)"}, + {"icmp_in", "ICMP packets (in)"}, + {"icmp_out", "ICMP packets (out)"}, + {"udp_in", "UDP dgrams (in)"}, + {"udp_out", "UDP dgrams (out)"}, + {"dns_in", "DNS requests (in)"}, + {"dns_out", "DNS requests (out)"}, + {"tcpsyn_in", "TCP sessions (in)"}, + {"tcpsyn_out", "TCP sessions (out)"}, + {"tcpack_in", "TCP acks (in)"}, + {"tcpack_out", "TCP acks (out)"}, + {"tcpfin_in", "TCP finish (in)"}, + {"tcpfin_out", "TCP finish (out)"}, + {"tcpmisc_in", "TCP misc (in)"}, + {"tcpmisc_out", "TCP misc (out)"}, + {"webaccess", "Webserver hits"}, + {"weberrors", "Webserver errors"}, + {"syslog", "New log entries (Syslog)"}, + {"messages", "New log entries (messages)"}, + {"temp0", "CPU Temperature 0"}, + {"temp1", "CPU Temperature 1"}, + {"temp2", "CPU Temperature 2"}, + {"temp3", "CPU Temperature 3"}, + {"cpu", "%CPU utilization (all)"}, + {"cpu0", "%CPU utilization 0"}, + {"cpu1", "%CPU utilization 1"}, + {"cpu2", "%CPU utilization 2"}, + {"cpu3", "%CPU utilization 3"}, + {"microsoft_ds_in", "Samba/MS_ds name sessions (in)"}, + {"microsoft_ds_out", "Samba/MS_ds name sessions (out)"}, + {"www_alt_in", "Alternative web service connections (in)"}, + {"www_alt_out", "Alternative web client connections (out)"}, + {"imaps_in", "encrypted imap mail service sessions (in)"}, + {"imaps_out", "encrypted imap mail client sessions (out)"}, + {"ldap_in", "LDAP directory service service sessions (in)"}, + {"ldap_out", "LDAP directory service client sessions (out)"}, + {"ldaps_in", "LDAP directory service service sessions (in)"}, + {"ldaps_out", "LDAP directory service client sessions (out)"}, + {"mongo_in", "Mongo database service sessions (in)"}, + {"mongo_out", "Mongo database client sessions (out)"}, + {"mysql_in", "MySQL database service sessions (in)"}, + {"mysql_out", "MySQL database client sessions (out)"}, + {"postgres_in", "PostgreSQL database service sessions (in)"}, + {"postgres_out", "PostgreSQL database client sessions (out)"}, + {"ipp_in", "Internet Printer Protocol (in)"}, + {"ipp_out", "Internet Printer Protocol (out)"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, + {"spare", "unused"}, +}; diff --git a/libpromises/conversion.c b/libpromises/conversion.c new file mode 100644 index 0000000000..8de8ba5710 --- /dev/null +++ b/libpromises/conversion.c @@ -0,0 +1,1204 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* GetUserID(), GetGroupID() */ + + +const char *MapAddress(const char *unspec_address) +{ /* Is the address a mapped ipv4 over ipv6 address */ + + if (strncmp(unspec_address, "::ffff:", 7) == 0) + { + return unspec_address + 7; + } + else + { + return unspec_address; + } +} + +int FindTypeInArray(const char *const haystack[], const char *needle, int default_value, int null_value) +{ + if (needle == NULL) + { + return null_value; + } + + for (int i = 0; haystack[i] != NULL; ++i) + { + if (strcmp(needle, haystack[i]) == 0) + { + return i; + } + } + + return default_value; +} + +MeasurePolicy MeasurePolicyFromString(const char *s) +{ + static const char *const MEASURE_POLICY_TYPES[] = + { "average", "sum", "first", "last", NULL }; + + return FindTypeInArray(MEASURE_POLICY_TYPES, s, MEASURE_POLICY_AVERAGE, MEASURE_POLICY_NONE); +} + +EnvironmentState EnvironmentStateFromString(const char *s) +{ + static const char *const ENV_STATE_TYPES[] = + { "create", "delete", "running", "suspended", "down", NULL }; + + return FindTypeInArray(ENV_STATE_TYPES, s, ENVIRONMENT_STATE_NONE, ENVIRONMENT_STATE_CREATE); +} + +InsertMatchType InsertMatchTypeFromString(const char *s) +{ + static const char *const INSERT_MATCH_TYPES[] = + { "ignore_leading", "ignore_trailing", "ignore_embedded", + "exact_match", NULL }; + + return FindTypeInArray(INSERT_MATCH_TYPES, s, INSERT_MATCH_TYPE_EXACT, INSERT_MATCH_TYPE_EXACT); +} + +int SyslogPriorityFromString(const char *s) +{ + static const char *const SYSLOG_PRIORITY_TYPES[] = + { "emergency", "alert", "critical", "error", "warning", "notice", + "info", "debug", NULL }; + + return FindTypeInArray(SYSLOG_PRIORITY_TYPES, s, 3, 3); +} + +ShellType ShellTypeFromString(const char *string) +{ + // For historical reasons, supports all CF_BOOL values (true/false/yes/no...), + // as well as "noshell,useshell,powershell". + char *start, *end; + char *options = "noshell,useshell,powershell," CF_BOOL; + int i; + int size; + + if (string == NULL) + { + return SHELL_TYPE_NONE; + } + + start = options; + size = strlen(string); + for (i = 0;; i++) + { + end = strchr(start, ','); + if (end == NULL) + { + break; + } + if (size == end - start && strncmp(string, start, end - start) == 0) + { + int cfBoolIndex; + switch (i) + { + case 0: + return SHELL_TYPE_NONE; + case 1: + return SHELL_TYPE_USE; + case 2: + return SHELL_TYPE_POWERSHELL; + default: + // Even cfBoolIndex is true, odd cfBoolIndex is false (from CF_BOOL). + cfBoolIndex = i-3; + return (cfBoolIndex & 1) ? SHELL_TYPE_NONE : SHELL_TYPE_USE; + } + } + start = end + 1; + } + return SHELL_TYPE_NONE; +} + +DatabaseType DatabaseTypeFromString(const char *s) +{ + static const char *const DB_TYPES[] = { "mysql", "postgres", NULL }; + + return FindTypeInArray(DB_TYPES, s, DATABASE_TYPE_NONE, DATABASE_TYPE_NONE); +} + +UserState UserStateFromString(const char *s) +{ + static const char *const U_TYPES[] = + { "present", "absent", "locked", NULL }; + + return FindTypeInArray(U_TYPES, s, USER_STATE_NONE, USER_STATE_NONE); +} + +PasswordFormat PasswordFormatFromString(const char *s) +{ + static const char *const U_TYPES[] = { "plaintext", "hash", NULL }; + + return FindTypeInArray(U_TYPES, s, PASSWORD_FORMAT_NONE, PASSWORD_FORMAT_NONE); +} + +PackageAction PackageActionFromString(const char *s) +{ + static const char *const PACKAGE_ACTION_TYPES[] = + { "add", "delete", "reinstall", "update", "addupdate", "patch", + "verify", NULL }; + + return FindTypeInArray(PACKAGE_ACTION_TYPES, s, PACKAGE_ACTION_NONE, PACKAGE_ACTION_NONE); +} + +NewPackageAction GetNewPackagePolicy(const char *s, const char **action_types) +{ + return FindTypeInArray(action_types, s, NEW_PACKAGE_ACTION_NONE, NEW_PACKAGE_ACTION_NONE); +} + +PackageVersionComparator PackageVersionComparatorFromString(const char *s) +{ + static const char *const PACKAGE_SELECT_TYPES[] = + { "==", "!=", ">", "<", ">=", "<=", NULL }; + + return FindTypeInArray(PACKAGE_SELECT_TYPES, s, PACKAGE_VERSION_COMPARATOR_NONE, PACKAGE_VERSION_COMPARATOR_NONE); +} + +PackageActionPolicy PackageActionPolicyFromString(const char *s) +{ + static const char *const ACTION_POLICY_TYPES[] = + { "individual", "bulk", NULL }; + + return FindTypeInArray(ACTION_POLICY_TYPES, s, PACKAGE_ACTION_POLICY_NONE, PACKAGE_ACTION_POLICY_NONE); +} + +/***************************************************************************/ + +char *Rlist2String(Rlist *list, char *sep) +{ + Writer *writer = StringWriter(); + + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + RvalWrite(writer, rp->val); + + if (rp->next != NULL) + { + WriterWrite(writer, sep); + } + } + + return StringWriterClose(writer); +} + +/***************************************************************************/ + +int SignalFromString(const char *s) +{ + char *signal_names[15] = { + "hup", "int", "trap", "kill", "pipe", "cont", "abrt", "stop", + "quit", "term", "child", "usr1", "usr2", "bus", "segv" + }; + int signals[15] = { + SIGHUP, SIGINT, SIGTRAP, SIGKILL, SIGPIPE, SIGCONT, SIGABRT, SIGSTOP, + SIGQUIT, SIGTERM, SIGCHLD, SIGUSR1, SIGUSR2, SIGBUS, SIGSEGV + }; + + for (size_t i = 0; i < 15; i++) + { + if (StringEqual(s, signal_names[i])) + { + return signals[i]; + } + } + + return -1; +} + +ContextScope ContextScopeFromString(const char *scope_str) +{ + static const char *const CONTEXT_SCOPES[] = { "namespace", "bundle" }; + return FindTypeInArray(CONTEXT_SCOPES, scope_str, CONTEXT_SCOPE_NAMESPACE, CONTEXT_SCOPE_NONE); +} + +FileLinkType FileLinkTypeFromString(const char *s) +{ + static const char *const LINK_TYPES[] = + { "symlink", "hardlink", "relative", "absolute", NULL }; + + return FindTypeInArray(LINK_TYPES, s, FILE_LINK_TYPE_SYMLINK, FILE_LINK_TYPE_SYMLINK); +} + +FileComparator FileComparatorFromString(const char *s) +{ + static const char *const FILE_COMPARISON_TYPES[] = + { "atime", "mtime", "ctime", "digest", "hash", "binary", "exists", NULL }; + + return FindTypeInArray(FILE_COMPARISON_TYPES, s, FILE_COMPARATOR_NONE, FILE_COMPARATOR_NONE); +} + +static const char *const datatype_strings[] = +{ + [CF_DATA_TYPE_STRING] = "string", + [CF_DATA_TYPE_INT] = "int", + [CF_DATA_TYPE_REAL] = "real", + [CF_DATA_TYPE_STRING_LIST] = "slist", + [CF_DATA_TYPE_INT_LIST] = "ilist", + [CF_DATA_TYPE_REAL_LIST] = "rlist", + [CF_DATA_TYPE_OPTION] = "option", + [CF_DATA_TYPE_OPTION_LIST] = "olist", + [CF_DATA_TYPE_BODY] = "body", + [CF_DATA_TYPE_BUNDLE] = "bundle", + [CF_DATA_TYPE_CONTEXT] = "context", + [CF_DATA_TYPE_CONTEXT_LIST] = "clist", + [CF_DATA_TYPE_INT_RANGE] = "irange", + [CF_DATA_TYPE_REAL_RANGE] = "rrange", + [CF_DATA_TYPE_COUNTER] = "counter", + [CF_DATA_TYPE_CONTAINER] = "data", + [CF_DATA_TYPE_NONE] = "none" +}; + +DataType DataTypeFromString(const char *name) +{ + for (int i = 0; i < CF_DATA_TYPE_NONE; i++) + { + if (strcmp(datatype_strings[i], name) == 0) + { + return i; + } + } + + return CF_DATA_TYPE_NONE; +} + +const char *DataTypeToString(DataType type) +{ + assert(type < CF_DATA_TYPE_NONE); + return datatype_strings[type]; +} + +DataType ConstraintSyntaxGetDataType(const ConstraintSyntax *body_syntax, const char *lval) +{ + int i = 0; + + for (i = 0; body_syntax[i].lval != NULL; i++) + { + if (lval && (strcmp(body_syntax[i].lval, lval) == 0)) + { + return body_syntax[i].dtype; + } + } + + return CF_DATA_TYPE_NONE; +} + +/****************************************************************************/ + +// Warning: Defaults to true on unexpected (non-bool) input +bool BooleanFromString(const char *s) +{ + assert(StringEqual(CF_BOOL, "true,false,yes,no,on,off")); + assert(s != NULL); + + if (StringEqual(s, "false") + || StringEqual(s, "no") + || StringEqual(s, "off")) + { + return false; + } + // Unnecessary to check here because the default is true anyway: + // if (StringEqual(s, "true") + // || StringEqual(s, "yes") + // || StringEqual(s, "on")) + // { + // return true; + // } + + // Default to true to preserve old behavior: + return true; +} + +bool StringIsBoolean(const char *s) +{ + assert(StringEqual(CF_BOOL, "true,false,yes,no,on,off")); + assert(s != NULL); + + return (StringEqual(s, "true") + || StringEqual(s, "false") + || StringEqual(s, "yes") + || StringEqual(s, "no") + || StringEqual(s, "on") + || StringEqual(s, "off")); +} + +/****************************************************************************/ + +/** + * @NOTE parameter #s might be NULL. It's already used by design like that, + * for example parsing inexistent attributes in GetVolumeConstraints(). + * + * @TODO see DoubleFromString(): return bool, have a value_out parameter, and + * kill CF_NOINT (goes deep!) + */ +long IntFromString(const char *s) +{ + long long ll = CF_NOINT; + char quantifier, remainder; + + if (s == NULL) + { + return CF_NOINT; + } + if (strcmp(s, "inf") == 0) + { + return (long) CF_INFINITY; + } + if (strcmp(s, "now") == 0) + { + return (long) CFSTARTTIME; + } + + int ret = sscanf(s, "%lld%c %c", &ll, &quantifier, &remainder); + + if (ret < 1 || ll == CF_NOINT) + { + if (strchr(s, '$') != NULL) /* don't log error, might converge */ + { + Log(LOG_LEVEL_VERBOSE, + "Ignoring failed to parse integer '%s'" + " because of possibly unexpanded variable", s); + } + else + { + Log(LOG_LEVEL_ERR, "Failed to parse integer number: %s", s); + } + } + else if (ret == 3) + { + ll = CF_NOINT; + if (quantifier == '$') /* don't log error, might converge */ + { + Log(LOG_LEVEL_VERBOSE, + "Ignoring failed to parse integer '%s'" + " because of possibly unexpanded variable", s); + } + else + { + Log(LOG_LEVEL_ERR, + "Anomalous ending '%c%c' while parsing integer number: %s", + quantifier, remainder, s); + } + } + else if (ret == 1) /* no quantifier */ + { + /* nop */ + } + else + { + assert(ret == 2); + + switch (quantifier) + { + case 'k': + ll *= 1000; + break; + case 'K': + ll *= 1024; + break; + case 'm': + ll *= 1000 * 1000; + break; + case 'M': + ll *= 1024 * 1024; + break; + case 'g': + ll *= 1000 * 1000 * 1000; + break; + case 'G': + ll *= 1024 * 1024 * 1024; + break; + case '%': + if ((ll < 0) || (ll > 100)) + { + Log(LOG_LEVEL_ERR, "Percentage out of range: %lld", ll); + return CF_NOINT; + } + else + { + /* Represent percentages internally as negative numbers */ + /* TODO fix? */ + ll *= -1; + } + break; + + case ' ': + break; + + default: + Log(LOG_LEVEL_VERBOSE, + "Ignoring bad quantifier '%c' in integer: %s", + quantifier, s); + break; + } + } + + /* TODO Use strtol() instead of scanf(), it properly checks for overflow + * but it is prone to coding errors, so even better bring OpenBSD's + * strtonum() for proper conversions. */ + + if (ll < LONG_MIN) + { + Log(LOG_LEVEL_VERBOSE, + "Number '%s' underflows a long int, truncating to %ld", + s, LONG_MIN); + return LONG_MIN; + } + else if (ll > LONG_MAX) + { + Log(LOG_LEVEL_VERBOSE, + "Number '%s' overflows a long int, truncating to %ld", + s, LONG_MAX); + return LONG_MAX; + } + + return (long) ll; +} + +Interval IntervalFromString(const char *string) +{ + static const char *const INTERVAL_TYPES[] = { "hourly", "daily", NULL }; + + return FindTypeInArray(INTERVAL_TYPES, string, INTERVAL_NONE, INTERVAL_NONE); +} + +bool DoubleFromString(const char *s, double *value_out) +{ + double d; + char quantifier, remainder; + + assert(s != NULL); + assert(value_out != NULL); + + int ret = sscanf(s, "%lf%c %c", &d, &quantifier, &remainder); + + if (ret < 1) + { + Log(LOG_LEVEL_ERR, "Failed to parse real number: %s", s); + return false; + } + else if (ret == 3) /* non-space remainder */ + { + Log(LOG_LEVEL_ERR, + "Anomalous ending '%c%c' while parsing real number: %s", + quantifier, remainder, s); + return false; + } + else if (ret == 1) /* no quantifier char */ + { + /* nop */ + } + else /* got quantifier */ + { + assert(ret == 2); + + switch (quantifier) + { + case 'k': + d *= 1000; + break; + case 'K': + d *= 1024; + break; + case 'm': + d *= 1000 * 1000; + break; + case 'M': + d *= 1024 * 1024; + break; + case 'g': + d *= 1000 * 1000 * 1000; + break; + case 'G': + d *= 1024 * 1024 * 1024; + break; + case '%': + if ((d < 0) || (d > 100)) + { + Log(LOG_LEVEL_ERR, "Percentage out of range: %.2lf", d); + return false; + } + else + { + /* Represent percentages internally as negative numbers */ + /* TODO fix? */ + d *= -1; + } + break; + + case ' ': + break; + + default: + Log(LOG_LEVEL_VERBOSE, + "Ignoring bad quantifier '%c' in real number: %s", + quantifier, s); + break; + } + } + + assert(ret == 1 || ret == 2); + + *value_out = d; + return true; +} + +/****************************************************************************/ + +/** + * @return true if successful + */ +bool IntegerRangeFromString(const char *intrange, long *min_out, long *max_out) +{ + Item *split; + long lmax = CF_LOWINIT, lmin = CF_HIGHINIT; + +/* Numeric types are registered by range separated by comma str "min,max" */ + + if (intrange == NULL) + { + *min_out = CF_NOINT; + *max_out = CF_NOINT; + return true; + } + + split = SplitString(intrange, ','); + + sscanf(split->name, "%ld", &lmin); + + if (strcmp(split->next->name, "inf") == 0) + { + lmax = (long) CF_INFINITY; + } + else + { + sscanf(split->next->name, "%ld", &lmax); + } + + DeleteItemList(split); + + if ((lmin == CF_HIGHINIT) || (lmax == CF_LOWINIT)) + { + return false; + } + + *min_out = lmin; + *max_out = lmax; + return true; +} + +AclMethod AclMethodFromString(const char *string) +{ + static const char *const ACL_METHOD_TYPES[] = + { "append", "overwrite", NULL }; + + return FindTypeInArray(ACL_METHOD_TYPES, string, ACL_METHOD_NONE, ACL_METHOD_NONE); +} + +AclType AclTypeFromString(const char *string) +{ + static const char *const ACL_TYPES[]= + { "generic", "posix", "ntfs", NULL }; + + return FindTypeInArray(ACL_TYPES, string, ACL_TYPE_NONE, ACL_TYPE_NONE); +} + +/* For the deprecated attribute acl_directory_inherit. */ +AclDefault AclInheritanceFromString(const char *string) +{ + static const char *const ACL_INHERIT_TYPES[5] = + { "nochange", "specify", "parent", "clear", NULL }; + + return FindTypeInArray(ACL_INHERIT_TYPES, string, ACL_DEFAULT_NONE, ACL_DEFAULT_NONE); +} + +AclDefault AclDefaultFromString(const char *string) +{ + static const char *const ACL_DEFAULT_TYPES[5] = + { "nochange", "specify", "access", "clear", NULL }; + + return FindTypeInArray(ACL_DEFAULT_TYPES, string, ACL_DEFAULT_NONE, ACL_DEFAULT_NONE); +} + +AclInherit AclInheritFromString(const char *string) +{ + char *start, *end; + char *options = CF_BOOL ",nochange"; + int i; + int size; + + if (string == NULL) + { + return ACL_INHERIT_NOCHANGE; + } + + start = options; + size = strlen(string); + for (i = 0;; i++) + { + end = strchr(start, ','); + if (end == NULL) + { + break; + } + if (size == end - start && strncmp(string, start, end - start) == 0) + { + // Even i is true, odd i is false (from CF_BOOL). + return (i & 1) ? ACL_INHERIT_FALSE : ACL_INHERIT_TRUE; + } + start = end + 1; + } + return ACL_INHERIT_NOCHANGE; +} + +const char *DataTypeShortToType(char *short_type) +{ + assert(short_type); + + if(strcmp(short_type, "s") == 0) + { + return "string"; + } + + if(strcmp(short_type, "i") == 0) + { + return "int"; + } + + if(strcmp(short_type, "r") == 0) + { + return "real"; + } + + if(strcmp(short_type, "m") == 0) + { + return "menu"; + } + + if(strcmp(short_type, "sl") == 0) + { + return "string list"; + } + + if(strcmp(short_type, "il") == 0) + { + return "int list"; + } + + if(strcmp(short_type, "rl") == 0) + { + return "real list"; + } + + if(strcmp(short_type, "ml") == 0) + { + return "menu list"; + } + + return "unknown type"; +} + +bool DataTypeIsIterable(DataType t) +{ + if (t == CF_DATA_TYPE_STRING_LIST || + t == CF_DATA_TYPE_INT_LIST || + t == CF_DATA_TYPE_REAL_LIST || + t == CF_DATA_TYPE_CONTAINER) + { + return true; + } + else + { + return false; + } +} + +bool CoarseLaterThan(const char *bigger, const char *smaller) +{ + char month_small[CF_SMALLBUF]; + char month_big[CF_SMALLBUF]; + int m_small, day_small, year_small, m_big, year_big, day_big; + + sscanf(smaller, "%d %s %d", &day_small, month_small, &year_small); + sscanf(bigger, "%d %s %d", &day_big, month_big, &year_big); + + if (year_big < year_small) + { + return false; + } + + m_small = Month2Int(month_small); + m_big = Month2Int(month_big); + + if (m_big < m_small) + { + return false; + } + + if (day_big < day_small && m_big == m_small && year_big == year_small) + { + return false; + } + + return true; +} + +int Month2Int(const char *string) +{ + int i; + + if (string == NULL) + { + return -1; + } + + for (i = 0; i < 12; i++) + { + if (strncmp(MONTH_TEXT[i], string, strlen(string)) == 0) + { + return i + 1; + break; + } + } + + return -1; +} + +/*********************************************************************/ + +void TimeToDateStr(time_t t, char *outStr, int outStrSz) +/** + * Formats a time as "30 Sep 2010". + */ +{ + char month[CF_SMALLBUF], day[CF_SMALLBUF], year[CF_SMALLBUF]; + char tmp[CF_SMALLBUF]; + + snprintf(tmp, sizeof(tmp), "%s", ctime(&t)); + sscanf(tmp, "%*s %5s %3s %*s %5s", month, day, year); + snprintf(outStr, outStrSz, "%s %s %s", day, month, year); +} + +/*********************************************************************/ + +/** + * Copy first argument of #src to #dst. Argument is delimited either by double + * quotes if first character is double quotes, or by space. + * + * @note Thread-safe version of CommandArg0(). + * + * @return The length of #dst, or (size_t) -1 in case of overflow. + */ +size_t CommandArg0_bound(char *dst, const char *src, size_t dst_size) +{ + const char *start; + char end_delimiter; + + if(src[0] == '\"') + { + start = &src[1]; + end_delimiter = '\"'; + } + else + { + start = src; + end_delimiter = ' '; + } + + char *end = strchrnul(start, end_delimiter); + size_t len = end - start; + if (len < dst_size) + { + memcpy(dst, start, len); + dst[len] = '\0'; + return len; + } + else + { + /* Check return value of CommandArg0_bound! If -1, the user should + * never use dst, but just in case we are writing a bogus string. */ + const char trap[] = "BUG: COMMANDARG0_TOO_LONG"; + strlcpy(dst, trap, dst_size); + return (size_t) -1; + } +} + +const char *CommandArg0(const char *execstr) +/** + * WARNING: Not thread-safe. + **/ +{ + static char arg[CF_BUFSIZE]; /* GLOBAL_R, no initialization needed */ + + const char *start; + char end_delimiter; + + if(execstr[0] == '\"') + { + start = execstr + 1; + end_delimiter = '\"'; + } + else + { + start = execstr; + end_delimiter = ' '; + } + + strlcpy(arg, start, sizeof(arg)); + + char *cut = strchr(arg, end_delimiter); + + if(cut) + { + *cut = '\0'; + } + + return arg; +} + +/*************************************************************/ + +void CommandPrefix(char *execstr, char *comm) +{ + char *sp; + + for (sp = execstr; (*sp != ' ') && (*sp != '\0'); sp++) + { + } + + if (sp - 10 >= execstr) + { + sp -= 10; /* copy 15 most relevant characters of command */ + } + else + { + sp = execstr; + } + + memset(comm, 0, 20); + strncpy(comm, sp, 15); +} + +/*******************************************************************/ + +bool IsRealNumber(const char *s) +{ + double d; + int ret = sscanf(s, "%lf", &d); + + if (ret != 1) + { + return false; + } + + return true; +} + +#ifndef __MINGW32__ + +/*******************************************************************/ +/* Unix-only functions */ +/*******************************************************************/ + +/****************************************************************************/ +/* Rlist to Uid/Gid lists */ +/****************************************************************************/ + +void UidListDestroy(UidList *uids) +{ + + while (uids) + { + UidList *ulp = uids; + uids = uids->next; + free(ulp->uidname); + free(ulp); + } +} + +static void AddSimpleUidItem(UidList ** uidlist, uid_t uid, char *uidname) +{ + UidList *ulp = xcalloc(1, sizeof(UidList)); + + ulp->uid = uid; + + if (uid == CF_UNKNOWN_OWNER) /* unknown user */ + { + ulp->uidname = xstrdup(uidname); + } + + if (*uidlist == NULL) + { + *uidlist = ulp; + } + else /* Hang new element off end of list: */ + { + UidList *u = *uidlist; + + while (u->next != NULL) + { + u = u->next; + } + u->next = ulp; + } +} + +UidList *Rlist2UidList(Rlist *uidnames, const Promise *pp) +{ + UidList *uidlist = NULL; + Rlist *rp; + char username[CF_MAXVARSIZE]; + uid_t uid; + + for (rp = uidnames; rp != NULL; rp = rp->next) + { + username[0] = '\0'; + uid = Str2Uid(RlistScalarValue(rp), username, pp); + AddSimpleUidItem(&uidlist, uid, username); + } + + if (uidlist == NULL) + { + AddSimpleUidItem(&uidlist, CF_SAME_OWNER, NULL); + } + + return uidlist; +} + +/*********************************************************************/ + +void GidListDestroy(GidList *gids) +{ + while (gids) + { + GidList *glp = gids; + gids = gids->next; + free(glp->gidname); + free(glp); + } +} + +static void AddSimpleGidItem(GidList ** gidlist, gid_t gid, char *gidname) +{ + GidList *glp = xcalloc(1, sizeof(GidList)); + + glp->gid = gid; + + if (gid == CF_UNKNOWN_GROUP) /* unknown group */ + { + glp->gidname = xstrdup(gidname); + } + + if (*gidlist == NULL) + { + *gidlist = glp; + } + else /* Hang new element off end of list: */ + { + GidList *g = *gidlist; + while (g->next != NULL) + { + g = g->next; + } + g->next = glp; + } +} + +GidList *Rlist2GidList(Rlist *gidnames, const Promise *pp) +{ + GidList *gidlist = NULL; + Rlist *rp; + char groupname[CF_MAXVARSIZE]; + gid_t gid; + + for (rp = gidnames; rp != NULL; rp = rp->next) + { + groupname[0] = '\0'; + gid = Str2Gid(RlistScalarValue(rp), groupname, pp); + AddSimpleGidItem(&gidlist, gid, groupname); + } + + if (gidlist == NULL) + { + AddSimpleGidItem(&gidlist, CF_SAME_GROUP, NULL); + } + + return gidlist; +} + +/*********************************************************************/ + +uid_t Str2Uid(const char *uidbuff, char *usercopy, const Promise *pp) +{ + if (StringEqual(uidbuff, "*")) + { + return CF_SAME_OWNER; /* signals wildcard */ + } + + if (StringIsNumeric(uidbuff)) + { +#ifdef __hpux + /* sscanf("0", "%ju", &tmp) with 'uintmax_t' fails, 'int' and '%d' work */ + int tmp; + NDEBUG_UNUSED int ret = sscanf(uidbuff, "%d", &tmp); + assert(ret == 1); +#else + uintmax_t tmp; + NDEBUG_UNUSED int ret = sscanf(uidbuff, "%ju", &tmp); + assert(ret == 1); +#endif + return (uid_t) tmp; + } + + uid_t uid = CF_UNKNOWN_OWNER; + if (uidbuff[0] == '+') + { + if (uidbuff[1] == '@') + { + uidbuff++; + } + + char *machine = NULL; + char *user = NULL; + char *domain = NULL; + setnetgrent(uidbuff); + while ((uid == CF_UNKNOWN_OWNER) && (getnetgrent(&machine, &user, &domain) == 1)) + { + if (user != NULL) + { + if (GetUserID(user, &uid, LOG_LEVEL_INFO)) + { + if (usercopy != NULL) + { + strcpy(usercopy, user); + } + } + else + { + if (pp != NULL) + { + PromiseRef(LOG_LEVEL_INFO, pp); + } + } + } + } + endnetgrent(); + + return uid; + } + + if (GetUserID(uidbuff, &uid, LOG_LEVEL_INFO)) + { + if (usercopy != NULL) + { + strcpy(usercopy, uidbuff); + } + } + else + { + if (pp) + { + PromiseRef(LOG_LEVEL_INFO, pp); + } + } + + return uid; +} + +/*********************************************************************/ + +gid_t Str2Gid(const char *gidbuff, char *groupcopy, const Promise *pp) +{ + if (StringEqual(gidbuff, "*")) + { + return CF_SAME_GROUP; /* signals wildcard */ + } + + if (StringIsNumeric(gidbuff)) + { +#ifdef __hpux + /* sscanf("0", "%ju", &tmp) with 'uintmax_t' fails, 'int' and '%d' work */ + int tmp; + NDEBUG_UNUSED int ret = sscanf(gidbuff, "%d", &tmp); + assert(ret == 1); +#else + uintmax_t tmp; + NDEBUG_UNUSED int ret = sscanf(gidbuff, "%ju", &tmp); + assert(ret == 1); +#endif + return (gid_t) tmp; + } + + gid_t gid = CF_UNKNOWN_GROUP; + if (GetGroupID(gidbuff, &gid, LOG_LEVEL_INFO)) + { + if (groupcopy != NULL) + { + strcpy(groupcopy, gidbuff); + } + } + else + { + if (pp) + { + PromiseRef(LOG_LEVEL_INFO, pp); + } + } + + return gid; +} + +#else /* !__MINGW32__ */ + +/* Release everything NovaWin_Rlist2SidList() allocates: */ +void UidListDestroy(UidList *uids) +{ + while (uids) + { + UidList *ulp = uids; + uids = uids->next; + free(ulp); + } +} + +void GidListDestroy(ARG_UNUSED GidList *gids) +{ + assert(gids == NULL); +} + +#endif diff --git a/libpromises/conversion.h b/libpromises/conversion.h new file mode 100644 index 0000000000..a8fbf1821c --- /dev/null +++ b/libpromises/conversion.h @@ -0,0 +1,90 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CONVERSION_H +#define CFENGINE_CONVERSION_H + +#include + +// Type-String conversion +MeasurePolicy MeasurePolicyFromString(const char *s); +EnvironmentState EnvironmentStateFromString(const char *s); +InsertMatchType InsertMatchTypeFromString(const char *s); +Interval IntervalFromString(const char *s); +DatabaseType DatabaseTypeFromString(const char *s); +UserState UserStateFromString(const char *s); +PasswordFormat PasswordFormatFromString(const char *s); +ContextScope ContextScopeFromString(const char *scope_str); +FileComparator FileComparatorFromString(const char *s); +FileLinkType FileLinkTypeFromString(const char *s); +DataType DataTypeFromString(const char *name); +const char *DataTypeToString(DataType dtype); +PackageActionPolicy PackageActionPolicyFromString(const char *s); +PackageVersionComparator PackageVersionComparatorFromString(const char *s); +PackageAction PackageActionFromString(const char *s); +NewPackageAction GetNewPackagePolicy(const char *s, const char **action_types); +AclMethod AclMethodFromString(const char *string); +AclType AclTypeFromString(const char *string); +AclDefault AclDefaultFromString(const char *string); +AclInherit AclInheritFromString(const char *string); +int SignalFromString(const char *s); +int SyslogPriorityFromString(const char *s); +ShellType ShellTypeFromString(const char *s); + +// Date/Time conversion +void TimeToDateStr(time_t t, char *outStr, int outStrSz); +int Month2Int(const char *string); + +// Evalaution conversion +bool BooleanFromString(const char *val); +bool StringIsBoolean(const char *val); +long IntFromString(const char *s); +bool DoubleFromString(const char *s, double *value_out); +bool IntegerRangeFromString(const char *intrange, long *min_out, long *max_out); +bool IsRealNumber(const char *s); + + +// Misc. +char *Rlist2String(Rlist *list, char *sep); // TODO: Yet another Rlist serialization scheme.. Found 5 so far. +DataType ConstraintSyntaxGetDataType(const ConstraintSyntax *body_syntax, const char *lval); +const char *MapAddress(const char *addr); +const char *CommandArg0(const char *execstr); +size_t CommandArg0_bound(char *dst, const char *src, size_t dst_size); +void CommandPrefix(char *execstr, char *comm); +const char *DataTypeShortToType(char *short_type); +bool DataTypeIsIterable(DataType t); + +bool CoarseLaterThan(const char *key, const char *from); +int FindTypeInArray(const char *const haystack[], const char *needle, int default_value, int null_value); + +void UidListDestroy(UidList *uids); +void GidListDestroy(GidList *gids); +UidList *Rlist2UidList(Rlist *uidnames, const Promise *pp); +GidList *Rlist2GidList(Rlist *gidnames, const Promise *pp); +#ifndef __MINGW32__ +uid_t Str2Uid(const char *uidbuff, char *copy, const Promise *pp); +gid_t Str2Gid(const char *gidbuff, char *copy, const Promise *pp); +#endif /* !__MINGW32__ */ + +#endif diff --git a/libpromises/crypto.c b/libpromises/crypto.c new file mode 100644 index 0000000000..8a2651559d --- /dev/null +++ b/libpromises/crypto.c @@ -0,0 +1,916 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include /* ERR_* */ +#include /* RAND_* */ +#include /* BN_* */ + +#if OPENSSL_VERSION_NUMBER > 0x30000000 +#include /* OSSL_PROVIDER_* */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* UnexpectedError,ProgrammingError */ +#include + +#ifdef DARWIN +// On Mac OSX 10.7 and later, majority of functions in /usr/include/openssl/crypto.h +// are deprecated. No known replacement, so shutting up compiler warnings +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100000 +void CRYPTO_set_id_callback(unsigned long (*func)(void)); +#endif + +static void RandomSeed(void); +static void SetupOpenSSLThreadLocks(void); +static void CleanupOpenSSLThreadLocks(void); + +/* TODO move crypto.[ch] to libutils. Will need to remove all manipulation of + * lastseen db. */ + +static bool crypto_initialized = false; /* GLOBAL_X */ + +#if OPENSSL_VERSION_NUMBER > 0x30000000 +static OSSL_PROVIDER *legacy_provider = NULL; +static OSSL_PROVIDER *default_provider = NULL; +#endif + +const char *CryptoLastErrorString() +{ + const char *errmsg = ERR_reason_error_string(ERR_get_error()); + return (errmsg != NULL) ? errmsg : "no error message"; +} + +void CryptoInitialize() +{ + if (!crypto_initialized) + { + SetupOpenSSLThreadLocks(); + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_digests(); + ERR_load_crypto_strings(); + +#if OPENSSL_VERSION_NUMBER > 0x30000000 + /* We need Blowfish for legacy encrypted network stuff and in OpenSSL + * 3+, it's only available when the legacy provider is loaded. And we + * also need the default provider. */ + legacy_provider = OSSL_PROVIDER_load(NULL, "legacy"); + default_provider = OSSL_PROVIDER_load(NULL, "default"); +#endif + RandomSeed(); + + crypto_initialized = true; + } +} + +void CryptoDeInitialize() +{ + if (crypto_initialized) + { + char randfile[CF_BUFSIZE]; + snprintf(randfile, CF_BUFSIZE, "%s%crandseed", + GetWorkDir(), FILE_SEPARATOR); + + /* Only write out a seed if the file doesn't exist + * and we have enough entropy to do so. If RAND_write_File + * returns a bad value, delete the poor seed. + */ + if (access(randfile, R_OK) && errno == ENOENT && RAND_write_file(randfile) != 1024) + { + Log(LOG_LEVEL_WARNING, + "Could not write randomness to '%s'", randfile); + unlink(randfile); /* do not reuse entropy */ + } + + chmod(randfile, 0600); + EVP_cleanup(); + CleanupOpenSSLThreadLocks(); + ERR_free_strings(); + +#if OPENSSL_VERSION_NUMBER > 0x30000000 + if (legacy_provider != NULL) + { + OSSL_PROVIDER_unload(legacy_provider); + legacy_provider = NULL; + } + if (default_provider != NULL) + { + OSSL_PROVIDER_unload(default_provider); + default_provider = NULL; + } +#endif + + crypto_initialized = false; + } +} + +static void RandomSeed(void) +{ + /* 1. Seed the weak C PRNGs. */ + + /* Mix various stuff. */ + pid_t pid = getpid(); + size_t fqdn_len = strlen(VFQNAME) > 0 ? strlen(VFQNAME) : 1; + time_t start_time = CFSTARTTIME; + time_t now = time(NULL); + + srand((unsigned) pid * start_time ^ + (unsigned) fqdn_len * now); + srand48((long) pid * start_time ^ + (long) fqdn_len * now); + + /* 2. Seed the strong OpenSSL PRNG. */ + +#ifndef __MINGW32__ + RAND_poll(); /* windows may hang */ +#else + RAND_screen(); /* noop unless openssl is very old */ +#endif + + if (RAND_status() != 1) + { + /* randseed file is written on deinitialization of crypto system */ + char randfile[CF_BUFSIZE]; + snprintf(randfile, CF_BUFSIZE, "%s%crandseed", + GetWorkDir(), FILE_SEPARATOR); + Log(LOG_LEVEL_VERBOSE, "Looking for a source of entropy in '%s'", + randfile); + + if (RAND_load_file(randfile, -1) != 1024) + { + Log(LOG_LEVEL_CRIT, + "Could not read randomness from '%s'", randfile); + unlink(randfile); /* kill randseed if error reading it */ + } + + /* If we've used the random seed, then delete */ + unlink(randfile); + } +} + +/* PEM functions need the const cast away, but hopefully the default + * call-back doesn't actually modify its user-data. */ +static const char priv_passphrase[] = PRIVKEY_PASSPHRASE; + +/** + * @param[in] priv_key_path path to the private key to use (%NULL to use the default) + * @param[in] pub_key_path path to the private key to use (%NULL to use the default) + * @param[out] priv_key a place to store the loaded private key (or %NULL to + * use the global PRIVKEY variable) + * @param[out] pub_key a place to store the loaded public key (or %NULL to + * use the global PUBKEY variable) + * @return true the error is not so severe that we must stop + */ +bool LoadSecretKeys(const char *const priv_key_path, + const char *const pub_key_path, + RSA **priv_key, RSA **pub_key) +{ + { + char *privkeyfile = NULL; + if (priv_key_path == NULL) + { + privkeyfile = PrivateKeyFile(GetWorkDir()); + } + FILE *fp = safe_fopen(privkeyfile != NULL ? privkeyfile : priv_key_path, "r"); + if (!fp) + { + /* VERBOSE in case it's a custom, local-only installation. */ + Log(LOG_LEVEL_VERBOSE, + "Couldn't find a private key at '%s', use cf-key to get one. (fopen: %s)", + privkeyfile != NULL ? privkeyfile : priv_key_path, GetErrorStr()); + free(privkeyfile); + return false; + } + + if (priv_key == NULL) + { + /* if no place to store the private key was specified, use the + * global variable PRIVKEY */ + priv_key = &PRIVKEY; + } + if (*priv_key != NULL) + { + DESTROY_AND_NULL(RSA_free, *priv_key); + } + + // Can read both encrypted (old) and unencrypted(new) key files: + *priv_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, (void*) priv_passphrase); + if (*priv_key == NULL) + { + Log(LOG_LEVEL_ERR, + "Error reading private key. (PEM_read_RSAPrivateKey: %s)", + CryptoLastErrorString()); + *priv_key = NULL; + fclose(fp); + return false; + } + + fclose(fp); + Log(LOG_LEVEL_VERBOSE, "Loaded private key at '%s'", privkeyfile); + free(privkeyfile); + } + + { + char *pubkeyfile = NULL; + if (pub_key_path == NULL) + { + pubkeyfile = PublicKeyFile(GetWorkDir()); + } + FILE *fp = safe_fopen(pubkeyfile != NULL ? pubkeyfile : pub_key_path, "r"); + if (!fp) + { + /* VERBOSE in case it's a custom, local-only installation. */ + Log(LOG_LEVEL_VERBOSE, + "Couldn't find a public key at '%s', use cf-key to get one (fopen: %s)", + pubkeyfile != NULL ? pubkeyfile : pub_key_path, GetErrorStr()); + free(pubkeyfile); + return false; + } + + if (pub_key == NULL) + { + /* if no place to store the public key was specified, use the + * global variable PUBKEY */ + pub_key = &PUBKEY; + } + if (*pub_key != NULL) + { + DESTROY_AND_NULL(RSA_free, *pub_key); + } + *pub_key = PEM_read_RSAPublicKey(fp, NULL, NULL, (void*) priv_passphrase); + if (*pub_key == NULL) + { + Log(LOG_LEVEL_ERR, + "Error reading public key at '%s'. (PEM_read_RSAPublicKey: %s)", + pubkeyfile, CryptoLastErrorString()); + fclose(fp); + free(pubkeyfile); + return false; + } + + Log(LOG_LEVEL_VERBOSE, "Loaded public key '%s'", pubkeyfile); + free(pubkeyfile); + fclose(fp); + } + + if (*pub_key != NULL) + { + const BIGNUM *n, *e; + RSA_get0_key(*pub_key, &n, &e, NULL); + if ((BN_num_bits(e) < 2) || (!BN_is_odd(e))) + { + Log(LOG_LEVEL_ERR, "The public key RSA exponent is too small or not odd"); + return false; + } + } + + return true; +} + +void PolicyHubUpdateKeys(const char *policy_server) +{ + if (GetAmPolicyHub() && PUBKEY != NULL) + { + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + const char* const workdir = GetWorkDir(); + + char dst_public_key_filename[CF_BUFSIZE] = ""; + { + char buffer[CF_HOSTKEY_STRING_SIZE]; + HashPubKey(PUBKEY, digest, CF_DEFAULT_DIGEST); + snprintf(dst_public_key_filename, sizeof(dst_public_key_filename), + "%s/ppkeys/%s-%s.pub", + workdir, "root", + HashPrintSafe(buffer, sizeof(buffer), digest, + CF_DEFAULT_DIGEST, true)); + MapName(dst_public_key_filename); + } + + struct stat sb; + if ((stat(dst_public_key_filename, &sb) == -1)) + { + char src_public_key_filename[CF_BUFSIZE] = ""; + snprintf(src_public_key_filename, CF_MAXVARSIZE, "%s/ppkeys/localhost.pub", workdir); + MapName(src_public_key_filename); + + // copy localhost.pub to root-HASH.pub on policy server + if (!LinkOrCopy(src_public_key_filename, dst_public_key_filename, false)) + { + Log(LOG_LEVEL_ERR, "Unable to copy policy server's own public key from '%s' to '%s'", src_public_key_filename, dst_public_key_filename); + } + + if (policy_server) + { + LastSaw(policy_server, digest, LAST_SEEN_ROLE_CONNECT); + } + } + } +} + +/*********************************************************************/ + +/** + * @brief Search for a key given an IP address, by getting the + * key hash value from lastseen db. + * @return NULL if the key was not found in any form. + */ +RSA *HavePublicKeyByIP(const char *username, const char *ipaddress) +{ + char hash[CF_HOSTKEY_STRING_SIZE]; + + /* Get the key hash for that address from lastseen db. */ + bool found = Address2Hostkey(hash, sizeof(hash), ipaddress); + + /* If not found, by passing "" as digest, we effectively look only for + * the old-style key file, e.g. root-1.2.3.4.pub. */ + return HavePublicKey(username, ipaddress, + found ? hash : ""); +} + +static const char *const pub_passphrase = "public"; + +/** + * @brief Search for a key: + * 1. username-hash.pub + * 2. username-ip.pub + * @return NULL if key not found in any form + */ +RSA *HavePublicKey(const char *username, const char *ipaddress, const char *digest) +{ + char keyname[CF_MAXVARSIZE], newname[CF_BUFSIZE], oldname[CF_BUFSIZE]; + struct stat statbuf; + RSA *newkey = NULL; + const char* const workdir = GetWorkDir(); + + snprintf(keyname, CF_MAXVARSIZE, "%s-%s", username, digest); + + snprintf(newname, CF_BUFSIZE, "%s/ppkeys/%s.pub", workdir, keyname); + MapName(newname); + + if (stat(newname, &statbuf) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Did not find new key format '%s'", newname); + + snprintf(oldname, CF_BUFSIZE, "%s/ppkeys/%s-%s.pub", + workdir, username, ipaddress); + MapName(oldname); + Log(LOG_LEVEL_VERBOSE, "Trying old style '%s'", oldname); + + if (stat(oldname, &statbuf) == -1) + { + Log(LOG_LEVEL_DEBUG, "Did not have old-style key '%s'", oldname); + return NULL; + } + + if (strlen(digest) > 0) + { + Log(LOG_LEVEL_INFO, "Renaming old key from '%s' to '%s'", oldname, newname); + + if (rename(oldname, newname) != 0) + { + Log(LOG_LEVEL_ERR, "Could not rename from old key format '%s' to new '%s'. (rename: %s)", oldname, newname, GetErrorStr()); + } + } + else + { + /* We don't know the digest (e.g. because we are a client and have + no lastseen-map yet), so we're using old file format + (root-IP.pub). */ + Log(LOG_LEVEL_VERBOSE, + "We have no digest yet, using old keyfile name: %s", + oldname); + snprintf(newname, sizeof(newname), "%s", oldname); + } + } + + FILE *fp = safe_fopen(newname, "r"); + if (fp == NULL) + { + Log(LOG_LEVEL_ERR, "Couldn't open public key file '%s' (fopen: %s)", + newname, GetErrorStr()); + return NULL; + } + + if ((newkey = PEM_read_RSAPublicKey(fp, NULL, NULL, + (void *)pub_passphrase)) == NULL) + { + Log(LOG_LEVEL_ERR, + "Error reading public key from '%s' (PEM_read_RSAPublicKey: %s)", + newname, CryptoLastErrorString()); + fclose(fp); + return NULL; + } + + fclose(fp); + + { + const BIGNUM *n, *e; + RSA_get0_key(newkey, &n, &e, NULL); + if ((BN_num_bits(e) < 2) || (!BN_is_odd(e))) + { + Log(LOG_LEVEL_ERR, "RSA Exponent too small or not odd for key: %s", + newname); + RSA_free(newkey); + return NULL; + } + } + + return newkey; +} + +/*********************************************************************/ + +bool SavePublicKey(const char *user, const char *digest, const RSA *key) +{ + char keyname[CF_MAXVARSIZE], filename[CF_BUFSIZE]; + struct stat statbuf; + int ret; + + ret = snprintf(keyname, sizeof(keyname), "%s-%s", user, digest); + if (ret < 0) + { + Log(LOG_LEVEL_ERR, "snprintf failed: %s", GetErrorStr()); + return false; + } + else if ((unsigned long) ret >= sizeof(keyname)) + { + Log(LOG_LEVEL_ERR, "USERNAME-KEY (%s-%s) string too long!", + user, digest); + return false; + } + + ret = snprintf(filename, sizeof(filename), "%s/ppkeys/%s.pub", + GetWorkDir(), keyname); + if (ret < 0) + { + Log(LOG_LEVEL_ERR, "snprintf failed: %s", GetErrorStr()); + return false; + } + else if ((unsigned long) ret >= sizeof(filename)) + { + Log(LOG_LEVEL_ERR, "Filename too long!"); + return false; + } + + MapName(filename); + if (stat(filename, &statbuf) != -1) + { + Log(LOG_LEVEL_VERBOSE, + "Public key file '%s' already exists, not rewriting", + filename); + return true; + } + + Log(LOG_LEVEL_VERBOSE, "Saving public key to file '%s'", filename); + + FILE *fp = safe_fopen_create_perms(filename, "w", CF_PERMS_DEFAULT); + if (fp == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to write a public key '%s'. (fopen: %s)", filename, GetErrorStr()); + return false; + } + + if (!PEM_write_RSAPublicKey(fp, key)) + { + Log(LOG_LEVEL_ERR, + "Error saving public key to '%s'. (PEM_write_RSAPublicKey: %s)", + filename, CryptoLastErrorString()); + fclose(fp); + return false; + } + + fclose(fp); + return true; +} + +RSA *LoadPublicKey(const char *filename) +{ + FILE *fp; + RSA *key; + const BIGNUM *n, *e; + + fp = safe_fopen(filename, "r"); + if (fp == NULL) + { + Log(LOG_LEVEL_ERR, "Cannot open public key file '%s' (fopen: %s)", filename, GetErrorStr()); + return NULL; + }; + + if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, + (void *)priv_passphrase)) == NULL) + { + Log(LOG_LEVEL_ERR, + "Error while reading public key '%s' (PEM_read_RSAPublicKey: %s)", + filename, + CryptoLastErrorString()); + fclose(fp); + return NULL; + }; + + fclose(fp); + + RSA_get0_key(key, &n, &e, NULL); + + if (BN_num_bits(e) < 2 || !BN_is_odd(e)) + { + Log(LOG_LEVEL_ERR, "Error while reading public key '%s' - RSA Exponent is too small or not odd. (BN_num_bits: %s)", + filename, GetErrorStr()); + return NULL; + }; + + return key; +} + +/** Return a string with the printed digest of the given key file, + or NULL if an error occurred. */ +char *LoadPubkeyDigest(const char *filename) +{ + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + RSA *key = NULL; + char *buffer = xmalloc(CF_HOSTKEY_STRING_SIZE); + + key = LoadPublicKey(filename); + if (key == NULL) + { + return NULL; + } + + HashPubKey(key, digest, CF_DEFAULT_DIGEST); + HashPrintSafe(buffer, CF_HOSTKEY_STRING_SIZE, + digest, CF_DEFAULT_DIGEST, true); + RSA_free(key); + return buffer; +} + +/** Return a string with the printed digest of the given key file. */ +char *GetPubkeyDigest(RSA *pubkey) +{ + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + char *buffer = xmalloc(CF_HOSTKEY_STRING_SIZE); + + HashPubKey(pubkey, digest, CF_DEFAULT_DIGEST); + HashPrintSafe(buffer, CF_HOSTKEY_STRING_SIZE, + digest, CF_DEFAULT_DIGEST, true); + return buffer; +} + + +/** + * Trust the given key. If #ipaddress is not NULL, then also + * update the "last seen" database. The IP address is required for + * trusting a server key (on the client); it is -currently- optional + * for trusting a client key (on the server). + */ +bool TrustKey(const char *filename, const char *ipaddress, const char *username) +{ + RSA* key; + char *digest; + + key = LoadPublicKey(filename); + if (key == NULL) + { + return false; + } + + digest = GetPubkeyDigest(key); + if (digest == NULL) + { + RSA_free(key); + return false; + } + + if (ipaddress != NULL) + { + Log(LOG_LEVEL_VERBOSE, + "Adding a CONNECT entry in lastseen db: IP '%s', key '%s'", + ipaddress, digest); + LastSaw1(ipaddress, digest, LAST_SEEN_ROLE_CONNECT); + } + + bool ret = SavePublicKey(username, digest, key); + RSA_free(key); + free(digest); + + return ret; +} + +int EncryptString(char *out, size_t out_size, const char *in, int plainlen, + char type, unsigned char *key) +{ + int cipherlen = 0, tmplen; + unsigned char iv[32] = + { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; + + if (key == NULL) + ProgrammingError("EncryptString: session key == NULL"); + + size_t max_ciphertext_size = CipherTextSizeMax(CfengineCipher(type), plainlen); + + if(max_ciphertext_size > out_size) + { + ProgrammingError("EncryptString: output buffer too small: max_ciphertext_size (%zd) > out_size (%zd)", + max_ciphertext_size, out_size); + } + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, CfengineCipher(type), NULL, key, iv); + + if (!EVP_EncryptUpdate(ctx, out, &cipherlen, in, plainlen)) + { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + + if (!EVP_EncryptFinal_ex(ctx, out + cipherlen, &tmplen)) + { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + + cipherlen += tmplen; + + if (cipherlen < 0) + { + ProgrammingError("EncryptString: chipherlen (%d) < 0", cipherlen); + } + else if ((size_t) cipherlen > max_ciphertext_size) + { + ProgrammingError("EncryptString: too large ciphertext written: cipherlen (%d) > max_ciphertext_size (%zd)", + cipherlen, max_ciphertext_size); + } + + EVP_CIPHER_CTX_free(ctx); + return cipherlen; +} + +size_t CipherBlockSizeBytes(const EVP_CIPHER *cipher) +{ + return EVP_CIPHER_block_size(cipher); +} + +size_t CipherTextSizeMax(const EVP_CIPHER* cipher, size_t plaintext_size) +{ + // see man EVP_DecryptUpdate() and EVP_DecryptFinal_ex() + size_t padding_size = (CipherBlockSizeBytes(cipher) * 2) - 1; + + // check for potential integer overflow, leave some buffer + if(plaintext_size > SIZE_MAX - padding_size) + { + ProgrammingError("CipherTextSizeMax: plaintext_size is too large (%zu)", + plaintext_size); + } + + return plaintext_size + padding_size; +} + +size_t PlainTextSizeMax(const EVP_CIPHER* cipher, size_t ciphertext_size) +{ + // see man EVP_DecryptUpdate() and EVP_DecryptFinal_ex() + size_t padding_size = (CipherBlockSizeBytes(cipher) * 2); + + // check for potential integer overflow, leave some buffer + if(ciphertext_size > SIZE_MAX - padding_size) + { + ProgrammingError("PlainTextSizeMax: ciphertext_size is too large (%zu)", + ciphertext_size); + } + + return ciphertext_size + padding_size; +} + +/*********************************************************************/ + +int DecryptString(char *out, size_t out_size, const char *in, int cipherlen, + char type, unsigned char *key) +{ + int plainlen = 0, tmplen; + unsigned char iv[32] = + { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; + + if (key == NULL) + ProgrammingError("DecryptString: session key == NULL"); + + size_t max_plaintext_size = PlainTextSizeMax(CfengineCipher(type), cipherlen); + + if(max_plaintext_size > out_size) + { + ProgrammingError("DecryptString: output buffer too small: max_plaintext_size (%zd) > out_size (%zd)", + max_plaintext_size, out_size); + } + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, CfengineCipher(type), NULL, key, iv); + + if (!EVP_DecryptUpdate(ctx, out, &plainlen, in, cipherlen)) + { + Log(LOG_LEVEL_ERR, "Failed to decrypt string"); + EVP_CIPHER_CTX_free(ctx); + return -1; + } + + if (!EVP_DecryptFinal_ex(ctx, out + plainlen, &tmplen)) + { + unsigned long err = ERR_get_error(); + + Log(LOG_LEVEL_ERR, "Failed to decrypt at final of cipher length %d. (EVP_DecryptFinal_ex: %s)", cipherlen, ERR_error_string(err, NULL)); + EVP_CIPHER_CTX_free(ctx); + return -1; + } + + plainlen += tmplen; + + if (plainlen < 0) + { + ProgrammingError("DecryptString: plainlen (%d) < 0", plainlen); + } + if ((size_t) plainlen > max_plaintext_size) + { + ProgrammingError("DecryptString: too large plaintext written: plainlen (%d) > max_plaintext_size (%zd)", + plainlen, max_plaintext_size); + } + + EVP_CIPHER_CTX_free(ctx); + return plainlen; +} + +/*********************************************************************/ + +void DebugBinOut(char *buffer, int len, char *comment) +{ + unsigned char *sp; + char buf[CF_BUFSIZE]; + char hexStr[3]; // one byte as hex + + if (len < 0) + { + Log(LOG_LEVEL_ERR, "Debug binary print negative len param (len = %d)", len); + } + else if ((unsigned long) len >= (sizeof(buf) / 2)) // hex uses two chars per byte + { + Log(LOG_LEVEL_DEBUG, "Debug binary print is too large (len = %d)", len); + return; + } + + memset(buf, 0, sizeof(buf)); + + for (sp = buffer; sp < (unsigned char *) (buffer + len); sp++) + { + xsnprintf(hexStr, sizeof(hexStr), "%2.2x", (int) *sp); + strcat(buf, hexStr); + } + + Log(LOG_LEVEL_VERBOSE, "BinaryBuffer, %d bytes, comment '%s', buffer '%s'", len, comment, buf); +} + +char *PublicKeyFile(const char *workdir) +{ + char *keyfile; + xasprintf(&keyfile, + "%s" FILE_SEPARATOR_STR "ppkeys" FILE_SEPARATOR_STR "localhost.pub", workdir); + return keyfile; +} + +char *PrivateKeyFile(const char *workdir) +{ + char *keyfile; + xasprintf(&keyfile, + "%s" FILE_SEPARATOR_STR "ppkeys" FILE_SEPARATOR_STR "localhost.priv", workdir); + return keyfile; +} + + +/********************************************************************* + * Functions for threadsafe OpenSSL usage * + * Only pthread support - we don't create threads with any other API * + *********************************************************************/ + +static pthread_mutex_t *cf_openssl_locks = NULL; + + +#ifndef __MINGW32__ +#if OPENSSL_VERSION_NUMBER < 0x10100000 +unsigned long ThreadId_callback(void) +{ + return (unsigned long) pthread_self(); +} +#endif +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + +static void OpenSSLLock_callback(int mode, int index, char *file, int line) +{ + if (mode & CRYPTO_LOCK) + { + int ret = pthread_mutex_lock(&(cf_openssl_locks[index])); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, + "OpenSSL locking failure at %s:%d! (pthread_mutex_lock: %s)", + file, line, GetErrorStrFromCode(ret)); + } + } + else + { + int ret = pthread_mutex_unlock(&(cf_openssl_locks[index])); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, + "OpenSSL locking failure at %s:%d! (pthread_mutex_unlock: %s)", + file, line, GetErrorStrFromCode(ret)); + } + } +} + +#endif // Callback only for openssl < 1.1.0 + +static void SetupOpenSSLThreadLocks(void) +{ + const int num_locks = CRYPTO_num_locks(); + assert(cf_openssl_locks == NULL); + cf_openssl_locks = xmalloc(num_locks * sizeof(*cf_openssl_locks)); + + for (int i = 0; i < num_locks; i++) + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + int ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, + "Failed to use error-checking mutexes for openssl," + " falling back to normal ones (pthread_mutexattr_settype: %s)", + GetErrorStrFromCode(ret)); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + } + ret = pthread_mutex_init(&cf_openssl_locks[i], &attr); + if (ret != 0) + { + Log(LOG_LEVEL_CRIT, + "Failed to use initialise mutexes for openssl" + " (pthread_mutex_init: %s)!", + GetErrorStrFromCode(ret)); + } + pthread_mutexattr_destroy(&attr); + } + +#ifndef __MINGW32__ +#if OPENSSL_VERSION_NUMBER < 0x10100000 + CRYPTO_set_id_callback((unsigned long (*)())ThreadId_callback); +#endif +#endif +#if OPENSSL_VERSION_NUMBER < 0x10100000 + CRYPTO_set_locking_callback((void (*)())OpenSSLLock_callback); +#endif +} + +static void CleanupOpenSSLThreadLocks(void) +{ + const int numLocks = CRYPTO_num_locks(); + CRYPTO_set_locking_callback(NULL); +#ifndef __MINGW32__ +#if OPENSSL_VERSION_NUMBER < 0x10100000 + CRYPTO_set_id_callback(NULL); +#endif +#endif + + for (int i = 0; i < numLocks; i++) + { + pthread_mutex_destroy(&(cf_openssl_locks[i])); + } + + free(cf_openssl_locks); + cf_openssl_locks = NULL; +} diff --git a/libpromises/crypto.h b/libpromises/crypto.h new file mode 100644 index 0000000000..74a8185414 --- /dev/null +++ b/libpromises/crypto.h @@ -0,0 +1,68 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_CRYPTO_H +#define CFENGINE_CRYPTO_H + +#include + +#include + +#include + +// This passphrase was used to encrypt private keys. +// We no longer encrypt new keys, but the passphrase is kept +// for backwards compatibility - old encrypted keys will still work. +#define PRIVKEY_PASSPHRASE "Cfengine passphrase" +#define PRIVKEY_PASSPHRASE_LEN 19 + +void CryptoInitialize(void); +void CryptoDeInitialize(void); + +const char *CryptoLastErrorString(void); +void DebugBinOut(char *buffer, int len, char *com); +bool LoadSecretKeys(const char *const priv_key_path, + const char *const pub_key_path, + RSA **priv_key, RSA **pub_key); +void PolicyHubUpdateKeys(const char *policy_server); +int EncryptString(char *out, size_t out_size, const char *in, int plainlen, + char type, unsigned char *key); +size_t CipherBlockSizeBytes(const EVP_CIPHER *cipher); +size_t CipherTextSizeMax(const EVP_CIPHER* cipher, size_t plaintext_size); +size_t PlainTextSizeMax(const EVP_CIPHER* cipher, size_t ciphertext_size); +int DecryptString(char *out, size_t out_size, const char *in, int cipherlen, + char type, unsigned char *key); +RSA *HavePublicKey(const char *username, const char *ipaddress, const char *digest); +RSA *HavePublicKeyByIP(const char *username, const char *ipaddress); +bool SavePublicKey(const char *username, const char *digest, const RSA *key); +RSA *LoadPublicKey(const char *filename); +char *LoadPubkeyDigest(const char *pubkey); +char *GetPubkeyDigest(RSA *pubkey); +bool TrustKey(const char *pubkey, const char *ipaddress, const char *username); + +char *PublicKeyFile(const char *workdir); +char *PrivateKeyFile(const char *workdir); +LogLevel CryptoGetMissingKeyLogLevel(void); + +#endif diff --git a/libpromises/dbm_api.c b/libpromises/dbm_api.c new file mode 100644 index 0000000000..168a7524ad --- /dev/null +++ b/libpromises/dbm_api.c @@ -0,0 +1,777 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#include /* ThreadLock */ +#include +#include +#include +#include +#include +#include +#include +#include +#include /* time() */ +#include + + +static bool DBPathLock(FileLock *lock, const char *filename); +static void DBPathUnLock(FileLock *lock); +static void DBPathMoveBroken(const char *filename); + +struct DBHandle_ +{ + /* Filename of database file */ + char *filename; + + /* Name of specific sub-db */ + char *subname; + + /* Actual database-specific data */ + DBPriv *priv; + + int refcount; + + /* This lock protects initialization of .priv element, and .refcount manipulation */ + pthread_mutex_t lock; + + /* Record when the DB was opened (to check if possible corruptions are + * already repaired) */ + time_t open_tstamp; + + /** + * @see FreezeDB() + */ + bool frozen; +}; + +struct DBCursor_ +{ + DBCursorPriv *cursor; +}; + +typedef struct dynamic_db_handles_ +{ + DBHandle *handle; + struct dynamic_db_handles_ *next; +} DynamicDBHandles; + +/******************************************************************************/ + +/* + * This lock protects on-demand initialization of db_handles[i].lock and + * db_handles[i].name. + */ +static pthread_mutex_t db_handles_lock = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; /* GLOBAL_T */ + +static DBHandle db_handles[dbid_max] = { { 0 } }; /* GLOBAL_X */ +static DynamicDBHandles *db_dynamic_handles; + +static pthread_once_t db_shutdown_once = PTHREAD_ONCE_INIT; /* GLOBAL_T */ + +/******************************************************************************/ + +// Only append to the end, keep in sync with dbid enum in dbm_api.h +static const char *const DB_PATHS_STATEDIR[] = { + [dbid_classes] = "cf_classes", + [dbid_variables] = "cf_variables", + [dbid_performance] = "performance", + [dbid_checksums] = "checksum_digests", + [dbid_filestats] = "stats", + [dbid_changes] = "cf_changes", + [dbid_observations] = "cf_observations", + [dbid_state] = "cf_state", + [dbid_lastseen] = "cf_lastseen", + [dbid_audit] = "cf_audit", + [dbid_locks] = "cf_lock", + [dbid_history] = "history", + [dbid_measure] = "nova_measures", + [dbid_static] = "nova_static", + [dbid_scalars] = "nova_pscalar", + [dbid_windows_registry] = "mswin", + [dbid_cache] = "nova_cache", + [dbid_license] = "nova_track", + [dbid_value] = "nova_value", + [dbid_agent_execution] = "nova_agent_execution", + [dbid_bundles] = "bundles", + [dbid_packages_installed] = "packages_installed", + [dbid_packages_updates] = "packages_updates", + [dbid_cookies] = "nova_cookies", +}; + +/* + These are the old (pre 3.7) paths in workdir, supported for installations that + still have them. We will never create a database here. NULL means that the + database was always in the state directory. +*/ +static const char *const DB_PATHS_WORKDIR[sizeof(DB_PATHS_STATEDIR) / sizeof(const char * const)] = { + [dbid_classes] = "cf_classes", + [dbid_variables] = NULL, + [dbid_performance] = "performance", + [dbid_checksums] = "checksum_digests", + [dbid_filestats] = "stats", + [dbid_changes] = NULL, + [dbid_observations] = NULL, + [dbid_state] = NULL, + [dbid_lastseen] = "cf_lastseen", + [dbid_audit] = "cf_audit", + [dbid_locks] = NULL, + [dbid_history] = NULL, + [dbid_measure] = NULL, + [dbid_static] = NULL, + [dbid_scalars] = NULL, + [dbid_windows_registry] = "mswin", + [dbid_cache] = "nova_cache", + [dbid_license] = "nova_track", + [dbid_value] = "nova_value", + [dbid_agent_execution] = "nova_agent_execution", + [dbid_bundles] = "bundles", +}; + +/******************************************************************************/ + +char *DBIdToSubPath(dbid id, const char *subdb_name) +{ + char *filename; + if (xasprintf(&filename, "%s/%s_%s.%s", GetStateDir(), DB_PATHS_STATEDIR[id], + subdb_name, DBPrivGetFileExtension()) == -1) + { + ProgrammingError("Unable to construct sub database filename for file" + "%s_%s", DB_PATHS_STATEDIR[id], subdb_name); + } + + char *native_filename = MapNameCopy(filename); + free(filename); + + return native_filename; +} + +Seq *SearchExistingSubDBNames(const dbid id) +{ + char *const glob_pattern = DBIdToSubPath(id, "*"); + StringSet *const db_paths = GlobFileList(glob_pattern); + free(glob_pattern); + + Seq *const db_names = SeqNew(StringSetSize(db_paths), free); + StringSetIterator iter = StringSetIteratorInit(db_paths); + const char *db_path; + + // Start after the '$(sys.statedir)/packages_installed_' part of the path + const size_t from = strlen(GetStateDir()) + 1 + + strlen(DB_PATHS_STATEDIR[id]) + 1; + + // End before the '.lmdb' part of the path + const size_t chop = from + 1 + strlen(DBPrivGetFileExtension()); + + while ((db_path = StringSetIteratorNext(&iter)) != NULL) + { + char *const db_name = SafeStringNDuplicate(db_path + from, + strlen(db_path) - chop); + SeqAppend(db_names, db_name); + } + + StringSetDestroy(db_paths); + return db_names; +} + +char *DBIdToPath(dbid id) +{ + assert(DB_PATHS_STATEDIR[id] != NULL); + + char *filename = NULL; + + if (DB_PATHS_WORKDIR[id]) + { + xasprintf(&filename, "%s/%s.%s", GetWorkDir(), DB_PATHS_WORKDIR[id], + DBPrivGetFileExtension()); + struct stat statbuf; + if (stat(filename, &statbuf) == -1) + { + // Old database in workdir is not there. Use new database in statedir. + free(filename); + filename = NULL; + } + } + + if (!filename) + { + xasprintf(&filename, "%s/%s.%s", GetStateDir(), DB_PATHS_STATEDIR[id], + DBPrivGetFileExtension()); + } + + char *native_filename = MapNameCopy(filename); + free(filename); + + return native_filename; +} + +static +bool IsSubHandle(DBHandle *handle, dbid id, const char *name) +{ + char *sub_path = DBIdToSubPath(id, name); + bool result = StringEqual(handle->filename, sub_path); + free(sub_path); + return result; +} + +static DBHandle *DBHandleGetSubDB(dbid id, const char *name) +{ + ThreadLock(&db_handles_lock); + + DynamicDBHandles *handles_list = db_dynamic_handles; + + while (handles_list) + { + if (IsSubHandle(handles_list->handle, id, name)) + { + ThreadUnlock(&db_handles_lock); + return handles_list->handle; + } + handles_list = handles_list->next; + } + + DBHandle *handle = xcalloc(1, sizeof(DBHandle)); + handle->filename = DBIdToSubPath(id, name); + handle->subname = SafeStringDuplicate(name); + + /* Initialize mutexes as error-checking ones. */ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + pthread_mutex_init(&handle->lock, &attr); + pthread_mutexattr_destroy(&attr); + + /* Prepend handle to global list. */ + handles_list = xcalloc(1, sizeof(DynamicDBHandles)); + handles_list->handle = handle; + handles_list->next = db_dynamic_handles; + db_dynamic_handles = handles_list; + + ThreadUnlock(&db_handles_lock); + + return handle; +} + +static DBHandle *DBHandleGet(int id) +{ + assert(id >= 0 && id < dbid_max); + + ThreadLock(&db_handles_lock); + if (db_handles[id].filename == NULL) + { + db_handles[id].filename = DBIdToPath(id); + + /* Initialize mutexes as error-checking ones. */ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + pthread_mutex_init(&db_handles[id].lock, &attr); + pthread_mutexattr_destroy(&attr); + } + + ThreadUnlock(&db_handles_lock); + + return &db_handles[id]; +} + +static inline +void CloseDBInstance(DBHandle *handle) +{ + /* Wait until all DB users are served, or a threshold is reached */ + int count = 0; + ThreadLock(&handle->lock); + if (handle->frozen) + { + /* Just clean some allocated memory, but don't touch the DB itself. */ + free(handle->filename); + free(handle->subname); + ThreadUnlock(&handle->lock); + return; + } + while (handle->refcount > 0 && count < 1000) + { + ThreadUnlock(&handle->lock); + + struct timespec sleeptime = { + .tv_sec = 0, + .tv_nsec = 10000000 /* 10 ms */ + }; + nanosleep(&sleeptime, NULL); + count++; + + ThreadLock(&handle->lock); + } + /* Keep mutex locked. */ + + /* If we exited because of timeout make sure we Log() it. */ + if (handle->refcount != 0) + { + Log(LOG_LEVEL_ERR, + "Database %s refcount is still not zero (%d), forcing CloseDB()!", + handle->filename, handle->refcount); + DBPrivCloseDB(handle->priv); + } + else /* TODO: can we clean this up unconditionally ? */ + { + free(handle->filename); + free(handle->subname); + handle->filename = NULL; + } +} + + +/** + * @brief Wait for all users of all databases to close the DBs. Then acquire + * the mutexes *AND KEEP THEM LOCKED* so that no background thread can open + * any database. So make sure you exit soon... + * + * @warning This is usually register with atexit(), however you have to make + * sure no other DB-cleaning exit hook was registered before, so that this is + * called last. + **/ +void CloseAllDBExit() +{ + ThreadLock(&db_handles_lock); + + for (int i = 0; i < dbid_max; i++) + { + if (db_handles[i].filename) + { + CloseDBInstance(&db_handles[i]); + } + } + + DynamicDBHandles *db_dynamic_handles_list = db_dynamic_handles; + while (db_dynamic_handles_list) + { + DBHandle *handle = db_dynamic_handles_list->handle; + CloseDBInstance(handle); + + DynamicDBHandles *next_free = db_dynamic_handles_list; + db_dynamic_handles_list = db_dynamic_handles_list->next; + + FREE_AND_NULL(handle); + FREE_AND_NULL(next_free); + } +} + +static void RegisterShutdownHandler(void) +{ + RegisterCleanupFunction(&CloseAllDBExit); +} + +/** + * Keeps track of the maximum number of concurrent transactions, which is + * expected to be set by agents as they start up. If it is not set it will use + * the existing value. If it is set, but the database cannot honor it, CFEngine + * will warn. + * @param max_txn Maximum number of concurrent transactions for a single + * database. + */ +void DBSetMaximumConcurrentTransactions(int max_txn) +{ + DBPrivSetMaximumConcurrentTransactions(max_txn); +} + +static inline +bool OpenDBInstance(DBHandle **dbp, dbid id, DBHandle *handle) +{ + assert(handle != NULL); + + ThreadLock(&handle->lock); + if (handle->frozen) + { + Log(LOG_LEVEL_WARNING, "Attempt to open a frozen DB '%s'", handle->filename); + ThreadUnlock(&handle->lock); + return false; + } + if (handle->refcount == 0) + { + FileLock lock = EMPTY_FILE_LOCK; + if (DBPathLock(&lock, handle->filename)) + { + handle->open_tstamp = time(NULL); + handle->priv = DBPrivOpenDB(handle->filename, id); + + if (handle->priv == DB_PRIV_DATABASE_BROKEN) + { + DBPathMoveBroken(handle->filename); + handle->priv = DBPrivOpenDB(handle->filename, id); + if (handle->priv == DB_PRIV_DATABASE_BROKEN) + { + handle->priv = NULL; + } + } + + DBPathUnLock(&lock); + } + + if (handle->priv) + { + if (!DBMigrate(handle, id)) + { + DBPrivCloseDB(handle->priv); + handle->priv = NULL; + handle->open_tstamp = -1; + } + } + } + + if (handle->priv) + { + handle->refcount++; + *dbp = handle; + + /* Only register shutdown handler if any database was opened + * correctly. Otherwise this shutdown caller may be called too early, + * and shutdown handler installed by the database library may end up + * being called before CloseAllDB function */ + + pthread_once(&db_shutdown_once, RegisterShutdownHandler); + } + else + { + *dbp = NULL; + } + + ThreadUnlock(&handle->lock); + return *dbp != NULL; +} + +bool OpenSubDB(DBHandle **dbp, dbid id, const char *sub_name) +{ + DBHandle *handle = DBHandleGetSubDB(id, sub_name); + return OpenDBInstance(dbp, id, handle); +} + +bool OpenDB(DBHandle **dbp, dbid id) +{ + DBHandle *handle = DBHandleGet(id); + return OpenDBInstance(dbp, id, handle); +} + +/** + * @db_file_name Absolute path of the DB file + */ +DBHandle *GetDBHandleFromFilename(const char *db_file_name) +{ + ThreadLock(&db_handles_lock); + for(dbid id=0; id < dbid_max; id++) + { + if (StringEqual(db_handles[id].filename, db_file_name)) + { + ThreadUnlock(&db_handles_lock); + return &(db_handles[id]); + } + } + ThreadUnlock(&db_handles_lock); + return NULL; +} + + +time_t GetDBOpenTimestamp(const DBHandle *handle) +{ + assert(handle != NULL); + return handle->open_tstamp; +} + +void CloseDB(DBHandle *handle) +{ + assert(handle != NULL); + + /* Skip in case of nested locking, for example signal handler. + * DB behaviour becomes erratic otherwise (CFE-1996). */ + ThreadLock(&handle->lock); + if (handle->frozen) + { + /* Just clean some allocated memory, but don't touch the DB itself. */ + free(handle->filename); + free(handle->subname); + ThreadUnlock(&handle->lock); + return; + } + DBPrivCommit(handle->priv); + + if (handle->refcount < 1) + { + Log(LOG_LEVEL_ERR, + "Trying to close database which is not open: %s", + handle->filename); + } + else + { + handle->refcount--; + if (handle->refcount == 0) + { + FileLock lock = EMPTY_FILE_LOCK; + bool locked = DBPathLock(&lock, handle->filename); + DBPrivCloseDB(handle->priv); + handle->open_tstamp = -1; + if (locked) + { + DBPathUnLock(&lock); + } + } + } + + ThreadUnlock(&handle->lock); +} + +bool CleanDB(DBHandle *handle) +{ + ThreadLock(&handle->lock); + if (handle->frozen) + { + Log(LOG_LEVEL_WARNING, "Attempt to clean a frozen DB '%s'", handle->filename); + ThreadUnlock(&handle->lock); + return false; + } + bool ret = DBPrivClean(handle->priv); + ThreadUnlock(&handle->lock); + + return ret; +} + +int GetDBUsagePercentage(const DBHandle *handle) +{ + assert(handle != NULL); + return DBPrivGetDBUsagePercentage(handle->filename); +} + +/** + * Freezes the DB so that it is never touched by this process again. In + * particular, new OpenDB() calls are ignored and CloseAllDBExit() also ignores + * the DB. + */ +void FreezeDB(DBHandle *handle) +{ + /* This is intentionally NOT using the handle->lock to avoid deadlocks. + * Nothing ever sets this to 'false' explicitly, that's only done in the + * initialization with '{ { 0 } }', so this bit-flip is safe. */ + Log(LOG_LEVEL_NOTICE, "Freezing the DB '%s'", handle->filename); + handle->frozen = true; +} + +/*****************************************************************************/ + +bool ReadComplexKeyDB(DBHandle *handle, const char *key, int key_size, + void *dest, int dest_size) +{ + return DBPrivRead(handle->priv, key, key_size, dest, dest_size); +} + +bool WriteComplexKeyDB(DBHandle *handle, const char *key, int key_size, + const void *value, int value_size) +{ + return DBPrivWrite(handle->priv, key, key_size, value, value_size); +} + +bool DeleteComplexKeyDB(DBHandle *handle, const char *key, int key_size) +{ + return DBPrivDelete(handle->priv, key, key_size); +} + +bool ReadDB(DBHandle *handle, const char *key, void *dest, int destSz) +{ + return DBPrivRead(handle->priv, key, strlen(key) + 1, dest, destSz); +} + +bool WriteDB(DBHandle *handle, const char *key, const void *src, int srcSz) +{ + return DBPrivWrite(handle->priv, key, strlen(key) + 1, src, srcSz); +} + +bool OverwriteDB(DBHandle *handle, const char *key, const void *value, size_t value_size, + OverwriteCondition Condition, void *data) +{ + assert(handle != NULL); + return DBPrivOverwrite(handle->priv, key, strlen(key) + 1, value, value_size, Condition, data); +} + +bool HasKeyDB(DBHandle *handle, const char *key, int key_size) +{ + return DBPrivHasKey(handle->priv, key, key_size); +} + +int ValueSizeDB(DBHandle *handle, const char *key, int key_size) +{ + return DBPrivGetValueSize(handle->priv, key, key_size); +} + +bool DeleteDB(DBHandle *handle, const char *key) +{ + return DBPrivDelete(handle->priv, key, strlen(key) + 1); +} + +bool NewDBCursor(DBHandle *handle, DBCursor **cursor) +{ + DBCursorPriv *priv = DBPrivOpenCursor(handle->priv); + if (!priv) + { + return false; + } + + *cursor = xcalloc(1, sizeof(DBCursor)); + (*cursor)->cursor = priv; + return true; +} + +bool NextDB(DBCursor *cursor, char **key, int *ksize, + void **value, int *vsize) +{ + return DBPrivAdvanceCursor(cursor->cursor, (void **)key, ksize, value, vsize); +} + +bool DBCursorDeleteEntry(DBCursor *cursor) +{ + return DBPrivDeleteCursorEntry(cursor->cursor); +} + +bool DBCursorWriteEntry(DBCursor *cursor, const void *value, int value_size) +{ + return DBPrivWriteCursorEntry(cursor->cursor, value, value_size); +} + +bool DeleteDBCursor(DBCursor *cursor) +{ + DBPrivCloseCursor(cursor->cursor); + free(cursor); + return true; +} + +static bool DBPathLock(FileLock *lock, const char *filename) +{ + char *filename_lock; + if (xasprintf(&filename_lock, "%s.lock", filename) == -1) + { + ProgrammingError("Unable to construct lock database filename for file %s", filename); + } + + if (ExclusiveFileLockPath(lock, filename_lock, true) != 0) + { + Log(LOG_LEVEL_ERR, "Unable to lock database lock file '%s'.", filename_lock); + free(filename_lock); + return false; + } + + free(filename_lock); + + return true; +} + +static void DBPathUnLock(FileLock *lock) +{ + ExclusiveFileUnlock(lock, true); +} + +static void DBPathMoveBroken(const char *filename) +{ + char *filename_broken; + if (xasprintf(&filename_broken, "%s.broken", filename) == -1) + { + ProgrammingError("Unable to construct broken database filename for file '%s'", filename); + } + + if(rename(filename, filename_broken) != 0) + { + Log(LOG_LEVEL_ERR, "Failed moving broken db out of the way '%s'", filename); + } + + free(filename_broken); +} + +StringMap *LoadDatabaseToStringMap(dbid database_id) +{ + CF_DB *db_conn = NULL; + CF_DBC *db_cursor = NULL; + char *key = NULL; + void *value = NULL; + int key_size = 0; + int value_size = 0; + + if (!OpenDB(&db_conn, database_id)) + { + return NULL; + } + + if (!NewDBCursor(db_conn, &db_cursor)) + { + Log(LOG_LEVEL_ERR, "Unable to scan db"); + CloseDB(db_conn); + return NULL; + } + + StringMap *db_map = StringMapNew(); + while (NextDB(db_cursor, &key, &key_size, &value, &value_size)) + { + if (!key) + { + continue; + } + + if (!value) + { + Log(LOG_LEVEL_VERBOSE, "Invalid entry (key='%s') in database.", key); + continue; + } + + void *val = xcalloc(1, value_size); + val = memcpy(val, value, value_size); + + StringMapInsert(db_map, xstrdup(key), val); + } + + DeleteDBCursor(db_cursor); + CloseDB(db_conn); + + return db_map; +} + +/** + * Checks if a DB repair flag file is present and if it is, removes it. + * + * @return Whether the DB repair flag file was present or not. + */ +bool CheckDBRepairFlagFile() +{ + /* The DB repair flag file can be created by user or by some process + * that hit an error condition potentially caused by local DB corruption + * that it was not able to handle properly by repairing the corrupted DB + * file(s). For example, if a process is killed by a signal. */ + char repair_flag_file[PATH_MAX] = { 0 }; + bool present = false; + xsnprintf(repair_flag_file, PATH_MAX, "%s%c%s", + GetStateDir(), FILE_SEPARATOR, CF_DB_REPAIR_TRIGGER); + /* This is full of race-conditions, but it's just a best-effort + * thing. If a force-repair is missed, it will happen next time. If it's + * done twice, no big deal. */ + if (access(repair_flag_file, F_OK) == 0) + { + present = true; + unlink(repair_flag_file); + } + return present; +} diff --git a/libpromises/dbm_api.h b/libpromises/dbm_api.h new file mode 100644 index 0000000000..fb277c23fc --- /dev/null +++ b/libpromises/dbm_api.h @@ -0,0 +1,120 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_DBM_API_H +#define CFENGINE_DBM_API_H + +#define EC_CORRUPTION_REPAIRED 120 +#define EC_CORRUPTION_REPAIR_FAILED 121 + +#include +#include +#include + +// Only append to the end, keep in sync with DB_PATHS_STATEDIR array +typedef enum +{ + dbid_classes = 0, // Deprecated + dbid_variables = 1, // Deprecated + dbid_performance = 2, + dbid_checksums = 3, // Deprecated + dbid_filestats = 4, // Deprecated + dbid_changes = 5, + dbid_observations = 6, + dbid_state = 7, + dbid_lastseen = 8, + dbid_audit = 9, + dbid_locks = 10, + dbid_history = 11, + dbid_measure = 12, + dbid_static = 13, + dbid_scalars = 14, + dbid_windows_registry = 15, + dbid_cache = 16, + dbid_license = 17, + dbid_value = 18, + dbid_agent_execution = 19, + dbid_bundles = 20, // Deprecated + dbid_packages_installed = 21, // new package promise installed packages list + dbid_packages_updates = 22, // new package promise list of available updates + dbid_cookies = 23, // Enterprise reporting cookies for duplicate host detection + + dbid_max +} dbid; + +typedef struct DBHandle_ DBHandle; +typedef struct DBCursor_ DBCursor; + +typedef DBHandle CF_DB; +typedef DBCursor CF_DBC; + +void DBSetMaximumConcurrentTransactions(int max_txn); + +bool OpenDB(CF_DB **dbp, dbid db); +bool OpenSubDB(DBHandle **dbp, dbid id, const char *sub_name); +bool CleanDB(DBHandle *handle); +void CloseDB(CF_DB *dbp); + +DBHandle *GetDBHandleFromFilename(const char *db_file_name); +time_t GetDBOpenTimestamp(const DBHandle *handle); + +/** + * @return -1 in case of unknown a number between 0 and 100 otherwise + */ +int GetDBUsagePercentage(const DBHandle *handle); + +bool HasKeyDB(CF_DB *dbp, const char *key, int key_size); +int ValueSizeDB(CF_DB *dbp, const char *key, int key_size); +bool ReadComplexKeyDB(CF_DB *dbp, const char *key, int key_size, void *dest, int destSz); +bool WriteComplexKeyDB(CF_DB *dbp, const char *key, int keySz, const void *src, int srcSz); +bool DeleteComplexKeyDB(CF_DB *dbp, const char *key, int size); +bool ReadDB(CF_DB *dbp, const char *key, void *dest, int destSz); +bool WriteDB(CF_DB *dbp, const char *key, const void *src, int srcSz); +bool OverwriteDB(DBHandle *handle, const char *key, const void *value, size_t value_size, + OverwriteCondition Condition, void *data); +bool DeleteDB(CF_DB *dbp, const char *key); +void FreezeDB(DBHandle *handle); + +/* + * Creating cursor locks the whole database, so keep the amount of work here to + * minimum. + * + * Don't use WriteDB/DeleteDB while iterating database, it will result in + * deadlock. Use cursor-specific operations instead. They work on the current + * key. + */ +bool NewDBCursor(CF_DB *dbp, CF_DBC **dbcp); +bool NextDB(CF_DBC *dbcp, char **key, int *ksize, void **value, int *vsize); +bool DBCursorDeleteEntry(CF_DBC *cursor); +bool DBCursorWriteEntry(CF_DBC *cursor, const void *value, int value_size); +bool DeleteDBCursor(CF_DBC *dbcp); + +char *DBIdToPath(dbid id); +char *DBIdToSubPath(dbid id, const char *subdb_name); +Seq *SearchExistingSubDBNames(dbid id); + +StringMap *LoadDatabaseToStringMap(dbid database_id); + +bool CheckDBRepairFlagFile(); +#endif /* NOT CFENGINE_DBM_API_H */ diff --git a/libpromises/dbm_api_types.h b/libpromises/dbm_api_types.h new file mode 100644 index 0000000000..054b66b382 --- /dev/null +++ b/libpromises/dbm_api_types.h @@ -0,0 +1,30 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_DBM_API_TYPES_H +#define CFENGINE_DBM_API_TYPES_H + +typedef bool (*OverwriteCondition) (void *value, size_t value_size, void *data); + +#endif /* CFENGINE_DBM_API_TYPES_H */ diff --git a/libpromises/dbm_lmdb.c b/libpromises/dbm_lmdb.c new file mode 100644 index 0000000000..2b0cfcb847 --- /dev/null +++ b/libpromises/dbm_lmdb.c @@ -0,0 +1,1406 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* + * Implementation using LMDB API. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LMDB + +#include +#include +#include /* cf_db_corruption_lock */ +#include +#include /* time() */ + +// Shared between threads. +struct DBPriv_ +{ + MDB_env *env; + MDB_dbi dbi; + // Used to keep track of transactions. + // We set this to the transaction address when a thread creates a + // transaction, and back to 0x0 when it is destroyed. + pthread_key_t txn_key; +}; + +// Not shared between threads. +typedef struct DBTxn_ +{ + MDB_txn *txn; + // Whether txn is a read/write (true) or read-only (false) transaction. + bool rw_txn; + bool cursor_open; +} DBTxn; + +struct DBCursorPriv_ +{ + DBPriv *db; + MDB_cursor *mc; + MDB_val delkey; + void *curkv; + bool pending_delete; +}; + +static int DB_MAX_READERS = -1; + +#define N_LMDB_EINVAL_RETRIES 5 + +/******************************************************************************/ + +static void HandleLMDBCorruption(MDB_env *env, const char *msg); +static void HandleFullLMDB(MDB_env *env); + +static inline void CheckLMDBUsable(int rc, MDB_env *env) +{ + if (rc == MDB_CORRUPTED) + { + HandleLMDBCorruption(env, ""); + } + else if (rc == MDB_MAP_FULL) + { + HandleFullLMDB(env); + } +} + +static int GetReadTransaction(DBPriv *const db, DBTxn **const txn) +{ + assert(db != NULL); + assert(txn != NULL); + + DBTxn *db_txn = pthread_getspecific(db->txn_key); + int rc = MDB_SUCCESS; + + if (db_txn == NULL) + { + db_txn = xcalloc(1, sizeof(DBTxn)); + pthread_setspecific(db->txn_key, db_txn); + } + + if (db_txn->txn == NULL) + { + rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &db_txn->txn); + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_ERR, "Unable to open read transaction in '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + } + } + + *txn = db_txn; + + return rc; +} + +static int GetWriteTransaction(DBPriv *const db, DBTxn **const txn) +{ + assert(db != NULL); + assert(txn != NULL); + + DBTxn *db_txn = pthread_getspecific(db->txn_key); + int rc = MDB_SUCCESS; + + if (db_txn == NULL) + { + db_txn = xcalloc(1, sizeof(DBTxn)); + pthread_setspecific(db->txn_key, db_txn); + } + + if (db_txn->txn != NULL && !db_txn->rw_txn) + { + rc = mdb_txn_commit(db_txn->txn); + CheckLMDBUsable(rc, db->env); + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_ERR, "Unable to close read-only transaction in '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + } + db_txn->txn = NULL; + } + + if (db_txn->txn == NULL) + { + rc = mdb_txn_begin(db->env, NULL, 0, &db_txn->txn); + CheckLMDBUsable(rc, db->env); + if (rc == MDB_SUCCESS) + { + db_txn->rw_txn = true; + } + else + { + Log(LOG_LEVEL_ERR, "Unable to open write transaction in '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + } + } + + *txn = db_txn; + + return rc; +} + +static void AbortTransaction(DBPriv *const db) +{ + assert(db != NULL); + + DBTxn *db_txn = pthread_getspecific(db->txn_key); + if (db_txn != NULL) + { + if (db_txn->txn != NULL) + { + mdb_txn_abort(db_txn->txn); + } + + pthread_setspecific(db->txn_key, NULL); + free(db_txn); + } +} + +static void DestroyTransaction(void *const ptr) +{ + DBTxn *const db_txn = (DBTxn *)ptr; + UnexpectedError("Transaction object still exists when terminating thread"); + if (db_txn->txn) + { + UnexpectedError("Transaction still open when terminating thread!"); + mdb_txn_abort(db_txn->txn); + } + free(db_txn); +} + +const char *DBPrivGetFileExtension(void) +{ + return "lmdb"; +} + +/* NOTE: Must be in sync with LMDB_MAXSIZE in cf-check/diagnose.c. */ +#ifndef LMDB_MAXSIZE +#define LMDB_MAXSIZE 104857600 +#endif + +void DBPrivSetMaximumConcurrentTransactions(const int max_txn) +{ + DB_MAX_READERS = max_txn; +} + +static int LmdbEnvOpen( + MDB_env *const env, + const char *const path, + const unsigned int flags, + const mdb_mode_t mode) +{ + assert(env != NULL); // dereferenced in lmdb (mdb_env_open) + assert(path != NULL); // dereferenced (strlen) in lmdb (mdb_env_open) + assert(mdb_env_get_maxkeysize(env) == 511); // Search for 511 in locks.c + + /* There is a race condition in LMDB that will fail to open the database + * environment if another process is opening it at the exact same time. This + * condition is signaled by returning ENOENT, which we should never get + * otherwise. This can lead to error messages on a heavily loaded machine, + * so try to open it again after allowing other threads to finish their + * opening process. */ + int attempts = 5; + while (attempts-- > 0) + { + int rc = mdb_env_open(env, path, flags, mode); + if (rc != ENOENT) + { + return rc; + } + +#if HAVE_DECL_SCHED_YIELD && defined(HAVE_SCHED_YIELD) + // Not required for this to work, but makes it less likely that the race + // condition will persist. + sched_yield(); +#endif + } + + // Return EBUSY for an error message slightly more related to reality. + return EBUSY; +} + +/** + * @warning Expects @fd_stamp to be locked. + */ +static bool RepairedAfterOpen(const char *lmdb_file, int fd_tstamp) +{ + time_t repaired_tstamp = -1; + ssize_t n_read = read(fd_tstamp, &repaired_tstamp, sizeof(time_t)); + lseek(fd_tstamp, 0, SEEK_SET); + + if (n_read < 0) + { + Log(LOG_LEVEL_ERR, "Failed to read %s: %s", lmdb_file, GetErrorStr()); + } + else if (n_read == 0) + { + /* EOF (empty file) => never repaired */ + Log(LOG_LEVEL_VERBOSE, "DB '%s' never repaired before", lmdb_file); + } + else if ((size_t) n_read < sizeof(time_t)) + { + /* error */ + Log(LOG_LEVEL_ERR, "Failed to read the timestamp of repair of the '%s' DB", + lmdb_file); + } + else + { + /* read the timestamp => Check if the LMDB file was repaired after + * we opened it last time. Or, IOW, if this is a new corruption or + * an already-handled one. */ + DBHandle *handle = GetDBHandleFromFilename(lmdb_file); + if (repaired_tstamp > GetDBOpenTimestamp(handle)) + { + return true; + } + } + return false; +} + +/** + * @warning Expects @fd_stamp to be locked. + */ +static bool RotatedAfterOpen(const char *lmdb_file, int fd_tstamp) +{ + time_t rotated_tstamp = -1; + ssize_t n_read = read(fd_tstamp, &rotated_tstamp, sizeof(time_t)); + lseek(fd_tstamp, 0, SEEK_SET); + + if (n_read < 0) + { + Log(LOG_LEVEL_ERR, "Failed to read %s: %s", lmdb_file, GetErrorStr()); + } + else if (n_read == 0) + { + /* EOF (empty file) => never rotated */ + Log(LOG_LEVEL_VERBOSE, "DB '%s' never rotated before", lmdb_file); + } + else if ((size_t) n_read < sizeof(time_t)) + { + /* error */ + Log(LOG_LEVEL_ERR, "Failed to read the timestamp of rotation of the '%s' DB", + lmdb_file); + } + else + { + /* read the timestamp => Check if the LMDB file was rotated after + * we opened it last time. */ + DBHandle *handle = GetDBHandleFromFilename(lmdb_file); + if (rotated_tstamp > GetDBOpenTimestamp(handle)) + { + return true; + } + } + return false; +} + +static void HandleLMDBCorruption(MDB_env *env, const char *msg) +{ + const char *lmdb_file = mdb_env_get_userctx(env); + Log(LOG_LEVEL_CRIT, "Corruption in the '%s' DB detected! %s", + lmdb_file, msg); + + /* Freeze the DB ASAP. This also makes the call to exit() safe regarding + * this particular DB because exit handlers will ignore it. */ + DBHandle *handle = GetDBHandleFromFilename(lmdb_file); + FreezeDB(handle); + +#ifdef __MINGW32__ + /* Not much we can do on Windows because there is no fork() and file locking + * is also not so nice. */ + Log(LOG_LEVEL_WARNING, "Removing the corrupted DB file '%s'", + lmdb_file); + if (unlink(lmdb_file) != 0) + { + Log(LOG_LEVEL_CRIT, "Failed to remove the corrupted DB file '%s'", + lmdb_file); + exit(EC_CORRUPTION_REPAIR_FAILED); + } + exit(EC_CORRUPTION_REPAIRED); +#else + /* Try to handle the corruption gracefully by repairing the LMDB file + * (replacing it with a new LMDB file with all the data we managed to read + * from the corrupted one). */ + + /* To avoid two processes acting on the same corrupted file at once, file + * locks are involved. Looking at OpenDBInstance() and DBPathLock() + * in libpromises/db_api.c might also be useful.*/ + + /* Only allow one thread at a time to handle DB corruption. File locks are + * *process* specific so threads could step on each others toes. */ + ThreadLock(cft_db_corruption_lock); + + char *tstamp_file = StringFormat("%s.repaired", lmdb_file); + char *db_lock_file = StringFormat("%s.lock", lmdb_file); + + int fd_tstamp = safe_open(tstamp_file, O_CREAT|O_RDWR); + if (fd_tstamp == -1) + { + Log(LOG_LEVEL_CRIT, "Failed to open the '%s' DB repair timestamp file", + lmdb_file); + ThreadUnlock(cft_db_corruption_lock); + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIR_FAILED); + } + FileLock tstamp_lock = { .fd = fd_tstamp }; + + int fd_db_lock = safe_open(db_lock_file, O_CREAT|O_RDWR); + if (fd_db_lock == -1) + { + Log(LOG_LEVEL_CRIT, "Failed to open the '%s' DB lock file", + lmdb_file); + ThreadUnlock(cft_db_corruption_lock); + close(fd_tstamp); + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIR_FAILED); + } + FileLock db_lock = { .fd = fd_db_lock }; + + int ret; + bool handle_corruption = true; + + /* Make sure we are not holding the DB's lock (potentially needed by some + * other process for the repair) to avoid deadlocks. */ + Log(LOG_LEVEL_DEBUG, "Releasing lock on the '%s' DB", lmdb_file); + ExclusiveFileUnlock(&db_lock, false); /* close=false */ + + ret = SharedFileLock(&tstamp_lock, true); + if (ret == 0) + { + if (RepairedAfterOpen(lmdb_file, fd_tstamp)) + { + /* The corruption has already been handled. This process should + * just die because we have no way to return to the point where + * it would just open the new (repaired) LMDB file. */ + handle_corruption = false; + } + SharedFileUnlock(&tstamp_lock, false); + } + else + { + /* should never happen (we tried to wait), but if it does, just log an + * error and keep going */ + Log(LOG_LEVEL_ERR, + "Failed to get shared lock for the repair timestamp of the '%s' DB", + lmdb_file); + } + + if (!handle_corruption) + { + /* Just clean after ourselves and terminate the process. */ + ThreadUnlock(cft_db_corruption_lock); + close(fd_db_lock); + close(fd_tstamp); + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIRED); + } + + /* HERE is a window for some other process to do the repair between when we + * checked the timestamp using the shared lock above and the attempt to get + * the exclusive lock right below. However, this is detected by checking the + * contents of the timestamp file again below, while holding the EXCLUSIVE + * lock. */ + + ret = ExclusiveFileLock(&tstamp_lock, true); + if (ret != 0) + { + /* should never happen (we tried to wait), but if it does, just + * terminate because doing the repair without the lock could be + * disasterous */ + Log(LOG_LEVEL_ERR, + "Failed to get shared lock for the repair timestamp of the '%s' DB", + lmdb_file); + + ThreadUnlock(cft_db_corruption_lock); + close(fd_db_lock); + close(fd_tstamp); + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIR_FAILED); + } + + /* Cleared to resolve the corruption. */ + + /* 1. Acquire the lock for the DB to prevent more processes trying to use + * it while it is corrupted (wait till the lock is available). */ + while (ExclusiveFileLock(&db_lock, false) == -1) + { + /* busy wait to do the logging */ + Log(LOG_LEVEL_INFO, "Waiting for the lock on the '%s' DB", + lmdb_file); + sleep(1); + } + + /* 2. Check the last repair timestamp again (see the big "HERE..." comment + * above) */ + if (RepairedAfterOpen(lmdb_file, fd_tstamp)) + { + /* Some other process repaired the DB since we checked last time, + * nothing more to do here. */ + ThreadUnlock(cft_db_corruption_lock); + close(fd_db_lock); /* releases locks */ + close(fd_tstamp); /* releases locks */ + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIRED); + } + + /* 3. Repair the DB or at least move it out of the way. */ + /* repair_lmdb_file() forks so it is safe (file locks are not + * inherited). */ + ret = repair_lmdb_file(lmdb_file, fd_tstamp); + + /* repair_lmdb_file returns -1 in case of error, 0 in case of successfull + * repair, >0 in case of failed repair, but successful remove */ + bool repair_successful = (ret != -1); + if (repair_successful) + { + Log(LOG_LEVEL_NOTICE, "DB '%s' successfully repaired", + lmdb_file); + } + else + { + Log(LOG_LEVEL_CRIT, "Failed to repair DB '%s'", lmdb_file); + } + + /* 4. Make the repaired DB available for others. Also release the locks + * in the opposite order in which they were acquired to avoid + * deadlocks. */ + if (ExclusiveFileUnlock(&db_lock, true) != 0) + { + Log(LOG_LEVEL_ERR, "Failed to release the acquired lock for '%s'", + db_lock_file); + } + + /* 5. Signal that the repair is done (also closes fd_tstamp). */ + if (ExclusiveFileUnlock(&tstamp_lock, true) != 0) + { + Log(LOG_LEVEL_ERR, "Failed to release the acquired lock for '%s'", + tstamp_file); + } + + ThreadUnlock(cft_db_corruption_lock); + free(db_lock_file); + free(tstamp_file); + /* fd_db_lock and fd_tstamp are already closed by the calls to + * ExclusiveFileUnlock above. */ + + if (repair_successful) + { + exit(EC_CORRUPTION_REPAIRED); + } + else + { + exit(EC_CORRUPTION_REPAIR_FAILED); + } +#endif /* __MINGW32__ */ +} + +/** + * A modified clone of HandleLMDBCorruption() for handling full LMDBs. It's not + * easy and nice to share much code between the two functions, unfortunately. + */ +static void HandleFullLMDB(MDB_env *env) +{ + const char *lmdb_file = mdb_env_get_userctx(env); + Log(LOG_LEVEL_CRIT, "'%s' DB full!", lmdb_file); + + /* Freeze the DB ASAP. This also makes the call to exit() safe regarding + * this particular DB because exit handlers will ignore it. */ + DBHandle *handle = GetDBHandleFromFilename(lmdb_file); + FreezeDB(handle); + +#ifdef _WIN32 + /* Not much we can do on Windows because there is no fork() and file locking + * is also not so nice. */ + Log(LOG_LEVEL_WARNING, "Moving the full DB file '%s' aside", + lmdb_file); + time_t now = time(NULL); + char *rotated_file = StringFormat("%s.rotated.%jd", lmdb_file, (intmax_t) now); + if (rename(lmdb_file, rotated_file) != 0) + { + free(rotated_file); + Log(LOG_LEVEL_CRIT, + "Failed to move the full DB file '%s' aside (%s), will be removed instead", + lmdb_file, GetErrorStr()); + if (unlink(lmdb_file) != 0) + { + Log(LOG_LEVEL_CRIT, "Failed to remove the full DB file '%s': %s", + lmdb_file, GetErrorStr()); + } + exit(EC_CORRUPTION_REPAIR_FAILED); + } + free(rotated_file); + exit(EC_CORRUPTION_REPAIRED); +#else + /* To avoid two processes acting on the same corrupted file at once, file + * locks are involved. Looking at OpenDBInstance() and DBPathLock() + * in libpromises/db_api.c might also be useful.*/ + + /* Only allow one thread at a time to handle a full or corrupted DB. File + * locks are *process* specific so threads could step on each others + * toes. */ + ThreadLock(cft_db_corruption_lock); + + char *tstamp_file = StringFormat("%s.rotated", lmdb_file); + char *db_lock_file = StringFormat("%s.lock", lmdb_file); + + int fd_tstamp = safe_open(tstamp_file, O_CREAT|O_RDWR); + if (fd_tstamp == -1) + { + Log(LOG_LEVEL_CRIT, "Failed to open the '%s' DB rotation timestamp file", + lmdb_file); + ThreadUnlock(cft_db_corruption_lock); + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIR_FAILED); + } + FileLock tstamp_lock = { .fd = fd_tstamp }; + + int fd_db_lock = safe_open(db_lock_file, O_CREAT|O_RDWR); + if (fd_db_lock == -1) + { + Log(LOG_LEVEL_CRIT, "Failed to open the '%s' DB lock file", + lmdb_file); + ThreadUnlock(cft_db_corruption_lock); + close(fd_tstamp); + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIR_FAILED); + } + FileLock db_lock = { .fd = fd_db_lock }; + + int ret; + bool handle_rotation = true; + + /* Make sure we are not holding the DB's lock (potentially needed by some + * other process for the repair or rotation) to avoid deadlocks. */ + Log(LOG_LEVEL_DEBUG, "Releasing lock on the '%s' DB", lmdb_file); + ExclusiveFileUnlock(&db_lock, false); /* close=false */ + + ret = SharedFileLock(&tstamp_lock, true); + if (ret == 0) + { + if (RotatedAfterOpen(lmdb_file, fd_tstamp)) + { + /* The corruption has already been handled. This process should just + * die because we have no way to return to the point where it would + * just open the new (repaired or rotated) LMDB file. */ + handle_rotation = false; + } + SharedFileUnlock(&tstamp_lock, false); + } + else + { + /* should never happen (we tried to wait), but if it does, just log an + * error and keep going */ + Log(LOG_LEVEL_ERR, + "Failed to get shared lock for the rotation timestamp of the '%s' DB", + lmdb_file); + } + + if (!handle_rotation) + { + /* Just clean after ourselves and terminate the process. */ + ThreadUnlock(cft_db_corruption_lock); + close(fd_db_lock); + close(fd_tstamp); + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIRED); + } + + /* HERE is a window for some other process to do the rotation between when we + * checked the timestamp using the shared lock above and the attempt to get + * the exclusive lock right below. However, this is detected by checking the + * contents of the timestamp file again below, while holding the EXCLUSIVE + * lock. */ + + ret = ExclusiveFileLock(&tstamp_lock, true); + if (ret != 0) + { + /* should never happen (we tried to wait), but if it does, just + * terminate because doing the rotation without the lock could be + * disasterous */ + Log(LOG_LEVEL_ERR, + "Failed to get shared lock for the rotation timestamp of the '%s' DB", + lmdb_file); + + ThreadUnlock(cft_db_corruption_lock); + close(fd_db_lock); + close(fd_tstamp); + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIR_FAILED); + } + + /* Cleared to resolve the corruption. */ + + /* 1. Acquire the lock for the DB to prevent more processes trying to use + * it while it is corrupted (wait till the lock is available). */ + while (ExclusiveFileLock(&db_lock, false) == -1) + { + /* busy wait to do the logging */ + Log(LOG_LEVEL_INFO, "Waiting for the lock on the '%s' DB", + lmdb_file); + sleep(1); + } + + /* 2. Check the last rotation timestamp again (see the big "HERE..." comment + * above) */ + if (RotatedAfterOpen(lmdb_file, fd_tstamp)) + { + /* Some other process rotated the DB since we checked last time, + * nothing more to do here. */ + ThreadUnlock(cft_db_corruption_lock); + close(fd_db_lock); /* releases locks */ + close(fd_tstamp); /* releases locks */ + free(db_lock_file); + free(tstamp_file); + + exit(EC_CORRUPTION_REPAIRED); + } + + /* 3. Rotate the DB or at least move it out of the way. */ + ret = rotate_lmdb_file(lmdb_file, fd_tstamp); + bool rotation_successful = (ret == 0); + if (rotation_successful) + { + Log(LOG_LEVEL_NOTICE, "DB '%s' successfully rotated", lmdb_file); + } + else + { + Log(LOG_LEVEL_CRIT, "Failed to rotate '%s' DB", lmdb_file); + } + + /* 4. Make the rotated DB available for others. Also release the locks + * in the opposite order in which they were acquired to avoid + * deadlocks. */ + if (ExclusiveFileUnlock(&db_lock, true) != 0) + { + Log(LOG_LEVEL_ERR, "Failed to release the acquired lock for '%s'", + db_lock_file); + } + + /* 5. Signal that the rotation is done (also closes fd_tstamp). */ + if (ExclusiveFileUnlock(&tstamp_lock, true) != 0) + { + Log(LOG_LEVEL_ERR, "Failed to release the acquired lock for '%s'", + tstamp_file); + } + + ThreadUnlock(cft_db_corruption_lock); + free(db_lock_file); + free(tstamp_file); + /* fd_db_lock and fd_tstamp are already closed by the calls to + * ExclusiveFileUnlock above. */ + + if (rotation_successful) + { + exit(EC_CORRUPTION_REPAIRED); + } + else + { + exit(EC_CORRUPTION_REPAIR_FAILED); + } +#endif /* _WIN32 */ +} + +DBPriv *DBPrivOpenDB(const char *const dbpath, const dbid id) +{ + DBPriv *const db = xcalloc(1, sizeof(DBPriv)); + MDB_txn *txn = NULL; + + int rc = pthread_key_create(&db->txn_key, &DestroyTransaction); + if (rc) + { + Log(LOG_LEVEL_ERR, "Could not create transaction key. (pthread_key_create: '%s')", + GetErrorStrFromCode(rc)); + free(db); + return NULL; + } + + rc = mdb_env_create(&db->env); + if (rc) + { + Log(LOG_LEVEL_ERR, "Could not create handle for database %s: %s", + dbpath, mdb_strerror(rc)); + goto err; + } + rc = mdb_env_set_userctx(db->env, xstrdup(dbpath)); + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_WARNING, "Could not store DB file path (%s) in the DB context", + dbpath); + } + rc = mdb_env_set_assert(db->env, (MDB_assert_func*) HandleLMDBCorruption); + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_WARNING, "Could not set the corruption handler for '%s'", + dbpath); + } + rc = mdb_env_set_mapsize(db->env, LMDB_MAXSIZE); + if (rc) + { + Log(LOG_LEVEL_ERR, "Could not set mapsize for database %s: %s", + dbpath, mdb_strerror(rc)); + goto err; + } + if (DB_MAX_READERS > 0) + { + rc = mdb_env_set_maxreaders(db->env, DB_MAX_READERS); + if (rc) + { + Log(LOG_LEVEL_ERR, "Could not set maxreaders for database %s: %s", + dbpath, mdb_strerror(rc)); + goto err; + } + } + + unsigned int open_flags = MDB_NOSUBDIR; +#if !defined(_AIX) && !defined(__sun) + /* The locks and lastseen (on hubs) DBs are heavily used and using + * MDB_NOSYNC increases performance. However, AIX and Solaris often suffer + * from some serious issues with consistency (ENT-4002) so it's better to + * sacrifice some performance there in favor of stability. */ + if (id == dbid_locks || (GetAmPolicyHub() && id == dbid_lastseen)) + { + open_flags |= MDB_NOSYNC; + } +#endif + +#ifdef __hpux + /* + * On HP-UX, a unified file cache was not introduced until version 11.31. + * This means that on 11.23 there are separate file caches for mmap()'ed + * files and open()'ed files. When these two are mixed, changes made using + * one mode won't be immediately seen by the other mode, which is an + * assumption LMDB is relying on. The MDB_WRITEMAP flag causes LMDB to use + * mmap() only, so that we stay within one file cache. + */ + open_flags |= MDB_WRITEMAP; +#endif + + rc = LmdbEnvOpen(db->env, dbpath, open_flags, CF_PERMS_DEFAULT); + if (rc) + { + Log(LOG_LEVEL_ERR, "Could not open database %s: %s", + dbpath, mdb_strerror(rc)); + if (rc == MDB_CORRUPTED || rc == MDB_INVALID) + { + HandleLMDBCorruption(db->env, mdb_strerror(rc)); + } + goto err; + } + if (DB_MAX_READERS > 0) + { + int max_readers; + rc = mdb_env_get_maxreaders(db->env, &max_readers); + if (rc) + { + Log(LOG_LEVEL_ERR, "Could not get maxreaders for database %s: %s", + dbpath, mdb_strerror(rc)); + goto err; + } + if (max_readers < DB_MAX_READERS) + { + // LMDB will only reinitialize maxreaders if no database handles are + // open, including in other processes, which is how we might end up + // here. + Log(LOG_LEVEL_VERBOSE, "Failed to set LMDB max reader limit on database '%s', " + "consider restarting CFEngine", + dbpath); + } + } + + /* There seems to be a race condition causing mdb_txn_begin() return + * EINVAL. We do a couple retries before giving up. */ + rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn); + int attempts = N_LMDB_EINVAL_RETRIES; + while ((rc != 0) && (attempts-- > 0)) + { + CheckLMDBUsable(rc, db->env); + if (rc != EINVAL) + { + Log(LOG_LEVEL_ERR, "Could not open database txn %s: %s", + dbpath, mdb_strerror(rc)); + goto err; + } +#if HAVE_DECL_SCHED_YIELD && defined(HAVE_SCHED_YIELD) + // Not required for this to work, but makes it less likely that the race + // condition will persist. + sched_yield(); +#endif + rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn); + } + if (rc != 0) + { + Log(LOG_LEVEL_ERR, "Could not open database txn %s: %s", + dbpath, mdb_strerror(rc)); + goto err; + } + rc = mdb_open(txn, NULL, 0, &db->dbi); + CheckLMDBUsable(rc, db->env); + if (rc) + { + Log(LOG_LEVEL_ERR, "Could not open database dbi %s: %s", + dbpath, mdb_strerror(rc)); + mdb_txn_abort(txn); + goto err; + } + rc = mdb_txn_commit(txn); + CheckLMDBUsable(rc, db->env); + if (rc) + { + Log(LOG_LEVEL_ERR, "Could not commit database dbi %s: %s", + dbpath, mdb_strerror(rc)); + goto err; + } + + return db; + +err: + if (db->env) + { + mdb_env_close(db->env); + } + pthread_key_delete(db->txn_key); + free(db); + if (rc == MDB_INVALID) + { + return DB_PRIV_DATABASE_BROKEN; + } + return NULL; +} + +void DBPrivCloseDB(DBPriv *db) +{ + assert(db != NULL); + + /* Abort LMDB transaction of the current thread. There should only be some + * transaction open when the signal handler or atexit() hook is called. */ + AbortTransaction(db); + + char *db_path = mdb_env_get_userctx(db->env); + if (db_path) + { + free(db_path); + } + if (db->env) + { + mdb_env_close(db->env); + } + + pthread_key_delete(db->txn_key); + free(db); +} + +#define EMPTY_DB 0 + +bool DBPrivClean(DBPriv *db) +{ + assert(db != NULL); + + DBTxn *txn; + const int rc = GetWriteTransaction(db, &txn); + + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_ERR, "Unable to get write transaction for '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + return false; + } + assert(txn != NULL); + assert(!txn->cursor_open); + + return (mdb_drop(txn->txn, db->dbi, EMPTY_DB) != 0); +} + +int DBPrivGetDBUsagePercentage(const char *db_path) +{ + struct stat sb; + int ret = stat(db_path, &sb); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, "Failed to get size of '%s': %s", db_path, GetErrorStr()); + return -1; + } + return (int) ((((float) sb.st_size) / LMDB_MAXSIZE) * 100); +} + +void DBPrivCommit(DBPriv *db) +{ + assert(db != NULL); + + DBTxn *db_txn = pthread_getspecific(db->txn_key); + if (db_txn != NULL && db_txn->txn != NULL) + { + assert(!db_txn->cursor_open); + const int rc = mdb_txn_commit(db_txn->txn); + CheckLMDBUsable(rc, db->env); + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_ERR, "Could not commit database transaction to '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + } + } + pthread_setspecific(db->txn_key, NULL); + free(db_txn); +} + +bool DBPrivHasKey(DBPriv *db, const void *key, int key_size) +{ + assert(db != NULL); + + MDB_val mkey, data; + DBTxn *txn; + // FIXME: distinguish between "entry not found" and "error occurred" + + int rc = GetReadTransaction(db, &txn); + if (rc == MDB_SUCCESS) + { + assert(!txn->cursor_open); + mkey.mv_data = (void *) key; + mkey.mv_size = key_size; + rc = mdb_get(txn->txn, db->dbi, &mkey, &data); + CheckLMDBUsable(rc, db->env); + if (rc != 0 && rc != MDB_NOTFOUND) + { + Log(LOG_LEVEL_ERR, "Could not read database entry from '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + AbortTransaction(db); + } + } + + return (rc == MDB_SUCCESS); +} + +int DBPrivGetValueSize(DBPriv *const db, const void *const key, const int key_size) +{ + assert(db != NULL); + assert(key_size >= 0); + + MDB_val mkey, data; + DBTxn *txn; + + data.mv_size = 0; + + int rc = GetReadTransaction(db, &txn); + if (rc == MDB_SUCCESS) + { + assert(!txn->cursor_open); + mkey.mv_data = (void *) key; + mkey.mv_size = key_size; + rc = mdb_get(txn->txn, db->dbi, &mkey, &data); + CheckLMDBUsable(rc, db->env); + if (rc && rc != MDB_NOTFOUND) + { + Log(LOG_LEVEL_ERR, "Could not read database entry from '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + AbortTransaction(db); + } + } + + size_t ret = data.mv_size; + assert(ret <= INT_MAX); + return ret; +} + +bool DBPrivRead( + DBPriv *const db, + const void *const key, + const int key_size, + void *const dest, + size_t dest_size) +{ + assert(db != NULL); + assert(key_size >= 0); + + DBTxn *txn; + bool ret = false; + + int rc = GetReadTransaction(db, &txn); + if (rc == MDB_SUCCESS) + { + MDB_val mkey, data; + assert(txn != NULL); + assert(!txn->cursor_open); + mkey.mv_data = (void *) key; + mkey.mv_size = key_size; + rc = mdb_get(txn->txn, db->dbi, &mkey, &data); + CheckLMDBUsable(rc, db->env); + if (rc == MDB_SUCCESS) + { + if (dest_size > data.mv_size) + { + dest_size = data.mv_size; + } + memcpy(dest, data.mv_data, dest_size); + ret = true; + } + else if (rc != MDB_NOTFOUND) + { + Log(LOG_LEVEL_ERR, "Could not read database entry from '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + AbortTransaction(db); + } + } + return ret; +} + +bool DBPrivWrite( + DBPriv *const db, + const void *const key, + const int key_size, + const void *const value, + const int value_size) +{ + assert(db != NULL); + assert(key_size >= 0); + + DBTxn *txn; + int rc = GetWriteTransaction(db, &txn); + if (rc == MDB_SUCCESS) + { + MDB_val mkey, data; + assert(txn != NULL); + assert(!txn->cursor_open); + mkey.mv_data = (void *) key; + mkey.mv_size = key_size; + data.mv_data = (void *)value; + data.mv_size = value_size; + rc = mdb_put(txn->txn, db->dbi, &mkey, &data, 0); + CheckLMDBUsable(rc, db->env); + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_ERR, "Could not write database entry to '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + AbortTransaction(db); + } + } + return (rc == MDB_SUCCESS); +} + +bool DBPrivOverwrite(DBPriv *db, const char *key, int key_size, const void *value, size_t value_size, + OverwriteCondition Condition, void *data) +{ + assert(db != NULL); + assert(key_size >= 0); + DBTxn *txn; + int rc = GetWriteTransaction(db, &txn); + + if (rc != MDB_SUCCESS) + { + return false; + } + + assert(txn != NULL); + assert(!txn->cursor_open); + + MDB_val mkey, orig_data; + mkey.mv_data = (void *) key; + mkey.mv_size = key_size; + rc = mdb_get(txn->txn, db->dbi, &mkey, &orig_data); + CheckLMDBUsable(rc, db->env); + if ((rc != MDB_SUCCESS) && (rc != MDB_NOTFOUND)) + { + Log(LOG_LEVEL_ERR, "Could not read database entry from '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + AbortTransaction(db); + return false; + } + + if (Condition != NULL) + { + if (rc == MDB_SUCCESS) + { + assert(orig_data.mv_size > 0); + + /* We have to copy the data because orig_data.mv_data is a pointer to + * the mmap()-ed area which can potentially have bad alignment causing + * a SIGBUS on some architectures. */ + unsigned char cur_val[orig_data.mv_size]; + memcpy(cur_val, orig_data.mv_data, orig_data.mv_size); + if (!Condition(cur_val, orig_data.mv_size, data)) + { + AbortTransaction(db); + return false; + } + } + else + { + assert(rc == MDB_NOTFOUND); + if (!Condition(NULL, 0, data)) + { + AbortTransaction(db); + return false; + } + } + } + + MDB_val new_data; + new_data.mv_data = (void *)value; + new_data.mv_size = value_size; + rc = mdb_put(txn->txn, db->dbi, &mkey, &new_data, 0); + CheckLMDBUsable(rc, db->env); + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_ERR, "Could not write database entry to '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + AbortTransaction(db); + return false; + } + DBPrivCommit(db); + return true; +} + +bool DBPrivDelete(DBPriv *const db, const void *const key, const int key_size) +{ + assert(key_size >= 0); + assert(db != NULL); + + MDB_val mkey; + DBTxn *txn; + int rc = GetWriteTransaction(db, &txn); + if (rc == MDB_SUCCESS) + { + assert(!txn->cursor_open); + mkey.mv_data = (void *) key; + mkey.mv_size = key_size; + rc = mdb_del(txn->txn, db->dbi, &mkey, NULL); + CheckLMDBUsable(rc, db->env); + if (rc == MDB_NOTFOUND) + { + Log(LOG_LEVEL_DEBUG, "Entry not found in '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + } + else if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_ERR, "Could not delete from '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + AbortTransaction(db); + } + } + return (rc == MDB_SUCCESS); +} + +DBCursorPriv *DBPrivOpenCursor(DBPriv *const db) +{ + assert(db != NULL); + + DBCursorPriv *cursor = NULL; + DBTxn *txn; + MDB_cursor *mc; + + int rc = GetWriteTransaction(db, &txn); + if (rc == MDB_SUCCESS) + { + assert(!txn->cursor_open); + rc = mdb_cursor_open(txn->txn, db->dbi, &mc); + CheckLMDBUsable(rc, db->env); + if (rc == MDB_SUCCESS) + { + cursor = xcalloc(1, sizeof(DBCursorPriv)); + cursor->db = db; + cursor->mc = mc; + txn->cursor_open = true; + } + else + { + Log(LOG_LEVEL_ERR, "Could not open cursor in '%s': %s", + (char *) mdb_env_get_userctx(db->env), mdb_strerror(rc)); + AbortTransaction(db); + } + /* txn remains with cursor */ + } + + return cursor; +} + +bool DBPrivAdvanceCursor( + DBCursorPriv *const cursor, + void **const key, + int *const key_size, + void **const value, + int *const value_size) +{ + assert(cursor != NULL); + assert(cursor->db != NULL); + + MDB_val mkey, data; + bool retval = false; + + if (cursor->curkv != NULL) + { + free(cursor->curkv); + cursor->curkv = NULL; + } + + int rc = mdb_cursor_get(cursor->mc, &mkey, &data, MDB_NEXT); + CheckLMDBUsable(rc, cursor->db->env); + if (rc == MDB_SUCCESS) + { + // Align second buffer to 64-bit boundary, to avoid alignment errors on + // certain platforms. + size_t keybuf_size = mkey.mv_size; + if (keybuf_size & 0x7) + { + keybuf_size += 8 - (keybuf_size % 8); + } + cursor->curkv = xmalloc(keybuf_size + data.mv_size); + memcpy(cursor->curkv, mkey.mv_data, mkey.mv_size); + *key = cursor->curkv; + *key_size = mkey.mv_size; + *value_size = data.mv_size; + memcpy((char *) cursor->curkv + keybuf_size, data.mv_data, data.mv_size); + *value = ((char *) cursor->curkv + keybuf_size); + retval = true; + } + else if (rc != MDB_NOTFOUND) + { + Log(LOG_LEVEL_ERR, "Could not advance cursor in '%s': %s", + (char *) mdb_env_get_userctx(cursor->db->env), mdb_strerror(rc)); + } + if (cursor->pending_delete) + { + int r2; + /* Position on key to delete */ + r2 = mdb_cursor_get(cursor->mc, &cursor->delkey, NULL, MDB_SET); + if (r2 == MDB_SUCCESS) + { + r2 = mdb_cursor_del(cursor->mc, 0); + // TODO: Should the return value be checked? + } + /* Reposition the cursor if it was valid before */ + if (rc == MDB_SUCCESS) + { + mkey.mv_data = *key; + rc = mdb_cursor_get(cursor->mc, &mkey, NULL, MDB_SET); + CheckLMDBUsable(rc, cursor->db->env); + // TODO: Should the return value be checked? + } + cursor->pending_delete = false; + } + return retval; +} + +bool DBPrivDeleteCursorEntry(DBCursorPriv *const cursor) +{ + assert(cursor != NULL); + + int rc = mdb_cursor_get(cursor->mc, &cursor->delkey, NULL, MDB_GET_CURRENT); + CheckLMDBUsable(rc, cursor->db->env); + if (rc == MDB_SUCCESS) + { + cursor->pending_delete = true; + } + return (rc == MDB_SUCCESS); +} + +bool DBPrivWriteCursorEntry( + DBCursorPriv *const cursor, const void *const value, const int value_size) +{ + assert(cursor != NULL); + assert(cursor->db != NULL); + + MDB_val data; + int rc; + + cursor->pending_delete = false; + data.mv_data = (void *) value; + data.mv_size = value_size; + + if (cursor->curkv) + { + MDB_val curkey; + curkey.mv_data = cursor->curkv; + curkey.mv_size = sizeof(cursor->curkv); + + rc = mdb_cursor_put(cursor->mc, &curkey, &data, MDB_CURRENT); + CheckLMDBUsable(rc, cursor->db->env); + if (rc != MDB_SUCCESS) + { + Log(LOG_LEVEL_ERR, "Could not write cursor entry to '%s': %s", + (char *) mdb_env_get_userctx(cursor->db->env), mdb_strerror(rc)); + } + } + else + { + Log(LOG_LEVEL_ERR, "Could not write cursor entry to '%s': cannot find current key", + (char *) mdb_env_get_userctx(cursor->db->env)); + rc = MDB_INVALID; + } + return (rc == MDB_SUCCESS); +} + +void DBPrivCloseCursor(DBCursorPriv *const cursor) +{ + assert(cursor != NULL); + assert(cursor->db != NULL); + + DBTxn *txn; + const int rc = GetWriteTransaction(cursor->db, &txn); + CF_ASSERT(rc == MDB_SUCCESS, "Could not get write transaction"); + CF_ASSERT(txn->cursor_open, "Transaction not open"); + txn->cursor_open = false; + + if (cursor->curkv) + { + free(cursor->curkv); + } + + if (cursor->pending_delete) + { + mdb_cursor_del(cursor->mc, 0); + } + + mdb_cursor_close(cursor->mc); + free(cursor); +} + +char *DBPrivDiagnose(const char *const dbpath) +{ + return StringFormat("Unable to diagnose LMDB file (not implemented) for '%s'", dbpath); +} +#endif diff --git a/libpromises/dbm_migration.c b/libpromises/dbm_migration.c new file mode 100644 index 0000000000..b5a54e3e7a --- /dev/null +++ b/libpromises/dbm_migration.c @@ -0,0 +1,76 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include + +extern const DBMigrationFunction dbm_migration_plan_lastseen[]; + + +#ifdef LMDB +bool DBMigrate(ARG_UNUSED DBHandle *db, ARG_UNUSED dbid id) +{ + return true; +} +#else +static size_t DBVersion(DBHandle *db) +{ + char version[64]; + if (ReadDB(db, "version", version, sizeof(version)) == false) + { + return 0; + } + else + { + return StringToLongDefaultOnError(version, 0); + } +} + +static const DBMigrationFunction *const dbm_migration_plans[dbid_max] = { + [dbid_lastseen] = dbm_migration_plan_lastseen +}; + +bool DBMigrate(DBHandle *db, dbid id) +{ + const DBMigrationFunction *plan = dbm_migration_plans[id]; + + if (plan) + { + size_t step_version = 0; + for (const DBMigrationFunction *step = plan; *step; step++, step_version++) + { + if (step_version == DBVersion(db)) + { + if (!(*step)(db)) + { + return false; + } + } + } + } + return true; +} +#endif diff --git a/libpromises/dbm_migration.h b/libpromises/dbm_migration.h new file mode 100644 index 0000000000..6078ffea51 --- /dev/null +++ b/libpromises/dbm_migration.h @@ -0,0 +1,34 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_DB_MIGRATION_H +#define CFENGINE_DB_MIGRATION_H + +#include + +typedef bool (*DBMigrationFunction)(DBHandle *db); + +bool DBMigrate(DBHandle *db, dbid id); + +#endif diff --git a/libpromises/dbm_migration_lastseen.c b/libpromises/dbm_migration_lastseen.c new file mode 100644 index 0000000000..9590efea6d --- /dev/null +++ b/libpromises/dbm_migration_lastseen.c @@ -0,0 +1,189 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include + +typedef struct +{ + double q; + double expect; + double var; +} QPoint0; + +#define QPOINT0_OFFSET 128 + +/* + * Structure of version 0 lastseen entry: + * + * flag | hostkey -> address | QPoint + * | | | \- 3*double + * | | \- 128 chars + * | \- N*chars + * \- 1 byte, '+' or '-' + */ + +static bool LastseenMigrationVersion0(DBHandle *db) +{ + bool errors = false; + DBCursor *cursor; + + if (!NewDBCursor(db, &cursor)) + { + return false; + } + + char *key; + void *value; + int ksize, vsize; + + while (NextDB(cursor, &key, &ksize, &value, &vsize)) + { + if (ksize == 0) + { + Log(LOG_LEVEL_INFO, "LastseenMigrationVersion0: Database structure error -- zero-length key."); + continue; + } + + /* Only look for old [+-]kH -> IP entries */ + if ((key[0] != '+') && (key[0] != '-')) + { + /* Warn about completely unexpected keys */ + + if ((key[0] != 'q') && (key[0] != 'k') && (key[0] != 'a')) + { + Log(LOG_LEVEL_INFO, "LastseenMigrationVersion0: Malformed key found '%s'", key); + } + + continue; + } + + bool incoming = key[0] == '-'; + const char *hostkey = key + 1; + + /* Only migrate sane data */ + if (vsize != QPOINT0_OFFSET + sizeof(QPoint0)) + { + Log(LOG_LEVEL_INFO, + "LastseenMigrationVersion0: invalid value size for key '%s', entry is deleted", + key); + DBCursorDeleteEntry(cursor); + continue; + } + + /* Properly align the data */ + const char *old_data_address = (const char *) value; + QPoint0 old_data_q; + memcpy(&old_data_q, (const char *) value + QPOINT0_OFFSET, + sizeof(QPoint0)); + + char hostkey_key[CF_BUFSIZE]; + snprintf(hostkey_key, CF_BUFSIZE, "k%s", hostkey); + + if (!WriteDB(db, hostkey_key, old_data_address, strlen(old_data_address) + 1)) + { + Log(LOG_LEVEL_INFO, "Unable to write version 1 lastseen entry for '%s'", key); + errors = true; + continue; + } + + char address_key[CF_BUFSIZE]; + snprintf(address_key, CF_BUFSIZE, "a%s", old_data_address); + + if (!WriteDB(db, address_key, hostkey, strlen(hostkey) + 1)) + { + Log(LOG_LEVEL_INFO, "Unable to write version 1 reverse lastseen entry for '%s'", key); + errors = true; + continue; + } + + char quality_key[CF_BUFSIZE]; + snprintf(quality_key, CF_BUFSIZE, "q%c%s", incoming ? 'i' : 'o', hostkey); + + /* + Ignore malformed connection quality data + */ + + if ((!isfinite(old_data_q.q)) + || (old_data_q.q < 0) + || (!isfinite(old_data_q.expect)) + || (!isfinite(old_data_q.var))) + { + Log(LOG_LEVEL_INFO, "Ignoring malformed connection quality data for '%s'", key); + DBCursorDeleteEntry(cursor); + continue; + } + + KeyHostSeen data = { + .lastseen = (time_t)old_data_q.q, + .Q = { + /* + Previously .q wasn't stored in database, but was calculated + every time as a difference between previous timestamp and a + new timestamp. Given we don't have this information during + the database upgrade, just assume that last reading is an + average one. + */ + .q = old_data_q.expect, + .dq = 0, + .expect = old_data_q.expect, + .var = old_data_q.var, + } + }; + + if (!WriteDB(db, quality_key, &data, sizeof(data))) + { + Log(LOG_LEVEL_INFO, "Unable to write version 1 connection quality key for '%s'", key); + errors = true; + continue; + } + + if (!DBCursorDeleteEntry(cursor)) + { + Log(LOG_LEVEL_INFO, "Unable to delete version 0 lastseen entry for '%s'", key); + errors = true; + } + } + + if (DeleteDBCursor(cursor) == false) + { + Log(LOG_LEVEL_ERR, "LastseenMigrationVersion0: Unable to close cursor"); + errors = true; + } + + if ((!errors) && (!WriteDB(db, "version", "1", sizeof("1")))) + { + errors = true; + } + + return !errors; +} + +const DBMigrationFunction dbm_migration_plan_lastseen[] = +{ + LastseenMigrationVersion0, + NULL +}; diff --git a/libpromises/dbm_priv.h b/libpromises/dbm_priv.h new file mode 100644 index 0000000000..3a813e5698 --- /dev/null +++ b/libpromises/dbm_priv.h @@ -0,0 +1,90 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_DBM_PRIV_H +#define CFENGINE_DBM_PRIV_H + +#include +/* DBM implementation is supposed to define the following structures and + * implement the following functions */ + +typedef struct DBPriv_ DBPriv; +typedef struct DBCursorPriv_ DBCursorPriv; + +const char *DBPrivGetFileExtension(void); + +#define DB_PRIV_DATABASE_BROKEN ((DBPriv *)-1) + +void DBPrivSetMaximumConcurrentTransactions(int max_txn); + +/* + * These two functions will always be called with a per-database lock held. + */ + +/* + * Should return either + * - NULL in case of generic error + * - DB_PRIV_DATABASE_BROKEN in case database file is broken, need to be moved + away and attempt to open database again should be performed. + * - valid pointer to DBPriv * in case database was opened successfully. + */ +DBPriv *DBPrivOpenDB(const char *dbpath, dbid id); +void DBPrivCloseDB(DBPriv *hdbp); +void DBPrivCommit(DBPriv *hdbp); +bool DBPrivClean(DBPriv *hdbp); + +int DBPrivGetDBUsagePercentage(const char *db_path); + +bool DBPrivHasKey(DBPriv *db, const void *key, int key_size); +int DBPrivGetValueSize(DBPriv *db, const void *key, int key_size); + +bool DBPrivRead(DBPriv *db, const void *key, int key_size, + void *dest, size_t dest_size); + +bool DBPrivWrite(DBPriv *db, const void *key, int key_size, + const void *value, int value_size); + +bool DBPrivOverwrite(DBPriv *handle, + const char *key, int key_size, + const void *value, size_t value_size, + OverwriteCondition Condition, void *data); + +bool DBPrivDelete(DBPriv *db, const void *key, int key_size); + + +DBCursorPriv *DBPrivOpenCursor(DBPriv *db); +bool DBPrivAdvanceCursor(DBCursorPriv *cursor, void **key, int *key_size, + void **value, int *value_size); +bool DBPrivDeleteCursorEntry(DBCursorPriv *cursor); +bool DBPrivWriteCursorEntry(DBCursorPriv *cursor, const void *value, int value_size); +void DBPrivCloseCursor(DBCursorPriv *cursor); + +/** + * @brief Check a database file for consistency + * @param dbpath Path to database file + * @return NULL if successful, else an error string that must be freed + */ +char *DBPrivDiagnose(const char *dbpath); + +#endif diff --git a/libpromises/dbm_quick.c b/libpromises/dbm_quick.c new file mode 100644 index 0000000000..898fd96d9f --- /dev/null +++ b/libpromises/dbm_quick.c @@ -0,0 +1,443 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* + * Implementation using QDBM + */ + +#include + +#include +#include +#include + +#ifdef QDB +# include + +struct DBPriv_ +{ + /* + * This mutex controls the access to depot, which is not thread-aware + */ + pthread_mutex_t lock; + + /* + * This mutex prevents two cursors to be active on depot at same time, as + * cursors are internal for QDBM. 'cursor_lock' is always taken before + * 'lock' to avoid deadlocks. + */ + pthread_mutex_t cursor_lock; + + DEPOT *depot; +}; + +struct DBCursorPriv_ +{ + DBPriv *db; + char *curkey; + int curkey_size; + char *curval; +}; + +/******************************************************************************/ + +static bool Lock(DBPriv *db) +{ + int ret = pthread_mutex_lock(&db->lock); + if (ret != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Unable to lock QDBM database. (pthread_mutex_lock: %s)", GetErrorStr()); + return false; + } + return true; +} + +static void Unlock(DBPriv *db) +{ + int ret = pthread_mutex_unlock(&db->lock); + if (ret != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Unable to unlock QDBM database. (pthread_mutex_unlock: %s)", GetErrorStr()); + } +} + +static bool LockCursor(DBPriv *db) +{ + int ret = pthread_mutex_lock(&db->cursor_lock); + if (ret != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Unable to obtain cursor lock for QDBM database. (pthread_mutex_lock: %s)", GetErrorStr()); + return false; + } + return true; +} + +static void UnlockCursor(DBPriv *db) +{ + int ret = pthread_mutex_unlock(&db->cursor_lock); + if (ret != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Unable to release cursor lock for QDBM database. (pthread_mutex_unlock: %s)", GetErrorStr()); + } +} + +const char *DBPrivGetFileExtension(void) +{ + return "qdbm"; +} + +void DBPrivSetMaximumConcurrentTransactions(ARG_UNUSED int max_txn) +{ +} + +DBPriv *DBPrivOpenDB(const char *filename, ARG_UNUSED dbid id) +{ + DBPriv *db = xcalloc(1, sizeof(DBPriv)); + + pthread_mutex_init(&db->lock, NULL); + pthread_mutex_init(&db->cursor_lock, NULL); + + db->depot = dpopen(filename, DP_OWRITER | DP_OCREAT, -1); + + if ((db->depot == NULL) && (dpecode == DP_EBROKEN)) + { + Log(LOG_LEVEL_ERR, "Database '%s' is broken, trying to repair...", filename); + + if (dprepair(filename)) + { + Log(LOG_LEVEL_INFO, "Successfully repaired database '%s'", filename); + } + else + { + Log(LOG_LEVEL_ERR, "Failed to repair database '%s', recreating...", filename); + return DB_PRIV_DATABASE_BROKEN; + } + + db->depot = dpopen(filename, DP_OWRITER | DP_OCREAT, -1); + } + + if (db->depot == NULL) + { + Log(LOG_LEVEL_ERR, "dpopen: Opening database '%s' failed. (dpopen: %s)", + filename, dperrmsg(dpecode)); + pthread_mutex_destroy(&db->cursor_lock); + pthread_mutex_destroy(&db->lock); + free(db); + return NULL; + } + + return db; +} + +void DBPrivCloseDB(DBPriv *db) +{ + int ret; + + if ((ret = pthread_mutex_destroy(&db->lock)) != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Lock is still active during QDBM database handle close. (pthread_mutex_destroy: %s)", GetErrorStr()); + } + + if ((ret = pthread_mutex_destroy(&db->cursor_lock)) != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Cursor lock is still active during QDBM database handle close. (pthread_mutex_destroy: %s)", GetErrorStr()); + } + + if (!dpclose(db->depot)) + { + Log(LOG_LEVEL_ERR, "Unable to close QDBM database. (dpclose: %s)", dperrmsg(dpecode)); + } + + free(db); +} + +void DBPrivCommit(ARG_UNUSED DBPriv *db) +{ +} + +bool DBPrivClean(DBPriv *db) +{ + if (!Lock(db)) + { + return false; + } + + if (!dpiterinit(db->depot)) + { + Log(LOG_LEVEL_ERR, "Could not initialize QuickDB iterator. (dpiterinit: %s)", dperrmsg(dpecode)); + Unlock(db); + return false; + } + + char *key = NULL; + while((key = dpiternext(db->depot, NULL))) + { + dpout(db->depot, key, -1); + } + + Unlock(db); + return true; +} + +int DBPrivGetDBUsagePercentage(ARG_UNUSED const char *db_path) +{ + Log(LOG_LEVEL_WARNING, "Cannot determine usage of a QuickDB database"); + return -1; +} + +bool DBPrivRead(DBPriv *db, const void *key, int key_size, void *dest, size_t dest_size) +{ + if (!Lock(db)) + { + return false; + } + + if (dpgetwb(db->depot, key, key_size, 0, dest_size, dest) == -1) + { + // FIXME: distinguish between "entry not found" and "failure to read" + + Log(LOG_LEVEL_DEBUG, "QDBM DBPrivRead: Could not read '%s', (dpgetwb: %s)", + (const char *)key, dperrmsg(dpecode)); + + Unlock(db); + return false; + } + + Unlock(db); + return true; +} + +bool DBPrivWrite(DBPriv *db, const void *key, int key_size, const void *value, int value_size) +{ + if (!Lock(db)) + { + return false; + } + + if (!dpput(db->depot, key, key_size, value, value_size, DP_DOVER)) + { + char *db_name = dpname(db->depot); + Log(LOG_LEVEL_ERR, "Could not write key to DB '%s'. (dpput: %s)", + db_name, dperrmsg(dpecode)); + free(db_name); + Unlock(db); + return false; + } + + Unlock(db); + return true; +} + +bool DBPrivOverwrite(DBPriv *db, const char *key, int key_size, const void *value, size_t value_size, + OverwriteCondition Condition, void *data) +{ + if (!Lock(db)) + { + return false; + } + + ssize_t cur_val_size = dpvsiz(db->depot, key, key_size); + bool exists = (cur_val_size != -1); + + void *cur_val = NULL; + if (exists) + { + assert(cur_val_size > 0); + cur_val = xmalloc((size_t) cur_val_size); + + if (dpgetwb(db->depot, key, key_size, 0, value_size, cur_val) == -1) + { + Log(LOG_LEVEL_DEBUG, "QDBM DBPrivRead: Could not read '%s', (dpgetwb: %s)", + (const char *)key, dperrmsg(dpecode)); + + Unlock(db); + return false; + } + } + if ((Condition != NULL) && !Condition(cur_val, cur_val_size, data)) + { + free(cur_val); + Unlock(db); + return false; + } + free(cur_val); + + if (!dpput(db->depot, key, key_size, value, value_size, DP_DOVER)) + { + char *db_name = dpname(db->depot); + Log(LOG_LEVEL_ERR, "Could not write key to DB '%s'. (dpput: %s)", + db_name, dperrmsg(dpecode)); + free(db_name); + Unlock(db); + return false; + } + Unlock(db); + return true; +} + +bool DBPrivHasKey(DBPriv *db, const void *key, int key_size) +{ + if (!Lock(db)) + { + return false; + } + + int ret = dpvsiz(db->depot, key, key_size) != -1; + + Unlock(db); + return ret; +} + +int DBPrivGetValueSize(DBPriv *db, const void *key, int key_size) +{ + if (!Lock(db)) + { + return false; + } + + int ret = dpvsiz(db->depot, key, key_size); + + Unlock(db); + return ret; +} + +bool DBPrivDelete(DBPriv *db, const void *key, int key_size) +{ + if (!Lock(db)) + { + return false; + } + + /* dpout returns false both for error and if key is not found */ + if (!dpout(db->depot, key, key_size) && dpecode != DP_ENOITEM) + { + Unlock(db); + return false; + } + + Unlock(db); + return true; +} + +DBCursorPriv *DBPrivOpenCursor(DBPriv *db) +{ + if (!LockCursor(db)) + { + return NULL; + } + + if (!Lock(db)) + { + UnlockCursor(db); + return NULL; + } + + if (!dpiterinit(db->depot)) + { + Log(LOG_LEVEL_ERR, "Could not initialize QuickDB iterator. (dpiterinit: %s)", dperrmsg(dpecode)); + Unlock(db); + UnlockCursor(db); + return NULL; + } + + DBCursorPriv *cursor = xcalloc(1, sizeof(DBCursorPriv)); + cursor->db = db; + + Unlock(db); + + /* Cursor remains locked */ + return cursor; +} + +bool DBPrivAdvanceCursor(DBCursorPriv *cursor, void **key, int *ksize, void **value, int *vsize) +{ + if (!Lock(cursor->db)) + { + return false; + } + + free(cursor->curkey); + free(cursor->curval); + + cursor->curkey = NULL; + cursor->curval = NULL; + + *key = dpiternext(cursor->db->depot, ksize); + + if (*key == NULL) + { + /* Reached the end of database */ + Unlock(cursor->db); + return false; + } + + *value = dpget(cursor->db->depot, *key, *ksize, 0, -1, vsize); + + // keep pointers for later free + cursor->curkey = *key; + cursor->curkey_size = *ksize; + cursor->curval = *value; + + Unlock(cursor->db); + return true; +} + +bool DBPrivDeleteCursorEntry(DBCursorPriv *cursor) +{ + return DBPrivDelete(cursor->db, cursor->curkey, cursor->curkey_size); +} + +bool DBPrivWriteCursorEntry(DBCursorPriv *cursor, const void *value, int value_size) +{ + return DBPrivWrite(cursor->db, cursor->curkey, cursor->curkey_size, value, value_size); +} + +void DBPrivCloseCursor(DBCursorPriv *cursor) +{ + DBPriv *db = cursor->db; + + /* FIXME: communicate the deadlock if happens */ + Lock(db); + + free(cursor->curkey); + free(cursor->curval); + free(cursor); + + Unlock(db); + /* Cursor lock was obtained in DBPrivOpenCursor */ + UnlockCursor(db); +} + +char *DBPrivDiagnose(const char *dbpath) +{ + return StringFormat("Unable to diagnose QuickDB file (not implemented) for '%s'", dbpath); +} + +#endif diff --git a/libpromises/dbm_test_api.c b/libpromises/dbm_test_api.c new file mode 100644 index 0000000000..def4d50b92 --- /dev/null +++ b/libpromises/dbm_test_api.c @@ -0,0 +1,737 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include /* lrand48_r() */ +#include /* usleep(), syscall()/gettid() */ +#include /* xstrndup() */ +#include +#include +#include +#include +#include /* StringFormat() */ + +#include + +#if !HAVE_DECL_GETTID +/* Older versions of glibc don't provide a wrapper function for the gettid() + * syscall. */ +#include +static inline pid_t gettid() +{ + return syscall(SYS_gettid); +} +#endif + +typedef struct DBItem { + char *key; + size_t val_size; + void *val; +} DBItem; + +DBItem *DBItemNew(const char *key, size_t val_size, void *val) +{ + DBItem *ret = xmalloc(sizeof(DBItem)); + ret->key = xstrdup(key); + ret->val_size = val_size; + ret->val = xmemdup(val, val_size); + + return ret; +} + +static void DBItemDestroy(DBItem *item) +{ + if (item != NULL) + { + free(item->key); + free(item->val); + free(item); + } +} + +static __thread struct drand48_data rng_data; + +static void InitializeRNG(long int seed) +{ + srand48_r(seed, &rng_data); +} + +static long GetRandomNumber(long limit) +{ + long rnd_val; + lrand48_r(&rng_data, &rnd_val); /* generates a value in the [0, 2^31) interval */ + const long random_max = ((0x80000000 - 1) / limit) * limit; + while (rnd_val > random_max) + { + /* got a bad value past the greatest multiple of the limit interval, + * retry (see "modulo bias" if this is unclear) */ + lrand48_r(&rng_data, &rnd_val); + } + return rnd_val % limit; +} + + +static Seq *GetDBKeys(DBHandle *db) +{ + DBCursor *cur; + bool success = NewDBCursor(db, &cur); + if (!success) + { + return NULL; + } + + Seq *ret = SeqNew(16, free); + while (success) + { + int key_size; + char *key; + int val_size; + void *val; + success = NextDB(cur, &key, &key_size, &val, &val_size); + if (success) + { + SeqAppend(ret, xstrndup(key, key_size)); + } + } + DeleteDBCursor(cur); + + return ret; +} + +/* TODO: LoadDBIntoMap() */ +static Seq *LoadDB(DBHandle *db, size_t limit) +{ + DBCursor *cur; + bool success = NewDBCursor(db, &cur); + if (!success) + { + return NULL; + } + + Seq *ret = SeqNew(16, DBItemDestroy); + size_t remaining = (limit != 0) ? limit : SIZE_MAX; + while (success && (remaining > 0)) + { + int key_size; + char *key; + int val_size; + void *val; + success = NextDB(cur, &key, &key_size, &val, &val_size); + if (success) + { + SeqAppend(ret, DBItemNew(key, val_size, val)); + remaining--; + } + } + DeleteDBCursor(cur); + + return ret; +} + + +void DoRandomReads(dbid db_id, + int keys_refresh_s, long min_interval_ms, long max_interval_ms, + bool *terminate) +{ + assert(terminate != NULL); + + InitializeRNG((long) gettid()); + + DBHandle *db; + bool success = OpenDB(&db, db_id); + assert(success); + + Seq *keys = GetDBKeys(db); + assert(keys != NULL); + CloseDB(db); + + size_t n_keys = SeqLength(keys); + unsigned long acc_sleeps_ms = 0; + while (!*terminate) + { + long rnd = GetRandomNumber(max_interval_ms - min_interval_ms); + unsigned long sleep_for_ms = min_interval_ms + rnd; + int ret = usleep(sleep_for_ms * 1000); + acc_sleeps_ms += sleep_for_ms; + if (ret == -1) + { + if (errno == EINTR) + { + continue; + } + /* else */ + /* Should never happen. */ + Log(LOG_LEVEL_ERR, "Failed to usleep() for %ld ms: %s", + (min_interval_ms + rnd) * 1000, GetErrorStr()); + SeqDestroy(keys); + return; + } + rnd = GetRandomNumber(n_keys); + unsigned char val[1024]; + char *key = SeqAt(keys, rnd); + + bool success = OpenDB(&db, db_id); + assert(success); + + success = ReadDB(db, key, val, sizeof(val)); + assert(success || !HasKeyDB(db, key, strlen(key))); + + if (acc_sleeps_ms > (keys_refresh_s * 1000)) + { + SeqDestroy(keys); + keys = GetDBKeys(db); + assert(keys != NULL); + n_keys = SeqLength(keys); + acc_sleeps_ms = 0; + } + CloseDB(db); + } + SeqDestroy(keys); +} + +struct ReadParams { + dbid db_id; + int keys_refresh_s; long min_interval_ms; long max_interval_ms; + bool *terminate; +}; + +static void *DoRandomReadsRoutine(void *data) +{ + struct ReadParams *params = data; + DoRandomReads(params->db_id, + params->keys_refresh_s, params->min_interval_ms, params->max_interval_ms, + params->terminate); + /* Always return NULL, pthread_create() requires a function which returns + (void *) */ + return NULL; +} + + +static bool PruneDB(DBHandle *db, const char *prefix, StringSet *written_keys) +{ + DBCursor *cur; + bool success = NewDBCursor(db, &cur); + assert(success); + + bool have_next = success; + while (success && have_next) + { + int key_size; + char *key; + int val_size; + void *val; + have_next = NextDB(cur, &key, &key_size, &val, &val_size); + if (have_next && StringStartsWith(key, prefix)) + { + success = DBCursorDeleteEntry(cur); + if (success) + { + StringSetRemove(written_keys, key); + } + } + } + DeleteDBCursor(cur); + + return success; +} + +void DoRandomWrites(dbid db_id, + int sample_size_pct, + int prune_interval_s, long min_interval_ms, long max_interval_ms, + bool *terminate) +{ + assert(terminate != NULL); + + InitializeRNG((long) gettid()); + + DBHandle *db; + bool success = OpenDB(&db, db_id); + assert(success); + + Seq *items = LoadDB(db, 0); + assert(items != NULL); + CloseDB(db); + + const size_t n_items = SeqLength(items); + const size_t n_samples = (n_items * sample_size_pct) / 100; + assert(n_samples > 0); + const size_t sample_nths = n_items / n_samples; /* see below */ + StringSet *written_keys = StringSetNew(); + unsigned long acc_sleeps_ms = 0; + while (!*terminate) + { + long rnd = GetRandomNumber(max_interval_ms - min_interval_ms); + unsigned long sleep_for_ms = min_interval_ms + rnd; + int ret = usleep(sleep_for_ms * 1000); + acc_sleeps_ms += sleep_for_ms; + if (ret == -1) + { + if (errno == EINTR) + { + continue; + } + /* else */ + /* Should never happen. */ + Log(LOG_LEVEL_ERR, "Failed to usleep() for %ld ms: %s", + (min_interval_ms + rnd) * 1000, GetErrorStr()); + SeqDestroy(items); + return; + } + + /* We only want to use a sample_size portion of all the items given by + * sample_size_pct. However, instead of picking a random number in the + * 0-sample_size range, we split the full range of all items into + * (n_items / n_samples)-ths and then use the first item from the + * respective nth. For example, if using 30% of 10 items, we would only + * use the items at indices 0, 3 and 6. + * + * This potentially gives us better sample from the original set where + * similar items are likely to be next to each other. */ + rnd = GetRandomNumber(n_items); + size_t idx = ((rnd * n_samples) / n_items) * sample_nths; + DBItem *item = SeqAt(items, idx); + + success = OpenDB(&db, db_id); + assert(success); + + /* Derive a key for our new item from the thread ID and the original + * item's key so that we don't mess with the original item and we can + * clean after ourselves (LMDB has a limit of 511 bytes for the key). */ + char *key = StringFormat("test_%ju_%.400s", (uintmax_t) gettid(), item->key); + success = WriteDB(db, key, item->val, item->val_size); + assert(success); + StringSetAdd(written_keys, key); /* takes ownership of key */ + + if (acc_sleeps_ms > (prune_interval_s * 1000)) + { + char *key_prefix = StringFormat("test_%ju_", (uintmax_t) gettid()); + success = PruneDB(db, key_prefix, written_keys); + free(key_prefix); + assert(success); + acc_sleeps_ms = 0; + } + CloseDB(db); + } + + success = OpenDB(&db, db_id); + assert(success); + + /* Clean after ourselves. */ + SetIterator iter = StringSetIteratorInit(written_keys); + char *key; + while ((key = StringSetIteratorNext(&iter)) != NULL) + { + success = DeleteDB(db, key); + assert(success); + } + CloseDB(db); + + SeqDestroy(items); +} + +struct WriteParams { + dbid db_id; + int sample_size_pct; + int prune_interval_s; long min_interval_ms; long max_interval_ms; + bool *terminate; +}; + +static void *DoRandomWritesRoutine(void *data) +{ + struct WriteParams *params = data; + DoRandomWrites(params->db_id, + params->sample_size_pct, + params->prune_interval_s, params->min_interval_ms, params->max_interval_ms, + params->terminate); + /* Always return NULL, pthread_create() requires a function which returns + (void *) */ + return NULL; +} + + +void DoRandomIterations(dbid db_id, + long min_interval_ms, long max_interval_ms, + bool *terminate) +{ + assert(terminate != NULL); + + InitializeRNG((long)gettid()); + + while (!terminate) + { + long rnd = GetRandomNumber(max_interval_ms - min_interval_ms); + unsigned long sleep_for_ms = min_interval_ms + rnd; + int ret = usleep(sleep_for_ms * 1000); + if (ret == -1) + { + if (errno == EINTR) + { + continue; + } + /* else */ + /* Should never happen. */ + Log(LOG_LEVEL_ERR, "Failed to usleep() for %ld ms: %s", + (min_interval_ms + rnd) * 1000, GetErrorStr()); + return; + } + + DBHandle *db; + bool success = OpenDB(&db, db_id); + assert(success); + + DBCursor *cur; + success = NewDBCursor(db, &cur); + assert(success); + + while (success) + { + int key_size; + char *key; + int val_size; + void *val; + success = NextDB(cur, &key, &key_size, &val, &val_size); + } + DeleteDBCursor(cur); + CloseDB(db); + } +} + +struct IterParams { + dbid db_id; + long min_interval_ms; long max_interval_ms; + bool *terminate; +}; + +static void *DoRandomIterationsRoutine(void *data) +{ + struct IterParams *params = data; + DoRandomIterations(params->db_id, + params->min_interval_ms, params->max_interval_ms, + params->terminate); + return NULL; +} + + +struct DBLoadSimulation_ { + struct ReadParams read_params; + pthread_t read_th; + bool read_th_started; + bool read_th_terminate; + + struct WriteParams write_params; + pthread_t write_th; + bool write_th_started; + bool write_th_terminate; + + struct IterParams iter_params; + pthread_t iter_th; + bool iter_th_started; + bool iter_th_terminate; +}; + +DBLoadSimulation *SimulateDBLoad(dbid db_id, + int read_keys_refresh_s, long read_min_interval_ms, long read_max_interval_ms, + int write_sample_size_pct, + int write_prune_interval_s, long write_min_interval_ms, long write_max_interval_ms, + long iter_min_interval_ms, long iter_max_interval_ms) +{ + /* Try to open the DB as a safety check. */ + DBHandle *db; + bool success = OpenDB(&db, db_id); + if (!success) + { + /* Not a nice log message, but this is testing/debugging code normal + * users should never run and face. */ + Log(LOG_LEVEL_ERR, "Failed to open DB with ID %d", db_id); + return NULL; + } + CloseDB(db); + + DBLoadSimulation *simulation = xcalloc(sizeof(DBLoadSimulation), 1); + simulation->read_params.db_id = db_id; + simulation->read_params.terminate = &(simulation->read_th_terminate); + simulation->read_params.keys_refresh_s = read_keys_refresh_s; + simulation->read_params.min_interval_ms = read_min_interval_ms; + simulation->read_params.max_interval_ms = read_max_interval_ms; + + simulation->write_params.db_id = db_id; + simulation->write_params.terminate = &(simulation->write_th_terminate); + simulation->write_params.sample_size_pct = write_sample_size_pct; + simulation->write_params.prune_interval_s = write_prune_interval_s; + simulation->write_params.min_interval_ms = write_min_interval_ms; + simulation->write_params.max_interval_ms = write_max_interval_ms; + + simulation->iter_params.db_id = db_id; + simulation->iter_params.terminate = &(simulation->iter_th_terminate); + simulation->iter_params.min_interval_ms = iter_min_interval_ms; + simulation->iter_params.max_interval_ms = iter_max_interval_ms; + + if ((simulation->read_params.keys_refresh_s != 0) || + (simulation->read_params.min_interval_ms != 0) || + (simulation->read_params.max_interval_ms != 0)) + { + int ret = pthread_create(&(simulation->read_th), NULL, + DoRandomReadsRoutine, + &(simulation->read_params)); + simulation->read_th_started = (ret == 0); + if (!simulation->read_th_started) + { + Log(LOG_LEVEL_ERR, "Failed to start read simulation thread: %s", + GetErrorStrFromCode(ret)); + } + } + + if ((simulation->write_params.prune_interval_s != 0) || + (simulation->write_params.min_interval_ms != 0) || + (simulation->write_params.max_interval_ms != 0)) + { + int ret = pthread_create(&(simulation->write_th), NULL, + DoRandomWritesRoutine, + &(simulation->write_params)); + simulation->write_th_started = (ret == 0); + if (!simulation->write_th_started) + { + Log(LOG_LEVEL_ERR, "Failed to start write simulation thread: %s", + GetErrorStrFromCode(ret)); + } + } + + if ((simulation->iter_params.min_interval_ms != 0) || + (simulation->iter_params.max_interval_ms != 0)) + { + int ret = pthread_create(&(simulation->iter_th), NULL, + DoRandomIterationsRoutine, + &(simulation->iter_params)); + simulation->iter_th_started = (ret == 0); + if (!simulation->iter_th_started) + { + Log(LOG_LEVEL_ERR, "Failed to start iteration simulation thread: %s", + GetErrorStrFromCode(ret)); + } + } + + if (!simulation->read_th_started && + !simulation->write_th_started && + !simulation->iter_th_started) + { + Log(LOG_LEVEL_ERR, "No simulation running"); + free(simulation); + return NULL; + } + + return simulation; +} + +void StopSimulation(DBLoadSimulation *simulation) +{ + /* Signal threads to terminate. */ + simulation->read_th_terminate = true; + simulation->write_th_terminate = true; + simulation->iter_th_terminate = true; + + int ret; + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to get real time clock: %s", GetErrorStr()); + Log(LOG_LEVEL_NOTICE, "Joining simulation threads with no timeout"); + if (simulation->read_th_started) + { + ret = pthread_join(simulation->read_th, NULL); + simulation->read_th_started = (ret == 0); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to join read simulation thread: %s", + GetErrorStrFromCode(ret)); + } + } + if (simulation->write_th_started) + { + ret = pthread_join(simulation->write_th, NULL); + simulation->write_th_started = (ret == 0); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to join write simulation thread: %s", + GetErrorStrFromCode(ret)); + } + } + if (simulation->iter_th_started) + { + ret = pthread_join(simulation->iter_th, NULL); + simulation->iter_th_started = (ret == 0); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to join iteration simulation thread: %s", + GetErrorStrFromCode(ret)); + } + } + } + + /* Give at most 5 seconds to threads to terminate. */ + ts.tv_sec += 5; + if (simulation->read_th_started) + { + ret = pthread_timedjoin_np(simulation->read_th, NULL, &ts); + simulation->read_th_started = (ret != 0); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to join read simulation thread: %s", GetErrorStrFromCode(ret)); + } + } + if (simulation->write_th_started) + { + ret = pthread_timedjoin_np(simulation->write_th, NULL, &ts); + simulation->write_th_started = (ret != 0); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to join write simulation thread: %s", GetErrorStrFromCode(ret)); + } + } + if (simulation->iter_th_started) + { + ret = pthread_timedjoin_np(simulation->iter_th, NULL, &ts); + simulation->iter_th_started = (ret != 0); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to join iteration simulation thread: %s", + GetErrorStrFromCode(ret)); + } + } + + if (simulation->read_th_started || + simulation->write_th_started || + simulation->iter_th_started) + { + Log(LOG_LEVEL_ERR, "Failed to stop simulation, leaking simulation data"); + } + else + { + free(simulation); + } +} + + +struct DBFilament_ { + dbid db_id; + StringSet *items; +}; + +DBFilament *FillUpDB(dbid db_id, int usage_pct) +{ + DBHandle *db; + bool success = OpenDB(&db, db_id); + if (!success) + { + Log(LOG_LEVEL_ERR, "Failed to open DB with ID %d", db_id); + return NULL; + } + + int usage = GetDBUsagePercentage(db); + if (usage == -1) + { + Log(LOG_LEVEL_ERR, "Cannot determine usage of the DB with ID %d", db_id); + CloseDB(db); + return NULL; + } + + /* We need just one item. */ + Seq *items = LoadDB(db, 1); + if ((items == NULL) || (SeqLength(items) == 0)) + { + Log(LOG_LEVEL_ERR, "No DB item to use as a template for fillament"); + CloseDB(db); + return NULL; + } + DBItem *item = SeqAt(items, 0); + CloseDB(db); + + StringSet *added_keys = StringSetNew(); + size_t iter_idx = 0; + pid_t tid = gettid(); + while (usage < usage_pct) + { + success = OpenDB(&db, db_id); + assert(success); + + /* Derive a key for our new item from an index, the thread ID and the + * original item's key so that we don't mess with the original item and + * we can clean after ourselves (LMDB has a limit of 511 bytes for the + * key). */ + /* Add 1000 items in each iteration so that each iteration makes some + * difference in the DB usage and we don't have to do so many + * iterations. */ + for (size_t i = 0; i < 1000; i++) + { + char *key = StringFormat("test_%ju_%.200s_%zd_%zd", (uintmax_t) tid, item->key, iter_idx, i); + success = WriteDB(db, key, item->val, item->val_size); + assert(success); + StringSetAdd(added_keys, key); /* takes ownership of key */ + } + iter_idx++; + usage = GetDBUsagePercentage(db); + assert(usage != -1); /* didn't happen at first, should always work */ + + CloseDB(db); + } + + SeqDestroy(items); + DBFilament *ret = xmalloc(sizeof(DBFilament)); + ret->db_id = db_id; + ret->items = added_keys; + return ret; +} + +void RemoveFilament(DBFilament *filament) +{ + if (filament == NULL) + { + /* Nothing to do. */ + return; + } + if (StringSetSize(filament->items) == 0) + { + StringSetDestroy(filament->items); + free(filament); + } + + DBHandle *db; + bool success = OpenDB(&db, filament->db_id); + if (!success) + { + Log(LOG_LEVEL_ERR, "Failed to open DB with ID %d", filament->db_id); + return; + } + + SetIterator iter = StringSetIteratorInit(filament->items); + char *key; + while ((key = StringSetIteratorNext(&iter)) != NULL) + { + success = DeleteDB(db, key); + assert(success); + } + + StringSetDestroy(filament->items); + free(filament); + CloseDB(db); +} diff --git a/libpromises/dbm_test_api.h b/libpromises/dbm_test_api.h new file mode 100644 index 0000000000..ffda177d75 --- /dev/null +++ b/libpromises/dbm_test_api.h @@ -0,0 +1,57 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_DBM_TEST_API_H +#define CFENGINE_DBM_TEST_API_H + +#include +#include + +void DoRandomReads(dbid db_id, + int keys_refresh_s, long min_interval_ms, long max_interval_ms, + bool *terminate); + +void DoRandomWrites(dbid db_id, + int sample_size_pct, + int prune_interval_s, long min_interval_ms, long max_interval_ms, + bool *terminate); + +void DoRandomIterations(dbid db_id, + long min_interval_ms, long max_interval_ms, + bool *terminate); + +typedef struct DBLoadSimulation_ DBLoadSimulation; +DBLoadSimulation *SimulateDBLoad(dbid db_id, + int read_keys_refresh_s, long read_min_interval_ms, long read_max_interval_ms, + int write_sample_size_pct, + int write_prune_interval_s, long write_min_interval_ms, long write_max_interval_ms, + long iter_min_interval_ms, long iter_max_interval_ms); + +void StopSimulation(DBLoadSimulation *simulation); + +typedef struct DBFilament_ DBFilament; +DBFilament *FillUpDB(dbid db_id, int usage_pct); +void RemoveFilament(DBFilament *filament); + +#endif /* CFENGINE_DBM_TEST_API_H */ diff --git a/libpromises/dbm_tokyocab.c b/libpromises/dbm_tokyocab.c new file mode 100644 index 0000000000..882f9b9246 --- /dev/null +++ b/libpromises/dbm_tokyocab.c @@ -0,0 +1,516 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* + * Implementation using Tokyo Cabinet hash API. + */ + +#include + +#include +#include +#include + +#ifdef TCDB + +# include +# include + +struct DBPriv_ +{ + /* + * This mutex prevents destructive modifications of the database (removing + * records) while the cursor is active on it. + */ + pthread_mutex_t cursor_lock; + + TCHDB *hdb; +}; + +struct DBCursorPriv_ +{ + DBPriv *db; + + char *current_key; + int current_key_size; + char *curval; + + /* + * Removing a key underneath the active cursor stops the database iteration, + * so if key needs to be deleted while database is iterated, this fact is + * remembered and once iterator advances to next key, this pending delete is + * executed. + * + * Writes to key underneath the active cursor are safe, so only deletes are + * tracked. + */ + bool pending_delete; +}; + +/******************************************************************************/ + +static bool LockCursor(DBPriv *db) +{ + int ret = pthread_mutex_lock(&db->cursor_lock); + if (ret != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Unable to obtain cursor lock for Tokyo Cabinet database. (pthread_mutex_lock: %s)", GetErrorStr()); + return false; + } + return true; +} + +static void UnlockCursor(DBPriv *db) +{ + int ret = pthread_mutex_unlock(&db->cursor_lock); + if (ret != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Unable to release cursor lock for Tokyo Cabinet database. (pthread_mutex_unlock: %s)", + GetErrorStr()); + } +} + +const char *DBPrivGetFileExtension(void) +{ + return "tcdb"; +} + +static const char *ErrorMessage(TCHDB *hdb) +{ + return tchdberrmsg(tchdbecode(hdb)); +} + +static bool OpenTokyoDatabase(const char *filename, TCHDB **hdb) +{ + *hdb = tchdbnew(); + + if (!tchdbsetmutex(*hdb)) + { + return false; + } + + if (!tchdbopen(*hdb, filename, HDBOWRITER | HDBOCREAT)) + { + return false; + } + + static int threshold = -1; /* GLOBAL_X */ + + if (threshold == -1) + { + /** + Optimize always if TCDB_OPTIMIZE_PERCENT is equal to 100 + Never optimize if TCDB_OPTIMIZE_PERCENT is equal to 0 + */ + const char *perc = getenv("TCDB_OPTIMIZE_PERCENT"); + if (perc != NULL) + { + /* Environment variable exists */ + char *end; + long result = strtol(perc, &end, 10); + + /* Environment variable is a number and in 0..100 range */ + if (!*end && result >-1 && result < 101) + { + threshold = 100 - (int)result; + } + else + { + /* This corresponds to 1% */ + threshold = 99; + } + } + else + { + /* This corresponds to 1% */ + threshold = 99; + } + } + if ((threshold != 100) && (threshold == 0 || (int)(rand()%threshold) == 0)) + { + if (!tchdboptimize(*hdb, -1, -1, -1, false)) + { + tchdbclose(*hdb); + return false; + } + } + + return true; +} + +void DBPrivSetMaximumConcurrentTransactions(ARG_UNUSED int max_txn) +{ +} + +DBPriv *DBPrivOpenDB(const char *dbpath, ARG_UNUSED dbid id) +{ + DBPriv *db = xcalloc(1, sizeof(DBPriv)); + + pthread_mutex_init(&db->cursor_lock, NULL); + + if (!OpenTokyoDatabase(dbpath, &db->hdb)) + { + Log(LOG_LEVEL_ERR, "Could not open Tokyo database at path '%s'. (OpenTokyoDatabase: %s)", + dbpath, ErrorMessage(db->hdb)); + + int errcode = tchdbecode(db->hdb); + + if(errcode != TCEMETA && errcode != TCEREAD) + { + goto err; + } + + tchdbdel(db->hdb); + + return DB_PRIV_DATABASE_BROKEN; + } + + return db; + +err: + pthread_mutex_destroy(&db->cursor_lock); + tchdbdel(db->hdb); + free(db); + return NULL; +} + +void DBPrivCloseDB(DBPriv *db) +{ + int ret; + + if ((ret = pthread_mutex_destroy(&db->cursor_lock)) != 0) + { + errno = ret; + Log(LOG_LEVEL_ERR, "Unable to destroy mutex during Tokyo Cabinet database handle close. (pthread_mutex_destroy: %s)", + GetErrorStr()); + } + + if (!tchdbclose(db->hdb)) + { + Log(LOG_LEVEL_ERR, "Closing database failed. (tchdbclose: %s)", ErrorMessage(db->hdb)); + } + + tchdbdel(db->hdb); + free(db); +} + +void DBPrivCommit(ARG_UNUSED DBPriv *db) +{ +} + +bool DBPrivClean(DBPriv *db) +{ + DBCursorPriv *cursor = DBPrivOpenCursor(db); + + if (!cursor) + { + return false; + } + + void *key; + int key_size; + void *value; + int value_size; + + while ((DBPrivAdvanceCursor(cursor, &key, &key_size, &value, &value_size))) + { + DBPrivDeleteCursorEntry(cursor); + } + + DBPrivCloseCursor(cursor); + + return true; +} + +int DBPrivGetDBUsagePercentage(ARG_UNUSED const char *db_path) +{ + Log(LOG_LEVEL_WARNING, "Cannot determine usage of a TokyoCabinet database"); + return -1; +} + +bool DBPrivHasKey(DBPriv *db, const void *key, int key_size) +{ + // FIXME: distinguish between "entry not found" and "error occurred" + + return tchdbvsiz(db->hdb, key, key_size) != -1; +} + +int DBPrivGetValueSize(DBPriv *db, const void *key, int key_size) +{ + return tchdbvsiz(db->hdb, key, key_size); +} + +bool DBPrivRead(DBPriv *db, const void *key, int key_size, void *dest, size_t dest_size) +{ + if (tchdbget3(db->hdb, key, key_size, dest, dest_size) == -1) + { + if (tchdbecode(db->hdb) != TCENOREC) + { + Log(LOG_LEVEL_ERR, "Could not read key '%s': (tchdbget3: %s)", (const char *)key, ErrorMessage(db->hdb)); + } + return false; + } + + return true; +} + +static bool Write(TCHDB *hdb, const void *key, int key_size, const void *value, int value_size) +{ + if (!tchdbput(hdb, key, key_size, value, value_size)) + { + Log(LOG_LEVEL_ERR, "Could not write key to Tokyo path '%s'. (tchdbput: %s)", + tchdbpath(hdb), ErrorMessage(hdb)); + return false; + } + return true; +} + +static bool Delete(TCHDB *hdb, const void *key, int key_size) +{ + if (!tchdbout(hdb, key, key_size) && tchdbecode(hdb) != TCENOREC) + { + Log(LOG_LEVEL_ERR, "Could not delete Tokyo key. (tchdbout: %s)", + ErrorMessage(hdb)); + return false; + } + + return true; +} + +/* + * This one has to be locked against cursor, or interaction between + * write/pending delete might yield surprising results. + */ +bool DBPrivWrite(DBPriv *db, const void *key, int key_size, const void *value, int value_size) +{ + /* FIXME: get a cursor and see what is the current key */ + + int ret = Write(db->hdb, key, key_size, value, value_size); + + return ret; +} + +bool DBPrivOverwrite(DBPriv *db, const char *key, int key_size, const void *value, size_t value_size, + OverwriteCondition Condition, void *data) +{ + ssize_t cur_val_size = tchdbvsiz(db->hdb, key, key_size); + void *cur_val = NULL; + bool exists = (cur_val_size > 0); + + if (exists) + { + assert(cur_val_size > 0); + cur_val = xmalloc(cur_val_size); + if (tchdbget3(db->hdb, key, key_size, cur_val, cur_val_size) == -1) + { + /* If exists, we should never get the TCENOREC error. */ + assert(tchdbecode(db->hdb) != TCENOREC); + + Log(LOG_LEVEL_ERR, "Could not read key '%s': (tchdbget3: %s)", (const char *)key, ErrorMessage(db->hdb)); + free(cur_val); + return false; + } + } + if ((Condition != NULL) && !Condition(cur_val, cur_val_size, data)) + { + free(cur_val); + return false; + } + free(cur_val); + + return Write(db->hdb, key, key_size, value, value_size); +} + +/* + * This one has to be locked against cursor -- deleting entries might interrupt + * iteration. + */ +bool DBPrivDelete(DBPriv *db, const void *key, int key_size) +{ + if (!LockCursor(db)) + { + return false; + } + + int ret = Delete(db->hdb, key, key_size); + + UnlockCursor(db); + return ret; +} + +DBCursorPriv *DBPrivOpenCursor(DBPriv *db) +{ + if (!LockCursor(db)) + { + return false; + } + + DBCursorPriv *cursor = xcalloc(1, sizeof(DBCursorPriv)); + cursor->db = db; + + /* Cursor remains locked */ + return cursor; +} + +bool DBPrivAdvanceCursor(DBCursorPriv *cursor, void **key, int *key_size, + void **value, int *value_size) +{ + *key = tchdbgetnext3(cursor->db->hdb, + cursor->current_key, cursor->current_key_size, + key_size, (const char **)value, value_size); + + /* + * If there is pending delete on the key, apply it + */ + if (cursor->pending_delete) + { + Delete(cursor->db->hdb, cursor->current_key, cursor->current_key_size); + } + + /* This will free the value as well: tchdbgetnext3 returns single allocated + * chunk of memory */ + + free(cursor->current_key); + + cursor->current_key = *key; + cursor->current_key_size = *key_size; + cursor->pending_delete = false; + + return *key != NULL; +} + +bool DBPrivDeleteCursorEntry(DBCursorPriv *cursor) +{ + cursor->pending_delete = true; + return true; +} + +bool DBPrivWriteCursorEntry(DBCursorPriv *cursor, const void *value, int value_size) +{ + /* + * If a pending deletion of entry has been requested, cancel it + */ + cursor->pending_delete = false; + + return Write(cursor->db->hdb, cursor->current_key, cursor->current_key_size, + value, value_size); +} + +void DBPrivCloseCursor(DBCursorPriv *cursor) +{ + DBPriv *db = cursor->db; + + if (cursor->pending_delete) + { + Delete(db->hdb, cursor->current_key, cursor->current_key_size); + } + + free(cursor->current_key); + free(cursor); + + /* Cursor lock was obtained in DBPrivOpenCursor */ + UnlockCursor(db); +} + + +char *DBPrivDiagnose(const char *dbpath) +{ +#define SWAB64(num) \ + ( \ + ((num & 0x00000000000000ffULL) << 56) | \ + ((num & 0x000000000000ff00ULL) << 40) | \ + ((num & 0x0000000000ff0000ULL) << 24) | \ + ((num & 0x00000000ff000000ULL) << 8) | \ + ((num & 0x000000ff00000000ULL) >> 8) | \ + ((num & 0x0000ff0000000000ULL) >> 24) | \ + ((num & 0x00ff000000000000ULL) >> 40) | \ + ((num & 0xff00000000000000ULL) >> 56) \ + ) + + static const char *const MAGIC="ToKyO CaBiNeT"; + + FILE *fp = fopen(dbpath, "r"); + if(!fp) + { + return StringFormat("Error opening file '%s': %s", dbpath, strerror(errno)); + } + + if(fseek(fp, 0, SEEK_END) != 0) + { + fclose(fp); + return StringFormat("Error seeking to end: %s\n", strerror(errno)); + } + + long size = ftell(fp); + if(size < 256) + { + fclose(fp); + return StringFormat("Seek-to-end size less than minimum required: %ld", size); + } + + char hbuf[256]; + memset(hbuf, 0, sizeof(hbuf)); + + if(fseek(fp, 0, SEEK_SET) != 0) + { + fclose(fp); + return StringFormat("Error seeking to offset 256: %s", strerror(errno)); + } + + if(fread(&hbuf, 256, 1, fp) != 1) + { + fclose(fp); + return StringFormat("Error reading 256 bytes: %s\n", strerror(errno)); + } + fclose(fp); + + if(strncmp(hbuf, MAGIC, strlen(MAGIC)) != 0) + { + return StringFormat("Magic string mismatch"); + } + + uint64_t declared_size = 0; + /* Read file size from tchdb header. It is stored in little endian. */ + memcpy(&declared_size, &hbuf[56], sizeof(uint64_t)); + if (declared_size == (uint64_t) size) + { + return NULL; // all is well + } + else + { + declared_size = SWAB64(declared_size); + if (declared_size == (uint64_t) size) + { + return StringFormat("Endianness mismatch, declared size SWAB64 '%ju' equals seek-to-end size '%ld'", (uintmax_t) declared_size, size); + } + else + { + return StringFormat("Size mismatch, declared size SWAB64 '%ju', seek-to-end-size '%ld'", (uintmax_t) declared_size, size); + } + } +} + +#endif diff --git a/libpromises/enterprise_extension.sed b/libpromises/enterprise_extension.sed new file mode 100644 index 0000000000..ab63bcc6d3 --- /dev/null +++ b/libpromises/enterprise_extension.sed @@ -0,0 +1,28 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +s,.*BEGIN_MARKER.*,/* #### THIS FILE WAS AUTOGENERATED FROM extensions_template.[ch].pre #### */,; +s/xEXTENSIONx/ENTERPRISE/g; +s/XextensionX/enterprise/g; +s/xExtensionX/Enterprise/g; diff --git a/libpromises/enterprise_stubs.c b/libpromises/enterprise_stubs.c new file mode 100644 index 0000000000..e269aeffbc --- /dev/null +++ b/libpromises/enterprise_stubs.c @@ -0,0 +1,230 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#include +#include +#include +#include +#include // StringCopy() + +#include + +/* + * This module contains numeruous functions which don't use all their parameters + * + * Temporarily, in order to avoid cluttering output with thousands of warnings, + * this module is excempted from producing warnings about unused function + * parameters. + * + * Please remove this #pragma ASAP and provide ARG_UNUSED declarations for + * unused parameters. + */ +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +extern int PR_KEPT; +extern int PR_REPAIRED; +extern int PR_NOTKEPT; + +ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, Nova_Initialize, EvalContext *, ctx) +{ +} + +/* all agents: generic_agent.c */ + +ENTERPRISE_FUNC_0ARG_DEFINE_STUB(const char *, GetConsolePrefix) +{ + return "cf3"; +} + + +/* all agents: sysinfo.c */ + +ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, EnterpriseContext, ARG_UNUSED EvalContext *, ctx) +{ +} + + +/* all agents: logging.c */ + + +ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, LogTotalCompliance, const char *, version, int, background_tasks) +{ + double total = (double) (PR_KEPT + PR_NOTKEPT + PR_REPAIRED) / 100.0; + + char string[CF_BUFSIZE] = { 0 }; + + snprintf(string, CF_BUFSIZE, + "Outcome of version %s (" CF_AGENTC "-%d): Promises observed to be kept %.2f%%, Promises repaired %.2f%%, Promises not repaired %.2f%%", + version, background_tasks, + (double) PR_KEPT / total, + (double) PR_REPAIRED / total, + (double) PR_NOTKEPT / total); + + Log(LOG_LEVEL_VERBOSE, "Logging total compliance, total '%s'", string); + + char filename[CF_BUFSIZE]; + snprintf(filename, CF_BUFSIZE, "%s/%s", GetLogDir(), CF_PROMISE_LOG); + MapName(filename); + + FILE *fout = safe_fopen(filename, "a"); + if (fout == NULL) + { + Log(LOG_LEVEL_ERR, "In total compliance logging, could not open file '%s'. (fopen: %s)", filename, GetErrorStr()); + } + else + { + fprintf(fout, "%jd,%jd: %s\n", (intmax_t)CFSTARTTIME, (intmax_t)time(NULL), string); + fclose(fout); + } +} + + +/* network communication: cf-serverd.c, client_protocol.c, client_code.c, crypto.c */ + + +ENTERPRISE_FUNC_1ARG_DEFINE_STUB(int, CfSessionKeySize, char, type) +{ + return CF_BLOWFISHSIZE; +} + +ENTERPRISE_FUNC_0ARG_DEFINE_STUB(char, CfEnterpriseOptions) +{ + return 'c'; +} + +ENTERPRISE_FUNC_1ARG_DEFINE_STUB(const EVP_CIPHER *, CfengineCipher, char, type) +{ + return EVP_bf_cbc(); +} + +/* cf-agent: evalfunction.c */ + +ENTERPRISE_FUNC_6ARG_DEFINE_STUB(char *, GetRemoteScalar, EvalContext *, ctx, char *, proto, char *, handle, + const char *, server, int, encrypted, char *, rcv) +{ + Log(LOG_LEVEL_VERBOSE, "Access to server literals is only available in CFEngine Enterprise"); + return ""; +} + +ENTERPRISE_VOID_FUNC_3ARG_DEFINE_STUB(void, CacheUnreliableValue, char *, caller, char *, handle, char *, buffer) +{ + Log(LOG_LEVEL_VERBOSE, "Value fault-tolerance only available in CFEngine Enterprise"); +} + +ENTERPRISE_FUNC_3ARG_DEFINE_STUB(int, RetrieveUnreliableValue, char *, caller, char *, handle, char *, buffer) +{ + Log(LOG_LEVEL_VERBOSE, "Value fault-tolerance only available in CFEngine Enterprise"); + return 0; // enterprise version returns strlen(buffer) or 0 for error +} + +#if defined(__MINGW32__) +ENTERPRISE_FUNC_4ARG_DEFINE_STUB(bool, GetRegistryValue, char *, key, char *, name, char *, buf, int, bufSz) +{ + return 0; +} +#endif + +ENTERPRISE_FUNC_6ARG_DEFINE_STUB(void *, CfLDAPValue, char *, uri, char *, dn, char *, filter, char *, name, char *, scope, char *, sec) +{ + Log(LOG_LEVEL_ERR, "LDAP support only available in CFEngine Enterprise"); + return NULL; +} + +ENTERPRISE_FUNC_6ARG_DEFINE_STUB(void *, CfLDAPList, char *, uri, char *, dn, char *, filter, char *, name, char *, scope, char *, sec) +{ + Log(LOG_LEVEL_ERR, "LDAP support only available in CFEngine Enterprise"); + return NULL; +} + +ENTERPRISE_FUNC_8ARG_DEFINE_STUB(void *, CfLDAPArray, EvalContext *, ctx, const Bundle *, caller, char *, array, char *, uri, char *, dn, + char *, filter, char *, scope, char *, sec) +{ + Log(LOG_LEVEL_ERR, "LDAP support only available in CFEngine Enterprise"); + return NULL; +} + +ENTERPRISE_FUNC_8ARG_DEFINE_STUB(void *, CfRegLDAP, EvalContext *, ctx, char *, uri, char *, dn, char *, filter, char *, name, char *, scope, char *, regex, char *, sec) +{ + Log(LOG_LEVEL_ERR, "LDAP support only available in CFEngine Enterprise"); + return NULL; +} + +ENTERPRISE_FUNC_4ARG_DEFINE_STUB(bool, ListHostsWithClass, EvalContext *, ctx, Rlist **, return_list, char *, class_name, char *, return_format) +{ + Log(LOG_LEVEL_ERR, "Host class counting is only available in CFEngine Enterprise"); + return false; +} + +/* cf-serverd: server_transform.c, cf-serverd.c */ + +ENTERPRISE_FUNC_3ARG_DEFINE_STUB(bool, TranslatePath, const char *, from, char *, to, size_t, to_size) +{ + const size_t length = StringCopy(from, to, to_size); + if (length >= to_size) + { + Log(LOG_LEVEL_ERR, + "File name was too long and got truncated: '%s'", + to); + return false; + } + return true; +} + + +ENTERPRISE_VOID_FUNC_3ARG_DEFINE_STUB(void, EvalContextLogPromiseIterationOutcome, + ARG_UNUSED EvalContext *, ctx, + ARG_UNUSED const Promise *, pp, + ARG_UNUSED PromiseResult, result) +{ +} + +ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, CheckAndSetHAState, ARG_UNUSED const char *, workdir, ARG_UNUSED EvalContext *, ctx) +{ +} + +ENTERPRISE_VOID_FUNC_0ARG_DEFINE_STUB(void, ReloadHAConfig) +{ +} + +ENTERPRISE_FUNC_0ARG_DEFINE_STUB(size_t, EnterpriseGetMaxCfHubProcesses) +{ + return 0; +} + +ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, Nova_ClassHistoryAddContextName, + ARG_UNUSED const StringSet *, list, + ARG_UNUSED const char *, context_name) +{ +} + +ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, Nova_ClassHistoryEnable, + ARG_UNUSED StringSet **, list, + ARG_UNUSED bool, enable) +{ +} diff --git a/libpromises/eval_context.c b/libpromises/eval_context.c new file mode 100644 index 0000000000..11b1c77764 --- /dev/null +++ b/libpromises/eval_context.c @@ -0,0 +1,3832 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include /* ExpandPrivateRval */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* DataTypeIsIterable */ +#include + +/* If we need to put a scoped variable into a special scope, use the string + * below to replace the original scope separator. + * (e.g. "config.var" -> "this.config___var" ) */ +#define NESTED_SCOPE_SEP "___" + +static const char *STACK_FRAME_TYPE_STR[STACK_FRAME_TYPE_MAX] = { + "BUNDLE", + "BODY", + "PROMISE_TYPE", + "PROMISE", + "PROMISE_ITERATION" +}; + + +/** + Define FuncCacheMap. + Key: an Rlist (which is linked list of Rvals) + listing all the argument of the function + Value: an Rval, the result of the function + */ + +static void RvalDestroy2(void *p) +{ + Rval *rv = p; + RvalDestroy(*rv); + free(rv); +} + +TYPED_MAP_DECLARE(FuncCache, Rlist *, Rval *) + +TYPED_MAP_DEFINE(FuncCache, Rlist *, Rval *, + RlistHash_untyped, + RlistEqual_untyped, + RlistDestroy_untyped, + RvalDestroy2) + +/** + Define RemoteVarsPromisesMap. + Key: bundle name (char *) + Value: a sequence of promises (const *Promise), only the container + (sequence) should be deallocated) + */ + +static void SeqDestroy_untyped(void *p) +{ + Seq *s = p; + SeqDestroy(s); +} + +TYPED_MAP_DECLARE(RemoteVarPromises, char *, Seq *) + +TYPED_MAP_DEFINE(RemoteVarPromises, char *, Seq *, + StringHash_untyped, + StringEqual_untyped, + free, + SeqDestroy_untyped) + + +static Regex *context_expression_whitespace_rx = NULL; + +#include + +static bool BundleAborted(const EvalContext *ctx); +static void SetBundleAborted(EvalContext *ctx); +static void SetEvalAborted(EvalContext *ctx); + +static bool EvalContextStackFrameContainsSoft(const EvalContext *ctx, const char *context); +static bool EvalContextHeapContainsSoft(const EvalContext *ctx, const char *ns, const char *name); +static bool EvalContextHeapContainsHard(const EvalContext *ctx, const char *name); +static bool EvalContextClassPut(EvalContext *ctx, const char *ns, const char *name, + bool is_soft, ContextScope scope, + const char *tags, const char *comment); +static const char *EvalContextCurrentNamespace(const EvalContext *ctx); +static ClassRef IDRefQualify(const EvalContext *ctx, const char *id); + +/** + * Every agent has only one EvalContext from process start to finish. + */ +struct EvalContext_ +{ + const GenericAgentConfig *config; + + int eval_options; + bool bundle_aborted; + bool eval_aborted; + bool checksum_updates_default; + Item *ip_addresses; + bool ignore_locks; + + int pass; + Rlist *args; + + Rlist *restrict_keys; + + Item *heap_abort; + Item *heap_abort_current_bundle; + + Seq *stack; + + ClassTable *global_classes; + VariableTable *global_variables; + + VariableTable *match_variables; + + StringSet *promise_lock_cache; + StringSet *dependency_handles; + FuncCacheMap *function_cache; + + uid_t uid; + uid_t gid; + pid_t pid; + pid_t ppid; + + // Full path to directory that the binary was launched from. + char *launch_directory; + + char *entry_point; + + /* new package promise evaluation context */ + PackagePromiseContext *package_promise_context; + + /* select_end_match_eof context*/ + bool select_end_match_eof; + + /* List if all classes set during policy evaluation */ + StringSet *all_classes; + + /* Negated classes (persistent classes that should not be defined). */ + StringSet *negated_classes; + + /* These following two fields are needed for remote variable injection + * detection (CFE-1915) */ + /* Names of all bundles */ + StringSet *bundle_names; + + /* Promises possibly remotely-injecting variables */ + /* ONLY INITIALIZED WHEN NON-EMPTY, OTHERWISE NULL */ + RemoteVarPromisesMap *remote_var_promises; + + bool dump_reports; +}; + +void EvalContextSetConfig(EvalContext *ctx, const GenericAgentConfig *config) +{ + assert(ctx != NULL); + ctx->config = config; +} + +const GenericAgentConfig *EvalContextGetConfig(EvalContext *ctx) +{ + assert(ctx != NULL); + return ctx->config; +} + +bool EvalContextGetSelectEndMatchEof(const EvalContext *ctx) +{ + return ctx->select_end_match_eof; +} + +void EvalContextSetSelectEndMatchEof(EvalContext *ctx, bool value) +{ + ctx->select_end_match_eof = value; +} + +Rlist *EvalContextGetRestrictKeys(const EvalContext *ctx) +{ + assert(ctx != NULL); + return ctx->restrict_keys; +} + +void EvalContextSetRestrictKeys(EvalContext *ctx, const Rlist *restrict_keys) +{ + assert(ctx != NULL); + ctx->restrict_keys = RlistCopy(restrict_keys); +} + +void AddDefaultPackageModuleToContext(const EvalContext *ctx, char *name) +{ + assert(ctx); + assert(ctx->package_promise_context); + + free(ctx->package_promise_context->control_package_module); + ctx->package_promise_context->control_package_module = + SafeStringDuplicate(name); +} + +void AddDefaultInventoryToContext(const EvalContext *ctx, Rlist *inventory) +{ + assert(ctx); + assert(ctx->package_promise_context); + + RlistDestroy(ctx->package_promise_context->control_package_inventory); + ctx->package_promise_context->control_package_inventory = + RlistCopy(inventory); +} + +static +int PackageManagerSeqCompare(const void *a, const void *b, ARG_UNUSED void *data) +{ + return StringSafeCompare((char*)a, ((PackageModuleBody*)b)->name); +} + +void AddPackageModuleToContext(const EvalContext *ctx, PackageModuleBody *pm) +{ + assert(ctx != NULL); + assert(pm != NULL); + + /* First check if the body is there added from previous pre-evaluation + * iteration. If it is there update it as we can have new expanded variables. */ + Seq *const bodies = ctx->package_promise_context->package_modules_bodies; + ssize_t index = SeqIndexOf(bodies, pm->name, PackageManagerSeqCompare); + if (index != -1) + { + SeqRemove(bodies, index); + } + SeqAppend(bodies, pm); +} + +PackageModuleBody *GetPackageModuleFromContext(const EvalContext *ctx, + const char *name) +{ + if (name == NULL) + { + return NULL; + } + + for (size_t i = 0; + i < SeqLength(ctx->package_promise_context->package_modules_bodies); + i++) + { + PackageModuleBody *pm = + SeqAt(ctx->package_promise_context->package_modules_bodies, i); + if (strcmp(name, pm->name) == 0) + { + return pm; + } + } + return NULL; +} + +PackageModuleBody *GetDefaultPackageModuleFromContext(const EvalContext *ctx) +{ + char *def_pm_name = ctx->package_promise_context->control_package_module; + return GetPackageModuleFromContext(ctx, def_pm_name); +} + +Rlist *GetDefaultInventoryFromContext(const EvalContext *ctx) +{ + return ctx->package_promise_context->control_package_inventory; +} + +PackagePromiseContext *GetPackagePromiseContext(const EvalContext *ctx) +{ + return ctx->package_promise_context; +} + + +static StackFrame *LastStackFrame(const EvalContext *ctx, size_t offset) +{ + if (SeqLength(ctx->stack) <= offset) + { + return NULL; + } + return SeqAt(ctx->stack, SeqLength(ctx->stack) - 1 - offset); +} + +static StackFrame *LastStackFrameByType(const EvalContext *ctx, StackFrameType type) +{ + for (size_t i = 0; i < SeqLength(ctx->stack); i++) + { + StackFrame *frame = LastStackFrame(ctx, i); + if (frame->type == type) + { + return frame; + } + } + + return NULL; +} + +static LogLevel AdjustLogLevel(LogLevel base, LogLevel adjust) +{ + if (adjust == -1) + { + return base; + } + else + { + return MAX(base, adjust); + } +} + +static LogLevel StringToLogLevel(const char *value) +{ + if (value) + { + if (!strcmp(value, "verbose")) + { + return LOG_LEVEL_VERBOSE; + } + if (!strcmp(value, "inform")) + { + return LOG_LEVEL_INFO; + } + if (!strcmp(value, "error")) + { + return LOG_LEVEL_NOTICE; /* Error level includes warnings and notices */ + } + } + return -1; +} + +static LogLevel GetLevelForPromise(const Promise *pp, const char *attr_name) +{ + return StringToLogLevel(PromiseGetConstraintAsRval(pp, attr_name, RVAL_TYPE_SCALAR)); +} + +static LogLevel CalculateLogLevel(const Promise *pp) +{ + LogLevel global_log_level = LogGetGlobalLevel(); + LogLevel system_log_level = LogGetGlobalSystemLogLevel(); + + LogLevel log_level = (system_log_level != LOG_LEVEL_NOTHING ? + system_log_level : global_log_level); + + if (pp) + { + log_level = AdjustLogLevel(log_level, GetLevelForPromise(pp, "log_level")); + } + + /* Disable system log for dry-runs */ + if (DONTDO) + { + log_level = LOG_LEVEL_NOTHING; + } + + return log_level; +} + +static LogLevel CalculateReportLevel(const Promise *pp) +{ + LogLevel report_level = LogGetGlobalLevel(); + + if (pp) + { + report_level = AdjustLogLevel(report_level, GetLevelForPromise(pp, "report_level")); + } + + return report_level; +} + +const char *EvalContextStackToString(EvalContext *ctx) +{ + StackFrame *last_frame = LastStackFrame(ctx, 0); + if (last_frame) + { + return last_frame->path; + } + return ""; +} + +static const char *GetAgentAbortingContext(const EvalContext *ctx) +{ + for (const Item *ip = ctx->heap_abort; ip != NULL; ip = ip->next) + { + if (IsDefinedClass(ctx, ip->classes)) + { + const char *regex = ip->name; + Class *cls = EvalContextClassMatch(ctx, regex); + + if (cls) + { + return cls->name; + } + } + } + return NULL; +} + +static void EvalContextStackFrameAddSoft(EvalContext *ctx, const char *context, const char *tags) +{ + assert(SeqLength(ctx->stack) > 0); + + StackFrameBundle frame; + { + StackFrame *last_frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE); + if (!last_frame) + { + ProgrammingError("Attempted to add a soft class on the stack, but stack had no bundle frame"); + } + frame = last_frame->data.bundle; + } + + char copy[CF_BUFSIZE]; + if (strcmp(frame.owner->ns, "default") != 0) + { + snprintf(copy, CF_MAXVARSIZE, "%s:%s", frame.owner->ns, context); + } + else + { + strlcpy(copy, context, CF_MAXVARSIZE); + } + + if (Chop(copy, CF_EXPANDSIZE) == -1) + { + Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator"); + } + + if (strlen(copy) == 0) + { + return; + } + + if (EvalContextHeapContainsSoft(ctx, frame.owner->ns, context)) + { + Log(LOG_LEVEL_WARNING, "Private class '%s' in bundle '%s' shadows a global class - you should choose a different name to avoid conflicts", + copy, frame.owner->name); + } + + if (IsRegexItemIn(ctx, ctx->heap_abort_current_bundle, copy)) + { + Log(LOG_LEVEL_ERR, "Bundle '%s' aborted on defined class '%s'", frame.owner->name, copy); + SetBundleAborted(ctx); + } + + if (IsRegexItemIn(ctx, ctx->heap_abort, copy)) + { + Log(LOG_LEVEL_NOTICE, "cf-agent aborted on defined class '%s'", copy); + SetEvalAborted(ctx); + } + + if (EvalContextStackFrameContainsSoft(ctx, copy)) + { + return; + } + + ClassTablePut(frame.classes, frame.owner->ns, context, true, + CONTEXT_SCOPE_BUNDLE, + NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ','), + NULL); + + if (!BundleAborted(ctx)) + { + for (const Item *ip = ctx->heap_abort_current_bundle; ip != NULL; ip = ip->next) + { + if (IsDefinedClass(ctx, ip->name)) + { + Log(LOG_LEVEL_ERR, "Setting abort for '%s' when setting '%s'", ip->name, context); + SetBundleAborted(ctx); + break; + } + } + } +} + +static ExpressionValue EvalTokenAsClass(const char *classname, void *param) +{ + const EvalContext *ctx = param; + ClassRef ref = ClassRefParse(classname); + if (ClassRefIsQualified(ref)) + { + if (strcmp(ref.ns, NamespaceDefault()) == 0) + { + if (EvalContextHeapContainsHard(ctx, ref.name)) + { + ClassRefDestroy(ref); + return EXPRESSION_VALUE_TRUE; + } + } + } + else + { + if (EvalContextHeapContainsHard(ctx, ref.name)) + { + ClassRefDestroy(ref); + return EXPRESSION_VALUE_TRUE; + } + + const char *ns = EvalContextCurrentNamespace(ctx); + if (ns) + { + ClassRefQualify(&ref, ns); + } + else + { + ClassRefQualify(&ref, NamespaceDefault()); + } + } + + assert(ClassRefIsQualified(ref)); + bool classy = (strcmp("any", ref.name) == 0 || + EvalContextHeapContainsSoft(ctx, ref.ns, ref.name) || + EvalContextStackFrameContainsSoft(ctx, ref.name)); + + ClassRefDestroy(ref); + return (ExpressionValue) classy; // ExpressionValue extends bool +} + +/**********************************************************************/ + +static char *EvalVarRef(ARG_UNUSED const char *varname, ARG_UNUSED VarRefType type, ARG_UNUSED void *param) +{ +/* + * There should be no unexpanded variables when we evaluate any kind of + * logic expressions, until parsing of logic expression changes and they are + * not pre-expanded before evaluation. + */ + return NULL; +} + +/**********************************************************************/ + +bool ClassCharIsWhitespace(char ch) +{ + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; +} + +ExpressionValue CheckClassExpression(const EvalContext *ctx, const char *context) +{ + assert(context != NULL); + ParseResult res; + + if (!context) + { + // TODO: Remove this, seems like a hack + return EXPRESSION_VALUE_TRUE; + } + + if (context_expression_whitespace_rx == NULL) + { + context_expression_whitespace_rx = CompileRegex(CFENGINE_REGEX_WHITESPACE_IN_CONTEXTS); + } + + if (context_expression_whitespace_rx == NULL) + { + Log(LOG_LEVEL_ERR, "The context expression whitespace regular expression could not be compiled, aborting."); + return EXPRESSION_VALUE_ERROR; + } + + if (StringMatchFullWithPrecompiledRegex(context_expression_whitespace_rx, context)) + { + Log(LOG_LEVEL_ERR, "class expressions can't be separated by whitespace without an intervening operator in expression '%s'", context); + return EXPRESSION_VALUE_ERROR; + } + + Buffer *condensed = BufferNewFrom(context, strlen(context)); + BufferRewrite(condensed, &ClassCharIsWhitespace, true); + res = ParseExpression(BufferData(condensed), 0, BufferSize(condensed)); + BufferDestroy(condensed); + + if (!res.result) + { + Log(LOG_LEVEL_ERR, "Unable to parse class expression '%s'", context); + return EXPRESSION_VALUE_ERROR; + } + else + { + ExpressionValue r = EvalExpression(res.result, + &EvalTokenAsClass, &EvalVarRef, + (void *)ctx); // controlled cast. None of these should modify EvalContext + + FreeExpression(res.result); + return r; + } +} + +/**********************************************************************/ + +static ExpressionValue EvalTokenFromList(const char *token, void *param) +{ + StringSet *set = param; + return (ExpressionValue) StringSetContains(set, token); // EV extends bool +} + +/**********************************************************************/ + +static bool EvalWithTokenFromList(const char *expr, StringSet *token_set) +{ + ParseResult res = ParseExpression(expr, 0, strlen(expr)); + + if (!res.result) + { + Log(LOG_LEVEL_ERR, "Syntax error in expression '%s'", expr); + return false; /* FIXME: return error */ + } + else + { + ExpressionValue r = EvalExpression(res.result, + &EvalTokenFromList, + &EvalVarRef, + token_set); + + FreeExpression(res.result); + + /* r is EvalResult which could be ERROR */ + return r == EXPRESSION_VALUE_TRUE; + } +} + +/**********************************************************************/ + +/* Process result expression */ + +bool EvalProcessResult(const char *process_result, StringSet *proc_attr) +{ + assert(process_result != NULL); + if (StringEqual(process_result, "")) + { + /* nothing to evaluate */ + return false; + } + return EvalWithTokenFromList(process_result, proc_attr); +} + +/**********************************************************************/ + +/* File result expressions */ + +bool EvalFileResult(const char *file_result, StringSet *leaf_attr) +{ + return EvalWithTokenFromList(file_result, leaf_attr); +} + +/*****************************************************************************/ + + +void EvalContextHeapPersistentSave(EvalContext *ctx, const char *name, unsigned int ttl_minutes, + PersistentClassPolicy policy, const char *tags) +{ + assert(tags); + + time_t now = time(NULL); + + CF_DB *dbp; + if (!OpenDB(&dbp, dbid_state)) + { + char *db_path = DBIdToPath(dbid_state); + Log(LOG_LEVEL_ERR, "While persisting class, unable to open database at '%s' (OpenDB: %s)", + db_path, GetErrorStr()); + free(db_path); + return; + } + + ClassRef ref = IDRefQualify(ctx, name); + char *key = ClassRefToString(ref.ns, ref.name); + ClassRefDestroy(ref); + + size_t tags_length = strlen(tags) + 1; + size_t new_info_size = sizeof(PersistentClassInfo) + tags_length; + + PersistentClassInfo *new_info = xcalloc(1, new_info_size); + + new_info->expires = now + ttl_minutes * 60; + new_info->policy = policy; + strlcpy(new_info->tags, tags, tags_length); + + // first see if we have an existing record, and if we should bother to update + { + int existing_info_size = ValueSizeDB(dbp, key, strlen(key)); + if (existing_info_size > 0) + { + PersistentClassInfo *existing_info = xcalloc(existing_info_size, 1); + if (ReadDB(dbp, key, existing_info, existing_info_size)) + { + if (existing_info->policy == CONTEXT_STATE_POLICY_PRESERVE && + now < existing_info->expires && + strcmp(existing_info->tags, new_info->tags) == 0) + { + Log(LOG_LEVEL_VERBOSE, "Persistent class '%s' is already in a preserved state -- %jd minutes to go", + key, (intmax_t)((existing_info->expires - now) / 60)); + CloseDB(dbp); + free(key); + free(new_info); + return; + } + } + else + { + Log(LOG_LEVEL_ERR, "While persisting class '%s', error reading existing value", key); + CloseDB(dbp); + free(key); + free(new_info); + return; + } + } + } + + Log(LOG_LEVEL_VERBOSE, "Updating persistent class '%s'", key); + + WriteDB(dbp, key, new_info, new_info_size); + + CloseDB(dbp); + free(key); + free(new_info); +} + +/*****************************************************************************/ + +void EvalContextHeapPersistentRemove(const char *context) +{ + CF_DB *dbp; + + if (!OpenDB(&dbp, dbid_state)) + { + return; + } + + DeleteDB(dbp, context); + Log(LOG_LEVEL_DEBUG, "Deleted persistent class '%s'", context); + CloseDB(dbp); +} + +/*****************************************************************************/ + +void EvalContextHeapPersistentLoadAll(EvalContext *ctx) +{ + assert(ctx != NULL); + + time_t now = time(NULL); + + Log(LOG_LEVEL_VERBOSE, "Loading persistent classes"); + + CF_DB *dbp; + if (!OpenDB(&dbp, dbid_state)) + { + return; + } + + CF_DBC *dbcp; + if (!NewDBCursor(dbp, &dbcp)) + { + Log(LOG_LEVEL_INFO, "Unable to scan persistence cache"); + return; + } + + const char *key; + int key_size = 0; + void *info_p; + int info_size = 0; + + while (NextDB(dbcp, (char **)&key, &key_size, &info_p, &info_size)) + { + Log(LOG_LEVEL_DEBUG, "Found key persistent class key '%s'", key); + + /* Info points to db-owned data, which is not aligned properly and + * dereferencing might be slow or even cause SIGBUS! */ + PersistentClassInfo info = { 0 }; + memcpy(&info, info_p, + info_size < (int) sizeof(info) ? info_size : (int) sizeof(info)); + + const char *tags = NULL; + if (info_size > (int) sizeof(PersistentClassInfo)) + { + /* This is char pointer, it can point to unaligned data. */ + tags = ((PersistentClassInfo *) info_p)->tags; + } + else + { + tags = ""; /* no tags */ + } + + if (now > info.expires) + { + Log(LOG_LEVEL_VERBOSE, "Persistent class '%s' expired", key); + DBCursorDeleteEntry(dbcp); + } + else + { + Log(LOG_LEVEL_VERBOSE, "Persistent class '%s' for %jd more minutes", + key, (intmax_t) ((info.expires - now) / 60)); + if ((ctx->negated_classes != NULL) && StringSetContains(ctx->negated_classes, key)) + { + Log(LOG_LEVEL_VERBOSE, + "Not adding persistent class '%s' due to match in -N/--negate", key); + } + else + { + Log(LOG_LEVEL_DEBUG, "Adding persistent class '%s'", key); + + ClassRef ref = ClassRefParse(key); + EvalContextClassPut(ctx, ref.ns, ref.name, true, CONTEXT_SCOPE_NAMESPACE, tags, NULL); + + StringSet *tag_set = EvalContextClassTags(ctx, ref.ns, ref.name); + assert(tag_set); + + StringSetAdd(tag_set, xstrdup("source=persistent")); + + ClassRefDestroy(ref); + } + } + } + + DeleteDBCursor(dbcp); + CloseDB(dbp); +} + +void EvalContextSetNegatedClasses(EvalContext *ctx, StringSet *negated_classes) +{ + assert(ctx != NULL); + ctx->negated_classes = negated_classes; +} + + +bool BundleAbort(EvalContext *ctx) +{ + assert(ctx != NULL); + if (ctx->bundle_aborted) + { + ctx->bundle_aborted = false; + return true; + } + + return false; +} + +static bool BundleAborted(const EvalContext* ctx) +{ + assert(ctx != NULL); + return ctx->bundle_aborted; +} + +static void SetBundleAborted(EvalContext *ctx) +{ + assert(ctx != NULL); + ctx->bundle_aborted = true; +} + +static void SetEvalAborted(EvalContext *ctx) +{ + assert(ctx != NULL); + ctx->eval_aborted = true; +} + +bool EvalAborted(const EvalContext *ctx) +{ + assert(ctx != NULL); + return ctx->eval_aborted; +} + +void EvalContextHeapAddAbort(EvalContext *ctx, const char *context, const char *activated_on_context) +{ + assert(ctx != NULL); + if (!IsItemIn(ctx->heap_abort, context)) + { + AppendItem(&ctx->heap_abort, context, activated_on_context); + } + + const char *aborting_context = GetAgentAbortingContext(ctx); + + if (aborting_context) + { + Log(LOG_LEVEL_NOTICE, "cf-agent aborted on defined class '%s'", aborting_context); + SetEvalAborted(ctx); + } +} + +void EvalContextHeapAddAbortCurrentBundle(EvalContext *ctx, const char *context, const char *activated_on_context) +{ + assert(ctx != NULL); + if (!IsItemIn(ctx->heap_abort_current_bundle, context)) + { + AppendItem(&ctx->heap_abort_current_bundle, context, activated_on_context); + } +} + +/*****************************************************************************/ + +bool MissingDependencies(EvalContext *ctx, const Promise *pp) +{ + assert(ctx != NULL); + const Rlist *dependenies = PromiseGetConstraintAsList(ctx, "depends_on", pp); + if (RlistIsNullList(dependenies)) + { + return false; + } + + for (const Rlist *rp = PromiseGetConstraintAsList(ctx, "depends_on", pp); rp; rp = rp->next) + { + if (rp->val.type != RVAL_TYPE_SCALAR) + { + return true; + } + + if (!StringSetContains(ctx->dependency_handles, RlistScalarValue(rp))) + { + Log(LOG_LEVEL_VERBOSE, "Skipping promise '%s', as promise dependency '%s' has not yet been kept", + pp->promiser, RlistScalarValue(rp)); + return true; + } + } + + return false; +} + +static void StackFrameBundleDestroy(StackFrameBundle frame) +{ + ClassTableDestroy(frame.classes); + VariableTableDestroy(frame.vars); +} + +static void StackFrameBodyDestroy(ARG_UNUSED StackFrameBody frame) +{ + VariableTableDestroy(frame.vars); +} + +static void StackFramePromiseDestroy(StackFramePromise frame) +{ + VariableTableDestroy(frame.vars); +} + +static void StackFramePromiseIterationDestroy(StackFramePromiseIteration frame) +{ + PromiseDestroy(frame.owner); + RingBufferDestroy(frame.log_messages); +} + +static void StackFrameDestroy(StackFrame *frame) +{ + if (frame) + { + switch (frame->type) + { + case STACK_FRAME_TYPE_BUNDLE: + StackFrameBundleDestroy(frame->data.bundle); + break; + + case STACK_FRAME_TYPE_BODY: + StackFrameBodyDestroy(frame->data.body); + break; + + case STACK_FRAME_TYPE_BUNDLE_SECTION: + break; + + case STACK_FRAME_TYPE_PROMISE: + StackFramePromiseDestroy(frame->data.promise); + break; + + case STACK_FRAME_TYPE_PROMISE_ITERATION: + StackFramePromiseIterationDestroy(frame->data.promise_iteration); + break; + + default: + ProgrammingError("Unhandled stack frame type"); + } + + free(frame->path); + free(frame); + } +} + +static +void FreePackageManager(PackageModuleBody *manager) +{ + assert(manager != NULL); + + free(manager->name); + free(manager->interpreter); + free(manager->module_path); + RlistDestroy(manager->options); + free(manager); +} + +static +PackagePromiseContext *PackagePromiseConfigNew() +{ + PackagePromiseContext *package_promise_defaults = + xmalloc(sizeof(PackagePromiseContext)); + package_promise_defaults->control_package_module = NULL; + package_promise_defaults->control_package_inventory = NULL; + package_promise_defaults->package_modules_bodies = + SeqNew(5, FreePackageManager); + + return package_promise_defaults; +} + +static +void FreePackagePromiseContext(PackagePromiseContext *pp_ctx) +{ + SeqDestroy(pp_ctx->package_modules_bodies); + RlistDestroy(pp_ctx->control_package_inventory); + free(pp_ctx->control_package_module); + free(pp_ctx); +} + +/* Keeps the last 5 messages of each promise in a ring buffer in the + * EvalContext, which are written to a JSON file from the Enterprise function + * EvalContextLogPromiseIterationOutcome() at the end of each promise. */ +char *MissionPortalLogHook(LoggingPrivContext *pctx, LogLevel level, const char *message) +{ + const EvalContext *ctx = pctx->param; + + StackFrame *last_frame = LastStackFrame(ctx, 0); + if (last_frame + && last_frame->type == STACK_FRAME_TYPE_PROMISE_ITERATION + && level <= LOG_LEVEL_INFO) + { + RingBufferAppend(last_frame->data.promise_iteration.log_messages, xstrdup(message)); + } + return xstrdup(message); +} + +ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, EvalContextSetupMissionPortalLogHook, + ARG_UNUSED EvalContext *, ctx) +{ +} + +EvalContext *EvalContextNew(void) +{ + EvalContext *ctx = xcalloc(1, sizeof(EvalContext)); + + ctx->eval_options = EVAL_OPTION_FULL; + ctx->stack = SeqNew(10, StackFrameDestroy); + ctx->global_classes = ClassTableNew(); + ctx->global_variables = VariableTableNew(); + ctx->match_variables = VariableTableNew(); + ctx->dependency_handles = StringSetNew(); + + ctx->uid = getuid(); + ctx->gid = getgid(); + ctx->pid = getpid(); + +#ifndef __MINGW32__ + ctx->ppid = getppid(); +#endif + + ctx->promise_lock_cache = StringSetNew(); + ctx->function_cache = FuncCacheMapNew(); + + EvalContextSetupMissionPortalLogHook(ctx); + + ctx->package_promise_context = PackagePromiseConfigNew(); + + ctx->all_classes = NULL; + ctx->negated_classes = NULL; + ctx->bundle_names = StringSetNew(); + ctx->remote_var_promises = NULL; + + ctx->select_end_match_eof = false; + + ctx->dump_reports = false; + + return ctx; +} + +void EvalContextDestroy(EvalContext *ctx) +{ + if (ctx) + { + free(ctx->launch_directory); + free(ctx->entry_point); + + // Freeing logging context doesn't belong here... + { + LoggingPrivContext *pctx = LoggingPrivGetContext(); + free(pctx); + LoggingPrivSetContext(NULL); + } + LoggingFreeCurrentThreadContext(); + + EvalContextDeleteIpAddresses(ctx); + + DeleteItemList(ctx->heap_abort); + DeleteItemList(ctx->heap_abort_current_bundle); + + RlistDestroy(ctx->args); + + SeqDestroy(ctx->stack); + + ClassTableDestroy(ctx->global_classes); + VariableTableDestroy(ctx->global_variables); + VariableTableDestroy(ctx->match_variables); + + StringSetDestroy(ctx->dependency_handles); + StringSetDestroy(ctx->promise_lock_cache); + + FuncCacheMapDestroy(ctx->function_cache); + + FreePackagePromiseContext(ctx->package_promise_context); + + StringSetDestroy(ctx->all_classes); + StringSetDestroy(ctx->negated_classes); + StringSetDestroy(ctx->bundle_names); + if (ctx->remote_var_promises != NULL) + { + RemoteVarPromisesMapDestroy(ctx->remote_var_promises); + ctx->remote_var_promises = NULL; + } + + free(ctx); + } +} + +static bool EvalContextHeapContainsSoft(const EvalContext *ctx, const char *ns, const char *name) +{ + const Class *cls = ClassTableGet(ctx->global_classes, ns, name); + return cls && cls->is_soft; +} + +static bool EvalContextHeapContainsHard(const EvalContext *ctx, const char *name) +{ + const Class *cls = ClassTableGet(ctx->global_classes, NULL, name); + return cls && !cls->is_soft; +} + +bool StackFrameContainsSoftRecursive(const EvalContext *ctx, const char *context, size_t stack_index) +{ + StackFrame *frame = SeqAt(ctx->stack, stack_index); + if (frame->type == STACK_FRAME_TYPE_BUNDLE && ClassTableGet(frame->data.bundle.classes, frame->data.bundle.owner->ns, context) != NULL) + { + return true; + } + else if (stack_index > 0 && frame->inherits_previous) + { + return StackFrameContainsSoftRecursive(ctx, context, stack_index - 1); + } + else + { + return false; + } +} + +static bool EvalContextStackFrameContainsSoft(const EvalContext *ctx, const char *context) +{ + if (SeqLength(ctx->stack) == 0) + { + return false; + } + + size_t stack_index = SeqLength(ctx->stack) - 1; + return StackFrameContainsSoftRecursive(ctx, context, stack_index); +} + +bool EvalContextHeapRemoveSoft(EvalContext *ctx, const char *ns, const char *name) +{ + return ClassTableRemove(ctx->global_classes, ns, name); +} + +bool EvalContextHeapRemoveHard(EvalContext *ctx, const char *name) +{ + return ClassTableRemove(ctx->global_classes, NULL, name); +} + +void EvalContextClear(EvalContext *ctx) +{ + ClassTableClear(ctx->global_classes); + EvalContextDeleteIpAddresses(ctx); + VariableTableClear(ctx->global_variables, NULL, NULL, NULL); + VariableTableClear(ctx->match_variables, NULL, NULL, NULL); + StringSetClear(ctx->promise_lock_cache); + SeqClear(ctx->stack); + FuncCacheMapClear(ctx->function_cache); +} + +Rlist *EvalContextGetPromiseCallerMethods(EvalContext *ctx) { + Rlist *callers_promisers = NULL; + + for (size_t i = 0; i < SeqLength(ctx->stack); i++) + { + StackFrame *frame = SeqAt(ctx->stack, i); + switch (frame->type) + { + case STACK_FRAME_TYPE_BODY: + break; + + case STACK_FRAME_TYPE_BUNDLE: + break; + + case STACK_FRAME_TYPE_PROMISE_ITERATION: + break; + + case STACK_FRAME_TYPE_PROMISE: + if (strcmp(frame->data.promise.owner->parent_section->promise_type, "methods") == 0) { + RlistAppendScalar(&callers_promisers, frame->data.promise.owner->promiser); + } + break; + + case STACK_FRAME_TYPE_BUNDLE_SECTION: + break; + + default: + ProgrammingError("Unhandled stack frame type"); + } + } + return callers_promisers; +} + +JsonElement *EvalContextGetPromiseCallers(EvalContext *ctx) { + JsonElement *callers = JsonArrayCreate(4); + size_t depth = SeqLength(ctx->stack); + + for (size_t i = 0; i < depth; i++) + { + StackFrame *frame = SeqAt(ctx->stack, i); + JsonElement *f = JsonObjectCreate(10); + JsonObjectAppendInteger(f, "frame", depth-i); + JsonObjectAppendInteger(f, "depth", i); + + switch (frame->type) + { + case STACK_FRAME_TYPE_BODY: + JsonObjectAppendString(f, "type", "body"); + JsonObjectAppendObject(f, "body", BodyToJson(frame->data.body.owner)); + break; + + case STACK_FRAME_TYPE_BUNDLE: + JsonObjectAppendString(f, "type", "bundle"); + JsonObjectAppendObject(f, "bundle", BundleToJson(frame->data.bundle.owner)); + break; + + case STACK_FRAME_TYPE_PROMISE_ITERATION: + JsonObjectAppendString(f, "type", "iteration"); + JsonObjectAppendInteger(f, "iteration_index", frame->data.promise_iteration.index); + + break; + + case STACK_FRAME_TYPE_PROMISE: + JsonObjectAppendString(f, "type", "promise"); + JsonObjectAppendString(f, "promise_type", frame->data.promise.owner->parent_section->promise_type); + JsonObjectAppendString(f, "promiser", frame->data.promise.owner->promiser); + JsonObjectAppendString(f, "promise_classes", frame->data.promise.owner->classes); + JsonObjectAppendString(f, "promise_comment", + (frame->data.promise.owner->comment == NULL) ? "" : frame->data.promise.owner->comment); + break; + + case STACK_FRAME_TYPE_BUNDLE_SECTION: + JsonObjectAppendString(f, "type", "promise_type"); + JsonObjectAppendString(f, "promise_type", frame->data.bundle_section.owner->promise_type); + break; + + default: + ProgrammingError("Unhandled stack frame type"); + } + + JsonArrayAppendObject(callers, f); + } + + return callers; +} + +void EvalContextSetBundleArgs(EvalContext *ctx, const Rlist *args) +{ + if (ctx->args) + { + RlistDestroy(ctx->args); + } + + ctx->args = RlistCopy(args); +} + +Rlist *EvalContextGetBundleArgs(EvalContext *ctx) +{ + return (Rlist *) ctx->args; +} + +void EvalContextSetPass(EvalContext *ctx, int pass) +{ + ctx->pass = pass; +} + +int EvalContextGetPass(EvalContext *ctx) +{ + return ctx->pass; +} + +static StackFrame *StackFrameNew(StackFrameType type, bool inherit_previous) +{ + StackFrame *frame = xmalloc(sizeof(StackFrame)); + + frame->type = type; + frame->inherits_previous = inherit_previous; + frame->path = NULL; + + return frame; +} + +static StackFrame *StackFrameNewBundle(const Bundle *owner, bool inherit_previous) +{ + StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_BUNDLE, inherit_previous); + + frame->data.bundle.owner = owner; + frame->data.bundle.classes = ClassTableNew(); + frame->data.bundle.vars = VariableTableNew(); + + return frame; +} + +static StackFrame *StackFrameNewBody(const Body *owner) +{ + StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_BODY, false); + + frame->data.body.owner = owner; + frame->data.body.vars = VariableTableNew(); + + return frame; +} + +static StackFrame *StackFrameNewBundleSection(const BundleSection *owner) +{ + StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_BUNDLE_SECTION, true); + + frame->data.bundle_section.owner = owner; + + return frame; +} + +static StackFrame *StackFrameNewPromise(const Promise *owner) +{ + StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_PROMISE, true); + + frame->data.promise.owner = owner; + + return frame; +} + +static StackFrame *StackFrameNewPromiseIteration(Promise *owner, const PromiseIterator *iter_ctx) +{ + StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_PROMISE_ITERATION, true); + + frame->data.promise_iteration.owner = owner; + frame->data.promise_iteration.iter_ctx = iter_ctx; + frame->data.promise_iteration.log_messages = RingBufferNew(5, NULL, free); + + return frame; +} + +void EvalContextStackFrameRemoveSoft(EvalContext *ctx, const char *context) +{ + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE); + assert(frame); + + ClassTableRemove(frame->data.bundle.classes, frame->data.bundle.owner->ns, context); +} + +static void EvalContextStackPushFrame(EvalContext *ctx, StackFrame *frame) +{ + StackFrame *last_frame = LastStackFrame(ctx, 0); + if (last_frame) + { + if (last_frame->type == STACK_FRAME_TYPE_PROMISE_ITERATION) + { + LogLevel global_log_level = LogGetGlobalLevel(); + LogLevel system_log_level = LogGetGlobalSystemLogLevel(); + LoggingPrivSetLevels(system_log_level != LOG_LEVEL_NOTHING ? system_log_level : global_log_level, + global_log_level); + } + } + + SeqAppend(ctx->stack, frame); + + assert(!frame->path); + frame->path = EvalContextStackPath(ctx); + + LogDebug(LOG_MOD_EVALCTX, "PUSHED FRAME (type %s)", + STACK_FRAME_TYPE_STR[frame->type]); +} + +void EvalContextStackPushBundleFrame(EvalContext *ctx, const Bundle *owner, const Rlist *args, bool inherits_previous) +{ + assert(!LastStackFrame(ctx, 0) || LastStackFrame(ctx, 0)->type == STACK_FRAME_TYPE_PROMISE_ITERATION); + + EvalContextStackPushFrame(ctx, StackFrameNewBundle(owner, inherits_previous)); + + if (RlistLen(args) > 0) + { + const Promise *caller = EvalContextStackCurrentPromise(ctx); + if (caller) + { + VariableTable *table = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE)->data.bundle.vars; + VariableTableClear(table, NULL, NULL, NULL); + } + + ScopeAugment(ctx, owner, caller, args); + } + + { + VariableTableIterator *iter = VariableTableIteratorNew(ctx->global_variables, owner->ns, owner->name, NULL); + Variable *var = NULL; + while ((var = VariableTableIteratorNext(iter))) + { + Rval var_rval = VariableGetRval(var, true); + Rval retval = ExpandPrivateRval(ctx, owner->ns, owner->name, var_rval.item, var_rval.type); + VariableSetRval(var, retval); + } + VariableTableIteratorDestroy(iter); + } +} + +void EvalContextStackPushBodyFrame(EvalContext *ctx, const Promise *caller, const Body *body, const Rlist *args) +{ +#ifndef NDEBUG + StackFrame *last_frame = LastStackFrame(ctx, 0); + if (last_frame) + { + assert(last_frame->type == STACK_FRAME_TYPE_BUNDLE_SECTION); + } + else + { + assert(strcmp("control", body->name) == 0); + } +#endif + + + EvalContextStackPushFrame(ctx, StackFrameNewBody(body)); + + if (RlistLen(body->args) != RlistLen(args)) + { + if (caller) + { + Log(LOG_LEVEL_ERR, "Argument arity mismatch in body '%s' at line %zu in file '%s', expected %d, got %d", + body->name, caller->offset.line, PromiseGetBundle(caller)->source_path, RlistLen(body->args), RlistLen(args)); + } + else + { + assert(strcmp("control", body->name) == 0); + ProgrammingError("Control body stack frame was pushed with arguments. This should have been caught before"); + } + return; + } + else + { + ScopeMapBodyArgs(ctx, body, args); + } +} + +void EvalContextStackPushBundleSectionFrame(EvalContext *ctx, const BundleSection *owner) +{ + assert(LastStackFrame(ctx, 0) && LastStackFrame(ctx, 0)->type == STACK_FRAME_TYPE_BUNDLE); + + StackFrame *frame = StackFrameNewBundleSection(owner); + EvalContextStackPushFrame(ctx, frame); +} + +void EvalContextStackPushPromiseFrame(EvalContext *ctx, const Promise *owner) +{ + assert(LastStackFrame(ctx, 0)); + assert(LastStackFrame(ctx, 0)->type == STACK_FRAME_TYPE_BUNDLE_SECTION); + + EvalContextVariableClearMatch(ctx); + + StackFrame *frame = StackFrameNewPromise(owner); + + EvalContextStackPushFrame(ctx, frame); + + // create an empty table + frame->data.promise.vars = VariableTableNew(); + + if (PromiseGetBundle(owner)->source_path) + { + char path[CF_BUFSIZE]; + if (!IsAbsoluteFileName(PromiseGetBundle(owner)->source_path) && ctx->launch_directory) + { + snprintf(path, CF_BUFSIZE, "%s%c%s", ctx->launch_directory, FILE_SEPARATOR, PromiseGetBundle(owner)->source_path); + } + else + { + strlcpy(path, PromiseGetBundle(owner)->source_path, CF_BUFSIZE); + } + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promise_filename", path, CF_DATA_TYPE_STRING, "source=promise"); + + // We now make path just the directory name! + DeleteSlash(path); + ChopLastNode(path); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promise_dirname", path, CF_DATA_TYPE_STRING, "source=promise"); + char number[PRINTSIZE(uintmax_t)]; + xsnprintf(number, CF_SMALLBUF, "%ju", (uintmax_t) owner->offset.line); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promise_linenumber", number, CF_DATA_TYPE_STRING, "source=promise"); + } + + char v[PRINTSIZE(int)]; + xsnprintf(v, sizeof(v), "%d", (int) ctx->uid); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser_uid", v, CF_DATA_TYPE_INT, "source=agent"); + xsnprintf(v, sizeof(v), "%d", (int) ctx->gid); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser_gid", v, CF_DATA_TYPE_INT, "source=agent"); + xsnprintf(v, sizeof(v), "%d", (int) ctx->pid); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser_pid", v, CF_DATA_TYPE_INT, "source=agent"); + xsnprintf(v, sizeof(v), "%d", (int) ctx->ppid); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser_ppid", v, CF_DATA_TYPE_INT, "source=agent"); + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "bundle", PromiseGetBundle(owner)->name, CF_DATA_TYPE_STRING, "source=promise"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "namespace", PromiseGetNamespace(owner), CF_DATA_TYPE_STRING, "source=promise"); + + // Recompute `with` + for (size_t i = 0; i < SeqLength(owner->conlist); i++) + { + Constraint *cp = SeqAt(owner->conlist, i); + if (StringEqual(cp->lval, "with")) + { + Rval final = EvaluateFinalRval(ctx, PromiseGetPolicy(owner), NULL, + "this", cp->rval, false, owner); + if (final.type == RVAL_TYPE_SCALAR && + ((EvalContextGetPass(ctx) == CF_DONEPASSES - 1) || !IsCf3VarString(RvalScalarValue(final)))) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "with", RvalScalarValue(final), CF_DATA_TYPE_STRING, "source=promise_iteration/with"); + } + RvalDestroy(final); + } + } +} + +Promise *EvalContextStackPushPromiseIterationFrame(EvalContext *ctx, const PromiseIterator *iter_ctx) +{ + const StackFrame *last_frame = LastStackFrame(ctx, 0); + + assert(last_frame != NULL); + assert(last_frame->type == STACK_FRAME_TYPE_PROMISE); + + /* Evaluate all constraints by calling functions etc. */ + bool excluded; + Promise *pexp = ExpandDeRefPromise(ctx, last_frame->data.promise.owner, + &excluded); + if (excluded || !pexp) + { + PromiseDestroy(pexp); + return NULL; + } + + EvalContextStackPushFrame(ctx, StackFrameNewPromiseIteration(pexp, iter_ctx)); + LoggingPrivSetLevels(CalculateLogLevel(pexp), CalculateReportLevel(pexp)); + + return pexp; +} + +void EvalContextStackPopFrame(EvalContext *ctx) +{ + assert(SeqLength(ctx->stack) > 0); + + StackFrame *last_frame = LastStackFrame(ctx, 0); + StackFrameType last_frame_type = last_frame->type; + + switch (last_frame_type) + { + case STACK_FRAME_TYPE_BUNDLE: + { + const Bundle *bp = last_frame->data.bundle.owner; + if (strcmp(bp->type, "edit_line") == 0 || strcmp(bp->type, "edit_xml") == 0) + { + VariableTableClear(last_frame->data.bundle.vars, "default", "edit", NULL); + } + } + break; + + case STACK_FRAME_TYPE_PROMISE_ITERATION: + { + LogLevel global_log_level = LogGetGlobalLevel(); + LogLevel system_log_level = LogGetGlobalSystemLogLevel(); + LoggingPrivSetLevels(system_log_level != LOG_LEVEL_NOTHING ? system_log_level : global_log_level, + global_log_level); + } + break; + + default: + break; + } + + SeqRemove(ctx->stack, SeqLength(ctx->stack) - 1); + + last_frame = LastStackFrame(ctx, 0); + if (last_frame) + { + if (last_frame->type == STACK_FRAME_TYPE_PROMISE_ITERATION) + { + const Promise *pp = EvalContextStackCurrentPromise(ctx); + LoggingPrivSetLevels(CalculateLogLevel(pp), CalculateReportLevel(pp)); + } + } + + LogDebug(LOG_MOD_EVALCTX, "POPPED FRAME (type %s)", + STACK_FRAME_TYPE_STR[last_frame_type]); +} + +bool EvalContextClassRemove(EvalContext *ctx, const char *ns, const char *name) +{ + for (size_t i = 0; i < SeqLength(ctx->stack); i++) + { + StackFrame *frame = SeqAt(ctx->stack, i); + if (frame->type != STACK_FRAME_TYPE_BUNDLE) + { + continue; + } + + ClassTableRemove(frame->data.bundle.classes, ns, name); + } + + return ClassTableRemove(ctx->global_classes, ns, name); +} + +Class *EvalContextClassGet(const EvalContext *ctx, const char *ns, const char *name) +{ + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE); + if (frame) + { + Class *cls = ClassTableGet(frame->data.bundle.classes, ns, name); + if (cls) + { + return cls; + } + } + + return ClassTableGet(ctx->global_classes, ns, name); +} + +Class *EvalContextClassMatch(const EvalContext *ctx, const char *regex) +{ + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE); + if (frame) + { + Class *cls = ClassTableMatch(frame->data.bundle.classes, regex); + if (cls) + { + return cls; + } + } + + return ClassTableMatch(ctx->global_classes, regex); +} + +static bool EvalContextClassPutTagsSet(EvalContext *ctx, const char *ns, const char *name, bool is_soft, + ContextScope scope, StringSet *tags, const char *comment) +{ + { + char context_copy[2 * CF_MAXVARSIZE]; + char canonified_context[CF_MAXVARSIZE]; + + + /* Redmine #7013 + * Fix for classes names longer than CF_MAXVARSIZE. */ + if (strlen(name) >= sizeof(canonified_context)) + { + Log(LOG_LEVEL_WARNING, "Skipping adding class [%s] as its name " + "is equal or longer than %zu", name, sizeof(canonified_context)); + return false; + } + + strlcpy(canonified_context, name, sizeof(canonified_context)); + + if (Chop(canonified_context, CF_EXPANDSIZE) == -1) + { + Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator"); + } + CanonifyNameInPlace(canonified_context); + + if (ns && strcmp(ns, "default") != 0) + { + snprintf(context_copy, sizeof(context_copy), "%s:%s", ns, canonified_context); + } + else + { + strlcpy(context_copy, canonified_context, sizeof(context_copy)); + } + + if (strlen(context_copy) == 0) + { + return false; + } + + if (IsRegexItemIn(ctx, ctx->heap_abort_current_bundle, context_copy)) + { + const Bundle *bundle = EvalContextStackCurrentBundle(ctx); + if (bundle != NULL) + { + Log(LOG_LEVEL_ERR, "Bundle '%s' aborted on defined class '%s'", bundle->name, context_copy); + } + else + { + Log(LOG_LEVEL_ERR, "Bundle (unknown) aborted on defined class '%s'", context_copy); + } + SetBundleAborted(ctx); + } + + if (IsRegexItemIn(ctx, ctx->heap_abort, context_copy)) + { + Log(LOG_LEVEL_NOTICE, "cf-agent aborted on defined class '%s'", context_copy); + SetEvalAborted(ctx); + } + } + + Class *existing_class = EvalContextClassGet(ctx, ns, name); + if (existing_class && existing_class->scope == scope) + { + return false; + } + + Nova_ClassHistoryAddContextName(ctx->all_classes, name); + + switch (scope) + { + case CONTEXT_SCOPE_BUNDLE: + { + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE); + if (!frame) + { + ProgrammingError("Attempted to add bundle class '%s' while not evaluating a bundle", name); + } + ClassTablePut(frame->data.bundle.classes, ns, name, is_soft, scope, tags, comment); + } + break; + + case CONTEXT_SCOPE_NAMESPACE: + ClassTablePut(ctx->global_classes, ns, name, is_soft, scope, tags, comment); + break; + + case CONTEXT_SCOPE_NONE: + ProgrammingError("Attempted to add a class without a set scope"); + } + + if (!BundleAborted(ctx)) + { + for (const Item *ip = ctx->heap_abort_current_bundle; ip != NULL; ip = ip->next) + { + const char *class_expr = ip->name; + + if (IsDefinedClass(ctx, class_expr)) + { + Log(LOG_LEVEL_ERR, "Setting abort for '%s' when setting class '%s'", ip->name, name); + SetBundleAborted(ctx); + break; + } + } + } + + return true; +} + +static bool EvalContextClassPut(EvalContext *ctx, const char *ns, const char *name, bool is_soft, + ContextScope scope, const char *tags, const char *comment) +{ + StringSet *tags_set = (NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ',')); + bool ret = EvalContextClassPutTagsSet(ctx, ns, name, is_soft, scope, tags_set, comment); + if (!ret) + { + StringSetDestroy(tags_set); + } + return ret; +} + +static const char *EvalContextCurrentNamespace(const EvalContext *ctx) +{ + size_t i = SeqLength(ctx->stack); + while (i > 0) + { + i--; + StackFrame *frame = SeqAt(ctx->stack, i); + switch (frame->type) + { + case STACK_FRAME_TYPE_BUNDLE: + return frame->data.bundle.owner->ns; + case STACK_FRAME_TYPE_BODY: + return frame->data.body.owner->ns; + default: + break; /* out of the switch but not the loop ! */ + } + } + + return NULL; +} + +bool EvalContextClassPutHard(EvalContext *ctx, const char *name, const char *tags) +{ + return EvalContextClassPut(ctx, NULL, name, false, CONTEXT_SCOPE_NAMESPACE, tags, NULL); +} + +bool EvalContextClassPutSoft(EvalContext *ctx, const char *name, ContextScope scope, const char *tags) +{ + StringSet *tags_set = (NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ',')); + bool ret = EvalContextClassPutSoftTagsSet(ctx, name, scope, tags_set); + if (!ret) + { + StringSetDestroy(tags_set); + } + return ret; +} + +bool EvalContextClassPutSoftTagsSet(EvalContext *ctx, const char *name, ContextScope scope, StringSet *tags) +{ + return EvalContextClassPutSoftTagsSetWithComment(ctx, name, scope, tags, NULL); +} + +bool EvalContextClassPutSoftTagsSetWithComment(EvalContext *ctx, const char *name, ContextScope scope, + StringSet *tags, const char *comment) +{ + bool ret; + char *ns = NULL; + char *delim = strchr(name, ':'); + + if (delim) + { + ns = xstrndup(name, delim - name); + } + + ret = EvalContextClassPutTagsSet(ctx, ns ? ns : EvalContextCurrentNamespace(ctx), + ns ? delim + 1 : name, true, scope, tags, comment); + free(ns); + return ret; +} + +bool EvalContextClassPutSoftNS(EvalContext *ctx, const char *ns, const char *name, + ContextScope scope, const char *tags) +{ + return EvalContextClassPut(ctx, ns, name, true, scope, tags, NULL); +} + +/** + * Takes over #tags in case of success. + */ +bool EvalContextClassPutSoftNSTagsSet(EvalContext *ctx, const char *ns, const char *name, + ContextScope scope, StringSet *tags) +{ + return EvalContextClassPutSoftNSTagsSetWithComment(ctx, ns, name, scope, tags, NULL); +} + +bool EvalContextClassPutSoftNSTagsSetWithComment(EvalContext *ctx, const char *ns, const char *name, + ContextScope scope, StringSet *tags, const char *comment) +{ + return EvalContextClassPutTagsSet(ctx, ns, name, true, scope, tags, comment); +} + +ClassTableIterator *EvalContextClassTableIteratorNewGlobal(const EvalContext *ctx, const char *ns, bool is_hard, bool is_soft) +{ + return ClassTableIteratorNew(ctx->global_classes, ns, is_hard, is_soft); +} + +ClassTableIterator *EvalContextClassTableIteratorNewLocal(const EvalContext *ctx) +{ + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE); + if (!frame) + { + return NULL; + } + + return ClassTableIteratorNew(frame->data.bundle.classes, frame->data.bundle.owner->ns, false, true); +} + +const Promise *EvalContextStackCurrentPromise(const EvalContext *ctx) +{ + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_PROMISE_ITERATION); + return frame ? frame->data.promise_iteration.owner : NULL; +} + +const Bundle *EvalContextStackCurrentBundle(const EvalContext *ctx) +{ + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE); + return frame ? frame->data.bundle.owner : NULL; +} + +const RingBuffer *EvalContextStackCurrentMessages(const EvalContext *ctx) +{ + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_PROMISE_ITERATION); + return frame ? frame->data.promise_iteration.log_messages : NULL; +} + + + +/** + * @brief Concatenate string #str to #buf, replacing mangling + * characters '*' and '#' with their visible counterparts. + */ +static void BufferAppendPromiseStr(Buffer *buf, const char *str) +{ + for (const char *ch = str; *ch != '\0'; ch++) + { + switch (*ch) + { + case CF_MANGLED_NS: + BufferAppendChar(buf, ':'); + break; + + case CF_MANGLED_SCOPE: + BufferAppendChar(buf, '.'); + break; + + default: + BufferAppendChar(buf, *ch); + break; + } + } +} + +/** + * @brief Like @c BufferAppendPromiseStr, but if @c str contains newlines + * and is longer than 2*N+3, then only copy an abbreviated version + * consisting of the first and last N characters, separated by @c `...` + * + * @param buffer Buffer to be used. + * @param promiser Constant string to append + * @param N Max. length of initial/final segment of @c promiser to keep + * @note 2*N+3 is the maximum length of the appended string (excl. terminating NULL) + * + */ +static void BufferAppendAbbreviatedStr(Buffer *buf, + const char *promiser, const int N) +{ + /* check if `promiser` contains a new line (may happen for "insert_lines") */ + const char *const nl = strchr(promiser, '\n'); + if (NULL == nl) + { + BufferAppendPromiseStr(buf, promiser); + } + else + { + /* `promiser` contains a newline: abbreviate it by taking the first and last few characters */ + static const char sep[] = "..."; + char abbr[sizeof(sep) + 2 * N]; + const int head = (nl > promiser + N) ? N : (nl - promiser); + const char * last_line = strrchr(promiser, '\n') + 1; + assert(last_line); /* not NULL, we know we have at least one '\n' */ + const int tail = strlen(last_line); + if (tail > N) + { + last_line += tail - N; + } + memcpy(abbr, promiser, head); + strcpy(abbr + head, sep); + strcat(abbr, last_line); + BufferAppendPromiseStr(buf, abbr); + } +} + +char *EvalContextStackPath(const EvalContext *ctx) +{ + Buffer *path = BufferNew(); + + for (size_t i = 0; i < SeqLength(ctx->stack); i++) + { + StackFrame *frame = SeqAt(ctx->stack, i); + switch (frame->type) + { + case STACK_FRAME_TYPE_BODY: + BufferAppendChar(path, '/'); + BufferAppend(path, frame->data.body.owner->name, CF_BUFSIZE); + break; + + case STACK_FRAME_TYPE_BUNDLE: + BufferAppendChar(path, '/'); + BufferAppend(path, frame->data.bundle.owner->ns, CF_BUFSIZE); + BufferAppendChar(path, '/'); + BufferAppend(path, frame->data.bundle.owner->name, CF_BUFSIZE); + break; + + case STACK_FRAME_TYPE_BUNDLE_SECTION: + BufferAppendChar(path, '/'); + BufferAppend(path, frame->data.bundle_section.owner->promise_type, CF_BUFSIZE); + + case STACK_FRAME_TYPE_PROMISE: + break; + + case STACK_FRAME_TYPE_PROMISE_ITERATION: + BufferAppendChar(path, '/'); + BufferAppendChar(path, '\''); + BufferAppendAbbreviatedStr(path, frame->data.promise_iteration.owner->promiser, CF_MAXFRAGMENT); + BufferAppendChar(path, '\''); + if (i == SeqLength(ctx->stack) - 1 && + /* For some reason verify_packages.c is adding NULL iteration + * frames all over the place; TODO fix. */ + frame->data.promise_iteration.iter_ctx != NULL) + { + BufferAppendF(path, "[%zu]", + PromiseIteratorIndex(frame->data.promise_iteration.iter_ctx)); + } + break; + + default: + ProgrammingError("Unhandled stack frame type"); + } + } + + return BufferClose(path); +} + +StringSet *EvalContextStackPromisees(const EvalContext *ctx) +{ + StringSet *promisees = StringSetNew(); + + for (size_t i = 0; i < SeqLength(ctx->stack); i++) + { + StackFrame *frame = SeqAt(ctx->stack, i); + if (frame->type != STACK_FRAME_TYPE_PROMISE_ITERATION) + { + continue; + } + + Rval promisee = frame->data.promise_iteration.owner->promisee; + + switch (promisee.type) + { + case RVAL_TYPE_SCALAR: + StringSetAdd(promisees, xstrdup(RvalScalarValue(promisee))); + break; + + case RVAL_TYPE_LIST: + { + for (const Rlist *rp = RvalRlistValue(promisee); rp; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_SCALAR) + { + StringSetAdd(promisees, xstrdup(RvalScalarValue(rp->val))); + } + else + { + assert(false && "Canary: promisee list contained non-scalar value"); + } + } + } + break; + + case RVAL_TYPE_NOPROMISEE: + break; + + default: + assert(false && "Canary: promisee not scalar or list"); + } + } + + return promisees; +} + +/** + * We cannot have double-scoped variables (e.g. "this.config.var1"), so if we + * want to put a scoped variable into a special scope, we need to mangle the + * name like this: + * "config.var1" -> "config___var1" + */ +static inline char *MangleScopedVarNameIntoSpecialScopeName(const char *scope, const char *var_name) +{ + const size_t var_name_len = strlen(var_name); + + /* Replace '.' with NESTED_SCOPE_SEP */ + char *new_var_name = xmalloc(var_name_len + sizeof(NESTED_SCOPE_SEP)); + memcpy(new_var_name, var_name, var_name_len + 1 /* including '\0' */); + + /* Make sure we only replace the "scope." string, not all dots. */ + char *scope_with_dot = StringConcatenate(2, scope, "."); + char *scope_with_underscores = StringConcatenate(2, scope, NESTED_SCOPE_SEP); + + /* Only replace the first "scope." occurrence (there might be "scope." + * inside square brackets). */ + NDEBUG_UNUSED ssize_t ret = StringReplaceN(new_var_name, var_name_len + sizeof(NESTED_SCOPE_SEP), + scope_with_dot, scope_with_underscores, 1); + assert(ret == (var_name_len + sizeof(NESTED_SCOPE_SEP) - 2)); + + free(scope_with_dot); + free(scope_with_underscores); + + return new_var_name; +} + +/* + * Copies value, so you need to free your own copy afterwards. + */ +bool EvalContextVariablePutSpecial(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags) +{ + StringSet *tags_set = (NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ',')); + bool ret = EvalContextVariablePutSpecialTagsSet(ctx, scope, lval, value, type, tags_set); + if (!ret) + { + StringSetDestroy(tags_set); + } + return ret; +} + +/** + * Copies value, so you need to free your own copy afterwards, EXCEPT FOR THE + * 'tags' SET which is taken over as-is IF THE VARIABLE IS SUCCESSFULLY ADDED. + */ +bool EvalContextVariablePutSpecialTagsSet(EvalContext *ctx, SpecialScope scope, + const char *lval, const void *value, + DataType type, StringSet *tags) +{ + return EvalContextVariablePutSpecialTagsSetWithComment(ctx, scope, lval, value, type, tags, NULL); +} + +bool EvalContextVariablePutSpecialTagsSetWithComment(EvalContext *ctx, SpecialScope scope, + const char *lval, const void *value, + DataType type, StringSet *tags, + const char *comment) +{ + char *new_lval = NULL; + if (strchr(lval, '.') != NULL) + { + VarRef *ref = VarRefParse(lval); + if (ref->scope != NULL) + { + new_lval = MangleScopedVarNameIntoSpecialScopeName(ref->scope, lval); + } + VarRefDestroy(ref); + } + if (strchr(lval, '[')) + { + // dealing with (legacy) array reference in lval, must parse + VarRef *ref = VarRefParseFromScope(new_lval ? new_lval : lval, SpecialScopeToString(scope)); + bool ret = EvalContextVariablePutTagsSetWithComment(ctx, ref, value, type, tags, comment); + free(new_lval); + VarRefDestroy(ref); + return ret; + } + else + { + // plain lval, skip parsing + const VarRef ref = VarRefConst(NULL, SpecialScopeToString(scope), new_lval ? new_lval : lval); + bool ret = EvalContextVariablePutTagsSetWithComment(ctx, &ref, value, type, tags, comment); + free(new_lval); + return ret; + } +} + +const void *EvalContextVariableGetSpecial( + const EvalContext *const ctx, + const SpecialScope scope, + const char *const varname, + DataType *const type_out) +{ + VarRef *const ref = VarRefParseFromScope( + varname, SpecialScopeToString(scope)); + const void *const result = EvalContextVariableGet(ctx, ref, type_out); + VarRefDestroy(ref); + + return result; +} + +/** + * @note Only use this when you know the variable is a string + * @see EvalContextVariableGetSpecial() + */ +const char *EvalContextVariableGetSpecialString( + const EvalContext *const ctx, + const SpecialScope scope, + const char *const varname) +{ + DataType type_out; + const void *const result = EvalContextVariableGetSpecial( + ctx, scope, varname, &type_out); + assert(type_out == CF_DATA_TYPE_STRING); // Programming error if not string + return (type_out == CF_DATA_TYPE_STRING) ? result : NULL; +} + +bool EvalContextVariableRemoveSpecial(const EvalContext *ctx, SpecialScope scope, const char *lval) +{ + switch (scope) + { + case SPECIAL_SCOPE_SYS: + case SPECIAL_SCOPE_MON: + case SPECIAL_SCOPE_CONST: + case SPECIAL_SCOPE_EDIT: + case SPECIAL_SCOPE_BODY: + case SPECIAL_SCOPE_THIS: + { + VarRef *ref = VarRefParseFromScope(lval, SpecialScopeToString(scope)); + bool ret = EvalContextVariableRemove(ctx, ref); + VarRefDestroy(ref); + return ret; + } + + case SPECIAL_SCOPE_NONE: + assert(false && "Attempted to remove none-special variable"); + return false; + + default: + assert(false && "Unhandled case in switch"); + return false; + } +} + +static VariableTable *GetVariableTableForScope(const EvalContext *ctx, + NDEBUG_UNUSED const char *ns, /* only used in assertions ... */ + const char *scope) +{ + assert(ctx != NULL); + + switch (SpecialScopeFromString(scope)) + { + case SPECIAL_SCOPE_DEF: + /* 'def.' is not as special as the other scopes below. (CFE-3668) */ + return ctx->global_variables; + + case SPECIAL_SCOPE_SYS: + case SPECIAL_SCOPE_MON: + case SPECIAL_SCOPE_CONST: + assert(!ns || strcmp("default", ns) == 0); + return ctx->global_variables; + + case SPECIAL_SCOPE_MATCH: + assert(!ns || strcmp("default", ns) == 0); + return ctx->match_variables; + + case SPECIAL_SCOPE_EDIT: + assert(!ns || strcmp("default", ns) == 0); + { + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE); + assert(frame); + return frame->data.bundle.vars; + } + + case SPECIAL_SCOPE_BODY: + assert(!ns || strcmp("default", ns) == 0); + { + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BODY); + return frame ? frame->data.body.vars : NULL; + } + + // "this" variables can be in local or global variable table (when this is used for non-special + // varables), so return local as VariableResolve will try global table anyway. + case SPECIAL_SCOPE_THIS: + { + StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_PROMISE); + return frame ? frame->data.promise.vars : NULL; + } + + case SPECIAL_SCOPE_NONE: + return ctx->global_variables; + + default: + assert(false && "Unhandled case in switch"); + return NULL; + } +} + +bool EvalContextVariableRemove(const EvalContext *ctx, const VarRef *ref) +{ + VariableTable *table = GetVariableTableForScope(ctx, ref->ns, ref->scope); + return VariableTableRemove(table, ref); +} + +static bool IsVariableSelfReferential(const VarRef *ref, const void *value, RvalType rval_type) +{ + switch (rval_type) + { + case RVAL_TYPE_SCALAR: + if (StringContainsVar(value, ref->lval)) + { + char *ref_str = VarRefToString(ref, true); + Log(LOG_LEVEL_ERR, "The value of variable '%s' contains a reference to itself, '%s'", ref_str, (char *)value); + free(ref_str); + return true; + } + break; + + case RVAL_TYPE_LIST: + for (const Rlist *rp = value; rp != NULL; rp = rp->next) + { + if (rp->val.type != RVAL_TYPE_SCALAR) + { + continue; + } + + if (StringContainsVar(RlistScalarValue(rp), ref->lval)) + { + char *ref_str = VarRefToString(ref, true); + Log(LOG_LEVEL_ERR, "An item in list variable '%s' contains a reference to itself", ref_str); + free(ref_str); + return true; + } + } + break; + + case RVAL_TYPE_FNCALL: + case RVAL_TYPE_CONTAINER: + case RVAL_TYPE_NOPROMISEE: + break; + } + + return false; +} + +static void VarRefStackQualify(const EvalContext *ctx, VarRef *ref) +{ + StackFrame *last_frame = LastStackFrame(ctx, 0); + assert(last_frame); + + switch (last_frame->type) + { + case STACK_FRAME_TYPE_BODY: + VarRefQualify(ref, NULL, SpecialScopeToString(SPECIAL_SCOPE_BODY)); + break; + + case STACK_FRAME_TYPE_BUNDLE_SECTION: + { + StackFrame *last_last_frame = LastStackFrame(ctx, 1); + assert(last_last_frame); + assert(last_last_frame->type == STACK_FRAME_TYPE_BUNDLE); + VarRefQualify(ref, + last_last_frame->data.bundle.owner->ns, + last_last_frame->data.bundle.owner->name); + } + break; + + case STACK_FRAME_TYPE_BUNDLE: + VarRefQualify(ref, + last_frame->data.bundle.owner->ns, + last_frame->data.bundle.owner->name); + break; + + case STACK_FRAME_TYPE_PROMISE: + case STACK_FRAME_TYPE_PROMISE_ITERATION: + // Allow special "this" variables to work when used without "this" + VarRefQualify(ref, NULL, SpecialScopeToString(SPECIAL_SCOPE_THIS)); + break; + + default: + ProgrammingError("Unhandled stack frame type"); + } +} + +/* + * Copies value, so you need to free your own copy afterwards. + */ +bool EvalContextVariablePut(EvalContext *ctx, + const VarRef *ref, const void *value, + DataType type, const char *tags) +{ + StringSet *tags_set = (NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ',')); + bool ret = EvalContextVariablePutTagsSet(ctx, ref, value, type, tags_set); + if (!ret) + { + StringSetDestroy(tags_set); + } + return ret; +} + +/** + * Copies value, so you need to free your own copy afterwards, EXCEPT FOR THE + * 'tags' SET which is taken over as-is IF THE VARIABLE IS SUCCESSFULLY ADDED. + */ +bool EvalContextVariablePutTagsSet(EvalContext *ctx, + const VarRef *ref, const void *value, + DataType type, StringSet *tags) +{ + return EvalContextVariablePutTagsSetWithComment(ctx, ref, value, type, tags, NULL); +} + +bool EvalContextVariablePutTagsSetWithComment(EvalContext *ctx, + const VarRef *ref, const void *value, + DataType type, StringSet *tags, + const char *comment) +{ + assert(type != CF_DATA_TYPE_NONE); + assert(ref); + assert(ref->lval); + + /* The only possible way to get a NULL value is if it's an empty linked + * list (Rlist usually). */ + assert(value != NULL || DataTypeIsIterable(type)); + + if (strlen(ref->lval) > CF_MAXVARSIZE) + { + char *lval_str = VarRefToString(ref, true); + Log(LOG_LEVEL_ERR, "Variable '%s'' cannot be added because " + "its length exceeds the maximum length allowed ('%d' characters)", + lval_str, CF_MAXVARSIZE); + free(lval_str); + return false; + } + + if (strcmp(ref->scope, "body") != 0 && + IsVariableSelfReferential(ref, value, DataTypeToRvalType(type))) + { + return false; + } + + Rval rval = (Rval) { (void *)value, DataTypeToRvalType(type) }; + VariableTable *table = GetVariableTableForScope(ctx, ref->ns, ref->scope); + const Promise *pp = EvalContextStackCurrentPromise(ctx); + VariableTablePut(table, ref, &rval, type, tags, SafeStringDuplicate(comment), pp ? pp->org_pp : pp); + return true; +} + +/** + * Change ref for e.g. 'config.var1' to 'this.config___var1' + * + * @see MangleScopedVarNameIntoSpecialScopeName() + */ +static inline VarRef *MangledThisScopedRef(const VarRef *ref) +{ + VarRef *mangled_this_ref = VarRefCopy(ref); + char *scope_underscores_lval = StringConcatenate(3, mangled_this_ref->scope, + NESTED_SCOPE_SEP, + mangled_this_ref->lval); + free(mangled_this_ref->lval); + mangled_this_ref->lval = scope_underscores_lval; + free(mangled_this_ref->scope); + mangled_this_ref->scope = xstrdup("this"); + + return mangled_this_ref; +} + +static Variable *VariableResolve2(const EvalContext *ctx, const VarRef *ref) +{ + assert(ref != NULL); + + // Get the variable table associated to the scope + VariableTable *table = GetVariableTableForScope(ctx, ref->ns, ref->scope); + + Variable *var; + if (table) + { + /* NOTE: The 'this.' scope should ignore namespaces because it contains + * iteration variables that don't have the namespace in their ref + * string and so VariableTableGet() would fail to find them with + * the namespace. And similar logic applies to other special + * scopes except for 'def.' which is actually not so special. */ + if ((SpecialScopeFromString(ref->scope) != SPECIAL_SCOPE_NONE) && + (SpecialScopeFromString(ref->scope) != SPECIAL_SCOPE_DEF) && + (ref->ns != NULL)) + { + VarRef *ref2 = VarRefCopy(ref); + free(ref2->ns); + ref2->ns = NULL; + var = VariableTableGet(table, ref2); + VarRefDestroy(ref2); + } + else + { + var = VariableTableGet(table, ref); + } + if (var) + { + return var; + } + else if (ref->num_indices > 0) + { + /* Iteration over slists creates special variables in the 'this.' + * scope with the slist variable replaced by the individual + * values. However, if a scoped variable is part of the variable + * reference, e.g. 'config.data[$(list)]', the special iteration + * variables use mangled names to avoid having two scopes + * (e.g. 'this.config___data[list_item1]' instead of + * 'this.config.data[list_item1]'). + * + * If the ref we are looking for has indices and it has a scope, it + * might be the case described above. Let's give it a try before + * falling back to the indexless container lookup described below + * (which will not have the list-iteration variables expanded). */ + if (ref->scope != NULL) + { + VariableTable *this_table = GetVariableTableForScope(ctx, ref->ns, + SpecialScopeToString(SPECIAL_SCOPE_THIS)); + if (this_table != NULL) + { + VarRef *mangled_this_ref = MangledThisScopedRef(ref); + var = VariableTableGet(this_table, mangled_this_ref); + VarRefDestroy(mangled_this_ref); + if (var != NULL) + { + return var; + } + } + } + + /* If the lookup with indices (the [idx1][idx2]... part of the + * variable reference) fails, there might still be a container + * variable where the indices actually refer to child objects inside + * the container structure. */ + VarRef *base_ref = VarRefCopyIndexless(ref); + var = VariableTableGet(table, base_ref); + VarRefDestroy(base_ref); + + if (var && (VariableGetType(var) == CF_DATA_TYPE_CONTAINER)) + { + return var; + } + } + } + + return NULL; +} + +/* + * Looks up a variable in the the context of the 'current scope'. This + * basically means that an unqualified reference will be looked up in the + * context of the top stack frame. + * + * Note that when evaluating a promise, this + * will qualify a reference to 'this' scope and when evaluating a body, it + * will qualify a reference to 'body' scope. + */ +static Variable *VariableResolve(const EvalContext *ctx, const VarRef *ref) +{ + assert(ref->lval); + + /* We will make a first lookup that works in almost all cases: will look + * for local or global variables, depending of the current scope. */ + + Variable *ret_var = VariableResolve2(ctx, ref); + if (ret_var != NULL) + { + return ret_var; + } + + /* Try to qualify non-scoped vars to the scope: + "this" for promises, "body" for bodies, current bundle for bundles. */ + VarRef *scoped_ref = NULL; + if (!VarRefIsQualified(ref)) + { + scoped_ref = VarRefCopy(ref); + VarRefStackQualify(ctx, scoped_ref); + ret_var = VariableResolve2(ctx, scoped_ref); + if (ret_var != NULL) + { + VarRefDestroy(scoped_ref); + return ret_var; + } + ref = scoped_ref; /* continue with the scoped variable */ + } + + const Bundle *last_bundle = EvalContextStackCurrentBundle(ctx); + + /* If we are in a promise or a body, the variable might be coming from the + * last bundle. So try a last lookup with "this" or "body" special scopes + * replaced with the last bundle. */ + + if ((SpecialScopeFromString(ref->scope) == SPECIAL_SCOPE_THIS || + SpecialScopeFromString(ref->scope) == SPECIAL_SCOPE_BODY) + && last_bundle != NULL) + { + VarRef *ref2 = VarRefCopy(ref); + VarRefQualify(ref2, last_bundle->ns, last_bundle->name); + ret_var = VariableResolve2(ctx, ref2); + + VarRefDestroy(scoped_ref); + VarRefDestroy(ref2); + return ret_var; + } + VarRefDestroy(scoped_ref); + + return NULL; +} + +/** + * + * @NOTE NULL is a valid return value if #type_out is of list type and the + * list is empty. To check if the variable didn't resolve, check if + * #type_out was set to CF_DATA_TYPE_NONE. + */ +const void *EvalContextVariableGet(const EvalContext *ctx, const VarRef *ref, DataType *type_out) +{ + Variable *var = VariableResolve(ctx, ref); + if (var) + { + const VarRef *var_ref = VariableGetRef(var); + DataType var_type = VariableGetType(var); + Rval var_rval = VariableGetRval(var, true); + + if (var_ref->num_indices == 0 && + ref->num_indices > 0 && + var_type == CF_DATA_TYPE_CONTAINER) + { + JsonElement *child = JsonSelect(RvalContainerValue(var_rval), + ref->num_indices, ref->indices); + if (child) + { + if (type_out) + { + *type_out = CF_DATA_TYPE_CONTAINER; + } + return child; + } + } + else + { + if (type_out) + { + *type_out = var_type; + } + return var_rval.item; + } + } + + if (type_out) + { + *type_out = CF_DATA_TYPE_NONE; + } + return NULL; +} + +const Promise *EvalContextVariablePromiseGet(const EvalContext *ctx, const VarRef *ref) +{ + Variable *var = VariableResolve(ctx, ref); + return var ? VariableGetPromise(var) : NULL; +} + +StringSet *EvalContextClassTags(const EvalContext *ctx, const char *ns, const char *name) +{ + Class *cls = EvalContextClassGet(ctx, ns, name); + if (!cls) + { + return NULL; + } + + assert(cls->tags != NULL); + return cls->tags; +} + +StringSet *EvalContextVariableTags(const EvalContext *ctx, const VarRef *ref) +{ + Variable *var = VariableResolve(ctx, ref); + if (!var) + { + return NULL; + } + + StringSet *var_tags = VariableGetTags(var); + return var_tags; +} + +bool EvalContextVariableClearMatch(EvalContext *ctx) +{ + return VariableTableClear(ctx->match_variables, NULL, NULL, NULL); +} + +VariableTableIterator *EvalContextVariableTableIteratorNew(const EvalContext *ctx, const char *ns, const char *scope, const char *lval) +{ + VariableTable *table = scope ? GetVariableTableForScope(ctx, ns, scope) : ctx->global_variables; + return table ? VariableTableIteratorNew(table, ns, scope, lval) : NULL; +} + + +VariableTableIterator *EvalContextVariableTableFromRefIteratorNew(const EvalContext *ctx, const VarRef *ref) +{ + assert(ref); + VariableTable *table = ref->scope ? GetVariableTableForScope(ctx, ref->ns, ref->scope) : ctx->global_variables; + return table ? VariableTableIteratorNewFromVarRef(table, ref) : NULL; +} + +const void *EvalContextVariableControlCommonGet(const EvalContext *ctx, CommonControl lval) +{ + assert(lval >= 0 && lval < COMMON_CONTROL_MAX); + + VarRef *ref = VarRefParseFromScope(CFG_CONTROLBODY[lval].lval, "control_common"); + const void *ret = EvalContextVariableGet(ctx, ref, NULL); + VarRefDestroy(ref); + return ret; +} + +static ClassRef IDRefQualify(const EvalContext *ctx, const char *id) +{ + // HACK: Because call reference names are equivalent to class names, we abuse ClassRef here + ClassRef ref = ClassRefParse(id); + if (!ClassRefIsQualified(ref)) + { + const char *ns = EvalContextCurrentNamespace(ctx); + if (ns) + { + ClassRefQualify(&ref, ns); + } + else + { + ClassRefQualify(&ref, NamespaceDefault()); + } + } + + return ref; +} + +const Bundle *EvalContextResolveBundleExpression(const EvalContext *ctx, const Policy *policy, + const char *callee_reference, const char *callee_type) +{ + ClassRef ref = IDRefQualify(ctx, callee_reference); + + const Bundle *bp = NULL; + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + const Bundle *curr_bp = SeqAt(policy->bundles, i); + if ((strcmp(curr_bp->type, callee_type) != 0) || + (strcmp(curr_bp->name, ref.name) != 0) || + !StringEqual(curr_bp->ns, ref.ns)) + { + continue; + } + + bp = curr_bp; + break; + } + + ClassRefDestroy(ref); + return bp; +} + +const Body *EvalContextFindFirstMatchingBody(const Policy *policy, const char *type, + const char *namespace, const char *name) +{ + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + const Body *curr_bp = SeqAt(policy->bodies, i); + if ((strcmp(curr_bp->type, type) == 0) && + (strcmp(curr_bp->name, name) == 0) && + StringEqual(curr_bp->ns, namespace)) + { + return curr_bp; + } + } + + return NULL; +} + +void EvalContextAppendBodyParentsAndArgs(const EvalContext *ctx, const Policy *policy, + Seq* chain, const Body *bp, const char *callee_type, + int depth) +{ + if (depth > 30) // sanity check + { + Log(LOG_LEVEL_ERR, "EvalContextAppendBodyParentsAndArgs: body inheritance chain depth %d in body %s is too much, aborting", depth, bp->name); + DoCleanupAndExit(EXIT_FAILURE); + } + + for (size_t k = 0; bp->conlist && k < SeqLength(bp->conlist); k++) + { + Constraint *scp = SeqAt(bp->conlist, k); + if (strcmp("inherit_from", scp->lval) == 0) + { + char* call = NULL; + + if (RVAL_TYPE_SCALAR == scp->rval.type) + { + call = RvalScalarValue(scp->rval); + } + else if (RVAL_TYPE_FNCALL == scp->rval.type) + { + call = RvalFnCallValue(scp->rval)->name; + } + + ClassRef parent_ref = IDRefQualify(ctx, call); + + // We don't do a more detailed check for circular + // inheritance because the depth check above will catch it + if (strcmp(parent_ref.name, bp->name) == 0) + { + Log(LOG_LEVEL_ERR, "EvalContextAppendBodyParentsAndArgs: self body inheritance in %s->%s, aborting", bp->name, parent_ref.name); + DoCleanupAndExit(EXIT_FAILURE); + } + + const Body *parent = EvalContextFindFirstMatchingBody(policy, callee_type, parent_ref.ns, parent_ref.name); + if (parent) + { + SeqAppend(chain, (void *)parent); + SeqAppend(chain, &(scp->rval)); + EvalContextAppendBodyParentsAndArgs(ctx, policy, chain, parent, callee_type, depth+1); + } + ClassRefDestroy(parent_ref); + } + } +} + +Seq *EvalContextResolveBodyExpression(const EvalContext *ctx, const Policy *policy, + const char *callee_reference, const char *callee_type) +{ + ClassRef ref = IDRefQualify(ctx, callee_reference); + Seq *bodies = NULL; + + const Body *bp = EvalContextFindFirstMatchingBody(policy, callee_type, ref.ns, ref.name); + if (bp) + { + bodies = SeqNew(2, NULL); + SeqAppend(bodies, (void *)bp); + SeqAppend(bodies, (void *)NULL); + EvalContextAppendBodyParentsAndArgs(ctx, policy, bodies, bp, callee_type, 1); + } + + ClassRefDestroy(ref); + return bodies; +} + +bool EvalContextPromiseLockCacheContains(const EvalContext *ctx, const char *key) +{ + return StringSetContains(ctx->promise_lock_cache, key); +} + +void EvalContextPromiseLockCachePut(EvalContext *ctx, const char *key) +{ + StringSetAdd(ctx->promise_lock_cache, xstrdup(key)); +} + +void EvalContextPromiseLockCacheRemove(EvalContext *ctx, const char *key) +{ + StringSetRemove(ctx->promise_lock_cache, key); +} + +bool EvalContextFunctionCacheGet(const EvalContext *ctx, + const FnCall *fp ARG_UNUSED, + const Rlist *args, Rval *rval_out) +{ + assert(fp != NULL); + assert(fp->name != NULL); + assert(ctx != NULL); + + if (!(ctx->eval_options & EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS)) + { + return false; + } + + // The cache key is made of the function name and all args values + Rlist *args_copy = RlistCopy(args); + Rlist *key = RlistPrepend(&args_copy, fp->name, RVAL_TYPE_SCALAR); + Rval *rval = FuncCacheMapGet(ctx->function_cache, key); + RlistDestroy(key); + if (rval) + { + if (rval_out) + { + *rval_out = *rval; + } + return true; + } + else + { + return false; + } +} + +void EvalContextFunctionCachePut(EvalContext *ctx, + const FnCall *fp ARG_UNUSED, + const Rlist *args, const Rval *rval) +{ + assert(fp != NULL); + assert(fp->name != NULL); + assert(ctx != NULL); + + if (!(ctx->eval_options & EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS)) + { + return; + } + + Rval *rval_copy = xmalloc(sizeof(Rval)); + *rval_copy = RvalCopy(*rval); + + Rlist *args_copy = RlistCopy(args); + Rlist *key = RlistPrepend(&args_copy, fp->name, RVAL_TYPE_SCALAR); + + FuncCacheMapInsert(ctx->function_cache, key, rval_copy); +} + +/* cfPS and associated machinery */ + + + +/* + * Internal functions temporarily used from logging implementation + */ + +static const char *const NO_STATUS_TYPES[] = + { "vars", "classes", "insert_lines", "delete_lines", "replace_patterns", "field_edits", NULL }; +static const char *const NO_LOG_TYPES[] = + { "vars", "classes", "insert_lines", "delete_lines", "replace_patterns", "field_edits", NULL }; + +/* + * Vars, classes and similar promises which do not affect the system itself (but + * just support evalution) do not need to be counted as repaired/failed, as they + * may change every iteration and introduce lot of churn in reports without + * giving any value. + */ +static bool IsPromiseValuableForStatus(const Promise *pp) +{ + return pp && (PromiseGetPromiseType(pp) != NULL) && (!IsStrIn(PromiseGetPromiseType(pp), NO_STATUS_TYPES)); +} + +/* + * Vars, classes and subordinate promises (like edit_line) do not need to be + * logged, as they exist to support other promises. + */ + +static bool IsPromiseValuableForLogging(const Promise *pp) +{ + return pp && (PromiseGetPromiseType(pp) != NULL) && (!IsStrIn(PromiseGetPromiseType(pp), NO_LOG_TYPES)); +} + +static void AddAllClasses(EvalContext *ctx, const Rlist *list, unsigned int persistence_ttl, + PersistentClassPolicy policy, ContextScope context_scope) +{ + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + char *classname = xstrdup(RlistScalarValue(rp)); + if (strcmp(classname, "a_class_global_from_command") == 0 || strcmp(classname, "xxx:a_class_global_from_command") == 0) + { + Log(LOG_LEVEL_ERR, "Hit '%s'", classname); + } + + CanonifyNameInPlace(classname); + + if (EvalContextHeapContainsHard(ctx, classname)) + { + Log(LOG_LEVEL_ERR, "You cannot use reserved hard class '%s' as post-condition class", classname); + // TODO: ok.. but should we take any action? continue; maybe? + } + + if (persistence_ttl > 0) + { + if (context_scope != CONTEXT_SCOPE_NAMESPACE) + { + Log(LOG_LEVEL_INFO, "Automatically promoting context scope for '%s' to namespace visibility, due to persistence", classname); + } + + Log(LOG_LEVEL_VERBOSE, "C: + persistent outcome class '%s'", classname); + EvalContextHeapPersistentSave(ctx, classname, persistence_ttl, policy, ""); + EvalContextClassPutSoft(ctx, classname, CONTEXT_SCOPE_NAMESPACE, ""); + } + else + { + Log(LOG_LEVEL_VERBOSE, "C: + promise outcome class '%s'", classname); + + switch (context_scope) + { + case CONTEXT_SCOPE_BUNDLE: + EvalContextStackFrameAddSoft(ctx, classname, ""); + break; + + case CONTEXT_SCOPE_NONE: + case CONTEXT_SCOPE_NAMESPACE: + EvalContextClassPutSoft(ctx, classname, CONTEXT_SCOPE_NAMESPACE, ""); + break; + + default: + ProgrammingError("AddAllClasses: Unexpected context_scope %d!", + context_scope); + } + } + free(classname); + } +} + +static void DeleteAllClasses(EvalContext *ctx, const Rlist *list) +{ + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + if (CheckParseContext(RlistScalarValue(rp), CF_IDRANGE) != SYNTAX_TYPE_MATCH_OK) + { + return; // TODO: interesting course of action, but why is the check there in the first place? + } + + if (EvalContextHeapContainsHard(ctx, RlistScalarValue(rp))) + { + Log(LOG_LEVEL_ERR, "You cannot cancel a reserved hard class '%s' in post-condition classes", + RlistScalarValue(rp)); + return; + } + + const char *string = RlistScalarValue(rp); + + Log(LOG_LEVEL_VERBOSE, "Cancelling class '%s'", string); + + EvalContextHeapPersistentRemove(string); + + { + ClassRef ref = ClassRefParse(CanonifyName(string)); + EvalContextClassRemove(ctx, ref.ns, ref.name); + ClassRefDestroy(ref); + } + EvalContextStackFrameRemoveSoft(ctx, CanonifyName(string)); + } +} + +ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, TrackTotalCompliance, ARG_UNUSED PromiseResult, status, ARG_UNUSED const Promise *, pp) +{ +} + +void SetPromiseOutcomeClasses(EvalContext *ctx, PromiseResult status, const DefineClasses *dc) +{ + Rlist *add_classes = NULL; + Rlist *del_classes = NULL; + + switch (status) + { + case PROMISE_RESULT_CHANGE: + add_classes = dc->change; + del_classes = dc->del_change; + break; + + case PROMISE_RESULT_TIMEOUT: + add_classes = dc->timeout; + del_classes = dc->del_notkept; + break; + + case PROMISE_RESULT_WARN: + case PROMISE_RESULT_FAIL: + case PROMISE_RESULT_INTERRUPTED: + add_classes = dc->failure; + del_classes = dc->del_notkept; + break; + + case PROMISE_RESULT_DENIED: + add_classes = dc->denied; + del_classes = dc->del_notkept; + break; + + case PROMISE_RESULT_NOOP: + add_classes = dc->kept; + del_classes = dc->del_kept; + break; + + default: + ProgrammingError("Unexpected status '%c' has been passed to SetPromiseOutcomeClasses", status); + } + + AddAllClasses(ctx, add_classes, dc->persist, dc->timer, dc->scope); + DeleteAllClasses(ctx, del_classes); +} + +static void SummarizeTransaction(EvalContext *ctx, const TransactionContext *tc, const char *logname) +{ + if (logname && (tc->log_string)) + { + Buffer *buffer = BufferNew(); + ExpandScalar(ctx, NULL, NULL, tc->log_string, buffer); + + if (strcmp(logname, "udp_syslog") == 0) + { + RemoteSysLog(tc->log_priority, BufferData(buffer)); + } + else if (strcmp(logname, "stdout") == 0) + { + Log(LOG_LEVEL_INFO, "L: %s", BufferData(buffer)); + } + else + { + struct stat dsb; + + // Does the file exist already? + if (lstat(logname, &dsb) == -1) + { + mode_t filemode = 0600; /* Mode for log file creation */ + int fd = creat(logname, filemode); + if (fd >= 0) + { + Log(LOG_LEVEL_VERBOSE, + "Created log file '%s' with requested permissions %jo", + logname, (intmax_t) filemode); + close(fd); + } + } + + FILE *fout = safe_fopen(logname, "a"); + + if (fout == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to open private log '%s'", logname); + return; + } + + Log(LOG_LEVEL_VERBOSE, "Logging string '%s' to '%s'", BufferData(buffer), logname); + fprintf(fout, "%s\n", BufferData(buffer)); + + fclose(fout); + } + + BufferDestroy(buffer); + // FIXME: This was overwriting a local copy, with no side effects. + // The intention was clearly to skip this function if called + // repeatedly. Try to introduce this change: + // tc.log_string = NULL; /* To avoid repetition */ + } +} + +static void DoSummarizeTransaction(EvalContext *ctx, PromiseResult status, const Promise *pp, const TransactionContext *tc) +{ + if (!IsPromiseValuableForLogging(pp)) + { + return; + } + + char *log_name = NULL; + + switch (status) + { + case PROMISE_RESULT_CHANGE: + log_name = tc->log_repaired; + break; + + case PROMISE_RESULT_WARN: + /* FIXME: nothing? */ + return; + + case PROMISE_RESULT_TIMEOUT: + case PROMISE_RESULT_FAIL: + case PROMISE_RESULT_DENIED: + case PROMISE_RESULT_INTERRUPTED: + log_name = tc->log_failed; + break; + + case PROMISE_RESULT_NOOP: + log_name = tc->log_kept; + break; + + default: + ProgrammingError("Unexpected promise result status: %d", status); + } + + SummarizeTransaction(ctx, tc, log_name); +} + +void NotifyDependantPromises(EvalContext *ctx, const Promise *pp, PromiseResult result) +{ + switch (result) + { + case PROMISE_RESULT_CHANGE: + case PROMISE_RESULT_NOOP: + { + const char *handle = PromiseGetHandle(pp); + if (handle) + { + StringSetAdd(ctx->dependency_handles, xstrdup(handle)); + } + } + break; + + default: + /* This promise is not yet done, don't mark it is as such */ + break; + } +} + +void ClassAuditLog(EvalContext *ctx, const Promise *pp, const Attributes *attr, PromiseResult status) +{ + assert(attr != NULL); + if (IsPromiseValuableForStatus(pp)) + { + TrackTotalCompliance(status, pp); + UpdatePromiseCounters(status); + } + + SetPromiseOutcomeClasses(ctx, status, &(attr->classes)); + DoSummarizeTransaction(ctx, status, pp, &(attr->transaction)); +} + +static void LogPromiseContext(const EvalContext *ctx, const Promise *pp) +{ + if (!WouldLog(LOG_LEVEL_VERBOSE)) + { + return; + } + + Writer *w = StringWriter(); + WriterWrite(w, "Additional promise info:"); + if (PromiseGetHandle(pp)) + { + WriterWriteF(w, " handle '%s'", PromiseGetHandle(pp)); + } + + { + const char *version = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_VERSION); + if (version) + { + WriterWriteF(w, " version '%s'", version); + } + } + + if (PromiseGetBundle(pp)->source_path) + { + WriterWriteF(w, " source path '%s' at line %zu", PromiseGetBundle(pp)->source_path, pp->offset.line); + } + + switch (pp->promisee.type) + { + case RVAL_TYPE_SCALAR: + WriterWriteF(w, " promisee '%s'", RvalScalarValue(pp->promisee)); + break; + + case RVAL_TYPE_LIST: + WriterWrite(w, " promisee "); + RlistWrite(w, pp->promisee.item); + break; + default: + break; + } + + if (pp->comment) + { + WriterWriteF(w, " comment '%s'", pp->comment); + } + + Log(LOG_LEVEL_VERBOSE, "%s", StringWriterData(w)); + WriterClose(w); +} + +void cfPS(EvalContext *ctx, LogLevel level, PromiseResult status, const Promise *pp, const Attributes *attr, const char *fmt, ...) +{ + assert(pp != NULL); + assert(attr != NULL); + + /* Either logging something (based on 'fmt') or logging nothing. */ + assert(!NULL_OR_EMPTY(fmt) || (level == LOG_LEVEL_NOTHING)); + + if (!NULL_OR_EMPTY(fmt)) + { + if (level >= LOG_LEVEL_VERBOSE) + { + LogPromiseContext(ctx, pp); + } + + va_list ap; + va_start(ap, fmt); + VLog(level, fmt, ap); + va_end(ap); + } + + /* Now complete the exits status classes and auditing */ + + if (status != PROMISE_RESULT_SKIPPED) + { + ClassAuditLog(ctx, pp, attr, status); + } +} + +void RecordChange(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) +{ + assert(ctx != NULL); + assert(pp != NULL); + assert(attr != NULL); + + LogPromiseContext(ctx, pp); + + va_list ap; + va_start(ap, fmt); + VLog(LOG_LEVEL_INFO, fmt, ap); + va_end(ap); + + SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_CHANGE, &(attr->classes)); +} + +void RecordNoChange(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) +{ + assert(ctx != NULL); + assert(pp != NULL); + assert(attr != NULL); + + LogPromiseContext(ctx, pp); + + va_list ap; + va_start(ap, fmt); + VLog(LOG_LEVEL_VERBOSE, fmt, ap); + va_end(ap); + + SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_NOOP, &(attr->classes)); +} + +void RecordFailure(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) +{ + assert(ctx != NULL); + assert(pp != NULL); + assert(attr != NULL); + + LogPromiseContext(ctx, pp); + + va_list ap; + va_start(ap, fmt); + VLog(LOG_LEVEL_ERR, fmt, ap); + va_end(ap); + + SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_FAIL, &(attr->classes)); +} + +void RecordWarning(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) +{ + assert(ctx != NULL); + assert(pp != NULL); + assert(attr != NULL); + + LogPromiseContext(ctx, pp); + + va_list ap; + va_start(ap, fmt); + VLog(LOG_LEVEL_WARNING, fmt, ap); + va_end(ap); + + SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_WARN, &(attr->classes)); +} + +void RecordDenial(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) +{ + assert(ctx != NULL); + assert(pp != NULL); + assert(attr != NULL); + + LogPromiseContext(ctx, pp); + + va_list ap; + va_start(ap, fmt); + VLog(LOG_LEVEL_ERR, fmt, ap); + va_end(ap); + + SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_DENIED, &(attr->classes)); +} + +void RecordInterruption(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) +{ + assert(ctx != NULL); + assert(pp != NULL); + assert(attr != NULL); + + LogPromiseContext(ctx, pp); + + va_list ap; + va_start(ap, fmt); + VLog(LOG_LEVEL_ERR, fmt, ap); + va_end(ap); + + SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_INTERRUPTED, &(attr->classes)); +} + +bool MakingChanges(EvalContext *ctx, const Promise *pp, const Attributes *attr, + PromiseResult *result, const char *change_desc_fmt, ...) +{ + assert(attr != NULL); + + if ((EVAL_MODE != EVAL_MODE_DRY_RUN) && (attr->transaction.action != cfa_warn)) + { + return true; + } + /* else */ + char *fmt = NULL; + if (attr->transaction.action == cfa_warn) + { + xasprintf(&fmt, "Should %s, but only warning promised", change_desc_fmt); + } + else + { + xasprintf(&fmt, "Should %s", change_desc_fmt); + } + + LogPromiseContext(ctx, pp); + + va_list ap; + va_start(ap, change_desc_fmt); + VLog(LOG_LEVEL_WARNING, fmt, ap); + va_end(ap); + + free(fmt); + + SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_WARN, &(attr->classes)); + + if (result != NULL) + { + *result = PROMISE_RESULT_WARN; + } + + return false; +} + +bool MakingInternalChanges(EvalContext *ctx, const Promise *pp, const Attributes *attr, + PromiseResult *result, const char *change_desc_fmt, ...) +{ + assert(attr != NULL); + + if ((EVAL_MODE == EVAL_MODE_NORMAL) && (attr->transaction.action != cfa_warn)) + { + return true; + } + /* else */ + char *fmt = NULL; + if (attr->transaction.action == cfa_warn) + { + xasprintf(&fmt, "Should %s, but only warning promised", change_desc_fmt); + } + else + { + xasprintf(&fmt, "Should %s", change_desc_fmt); + } + + LogPromiseContext(ctx, pp); + + va_list ap; + va_start(ap, change_desc_fmt); + VLog(LOG_LEVEL_WARNING, fmt, ap); + va_end(ap); + + free(fmt); + + SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_WARN, &(attr->classes)); + + if (result != NULL) + { + *result = PROMISE_RESULT_WARN; + } + + return false; +} + +void SetChecksumUpdatesDefault(EvalContext *ctx, bool enabled) +{ + ctx->checksum_updates_default = enabled; +} + +bool GetChecksumUpdatesDefault(const EvalContext *ctx) +{ + return ctx->checksum_updates_default; +} + +void EvalContextAddIpAddress(EvalContext *ctx, const char *ip_address, const char *iface) +{ + AppendItem(&ctx->ip_addresses, ip_address, + (iface == NULL) ? "" : iface); +} + +void EvalContextDeleteIpAddresses(EvalContext *ctx) +{ + DeleteItemList(ctx->ip_addresses); + ctx->ip_addresses = NULL; +} + +Item *EvalContextGetIpAddresses(const EvalContext *ctx) +{ + return ctx->ip_addresses; +} + +void EvalContextSetEvalOption(EvalContext *ctx, EvalContextOption option, bool value) +{ + if (value) + { + ctx->eval_options |= option; + } + else + { + ctx->eval_options &= ~option; + } +} + +bool EvalContextGetEvalOption(EvalContext *ctx, EvalContextOption option) +{ + return ((ctx->eval_options & option) != 0); +} + +void EvalContextSetLaunchDirectory(EvalContext *ctx, const char *path) +{ + free(ctx->launch_directory); + ctx->launch_directory = xstrdup(path); +} + +void EvalContextSetEntryPoint( + EvalContext *const ctx, const char *const entry_point) +{ + assert(ctx != NULL); + free(ctx->entry_point); + ctx->entry_point = SafeStringDuplicate(entry_point); +} + +const char *EvalContextGetEntryPoint(EvalContext *const ctx) +{ + assert(ctx != NULL); + return ctx->entry_point; +} + +void EvalContextSetIgnoreLocks(EvalContext *ctx, bool ignore) +{ + ctx->ignore_locks = ignore; +} + +bool EvalContextIsIgnoringLocks(const EvalContext *ctx) +{ + return ctx->ignore_locks; +} + +StringSet *ClassesMatchingLocalRecursive( + const EvalContext *ctx, + const char *regex, + const Rlist *tags, + bool first_only, + size_t stack_index) +{ + assert(ctx != NULL); + StackFrame *frame = SeqAt(ctx->stack, stack_index); + StringSet *matches; + if (frame->type == STACK_FRAME_TYPE_BUNDLE) + { + ClassTableIterator *iter = ClassTableIteratorNew( + frame->data.bundle.classes, + frame->data.bundle.owner->ns, + false, + true); // from EvalContextClassTableIteratorNewLocal() + matches = ClassesMatching(ctx, iter, regex, tags, first_only); + ClassTableIteratorDestroy(iter); + } + else + { + matches = StringSetNew(); // empty for passing up the recursion chain + } + + if (stack_index > 0 && frame->inherits_previous) + { + StringSet *parent_matches = ClassesMatchingLocalRecursive( + ctx, regex, tags, first_only, stack_index - 1); + StringSetJoin(matches, parent_matches, xstrdup); + StringSetDestroy(parent_matches); + } + + return matches; +} + +StringSet *ClassesMatchingLocal( + const EvalContext *ctx, + const char *regex, + const Rlist *tags, + bool first_only) +{ + assert(ctx != NULL); + return ClassesMatchingLocalRecursive( + ctx, regex, tags, first_only, SeqLength(ctx->stack) - 1); +} + +StringSet *ClassesMatchingGlobal( + const EvalContext *ctx, + const char *regex, + const Rlist *tags, + bool first_only) +{ + ClassTableIterator *iter = + EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true); + StringSet *matches = ClassesMatching(ctx, iter, regex, tags, first_only); + ClassTableIteratorDestroy(iter); + return matches; +} +StringSet *ClassesMatching(const EvalContext *ctx, ClassTableIterator *iter, const char* regex, const Rlist *tags, bool first_only) +{ + StringSet *matching = StringSetNew(); + + Regex *rx = CompileRegex(regex); + + Class *cls; + while ((cls = ClassTableIteratorNext(iter))) + { + char *expr = ClassRefToString(cls->ns, cls->name); + + /* FIXME: review this strcmp. Moved out from StringMatch */ + if (!strcmp(regex, expr) || + (rx && StringMatchFullWithPrecompiledRegex(rx, expr))) + { + bool pass = false; + StringSet *tagset = EvalContextClassTags(ctx, cls->ns, cls->name); + + if (tags) + { + for (const Rlist *arg = tags; arg; arg = arg->next) + { + const char *tag_regex = RlistScalarValue(arg); + const char *element; + StringSetIterator it = StringSetIteratorInit(tagset); + while ((element = StringSetIteratorNext(&it))) + { + /* FIXME: review this strcmp. Moved out from StringMatch */ + if (strcmp(tag_regex, element) == 0 || + StringMatchFull(tag_regex, element)) + { + pass = true; + break; + } + } + } + } + else // without any tags queried, accept class + { + pass = true; + } + + if (pass) + { + StringSetAdd(matching, expr); + } + else + { + free(expr); + } + } + else + { + free(expr); + } + + if (first_only && StringSetSize(matching) > 0) + { + break; + } + } + + if (rx) + { + RegexDestroy(rx); + } + + return matching; +} + +JsonElement* JsonExpandElement(EvalContext *ctx, const JsonElement *source) +{ + if (JsonGetElementType(source) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + Buffer *expbuf; + JsonElement *expanded_json; + + if (JsonGetPrimitiveType(source) == JSON_PRIMITIVE_TYPE_STRING) + { + expbuf = BufferNew(); + ExpandScalar(ctx, NULL, "this", JsonPrimitiveGetAsString(source), expbuf); + expanded_json = JsonStringCreate(BufferData(expbuf)); + BufferDestroy(expbuf); + return expanded_json; + } + else + { + return JsonCopy(source); + } + } + else if (JsonGetElementType(source) == JSON_ELEMENT_TYPE_CONTAINER) + { + if (JsonGetContainerType(source) == JSON_CONTAINER_TYPE_OBJECT) + { + JsonElement *dest = JsonObjectCreate(JsonLength(source)); + JsonIterator iter = JsonIteratorInit(source); + const char *key; + while ((key = JsonIteratorNextKey(&iter))) + { + Buffer *expbuf = BufferNew(); + ExpandScalar(ctx, NULL, "this", key, expbuf); + JsonObjectAppendElement(dest, BufferData(expbuf), JsonExpandElement(ctx, JsonObjectGet(source, key))); + BufferDestroy(expbuf); + } + + return dest; + } + else + { + JsonElement *dest = JsonArrayCreate(JsonLength(source)); + for (size_t i = 0; i < JsonLength(source); i++) + { + JsonArrayAppendElement(dest, JsonExpandElement(ctx, JsonArrayGet(source, i))); + } + return dest; + } + } + + ProgrammingError("JsonExpandElement: unexpected container type"); + return NULL; +} + +const StringSet *EvalContextAllClassesGet(const EvalContext *ctx) +{ + assert (ctx); + return ctx->all_classes; +} + +void EvalContextAllClassesLoggingEnable(EvalContext *ctx, bool enable) +{ + assert (ctx); + Nova_ClassHistoryEnable(&(ctx->all_classes), enable); +} + +void EvalContextPushBundleName(const EvalContext *ctx, const char *bundle_name) +{ + assert (ctx); + StringSetAdd(ctx->bundle_names, xstrdup(bundle_name)); +} + +const StringSet *EvalContextGetBundleNames(const EvalContext *ctx) +{ + assert (ctx); + return ctx->bundle_names; +} + +void EvalContextPushRemoteVarPromise(EvalContext *ctx, const char *bundle_name, const Promise *pp) +{ + assert (ctx); + + /* initiliaze the map if needed */ + if (ctx->remote_var_promises == NULL) + { + ctx->remote_var_promises = RemoteVarPromisesMapNew(); + } + + Seq *promises = RemoteVarPromisesMapGet(ctx->remote_var_promises, bundle_name); + if (promises == NULL) + { + /* initialize the sequence if needed */ + /* ItemDestroy == NULL because we need to store the exact pointers not + * copies */ + promises = SeqNew(10, NULL); + RemoteVarPromisesMapInsert(ctx->remote_var_promises, xstrdup(bundle_name), promises); + } + /* intentionally not making a copy here, we need the exact pointer */ + SeqAppend(promises, (void *) pp); +} + +const Seq *EvalContextGetRemoteVarPromises(const EvalContext *ctx, const char *bundle_name) +{ + assert (ctx); + if (ctx->remote_var_promises == NULL) + { + return NULL; + } + return RemoteVarPromisesMapGet(ctx->remote_var_promises, bundle_name); +} + +void EvalContextSetDumpReports(EvalContext *ctx, bool dump_reports) +{ + assert(ctx != NULL); + ctx->dump_reports = dump_reports; + if (dump_reports) + { + Log(LOG_LEVEL_VERBOSE, "Report dumping is enabled"); + } +} + +bool EvalContextGetDumpReports(EvalContext *ctx) +{ + assert(ctx != NULL); + + return ctx->dump_reports; +} + +void EvalContextUpdateDumpReports(EvalContext *ctx) +{ + assert(ctx != NULL); + + char enable_file_path[PATH_MAX]; + snprintf( + enable_file_path, + PATH_MAX, + "%s%cenable_report_dumps", + GetWorkDir(), + FILE_SEPARATOR); + EvalContextSetDumpReports(ctx, (access(enable_file_path, F_OK) == 0)); +} + +static char chrooted_path[PATH_MAX + 1] = {0}; +static size_t chroot_len = 0; +void SetChangesChroot(const char *chroot) +{ + assert(chroot != NULL); + + /* This function should only be called once. */ + assert(chroot_len == 0); + + chroot_len = SafeStringLength(chroot); + + memcpy(chrooted_path, chroot, chroot_len); + + /* Make sure there is a file separator at the end. */ + if (!IsFileSep(chroot[chroot_len - 1])) + { + chroot_len++; + chrooted_path[chroot_len - 1] = FILE_SEPARATOR; + } +} + +const char *ToChangesChroot(const char *orig_path) +{ + /* SetChangesChroot() should be called first. */ + assert(chroot_len != 0); + + assert(orig_path != NULL); + assert(IsAbsPath(orig_path)); + assert(strlen(orig_path) <= (PATH_MAX - chroot_len - 1)); + + size_t offset = 0; +#ifdef __MINGW32__ + /* On Windows, absolute path starts with the drive letter and colon followed + * by '\'. Let's replace the ":\" with just "\" so that each drive has its + * own directory tree in the chroot. */ + if ((orig_path[0] > 'A') && ((orig_path[0] < 'Z')) && (orig_path[1] == ':')) + { + chrooted_path[chroot_len] = orig_path[0]; + chrooted_path[chroot_len + 1] = FILE_SEPARATOR; + orig_path += 2; + offset += 2; + } +#endif + + while (orig_path[0] == FILE_SEPARATOR) + { + orig_path++; + } + + /* Adds/copies the NUL-byte at the end of the string. */ + strncpy(chrooted_path + chroot_len + offset, orig_path, (PATH_MAX - chroot_len - offset - 1)); + + return chrooted_path; +} + +const char *ToNormalRoot(const char *orig_path) +{ + assert(strncmp(orig_path, chrooted_path, chroot_len) == 0); + + return orig_path + chroot_len - 1; +} diff --git a/libpromises/eval_context.h b/libpromises/eval_context.h new file mode 100644 index 0000000000..94df99c995 --- /dev/null +++ b/libpromises/eval_context.h @@ -0,0 +1,424 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_EVAL_CONTEXT_H +#define CFENGINE_EVAL_CONTEXT_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum +{ + STACK_FRAME_TYPE_BUNDLE, + STACK_FRAME_TYPE_BODY, + STACK_FRAME_TYPE_BUNDLE_SECTION, + STACK_FRAME_TYPE_PROMISE, + STACK_FRAME_TYPE_PROMISE_ITERATION, + STACK_FRAME_TYPE_MAX +} StackFrameType; + +typedef struct +{ + const Bundle *owner; + + ClassTable *classes; + VariableTable *vars; +} StackFrameBundle; + +typedef struct +{ + const Body *owner; + + VariableTable *vars; +} StackFrameBody; + +typedef struct +{ + const Promise *owner; + + VariableTable *vars; +} StackFramePromise; + +typedef struct +{ + const BundleSection *owner; +} StackFrameBundleSection; + +typedef struct +{ + Promise *owner; + const PromiseIterator *iter_ctx; + size_t index; + RingBuffer *log_messages; +} StackFramePromiseIteration; + +typedef struct +{ + StackFrameType type; + bool inherits_previous; // whether or not this frame inherits context from the previous frame + + union + { + StackFrameBundle bundle; + StackFrameBody body; + StackFrameBundleSection bundle_section; + StackFramePromise promise; + StackFramePromiseIteration promise_iteration; + } data; + + char *path; +} StackFrame; + +typedef enum +{ + EVAL_OPTION_NONE = 0, + + EVAL_OPTION_EVAL_FUNCTIONS = 1 << 0, + EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS = 1 << 1, + + EVAL_OPTION_FULL = 0xFFFFFFFF +} EvalContextOption; + +EvalContext *EvalContextNew(void); +void EvalContextDestroy(EvalContext *ctx); + +void EvalContextSetConfig(EvalContext *ctx, const GenericAgentConfig *config); +const GenericAgentConfig *EvalContextGetConfig(EvalContext *ctx); + +Rlist *EvalContextGetRestrictKeys(const EvalContext *ctx); +void EvalContextSetRestrictKeys(EvalContext *ctx, const Rlist *restrict_keys); + +void EvalContextHeapAddAbort(EvalContext *ctx, const char *context, const char *activated_on_context); +void EvalContextHeapAddAbortCurrentBundle(EvalContext *ctx, const char *context, const char *activated_on_context); + +void EvalContextHeapPersistentSave(EvalContext *ctx, const char *name, unsigned int ttl_minutes, PersistentClassPolicy policy, const char *tags); +void EvalContextHeapPersistentRemove(const char *context); +void EvalContextHeapPersistentLoadAll(EvalContext *ctx); + +/** + * Sets negated classes (persistent classes that should not be defined). + * + * @note Takes ownership of #negated_classes + */ +void EvalContextSetNegatedClasses(EvalContext *ctx, StringSet *negated_classes); + +bool EvalContextClassPutSoft(EvalContext *ctx, const char *name, ContextScope scope, const char *tags); +bool EvalContextClassPutSoftTagsSet(EvalContext *ctx, const char *name, ContextScope scope, StringSet *tags); +bool EvalContextClassPutSoftTagsSetWithComment(EvalContext *ctx, const char *name, ContextScope scope, + StringSet *tags, const char *comment); +bool EvalContextClassPutSoftNS(EvalContext *ctx, const char *ns, const char *name, + ContextScope scope, const char *tags); +bool EvalContextClassPutSoftNSTagsSet(EvalContext *ctx, const char *ns, const char *name, + ContextScope scope, StringSet *tags); +bool EvalContextClassPutSoftNSTagsSetWithComment(EvalContext *ctx, const char *ns, const char *name, + ContextScope scope, StringSet *tags, const char *comment); +bool EvalContextClassPutHard(EvalContext *ctx, const char *name, const char *tags); +Class *EvalContextClassGet(const EvalContext *ctx, const char *ns, const char *name); +Class *EvalContextClassMatch(const EvalContext *ctx, const char *regex); +bool EvalContextClassRemove(EvalContext *ctx, const char *ns, const char *name); +StringSet *EvalContextClassTags(const EvalContext *ctx, const char *ns, const char *name); + +ClassTableIterator *EvalContextClassTableIteratorNewGlobal(const EvalContext *ctx, const char *ns, bool is_hard, bool is_soft); +ClassTableIterator *EvalContextClassTableIteratorNewLocal(const EvalContext *ctx); + +// Class Logging +const StringSet *EvalContextAllClassesGet(const EvalContext *ctx); +void EvalContextAllClassesLoggingEnable(EvalContext *ctx, bool enable); + +void EvalContextPushBundleName(const EvalContext *ctx, const char *bundle_name); +const StringSet *EvalContextGetBundleNames(const EvalContext *ctx); + +void EvalContextPushRemoteVarPromise(EvalContext *ctx, const char *bundle_name, const Promise *pp); +const Seq *EvalContextGetRemoteVarPromises(const EvalContext *ctx, const char *bundle_name); + +void EvalContextClear(EvalContext *ctx); + +Rlist *EvalContextGetPromiseCallerMethods(EvalContext *ctx); + +void EvalContextStackPushBundleFrame(EvalContext *ctx, const Bundle *owner, const Rlist *args, bool inherits_previous); +void EvalContextStackPushBodyFrame(EvalContext *ctx, const Promise *caller, const Body *body, const Rlist *args); +void EvalContextStackPushBundleSectionFrame(EvalContext *ctx, const BundleSection *owner); +void EvalContextStackPushPromiseFrame(EvalContext *ctx, const Promise *owner); +Promise *EvalContextStackPushPromiseIterationFrame(EvalContext *ctx, const PromiseIterator *iter_ctx); +void EvalContextStackPopFrame(EvalContext *ctx); +const char *EvalContextStackToString(EvalContext *ctx); +void EvalContextSetBundleArgs(EvalContext *ctx, const Rlist *args); +void EvalContextSetPass(EvalContext *ctx, int pass); +Rlist *EvalContextGetBundleArgs(EvalContext *ctx); +int EvalContextGetPass(EvalContext *ctx); + +char *EvalContextStackPath(const EvalContext *ctx); +StringSet *EvalContextStackPromisees(const EvalContext *ctx); +const Promise *EvalContextStackCurrentPromise(const EvalContext *ctx); +const Bundle *EvalContextStackCurrentBundle(const EvalContext *ctx); +const RingBuffer *EvalContextStackCurrentMessages(const EvalContext *ctx); + +Rlist *EvalContextGetPromiseCallerMethods(EvalContext *ctx); +JsonElement *EvalContextGetPromiseCallers(EvalContext *ctx); + +bool EvalContextVariablePut(EvalContext *ctx, const VarRef *ref, const void *value, DataType type, const char *tags); +bool EvalContextVariablePutTagsSet(EvalContext *ctx, const VarRef *ref, const void *value, DataType type, StringSet *tags); +bool EvalContextVariablePutTagsSetWithComment(EvalContext *ctx, + const VarRef *ref, const void *value, + DataType type, StringSet *tags, + const char *comment); +bool EvalContextVariablePutSpecial(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags); +bool EvalContextVariablePutSpecialTagsSet(EvalContext *ctx, SpecialScope scope, const char *lval, + const void *value, DataType type, StringSet *tags); +bool EvalContextVariablePutSpecialTagsSetWithComment(EvalContext *ctx, SpecialScope scope, + const char *lval, const void *value, + DataType type, StringSet *tags, + const char *comment); +const void *EvalContextVariableGetSpecial(const EvalContext *ctx, const SpecialScope scope, const char *varname, DataType *type_out); +const char *EvalContextVariableGetSpecialString(const EvalContext *ctx, const SpecialScope scope, const char *varname); +const void *EvalContextVariableGet(const EvalContext *ctx, const VarRef *ref, DataType *type_out); +const Promise *EvalContextVariablePromiseGet(const EvalContext *ctx, const VarRef *ref); +bool EvalContextVariableRemoveSpecial(const EvalContext *ctx, SpecialScope scope, const char *lval); +bool EvalContextVariableRemove(const EvalContext *ctx, const VarRef *ref); +StringSet *EvalContextVariableTags(const EvalContext *ctx, const VarRef *ref); +bool EvalContextVariableClearMatch(EvalContext *ctx); +VariableTableIterator *EvalContextVariableTableIteratorNew(const EvalContext *ctx, const char *ns, const char *scope, const char *lval); +VariableTableIterator *EvalContextVariableTableFromRefIteratorNew(const EvalContext *ctx, const VarRef *ref); + +bool EvalContextPromiseLockCacheContains(const EvalContext *ctx, const char *key); +void EvalContextPromiseLockCachePut(EvalContext *ctx, const char *key); +void EvalContextPromiseLockCacheRemove(EvalContext *ctx, const char *key); +bool EvalContextFunctionCacheGet(const EvalContext *ctx, const FnCall *fp, const Rlist *args, Rval *rval_out); +void EvalContextFunctionCachePut(EvalContext *ctx, const FnCall *fp, const Rlist *args, const Rval *rval); + +const void *EvalContextVariableControlCommonGet(const EvalContext *ctx, CommonControl lval); + +/** + * @brief Find a bundle for a bundle call, given a callee reference (in the form of ns:bundle), and a type of bundle. + * This is requires EvalContext because the callee reference may be unqualified. + * Hopefully this should go away in the future if we make a more generalized API to simply call a bundle, + * but we have a few special rules around edit_line and so on. + */ +const Bundle *EvalContextResolveBundleExpression(const EvalContext *ctx, const Policy *policy, + const char *callee_reference, const char *callee_type); + +const Body *EvalContextFindFirstMatchingBody(const Policy *policy, const char *type, + const char *namespace, const char *name); + +/** + @brief Returns a Sequence of const Body* elements, first the body and then its parents + + Uses `inherit_from` to figure out the parents. + */ +Seq *EvalContextResolveBodyExpression(const EvalContext *ctx, const Policy *policy, + const char *callee_reference, const char *callee_type); + +/* - Parsing/evaluating expressions - */ +void ValidateClassSyntax(const char *str); +ExpressionValue CheckClassExpression(const EvalContext *ctx, const char *context); +static inline bool IsDefinedClass(const EvalContext *ctx, const char *context) +{ + return (CheckClassExpression(ctx, context) == EXPRESSION_VALUE_TRUE); +} +StringSet *ClassesMatching(const EvalContext *ctx, ClassTableIterator *iter, const char* regex, const Rlist *tags, bool first_only); +StringSet *ClassesMatchingGlobal(const EvalContext *ctx, const char* regex, const Rlist *tags, bool first_only); +StringSet *ClassesMatchingLocal(const EvalContext *ctx, const char* regex, const Rlist *tags, bool first_only); +bool EvalProcessResult(const char *process_result, StringSet *proc_attr); +bool EvalFileResult(const char *file_result, StringSet *leaf_attr); + +/* Various global options */ +void SetChecksumUpdatesDefault(EvalContext *ctx, bool enabled); +bool GetChecksumUpdatesDefault(const EvalContext *ctx); + +/* IP addresses */ +Item *EvalContextGetIpAddresses(const EvalContext *ctx); +void EvalContextAddIpAddress(EvalContext *ctx, const char *address, const char *iface); +void EvalContextDeleteIpAddresses(EvalContext *ctx); + +/* - Rest - */ +void EvalContextSetEvalOption(EvalContext *ctx, EvalContextOption option, bool value); +bool EvalContextGetEvalOption(EvalContext *ctx, EvalContextOption option); + +bool EvalContextIsIgnoringLocks(const EvalContext *ctx); +void EvalContextSetIgnoreLocks(EvalContext *ctx, bool ignore); + +void EvalContextSetLaunchDirectory(EvalContext *ctx, const char *path); +void EvalContextSetEntryPoint(EvalContext* ctx, const char *entry_point); +const char *EvalContextGetEntryPoint(EvalContext* ctx); + +bool BundleAbort(EvalContext *ctx); +bool EvalAborted(const EvalContext *ctx); +void NotifyDependantPromises(EvalContext *ctx, const Promise *pp, PromiseResult result); +bool MissingDependencies(EvalContext *ctx, const Promise *pp); + +/** + * Record promise status (result). + * + * This function should be called once for every promise to record its + * status/result. It logs the given message (#fmt and varargs) with the given + * #level based on the logging specifications in the 'action' body and + * increments the counters of actuated promises and promises + * kept/repaired/failed/... + * + * If #fmt is NULL or an empty string, nothing is logged. #level should be + * #LOG_LEVEL_NOTHING in such cases. + */ +void cfPS(EvalContext *ctx, LogLevel level, PromiseResult status, const Promise *pp, const Attributes *attr, const char *fmt, ...) FUNC_ATTR_PRINTF(6, 7); + +/** + * Log change done by the agent when evaluating policy and set the outcome + * classes. + * + * Unlike cfPS(), this function is expected to be called multiple times for the + * same promise. It's not recording a promise status, but rather one out of + * multiple changes done by the given promise. + */ +void RecordChange(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) FUNC_ATTR_PRINTF(4, 5); +void RecordNoChange(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) FUNC_ATTR_PRINTF(4, 5); +void RecordFailure(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) FUNC_ATTR_PRINTF(4, 5); +void RecordWarning(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) FUNC_ATTR_PRINTF(4, 5); +void RecordDenial(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) FUNC_ATTR_PRINTF(4, 5); +void RecordInterruption(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...) FUNC_ATTR_PRINTF(4, 5); + +/** + * Check if the given promise is allowed to make changes in the current agent + * run and if not, log the fact and update #result accordingly. + * + * The #change_desc_fmt argument and the ones following it should format a + * message that describes the action, like "rename the file '/etc/issue'", the + * implementation of this function prepends and, potentially, appends text to + * the message to form a complete sentence. + */ +bool MakingChanges(EvalContext *ctx, const Promise *pp, const Attributes *attr, + PromiseResult *result, const char *change_desc_fmt, ...) FUNC_ATTR_PRINTF(5, 6); + +/** + * Similar to MakingChanges() above, but checking if changes to internal + * structures are allowed. Audit modes should, for example, not make such + * changes, even though they make other changes (in a changes chroot). + */ +bool MakingInternalChanges(EvalContext *ctx, const Promise *pp, const Attributes *attr, + PromiseResult *result, const char *change_desc_fmt, ...) FUNC_ATTR_PRINTF(5, 6); + +/** + * Whether to make changes in a chroot or not. + */ +static inline bool ChrootChanges() +{ + return ((EVAL_MODE == EVAL_MODE_SIMULATE_DIFF) || + (EVAL_MODE == EVAL_MODE_SIMULATE_MANIFEST) || + (EVAL_MODE == EVAL_MODE_SIMULATE_MANIFEST_FULL)); +} + +/** + * Set the chroot for recording changes in files (in simulate mode(s)). + * + * @note This function should only be called once. + */ +void SetChangesChroot(const char *chroot); + +/** + * Get the path for #orig_path under the changes chroot (where changes in simulate + * mode(s) are done). #orig_path is expected to be an absolute path. + * + * @note Returns a pointer to an internal buffer and the value is only valid + * until the function is called again. + * @warning not thread-safe + */ +const char *ToChangesChroot(const char *orig_path); + +/** + * Possibly transform #path to the path where changes are to be made. + * @see ChrootChanges() + * @see ToChangesChroot() + */ +static inline const char *ToChangesPath(const char *path) +{ + return (ChrootChanges() ? ToChangesChroot(path) : path); +} + +/** + * Reverse operation for ToChangesChroot(). + * + * @return A pointer to an offset of #orig_path where the non-chrooted path starts. + * @warning Doesn't work on Windows (because on Windows the operation is not as easy as just + * shifting the pointer by the offset). + */ +#ifndef __MINGW32__ +const char *ToNormalRoot(const char *orig_path); +#else +const char *ToNormalRoot(const char *orig_path) __attribute__((error ("Not supported on Windows"))); +#endif + +PackagePromiseContext *GetPackageDefaultsFromCtx(const EvalContext *ctx); + +bool EvalContextGetSelectEndMatchEof(const EvalContext *ctx); +void EvalContextSetSelectEndMatchEof(EvalContext *ctx, bool value); + +void AddDefaultPackageModuleToContext(const EvalContext *ctx, char *name); +void AddDefaultInventoryToContext(const EvalContext *ctx, Rlist *inventory); + +void AddPackageModuleToContext(const EvalContext *ctx, PackageModuleBody *pm); +PackageModuleBody *GetPackageModuleFromContext(const EvalContext *ctx, const char *name); +PackageModuleBody *GetDefaultPackageModuleFromContext(const EvalContext *ctx); +Rlist *GetDefaultInventoryFromContext(const EvalContext *ctx); +PackagePromiseContext *GetPackagePromiseContext(const EvalContext *ctx); + +/* This function is temporarily exported. It needs to be made an detail of + * evaluator again, once variables promises are no longer specially handled */ +void ClassAuditLog(EvalContext *ctx, const Promise *pp, const Attributes *attr, PromiseResult status); + +/** + * Set classes based on the promise outcome/result. + * + * @note This function should only be called in special cases, ClassAuditLog() + * (which calls this function internally) should be called in most places. + */ +void SetPromiseOutcomeClasses(EvalContext *ctx, PromiseResult status, const DefineClasses *dc); + +ENTERPRISE_VOID_FUNC_2ARG_DECLARE(void, TrackTotalCompliance, ARG_UNUSED PromiseResult, status, ARG_UNUSED const Promise *, pp); + +ENTERPRISE_VOID_FUNC_3ARG_DECLARE(void, EvalContextLogPromiseIterationOutcome, + EvalContext *, ctx, + const Promise *, pp, + PromiseResult, result); + +ENTERPRISE_VOID_FUNC_1ARG_DECLARE(void, EvalContextSetupMissionPortalLogHook, + EvalContext *, ctx); +char *MissionPortalLogHook(LoggingPrivContext *pctx, LogLevel level, const char *message); + +JsonElement* JsonExpandElement(EvalContext *ctx, const JsonElement *source); + +void EvalContextSetDumpReports(EvalContext *ctx, bool dump_reports); +bool EvalContextGetDumpReports(EvalContext *ctx); +void EvalContextUpdateDumpReports(EvalContext *ctx); + +#endif diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c new file mode 100644 index 0000000000..2e925c03fa --- /dev/null +++ b/libpromises/evalfunction.c @@ -0,0 +1,10695 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* GetUserName(), GetGroupName() */ +#include +#include /* CompileRegex,StringMatchWithPrecompiledRegex */ +#include /* SocketConnect */ +#include +#include /* SendSocketStream */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* ThreadWait */ +#include + +#include + +#include + +#include + +#ifdef HAVE_LIBCURL +#include +#endif + +#ifdef HAVE_LIBCURL +static bool CURL_INITIALIZED = false; /* GLOBAL */ +static JsonElement *CURL_CACHE = NULL; +#endif + +#define SPLAY_PSEUDO_RANDOM_CONSTANT 8192 + +static FnCallResult FilterInternal(EvalContext *ctx, const FnCall *fp, const char *regex, const Rlist* rp, bool do_regex, bool invert, long max); + +static char *StripPatterns(char *file_buffer, const char *pattern, const char *filename); +static int BuildLineArray(EvalContext *ctx, const Bundle *bundle, const char *array_lval, const char *file_buffer, + const char *split, int maxent, DataType type, bool int_index); +static JsonElement* BuildData(EvalContext *ctx, const char *file_buffer, const char *split, int maxent, bool make_array); +static bool ExecModule(EvalContext *ctx, char *command); + +static bool CheckIDChar(const char ch); +static bool CheckID(const char *id); +static const Rlist *GetListReferenceArgument(const EvalContext *ctx, const FnCall *fp, const char *lval_str, DataType *datatype_out); +static char *CfReadFile(const char *filename, size_t maxsize); + +/*******************************************************************/ + +int FnNumArgs(const FnCallType *call_type) +{ + for (int i = 0;; i++) + { + if (call_type->args[i].pattern == NULL) + { + return i; + } + } +} + +/*******************************************************************/ + +/* assume args are all scalar literals by the time we get here + and each handler allocates the memory it returns. There is + a protocol to be followed here: + Set args, + Eval Content, + Set rtype, + ErrorFlags + + returnval = FnCallXXXResult(fp) + + */ + +/* + * Return successful FnCallResult with copy of str retained. + */ +static FnCallResult FnReturn(const char *str) +{ + return (FnCallResult) { FNCALL_SUCCESS, { xstrdup(str), RVAL_TYPE_SCALAR } }; +} + +/* + * Return successful FnCallResult with str as is. + */ +static FnCallResult FnReturnNoCopy(char *str) +{ + return (FnCallResult) { FNCALL_SUCCESS, { str, RVAL_TYPE_SCALAR } }; +} + +static FnCallResult FnReturnBuffer(Buffer *buf) +{ + return (FnCallResult) { FNCALL_SUCCESS, { BufferClose(buf), RVAL_TYPE_SCALAR } }; +} + +static FnCallResult FnReturnContainerNoCopy(JsonElement *container) +{ + return (FnCallResult) { FNCALL_SUCCESS, (Rval) { container, RVAL_TYPE_CONTAINER }}; +} + +// Currently only used for LIBCURL function, macro can be removed later +#ifdef HAVE_LIBCURL +static FnCallResult FnReturnContainer(JsonElement *container) +{ + return FnReturnContainerNoCopy(JsonCopy(container)); +} +#endif // HAVE_LIBCURL + +static FnCallResult FnReturnF(const char *fmt, ...) FUNC_ATTR_PRINTF(1, 2); + +static FnCallResult FnReturnF(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *buffer; + xvasprintf(&buffer, fmt, ap); + va_end(ap); + return FnReturnNoCopy(buffer); +} + +static FnCallResult FnReturnContext(bool result) +{ + return FnReturn(result ? "any" : "!any"); +} + +static FnCallResult FnFailure(void) +{ + return (FnCallResult) { FNCALL_FAILURE, { 0 } }; +} + +static VarRef* ResolveAndQualifyVarName(const FnCall *fp, const char *varname) +{ + VarRef *ref = NULL; + if (varname != NULL && + IsVarList(varname) && + strlen(varname) < CF_MAXVARSIZE) + { + char naked[CF_MAXVARSIZE] = ""; + GetNaked(naked, varname); + ref = VarRefParse(naked); + } + else + { + ref = VarRefParse(varname); + } + + if (!VarRefIsQualified(ref)) + { + if (fp->caller) + { + const Bundle *caller_bundle = PromiseGetBundle(fp->caller); + VarRefQualify(ref, caller_bundle->ns, caller_bundle->name); + } + else + { + Log(LOG_LEVEL_WARNING, + "Function '%s' was not called from a promise; " + "the unqualified variable reference %s cannot be qualified automatically.", + fp->name, + varname); + VarRefDestroy(ref); + return NULL; + } + } + + return ref; +} + +static JsonElement* VarRefValueToJson(const EvalContext *ctx, const FnCall *fp, const VarRef *ref, + const DataType disallowed_datatypes[], size_t disallowed_count, + bool allow_scalars, bool *allocated) +{ + assert(ref); + + DataType value_type = CF_DATA_TYPE_NONE; + const void *value = EvalContextVariableGet(ctx, ref, &value_type); + bool want_type = true; + + // Convenience storage for the name of the function, since fp can be NULL + const char* fp_name = (fp ? fp->name : "VarRefValueToJson"); + + for (size_t di = 0; di < disallowed_count; di++) + { + if (disallowed_datatypes[di] == value_type) + { + want_type = false; + break; + } + } + + JsonElement *convert = NULL; + if (want_type) + { + switch (DataTypeToRvalType(value_type)) + { + case RVAL_TYPE_LIST: + convert = JsonArrayCreate(RlistLen(value)); + for (const Rlist *rp = value; rp != NULL; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_SCALAR) /* TODO what if it's an ilist */ + { + JsonArrayAppendString(convert, RlistScalarValue(rp)); + } + else + { + ProgrammingError("Ignored Rval of list type: %s", + RvalTypeToString(rp->val.type)); + } + } + + *allocated = true; + break; + + case RVAL_TYPE_CONTAINER: + // TODO: look into optimizing this if necessary + convert = JsonCopy(value); + *allocated = true; + break; + + case RVAL_TYPE_SCALAR: + { + const char* data = value; + if (allow_scalars) + { + convert = JsonStringCreate(value); + *allocated = true; + break; + } + else + { + /* regarray,mergedata,maparray,mapdata only care for arrays + * and ignore strings, so they go through this path. */ + Log(LOG_LEVEL_DEBUG, + "Skipping scalar '%s' because 'allow_scalars' is false", + data); + } + } + // fallthrough + default: + *allocated = true; + + { + VariableTableIterator *iter = EvalContextVariableTableFromRefIteratorNew(ctx, ref); + convert = JsonObjectCreate(10); + const size_t ref_num_indices = ref->num_indices; + char *last_key = NULL; + Variable *var; + + while ((var = VariableTableIteratorNext(iter)) != NULL) + { + JsonElement *holder = convert; + JsonElement *holder_parent = NULL; + const VarRef *var_ref = VariableGetRef(var); + if (var_ref->num_indices - ref_num_indices == 1) + { + last_key = var_ref->indices[ref_num_indices]; + } + else if (var_ref->num_indices - ref_num_indices > 1) + { + Log(LOG_LEVEL_DEBUG, "%s: got ref with starting depth %zu and index count %zu", + fp_name, ref_num_indices, var_ref->num_indices); + for (size_t index = ref_num_indices; index < var_ref->num_indices-1; index++) + { + JsonElement *local = JsonObjectGet(holder, var_ref->indices[index]); + if (local == NULL) + { + local = JsonObjectCreate(1); + JsonObjectAppendObject(holder, var_ref->indices[index], local); + } + + last_key = var_ref->indices[index+1]; + holder_parent = holder; + holder = local; + } + } + + if (last_key != NULL && holder != NULL) + { + Rval var_rval = VariableGetRval(var, true); + switch (var_rval.type) + { + case RVAL_TYPE_SCALAR: + if (JsonGetElementType(holder) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_WARNING, + "Replacing a non-container JSON element '%s' with a new empty container" + " (for the '%s' subkey)", + JsonGetPropertyAsString(holder), last_key); + + assert(holder_parent != NULL); + + JsonElement *empty_container = JsonObjectCreate(10); + + /* we have to duplicate 'holder->propertyName' + * instead of just using a pointer to it here + * because 'holder' is destroyed as part of the + * JsonObjectAppendElement() call below */ + char *element_name = xstrdup(JsonGetPropertyAsString(holder)); + JsonObjectAppendElement(holder_parent, + element_name, + empty_container); + free (element_name); + holder = empty_container; + JsonObjectAppendString(holder, last_key, var_rval.item); + } + else + { + JsonElement *child = JsonObjectGet(holder, last_key); + if (child != NULL && JsonGetElementType(child) == JSON_ELEMENT_TYPE_CONTAINER) + { + Rval var_rval_secret = VariableGetRval(var, false); + Log(LOG_LEVEL_WARNING, + "Not replacing the container '%s' with a non-container value '%s'", + JsonGetPropertyAsString(child), (char*) var_rval_secret.item); + } + else + { + /* everything ok, just append the string */ + JsonObjectAppendString(holder, last_key, var_rval.item); + } + } + break; + + case RVAL_TYPE_LIST: + { + JsonElement *array = JsonArrayCreate(10); + for (const Rlist *rp = RvalRlistValue(var_rval); rp != NULL; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_SCALAR) + { + JsonArrayAppendString(array, RlistScalarValue(rp)); + } + } + JsonObjectAppendArray(holder, last_key, array); + } + break; + + default: + break; + } + } + } + + VariableTableIteratorDestroy(iter); + + if (JsonLength(convert) < 1) + { + char *varname = VarRefToString(ref, true); + Log(LOG_LEVEL_VERBOSE, "%s: argument '%s' does not resolve to a container or a list or a CFEngine array", + fp_name, varname); + free(varname); + JsonDestroy(convert); + return NULL; + } + + break; + } // end of default case + } // end of data type switch + } + else // !wanted_type + { + char *varname = VarRefToString(ref, true); + Log(LOG_LEVEL_DEBUG, "%s: argument '%s' resolved to an undesired data type", + fp_name, varname); + free(varname); + } + + return convert; +} + +static JsonElement *LookupVarRefToJson(void *ctx, const char **data) +{ + Buffer* varname = NULL; + Seq *s = StringMatchCaptures("^(([a-zA-Z0-9_]+\\.)?[a-zA-Z0-9._]+)(\\[[^\\[\\]]+\\])?", *data, false); + + if (s && SeqLength(s) > 0) // got a variable name + { + varname = BufferCopy((const Buffer*) SeqAt(s, 0)); + } + + if (s) + { + SeqDestroy(s); + } + + VarRef *ref = NULL; + if (varname) + { + ref = VarRefParse(BufferData(varname)); + // advance to the last character of the matched variable name + *data += strlen(BufferData(varname))-1; + BufferDestroy(varname); + } + + if (!ref) + { + return NULL; + } + + bool allocated = false; + JsonElement *vardata = VarRefValueToJson(ctx, NULL, ref, NULL, 0, true, &allocated); + VarRefDestroy(ref); + + // This should always return a copy + if (!allocated) + { + vardata = JsonCopy(vardata); + } + + return vardata; +} + +static JsonElement* VarNameOrInlineToJson(EvalContext *ctx, const FnCall *fp, const Rlist* rp, bool allow_scalars, bool *allocated) +{ + JsonElement *inline_data = NULL; + + assert(rp); + + if (rp->val.type == RVAL_TYPE_CONTAINER) + { + return (JsonElement*) rp->val.item; + } + + const char* data = RlistScalarValue(rp); + + JsonParseError res = JsonParseWithLookup(ctx, &LookupVarRefToJson, &data, &inline_data); + + if (res == JSON_PARSE_OK) + { + if (JsonGetElementType(inline_data) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + JsonDestroy(inline_data); + inline_data = NULL; + } + else + { + *allocated = true; + return inline_data; + } + } + + VarRef *ref = ResolveAndQualifyVarName(fp, data); + if (!ref) + { + return NULL; + } + + JsonElement *vardata = VarRefValueToJson(ctx, fp, ref, NULL, 0, allow_scalars, allocated); + VarRefDestroy(ref); + + return vardata; +} + +typedef struct { + char *address; + char *hostkey; + time_t lastseen; +} HostData; + +static HostData *HostDataNew(const char *address, const char *hostkey, time_t lastseen) +{ + HostData *data = xmalloc(sizeof(HostData)); + data->address = SafeStringDuplicate(address); + data->hostkey = SafeStringDuplicate(hostkey); + data->lastseen = lastseen; + return data; +} + +static void HostDataFree(HostData *hd) +{ + if (hd != NULL) + { + free(hd->address); + free(hd->hostkey); + free(hd); + } +} + +typedef enum { + NAME, + ADDRESS, + HOSTKEY, + NONE +} HostsSeenFieldOption; + +static HostsSeenFieldOption ParseHostsSeenFieldOption(const char *field) +{ + if (StringEqual(field, "name")) + { + return NAME; + } + else if (StringEqual(field, "address")) + { + return ADDRESS; + } + else if (StringEqual(field, "hostkey")) + { + return HOSTKEY; + } + else + { + return NONE; + } +} + +static Rlist *GetHostsFromLastseenDB(Seq *host_data, time_t horizon, HostsSeenFieldOption return_what, bool return_recent) +{ + Rlist *recent = NULL, *aged = NULL; + time_t now = time(NULL); + time_t entrytime; + char ret_host_data[CF_MAXVARSIZE]; // TODO: Could this be 1025 / NI_MAXHOST ? + + const size_t length = SeqLength(host_data); + for (size_t i = 0; i < length; ++i) + { + HostData *hd = SeqAt(host_data, i); + entrytime = hd->lastseen; + + if ((return_what == NAME || return_what == ADDRESS) + && HostKeyAddressUnknown(hd->hostkey)) + { + continue; + } + + switch (return_what) + { + case NAME: + { + char hostname[NI_MAXHOST]; + if (IPString2Hostname(hostname, hd->address, sizeof(hostname)) != -1) + { + StringCopy(hostname, ret_host_data, sizeof(ret_host_data)); + } + else + { + /* Not numeric address was requested, but IP was unresolvable. */ + StringCopy(hd->address, ret_host_data, sizeof(ret_host_data)); + } + break; + } + case ADDRESS: + StringCopy(hd->address, ret_host_data, sizeof(ret_host_data)); + break; + case HOSTKEY: + StringCopy(hd->hostkey, ret_host_data, sizeof(ret_host_data)); + break; + default: + ProgrammingError("Parser allowed invalid hostsseen() field argument"); + } + + if (entrytime < now - horizon) + { + Log(LOG_LEVEL_DEBUG, "Old entry"); + + if (RlistKeyIn(recent, ret_host_data)) + { + Log(LOG_LEVEL_DEBUG, "There is recent entry for this ret_host_data. Do nothing."); + } + else + { + Log(LOG_LEVEL_DEBUG, "Adding to list of aged hosts."); + RlistPrependScalarIdemp(&aged, ret_host_data); + } + } + else + { + Log(LOG_LEVEL_DEBUG, "Recent entry"); + + Rlist *r = RlistKeyIn(aged, ret_host_data); + if (r) + { + Log(LOG_LEVEL_DEBUG, "Purging from list of aged hosts."); + RlistDestroyEntry(&aged, r); + } + + Log(LOG_LEVEL_DEBUG, "Adding to list of recent hosts."); + RlistPrependScalarIdemp(&recent, ret_host_data); + } + } + + if (return_recent) + { + RlistDestroy(aged); + return recent; + } + else + { + RlistDestroy(recent); + return aged; + } +} + +/*********************************************************************/ + +static FnCallResult FnCallAnd(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "Function '%s', %s", fp->name, SyntaxTypeMatchToString(err)); + } + } + + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + if (!IsDefinedClass(ctx, RlistScalarValue(arg))) + { + return FnReturnContext(false); + } + } + + return FnReturnContext(true); +} + +/*******************************************************************/ + +static bool CallHostsSeenCallback(const char *hostkey, const char *address, + ARG_UNUSED bool incoming, const KeyHostSeen *quality, + void *ctx) +{ + SeqAppend(ctx, HostDataNew(address, hostkey, quality->lastseen)); + return true; +} + +/*******************************************************************/ + +static FnCallResult FnCallHostsSeen(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + Seq *host_data = SeqNew(1, HostDataFree); + + int horizon = IntFromString(RlistScalarValue(finalargs)) * 3600; + char *hostseen_policy = RlistScalarValue(finalargs->next); + char *field_str = RlistScalarValue(finalargs->next->next); + HostsSeenFieldOption field = ParseHostsSeenFieldOption(field_str); + + Log(LOG_LEVEL_DEBUG, "Calling hostsseen(%d,%s,%s)", + horizon, hostseen_policy, field_str); + + if (!ScanLastSeenQuality(&CallHostsSeenCallback, host_data)) + { + SeqDestroy(host_data); + return FnFailure(); + } + + Rlist *returnlist = GetHostsFromLastseenDB(host_data, horizon, + field, + StringEqual(hostseen_policy, "lastseen")); + + SeqDestroy(host_data); + + { + Writer *w = StringWriter(); + WriterWrite(w, "hostsseen return values:"); + for (Rlist *rp = returnlist; rp; rp = rp->next) + { + WriterWriteF(w, " '%s'", RlistScalarValue(rp)); + } + Log(LOG_LEVEL_DEBUG, "%s", StringWriterData(w)); + WriterClose(w); + } + + if (returnlist == NULL) + { + return FnFailure(); + } + + return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallHostsWithClass(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + Rlist *returnlist = NULL; + + char *class_name = RlistScalarValue(finalargs); + char *return_format = RlistScalarValue(finalargs->next); + + if (!ListHostsWithClass(ctx, &returnlist, class_name, return_format)) + { + return FnFailure(); + } + + return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +/** @brief Convert function call from/to variables to range + * + * Swap the two integers in place if the first is bigger + * Check for CF_NOINT, indicating invalid arguments + * + * @return Absolute (positive) difference, -1 for error (0 for equal) +*/ +static int int_range_convert(int *from, int *to) +{ + int old_from = *from; + int old_to = *to; + if (old_from == CF_NOINT || old_to == CF_NOINT) + { + return -1; + } + if (old_from == old_to) + { + return 0; + } + if (old_from > old_to) + { + *from = old_to; + *to = old_from; + } + assert(*to > *from); + return (*to) - (*from); +} + +static FnCallResult FnCallRandomInt(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + if (finalargs->next == NULL) + { + return FnFailure(); + } + + int from = IntFromString(RlistScalarValue(finalargs)); + int to = IntFromString(RlistScalarValue(finalargs->next)); + + int range = int_range_convert(&from, &to); + if (range == -1) + { + return FnFailure(); + } + if (range == 0) + { + return FnReturnF("%d", from); + } + + assert(range > 0); + + int result = from + (int) (drand48() * (double) range); + + return FnReturnF("%d", result); +} + +// Read an array of bytes as unsigned integers +// Convert to 64 bit unsigned integer +// Cross platform/arch, bytes[0] is always LSB of result +static uint64_t BytesToUInt64(uint8_t *bytes) +{ + uint64_t result = 0; + size_t n = 8; + for (size_t i = 0; inext == NULL || finalargs->next->next == NULL) + { + return FnFailure(); + } + signed int from = IntFromString(RlistScalarValue(finalargs)); + signed int to = IntFromString(RlistScalarValue(finalargs->next)); + + signed int range = int_range_convert(&from, &to); + if (range == -1) + { + return FnFailure(); + } + if (range == 0) + { + return FnReturnF("%d", from); + } + assert(range > 0); + + const unsigned char * const inp = RlistScalarValue(finalargs->next->next); + + // Use beginning of SHA checksum as basis: + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + memset(digest, 0, sizeof(digest)); + HashString(inp, strlen(inp), digest, HASH_METHOD_SHA256); + uint64_t converted_sha = BytesToUInt64((uint8_t*)digest); + + // Limit using modulo: + signed int result = from + (converted_sha % range); + return FnReturnF("%d", result); +} + +/*********************************************************************/ + +static FnCallResult FnCallGetEnv(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char buffer[CF_BUFSIZE] = "", ctrlstr[CF_SMALLBUF]; + + char *name = RlistScalarValue(finalargs); + int limit = IntFromString(RlistScalarValue(finalargs->next)); + + snprintf(ctrlstr, CF_SMALLBUF, "%%.%ds", limit); // -> %45s + + if (getenv(name)) + { + snprintf(buffer, CF_BUFSIZE - 1, ctrlstr, getenv(name)); + } + + return FnReturn(buffer); +} + +/*********************************************************************/ + +#if defined(HAVE_GETPWENT) && !defined(__ANDROID__) + +static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + const char *except_name = RlistScalarValue(finalargs); + const char *except_uid = RlistScalarValue(finalargs->next); + + Rlist *except_names = RlistFromSplitString(except_name, ','); + Rlist *except_uids = RlistFromSplitString(except_uid, ','); + + setpwent(); + + Rlist *newlist = NULL; + struct passwd *pw; + while ((pw = getpwent())) + { + char *pw_uid_str = StringFromLong((int)pw->pw_uid); + + if (!RlistKeyIn(except_names, pw->pw_name) && !RlistKeyIn(except_uids, pw_uid_str)) + { + RlistAppendScalarIdemp(&newlist, pw->pw_name); + } + + free(pw_uid_str); + } + + endpwent(); + + RlistDestroy(except_names); + RlistDestroy(except_uids); + + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; +} + +#else + +static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs) +{ + Log(LOG_LEVEL_ERR, "getusers is not implemented"); + return FnFailure(); +} + +#endif + +/*********************************************************************/ + +static FnCallResult FnCallEscape(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char buffer[CF_BUFSIZE]; + + buffer[0] = '\0'; + + char *name = RlistScalarValue(finalargs); + + EscapeSpecialChars(name, buffer, CF_BUFSIZE - 1, "", ""); + + return FnReturn(buffer); +} + +/*********************************************************************/ + +static FnCallResult FnCallHost2IP(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *name = RlistScalarValue(finalargs); + char ipaddr[CF_MAX_IP_LEN]; + + if (Hostname2IPString(ipaddr, name, sizeof(ipaddr)) != -1) + { + return FnReturn(ipaddr); + } + else + { + /* Retain legacy behaviour, + return hostname in case resolution fails. */ + return FnReturn(name); + } + +} + +/*********************************************************************/ + +static FnCallResult FnCallIP2Host(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + char hostname[NI_MAXHOST]; + char *ip = RlistScalarValue(finalargs); + + if (IPString2Hostname(hostname, ip, sizeof(hostname)) != -1) + { + return FnReturn(hostname); + } + else + { + /* Retain legacy behaviour, + return ip address in case resolution fails. */ + return FnReturn(ip); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallSysctlValue(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED ARG_UNUSED const Policy *policy, + ARG_LINUX_ONLY const FnCall *fp, + ARG_LINUX_ONLY const Rlist *finalargs) +{ +#ifdef __linux__ + const bool sysctlvalue_mode = (strcmp(fp->name, "sysctlvalue") == 0); + + size_t max_sysctl_data = 16 * 1024; + Buffer *procrootbuf = BufferNew(); + // Assumes that FILE_SEPARATOR is / + BufferAppendString(procrootbuf, GetRelocatedProcdirRoot()); + BufferAppendString(procrootbuf, "/proc/sys"); + + if (sysctlvalue_mode) + { + Buffer *key = BufferNewFrom(RlistScalarValue(finalargs), + strlen(RlistScalarValue(finalargs))); + + // Note that in the single-key mode, we just reuse procrootbuf. + Buffer *filenamebuf = procrootbuf; + // Assumes that FILE_SEPARATOR is / + BufferAppendChar(filenamebuf, '/'); + BufferSearchAndReplace(key, "\\.", "/", "gT"); + BufferAppendString(filenamebuf, BufferData(key)); + BufferDestroy(key); + + if (IsDir(BufferData(filenamebuf))) + { + Log(LOG_LEVEL_INFO, "Error while reading file '%s' because it's a directory (%s)", + BufferData(filenamebuf), GetErrorStr()); + BufferDestroy(filenamebuf); + return FnFailure(); + } + + Writer *w = NULL; + bool truncated = false; + int fd = safe_open(BufferData(filenamebuf), O_RDONLY | O_TEXT); + if (fd >= 0) + { + w = FileReadFromFd(fd, max_sysctl_data, &truncated); + close(fd); + } + + if (w == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Error while reading file '%s' (%s)", + BufferData(filenamebuf), GetErrorStr()); + BufferDestroy(filenamebuf); + return FnFailure(); + } + + BufferDestroy(filenamebuf); + + char *result = StringWriterClose(w); + StripTrailingNewline(result, max_sysctl_data); + return FnReturnNoCopy(result); + } + + JsonElement *sysctl_data = JsonObjectCreate(10); + + // For the remaining operations, we want the trailing slash on this. + BufferAppendChar(procrootbuf, '/'); + + Buffer *filematchbuf = BufferCopy(procrootbuf); + BufferAppendString(filematchbuf, "**/*"); + + StringSet *sysctls = GlobFileList(BufferData(filematchbuf)); + BufferDestroy(filematchbuf); + + StringSetIterator it = StringSetIteratorInit(sysctls); + const char *filename = NULL; + while ((filename = StringSetIteratorNext(&it))) + { + Writer *w = NULL; + bool truncated = false; + + if (IsDir(filename)) + { + // No warning: this is normal as we match wildcards. + continue; + } + + int fd = safe_open(filename, O_RDONLY | O_TEXT); + if (fd >= 0) + { + w = FileReadFromFd(fd, max_sysctl_data, &truncated); + close(fd); + } + + if (!w) + { + Log(LOG_LEVEL_INFO, "Error while reading file '%s' (%s)", + filename, GetErrorStr()); + continue; + } + + char *result = StringWriterClose(w); + StripTrailingNewline(result, max_sysctl_data); + + Buffer *var = BufferNewFrom(filename, strlen(filename)); + BufferSearchAndReplace(var, BufferData(procrootbuf), "", "T"); + BufferSearchAndReplace(var, "/", ".", "gT"); + JsonObjectAppendString(sysctl_data, BufferData(var), result); + free(result); + BufferDestroy(var); + } + + StringSetDestroy(sysctls); + BufferDestroy(procrootbuf); + return FnReturnContainerNoCopy(sysctl_data); +#else + return FnFailure(); +#endif +} + +/*********************************************************************/ + +/* TODO move platform-specific code to libenv. */ + +static FnCallResult FnCallGetUserInfo(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ +#ifdef __MINGW32__ + // TODO NetUserGetInfo(NULL, username, 1, &buf), see: + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370654(v=vs.85).aspx + return FnFailure(); + +#else /* !__MINGW32__ */ + + /* TODO: implement and use new GetUserInfo(uid_or_username) */ + struct passwd *pw = NULL; + + if (finalargs == NULL) + { + pw = getpwuid(getuid()); + } + else + { + char *arg = RlistScalarValue(finalargs); + if (StringIsNumeric(arg)) + { + uid_t uid = Str2Uid(arg, NULL, NULL); + if (uid == CF_SAME_OWNER) // user "*" + { + uid = getuid(); + } + else if (uid == CF_UNKNOWN_OWNER) + { + return FnFailure(); + } + + pw = getpwuid(uid); + } + else + { + pw = getpwnam(arg); + } + } + + JsonElement *result = GetUserInfo(pw); + + if (result == NULL) + { + return FnFailure(); + } + + return FnReturnContainerNoCopy(result); +#endif +} + +/*********************************************************************/ + +static FnCallResult FnCallGetUid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ +#ifdef __MINGW32__ + return FnFailure(); /* TODO */ + +#else /* !__MINGW32__ */ + + uid_t uid; + if (!GetUserID(RlistScalarValue(finalargs), &uid, LOG_LEVEL_ERR)) + { + return FnFailure(); + } + + return FnReturnF("%ju", (uintmax_t)uid); +#endif +} + +/*********************************************************************/ + +static FnCallResult FnCallGetGid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ +#ifdef __MINGW32__ + return FnFailure(); /* TODO */ + +#else /* !__MINGW32__ */ + + gid_t gid; + if (!GetGroupID(RlistScalarValue(finalargs), &gid, LOG_LEVEL_ERR)) + { + return FnFailure(); + } + + return FnReturnF("%ju", (uintmax_t)gid); +#endif +} + +/*********************************************************************/ + +static FnCallResult FnCallHandlerHash(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +/* Hash(string,md5|sha1|crypt) */ +{ + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + HashMethod type; + + char *string_or_filename = RlistScalarValue(finalargs); + char *typestring = RlistScalarValue(finalargs->next); + const bool filehash_mode = strcmp(fp->name, "file_hash") == 0; + + type = HashIdFromName(typestring); + + if (FIPS_MODE && type == HASH_METHOD_MD5) + { + Log(LOG_LEVEL_ERR, "FIPS mode is enabled, and md5 is not an approved algorithm in call to %s()", fp->name); + } + + if (filehash_mode) + { + HashFile(string_or_filename, digest, type, false); + } + else + { + HashString(string_or_filename, strlen(string_or_filename), digest, type); + } + + char hashbuffer[CF_HOSTKEY_STRING_SIZE]; + HashPrintSafe(hashbuffer, sizeof(hashbuffer), + digest, type, true); + + return FnReturn(SkipHashType(hashbuffer)); +} + +/*********************************************************************/ + +static FnCallResult FnCallHashMatch(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +/* HashMatch(string,md5|sha1|crypt,"abdxy98edj") */ +{ + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + HashMethod type; + + char *string = RlistScalarValue(finalargs); + char *typestring = RlistScalarValue(finalargs->next); + char *compare = RlistScalarValue(finalargs->next->next); + + type = HashIdFromName(typestring); + HashFile(string, digest, type, false); + + char hashbuffer[CF_HOSTKEY_STRING_SIZE]; + HashPrintSafe(hashbuffer, sizeof(hashbuffer), + digest, type, true); + + Log(LOG_LEVEL_VERBOSE, + "File '%s' hashes to '%s', compare to '%s'", + string, hashbuffer, compare); + + return FnReturnContext(strcmp(hashbuffer + 4, compare) == 0); +} + +/*********************************************************************/ + +static FnCallResult FnCallConcat(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char id[CF_BUFSIZE]; + char result[CF_BUFSIZE] = ""; + + snprintf(id, CF_BUFSIZE, "built-in FnCall concat-arg"); + +/* We need to check all the arguments, ArgTemplate does not check varadic functions */ + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err)); + } + } + + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + if (strlcat(result, RlistScalarValue(arg), CF_BUFSIZE) >= CF_BUFSIZE) + { + /* Complain */ + Log(LOG_LEVEL_ERR, "Unable to evaluate concat() function, arguments are too long"); + return FnFailure(); + } + } + + return FnReturn(result); +} + +/*********************************************************************/ + +static FnCallResult FnCallIfElse(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + unsigned int argcount = 0; + char id[CF_BUFSIZE]; + + snprintf(id, CF_BUFSIZE, "built-in FnCall ifelse-arg"); + + /* We need to check all the arguments, ArgTemplate does not check varadic functions */ + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err)); + } + argcount++; + } + + /* Require an odd number of arguments. We will always return something. */ + if ((argcount % 2) == 0) + { + FatalError(ctx, "in built-in FnCall ifelse: even number of arguments"); + } + + const Rlist *arg; + for (arg = finalargs; /* Start with arg set to finalargs. */ + arg && arg->next; /* We must have arg and arg->next to proceed. */ + arg = arg->next->next) /* arg steps forward *twice* every time. */ + { + /* Similar to classmatch(), we evaluate the first of the two + * arguments as a class. */ + if (IsDefinedClass(ctx, RlistScalarValue(arg))) + { + /* If the evaluation returned true in the current context, + * return the second of the two arguments. */ + return FnReturn(RlistScalarValue(arg->next)); + } + } + + /* If we get here, we've reached the last argument (arg->next is NULL). */ + return FnReturn(RlistScalarValue(arg)); +} + +/*********************************************************************/ + +static FnCallResult FnCallClassesMatching(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(finalargs != NULL); + bool count_only = false; + bool check_only = false; + unsigned count = 0; + + if (StringEqual(fp->name, "classesmatching")) + { + // Expected / default case + } + else if (StringEqual(fp->name, "classmatch")) + { + check_only = true; + } + else if (StringEqual(fp->name, "countclassesmatching")) + { + count_only = true; + } + else + { + FatalError(ctx, "FnCallClassesMatching: got unknown function name '%s', aborting", fp->name); + } + + if (!finalargs) + { + FatalError(ctx, "Function '%s' requires at least one argument", fp->name); + } + + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "in function '%s', '%s'", fp->name, SyntaxTypeMatchToString(err)); + } + } + + Rlist *matches = NULL; + + { + StringSet *global_matches = ClassesMatchingGlobal(ctx, RlistScalarValue(finalargs), finalargs->next, check_only); + + StringSetIterator it = StringSetIteratorInit(global_matches); + const char *element = NULL; + while ((element = StringSetIteratorNext(&it))) + { + if (count_only || check_only) + { + count++; + } + else + { + RlistPrepend(&matches, element, RVAL_TYPE_SCALAR); + } + } + + StringSetDestroy(global_matches); + } + + if (check_only && count >= 1) + { + return FnReturnContext(true); + } + + { + StringSet *local_matches = ClassesMatchingLocal(ctx, RlistScalarValue(finalargs), finalargs->next, check_only); + + StringSetIterator it = StringSetIteratorInit(local_matches); + const char *element = NULL; + while ((element = StringSetIteratorNext(&it))) + { + if (count_only || check_only) + { + count++; + } + else + { + RlistPrepend(&matches, element, RVAL_TYPE_SCALAR); + } + } + + StringSetDestroy(local_matches); + } + + if (check_only) + { + return FnReturnContext(count >= 1); + } + else if (count_only) + { + return FnReturnF("%u", count); + } + + // else, this is classesmatching() + return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } }; +} + + +static JsonElement *VariablesMatching(const EvalContext *ctx, const FnCall *fp, VariableTableIterator *iter, const Rlist *args, bool collect_full_data) +{ + JsonElement *matching = JsonObjectCreate(10); + + const char *regex = RlistScalarValue(args); + Regex *rx = CompileRegex(regex); + + Variable *v = NULL; + while ((v = VariableTableIteratorNext(iter))) + { + const VarRef *var_ref = VariableGetRef(v); + char *expr = VarRefToString(var_ref, true); + + if (rx != NULL && StringMatchFullWithPrecompiledRegex(rx, expr)) + { + StringSet *tagset = EvalContextVariableTags(ctx, var_ref); + bool pass = false; + + if ((tagset != NULL) && (args->next != NULL)) + { + for (const Rlist *arg = args->next; arg; arg = arg->next) + { + const char* tag_regex = RlistScalarValue(arg); + const char *element = NULL; + StringSetIterator it = StringSetIteratorInit(tagset); + while ((element = SetIteratorNext(&it))) + { + if (StringMatchFull(tag_regex, element)) + { + pass = true; + break; + } + } + } + } + else // without any tags queried, accept variable + { + pass = true; + } + + if (pass) + { + JsonElement *data = NULL; + bool allocated = false; + if (collect_full_data) + { + data = VarRefValueToJson(ctx, fp, var_ref, NULL, 0, true, &allocated); + } + + /* + * When we don't collect the full variable data + * (collect_full_data is false), we still create a JsonObject + * with empty strings as the values. It will be destroyed soon + * afterwards, but the code is cleaner if we do it this way than + * if we make a JsonArray in one branch and a JsonObject in the + * other branch. The empty strings provide assurance that + * serializing this JsonObject (e.g. for logging) will not + * create problems. The extra memory usage from the empty + * strings is negligible. + */ + if (data == NULL) + { + JsonObjectAppendString(matching, expr, ""); + } + else + { + if (!allocated) + { + data = JsonCopy(data); + } + JsonObjectAppendElement(matching, expr, data); + } + } + } + free(expr); + } + + if (rx) + { + RegexDestroy(rx); + } + + return matching; +} + +static FnCallResult FnCallVariablesMatching(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + bool fulldata = (strcmp(fp->name, "variablesmatching_as_data") == 0); + + if (!finalargs) + { + FatalError(ctx, "Function '%s' requires at least one argument", fp->name); + } + + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "In function '%s', %s", fp->name, SyntaxTypeMatchToString(err)); + } + } + + Rlist *matches = NULL; + + { + VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, NULL, NULL); + JsonElement *global_matches = VariablesMatching(ctx, fp, iter, finalargs, fulldata); + VariableTableIteratorDestroy(iter); + + assert (JsonGetContainerType(global_matches) == JSON_CONTAINER_TYPE_OBJECT); + + if (fulldata) + { + return FnReturnContainerNoCopy(global_matches); + + } + + JsonIterator jiter = JsonIteratorInit(global_matches); + const char *key; + while ((key = JsonIteratorNextKey(&jiter)) != NULL) + { + assert (key != NULL); + RlistPrepend(&matches, key, RVAL_TYPE_SCALAR); + } + + JsonDestroy(global_matches); + } + + return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallGetMetaTags(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + if (!finalargs) + { + FatalError(ctx, "Function '%s' requires at least one argument", fp->name); + } + + Rlist *tags = NULL; + StringSet *tagset = NULL; + + if (strcmp(fp->name, "getvariablemetatags") == 0) + { + VarRef *ref = VarRefParse(RlistScalarValue(finalargs)); + tagset = EvalContextVariableTags(ctx, ref); + VarRefDestroy(ref); + } + else if (strcmp(fp->name, "getclassmetatags") == 0) + { + ClassRef ref = ClassRefParse(RlistScalarValue(finalargs)); + tagset = EvalContextClassTags(ctx, ref.ns, ref.name); + ClassRefDestroy(ref); + } + else + { + FatalError(ctx, "FnCallGetMetaTags: got unknown function name '%s', aborting", fp->name); + } + + if (tagset == NULL) + { + Log(LOG_LEVEL_VERBOSE, "%s found variable or class %s without a tagset", fp->name, RlistScalarValue(finalargs)); + return (FnCallResult) { FNCALL_FAILURE, { 0 } }; + } + + char *key = NULL; + if (finalargs->next != NULL) + { + Buffer *keybuf = BufferNew(); + BufferPrintf(keybuf, "%s=", RlistScalarValue(finalargs->next)); + key = BufferClose(keybuf); + } + + char *element; + StringSetIterator it = StringSetIteratorInit(tagset); + while ((element = SetIteratorNext(&it))) + { + if (key != NULL) + { + if (StringStartsWith(element, key)) + { + RlistAppendScalar(&tags, element+strlen(key)); + } + } + else + { + RlistAppendScalar(&tags, element); + } + } + + free(key); + return (FnCallResult) { FNCALL_SUCCESS, { tags, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallBasename(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + assert(fp != NULL); + assert(fp->name != NULL); + if (args == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s requires a filename as first arg!", + fp->name); + return FnFailure(); + } + + char dir[PATH_MAX]; + strlcpy(dir, RlistScalarValue(args), PATH_MAX); + if (dir[0] == '\0') + { + return FnReturn(dir); + } + + char *base = basename(dir); + + if (args->next != NULL) + { + char *suffix = RlistScalarValue(args->next); + if (StringEndsWith(base, suffix)) + { + size_t base_len = strlen(base); + size_t suffix_len = strlen(suffix); + + // Remove only if actually a suffix, not the same string + if (suffix_len < base_len) + { + // On Solaris, trying to edit the buffer returned by basename + // causes segfault(!) + base = xstrndup(base, base_len - suffix_len); + return FnReturnNoCopy(base); + } + } + } + + return FnReturn(base); +} + +/*********************************************************************/ + +static FnCallResult FnCallBundlesMatching(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + if (!finalargs) + { + return FnFailure(); + } + + const char *regex = RlistScalarValue(finalargs); + Regex *rx = CompileRegex(regex); + if (!rx) + { + return FnFailure(); + } + + const Rlist *tag_args = finalargs->next; + + Rlist *matches = NULL; + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + const Bundle *bp = SeqAt(policy->bundles, i); + + char *bundle_name = BundleQualifiedName(bp); + if (StringMatchFullWithPrecompiledRegex(rx, bundle_name)) + { + VarRef *ref = VarRefParseFromBundle("tags", bp); + VarRefSetMeta(ref, true); + DataType type; + const void *bundle_tags = EvalContextVariableGet(ctx, ref, &type); + VarRefDestroy(ref); + + bool found = false; // case where tag_args are given and the bundle has no tags + + if (tag_args == NULL) + { + // we declare it found if no tags were requested + found = true; + } + /* was the variable "tags" found? */ + else if (type != CF_DATA_TYPE_NONE) + { + switch (DataTypeToRvalType(type)) + { + case RVAL_TYPE_SCALAR: + { + Rlist *searched = RlistFromSplitString(bundle_tags, ','); + found = RlistMatchesRegexRlist(searched, tag_args); + RlistDestroy(searched); + } + break; + + case RVAL_TYPE_LIST: + found = RlistMatchesRegexRlist(bundle_tags, tag_args); + break; + + default: + Log(LOG_LEVEL_WARNING, "Function '%s' only matches tags defined as a scalar or a list. " + "Bundle '%s' had meta defined as '%s'", fp->name, bundle_name, DataTypeToString(type)); + found = false; + break; + } + } + + if (found) + { + RlistPrepend(&matches, bundle_name, RVAL_TYPE_SCALAR); + } + } + + free(bundle_name); + } + + RegexDestroy(rx); + + return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static bool AddPackagesMatchingJsonLine(Regex *matcher, JsonElement *json, char *line) +{ + const size_t line_length = strlen(line); + if (line_length > CF_BUFSIZE - 80) + { + Log(LOG_LEVEL_ERR, + "Line from package inventory is too long (%zu) to be sensible", + line_length); + return false; + } + + + if (StringMatchFullWithPrecompiledRegex(matcher, line)) + { + Seq *list = SeqParseCsvString(line); + if (SeqLength(list) != 4) + { + Log(LOG_LEVEL_ERR, + "Line from package inventory '%s' did not yield correct number of elements.", + line); + SeqDestroy(list); + return true; + } + + JsonElement *line_obj = JsonObjectCreate(4); + JsonObjectAppendString(line_obj, "name", SeqAt(list, 0)); + JsonObjectAppendString(line_obj, "version", SeqAt(list, 1)); + JsonObjectAppendString(line_obj, "arch", SeqAt(list, 2)); + JsonObjectAppendString(line_obj, "method", SeqAt(list, 3)); + + SeqDestroy(list); + JsonArrayAppendObject(json, line_obj); + } + + return true; +} + +static bool GetLegacyPackagesMatching(Regex *matcher, JsonElement *json, const bool installed_mode) +{ + char filename[CF_MAXVARSIZE]; + if (installed_mode) + { + GetSoftwareCacheFilename(filename); + } + else + { + GetSoftwarePatchesFilename(filename); + } + + Log(LOG_LEVEL_DEBUG, "Reading inventory from '%s'", filename); + + FILE *const fin = fopen(filename, "r"); + if (fin == NULL) + { + Log(LOG_LEVEL_VERBOSE, + "Cannot open the %s packages inventory '%s' - " + "This is not necessarily an error. " + "Either the inventory policy has not been included, " + "or it has not had time to have an effect yet or you are using" + "new package promise and check for legacy promise is made." + "A future call may still succeed. (fopen: %s)", + installed_mode ? "installed" : "available", + filename, + GetErrorStr()); + + return true; + } + + char *line; + while ((line = GetCsvLineNext(fin)) != NULL) + { + if (!AddPackagesMatchingJsonLine(matcher, json, line)) + { + free(line); + break; + } + free(line); + } + + bool ret = (feof(fin) != 0); + fclose(fin); + + return ret; +} + +static bool GetPackagesMatching(Regex *matcher, JsonElement *json, const bool installed_mode, Rlist *default_inventory) +{ + dbid database = (installed_mode == true ? dbid_packages_installed : dbid_packages_updates); + + bool read_some_db = false; + + for (const Rlist *rp = default_inventory; rp != NULL; rp = rp->next) + { + const char *pm_name = RlistScalarValue(rp); + size_t pm_name_size = strlen(pm_name); + + if (StringContainsUnresolved(pm_name)) + { + Log(LOG_LEVEL_DEBUG, "Package module '%s' contains unresolved variables", pm_name); + continue; + } + + Log(LOG_LEVEL_DEBUG, "Reading packages (%d) for package module [%s]", + database, pm_name); + + CF_DB *db_cached; + if (!OpenSubDB(&db_cached, database, pm_name)) + { + Log(LOG_LEVEL_ERR, "Can not open database %d to get packages data.", database); + return false; + } + else + { + read_some_db = true; + } + + char *key = ""; + int data_size = ValueSizeDB(db_cached, key, strlen(key) + 1); + + Log(LOG_LEVEL_DEBUG, "Reading inventory from database: %d", data_size); + + /* For empty list we are storing one byte value in database. */ + if (data_size > 1) + { + char *buff = xmalloc(data_size + 1); + buff[data_size] = '\0'; + if (!ReadDB(db_cached, key, buff, data_size)) + { + Log(LOG_LEVEL_WARNING, "Can not read installed packages database " + "for '%s' package module.", pm_name); + continue; + } + + Seq *packages_from_module = SeqStringFromString(buff, '\n'); + free(buff); + + if (packages_from_module) + { + // Iterate over and see where match is. + for (size_t i = 0; i < SeqLength(packages_from_module); i++) + { + // With the new package promise we are storing inventory + // information it the database. This set of lines ('\n' separated) + // containing packages information. Each line is comma + // separated set of data containing name, version and architecture. + // + // Legacy package promise is using 4 values, where the last one + // is package method. In our case, method is simply package + // module name. To make sure regex matching is working as + // expected (we are comparing whole lines, containing package + // method) we need to extend the line to contain package + // module before regex match is taking place. + char *line = SeqAt(packages_from_module, i); + size_t new_line_size = strlen(line) + pm_name_size + 2; // we need coma and terminator + char new_line[new_line_size]; + strcpy(new_line, line); + strcat(new_line, ","); + strcat(new_line, pm_name); + + if (!AddPackagesMatchingJsonLine(matcher, json, new_line)) + { + break; + } + } + SeqDestroy(packages_from_module); + } + else + { + Log(LOG_LEVEL_WARNING, "Can not parse packages database for '%s' " + "package module.", pm_name); + + } + } + CloseDB(db_cached); + } + return read_some_db; +} + +static FnCallResult FnCallPackagesMatching(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + const bool installed_mode = (strcmp(fp->name, "packagesmatching") == 0); + Regex *matcher; + { + const char *regex_package = RlistScalarValue(finalargs); + const char *regex_version = RlistScalarValue(finalargs->next); + const char *regex_arch = RlistScalarValue(finalargs->next->next); + const char *regex_method = RlistScalarValue(finalargs->next->next->next); + char regex[CF_BUFSIZE]; + + // Here we will truncate the regex if the parameters add up to over CF_BUFSIZE + snprintf(regex, sizeof(regex), "^%s,%s,%s,%s$", + regex_package, regex_version, regex_arch, regex_method); + matcher = CompileRegex(regex); + if (matcher == NULL) + { + return FnFailure(); + } + } + + JsonElement *json = JsonArrayCreate(50); + bool ret = false; + + Rlist *default_inventory = GetDefaultInventoryFromContext(ctx); + + bool inventory_allocated = false; + if (default_inventory == NULL) + { + // Did not find default inventory from context, try looking for + // existing LMDB databases in the state directory + dbid database = (installed_mode ? dbid_packages_installed + : dbid_packages_updates); + Seq *const seq = SearchExistingSubDBNames(database); + const size_t length = SeqLength(seq); + for (size_t i = 0; i < length; i++) + { + const char *const db_name = SeqAt(seq, i); + RlistAppendString(&default_inventory, db_name); + inventory_allocated = true; + } + SeqDestroy(seq); + } + + if (!default_inventory) + { + // Legacy package promise + ret = GetLegacyPackagesMatching(matcher, json, installed_mode); + } + else + { + // We are using package modules. + bool some_valid_inventory = false; + for (const Rlist *rp = default_inventory; !some_valid_inventory && (rp != NULL); rp = rp->next) + { + const char *pm_name = RlistScalarValue(rp); + if (!StringContainsUnresolved(pm_name)) + { + some_valid_inventory = true; + } + } + + if (some_valid_inventory) + { + ret = GetPackagesMatching(matcher, json, installed_mode, default_inventory); + } + else + { + Log(LOG_LEVEL_DEBUG, "No valid package module inventory found"); + RegexDestroy(matcher); + JsonDestroy(json); + if (inventory_allocated) + { + RlistDestroy(default_inventory); + } + return FnFailure(); + } + } + + if (inventory_allocated) + { + RlistDestroy(default_inventory); + } + RegexDestroy(matcher); + + if (ret == false) + { + Log(LOG_LEVEL_ERR, + "%s: Unable to read package inventory.", fp->name); + JsonDestroy(json); + return FnFailure(); + } + + return FnReturnContainerNoCopy(json); +} + +/*********************************************************************/ + +static FnCallResult FnCallCanonify(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + char buf[CF_BUFSIZE]; + char *string = RlistScalarValue(finalargs); + + buf[0] = '\0'; + + if (!strcmp(fp->name, "canonifyuniquely")) + { + char hashbuffer[CF_HOSTKEY_STRING_SIZE]; + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + HashMethod type; + + type = HashIdFromName("sha1"); + HashString(string, strlen(string), digest, type); + snprintf(buf, CF_BUFSIZE, "%s_%s", string, + SkipHashType(HashPrintSafe(hashbuffer, sizeof(hashbuffer), + digest, type, true))); + } + else + { + snprintf(buf, CF_BUFSIZE, "%s", string); + } + + return FnReturn(CanonifyName(buf)); +} + +/*********************************************************************/ + +static FnCallResult FnCallTextXform(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + char *string = RlistScalarValue(finalargs); + const size_t len = strlen(string); + /* In case of string_length(), buf needs enough space to hold a number. */ + const size_t bufsiz = MAX(len + 1, PRINTSIZE(len)); + char *buf = xcalloc(bufsiz, sizeof(char)); + memcpy(buf, string, len + 1); + + if (StringEqual(fp->name, "string_downcase")) + { + for (size_t pos = 0; pos < len; pos++) + { + buf[pos] = tolower(buf[pos]); + } + } + else if (StringEqual(fp->name, "string_upcase")) + { + for (size_t pos = 0; pos < len; pos++) + { + buf[pos] = toupper(buf[pos]); + } + } + else if (StringEqual(fp->name, "string_reverse")) + { + if (len > 1) { + size_t c, i, j; + for (i = 0, j = len - 1; i < j; i++, j--) + { + c = buf[i]; + buf[i] = buf[j]; + buf[j] = c; + } + } + } + else if (StringEqual(fp->name, "string_length")) + { + xsnprintf(buf, bufsiz, "%zu", len); + } + else if (StringEqual(fp->name, "string_head")) + { + long max = IntFromString(RlistScalarValue(finalargs->next)); + // A negative offset -N on string_head() means the user wants up to the Nth from the end + if (max < 0) + { + max = len - labs(max); + } + + // If the negative offset was too big, return an empty string + if (max < 0) + { + max = 0; + } + + if ((size_t) max < bufsiz) + { + buf[max] = '\0'; + } + } + else if (StringEqual(fp->name, "string_tail")) + { + const long max = IntFromString(RlistScalarValue(finalargs->next)); + // A negative offset -N on string_tail() means the user wants up to the Nth from the start + + if (max < 0) + { + size_t offset = MIN(labs(max), len); + memcpy(buf, string + offset , len - offset + 1); + } + else if ((size_t) max < len) + { + memcpy(buf, string + len - max, max + 1); + } + } + else + { + Log(LOG_LEVEL_ERR, "text xform with unknown call function %s, aborting", fp->name); + free(buf); + return FnFailure(); + } + + return FnReturnNoCopy(buf); +} + +/*********************************************************************/ + +static FnCallResult FnCallLastNode(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *name = RlistScalarValue(finalargs); + char *split = RlistScalarValue(finalargs->next); + + Rlist *newlist = RlistFromSplitRegex(name, split, 100, true); + if (newlist != NULL) + { + char *res = NULL; + const Rlist *rp = newlist; + while (rp->next != NULL) + { + rp = rp->next; + } + assert(rp && !rp->next); + + if (rp->val.item) + { + res = xstrdup(RlistScalarValue(rp)); + } + + RlistDestroy(newlist); + if (res) + { + return FnReturnNoCopy(res); + } + } + return FnFailure(); +} + +/*******************************************************************/ + +static FnCallResult FnCallDirname(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + char dir[PATH_MAX]; + strlcpy(dir, RlistScalarValue(finalargs), PATH_MAX); + + DeleteSlash(dir); + ChopLastNode(dir); + + return FnReturn(dir); +} + +/*********************************************************************/ + +static FnCallResult FnCallClassify(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + bool is_defined = IsDefinedClass(ctx, CanonifyName(RlistScalarValue(finalargs))); + + return FnReturnContext(is_defined); +} + +/*********************************************************************/ + +static VersionComparison GenericVersionCheck( + const FnCall *fp, + const Rlist *args) +{ + assert(fp != NULL); + assert(fp->name != NULL); + if (args == NULL) + { + Log(LOG_LEVEL_ERR, + "Policy fuction %s requires version to compare against", + fp->name); + return VERSION_ERROR; + } + + const char *ver_string = RlistScalarValue(args); + VersionComparison comparison = CompareVersion(Version(), ver_string); + if (comparison == VERSION_ERROR) + { + Log(LOG_LEVEL_ERR, + "%s: Format of version comparison string '%s' is incorrect", + fp->name, ver_string); + return VERSION_ERROR; + } + + return comparison; +} + +static FnCallResult FnCallVersionCompare( + ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + assert(fp != NULL); + assert(fp->name != NULL); + if (args == NULL || args->next == NULL || args->next->next == NULL) + { + Log(LOG_LEVEL_ERR, + "Policy function %s requires 3 arguments:" + " %s(version, operator, version)", + fp->name, + fp->name); + return FnFailure(); + } + const char *const version_a = RlistScalarValue(args); + const char *const operator = RlistScalarValue(args->next); + const char *const version_b = RlistScalarValue(args->next->next); + + const BooleanOrError result = CompareVersionExpression(version_a, operator, version_b); + if (result == BOOLEAN_ERROR) { + Log(LOG_LEVEL_ERR, + "Cannot compare versions: %s(\"%s\", \"%s\", \"%s\")", + fp->name, + version_a, + operator, + version_b); + return FnFailure(); + } + return FnReturnContext(result == BOOLEAN_TRUE); +} + +static FnCallResult FnCallVersionMinimum( + ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + const VersionComparison comparison = GenericVersionCheck(fp, args); + if (comparison == VERSION_ERROR) + { + return FnFailure(); + } + + return FnReturnContext(comparison == VERSION_GREATER || + comparison == VERSION_EQUAL); +} + +static FnCallResult FnCallVersionAfter( + ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + const VersionComparison comparison = GenericVersionCheck(fp, args); + if (comparison == VERSION_ERROR) + { + return FnFailure(); + } + + return FnReturnContext(comparison == VERSION_GREATER); +} + +static FnCallResult FnCallVersionMaximum( + ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + const VersionComparison comparison = GenericVersionCheck(fp, args); + if (comparison == VERSION_ERROR) + { + return FnFailure(); + } + + return FnReturnContext(comparison == VERSION_SMALLER || + comparison == VERSION_EQUAL); +} + +static FnCallResult FnCallVersionBefore( + ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + const VersionComparison comparison = GenericVersionCheck(fp, args); + if (comparison == VERSION_ERROR) + { + return FnFailure(); + } + + return FnReturnContext(comparison == VERSION_SMALLER); +} + +static FnCallResult FnCallVersionAt( + ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + const VersionComparison comparison = GenericVersionCheck(fp, args); + if (comparison == VERSION_ERROR) + { + return FnFailure(); + } + + return FnReturnContext(comparison == VERSION_EQUAL); +} + +static FnCallResult FnCallVersionBetween( + ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + assert(fp != NULL); + assert(fp->name != NULL); + if (args == NULL || args->next == NULL) + { + Log(LOG_LEVEL_ERR, + "Policy fuction %s requires lower " + "and upper versions to compare against", + fp->name); + return FnFailure(); + } + + const char *ver_string_lower = RlistScalarValue(args); + const VersionComparison lower_comparison = + CompareVersion(Version(), ver_string_lower); + if (lower_comparison == VERSION_ERROR) + { + Log(LOG_LEVEL_ERR, + "%s: Format of lower version comparison string '%s' is incorrect", + fp->name, ver_string_lower); + return FnFailure(); + } + + const char *ver_string_upper = RlistScalarValue(args->next); + const VersionComparison upper_comparison = + CompareVersion(Version(), ver_string_upper); + if (upper_comparison == VERSION_ERROR) + { + Log(LOG_LEVEL_ERR, + "%s: Format of upper version comparison string '%s' is incorrect", + fp->name, ver_string_upper); + return FnFailure(); + } + + return FnReturnContext((lower_comparison == VERSION_GREATER || + lower_comparison == VERSION_EQUAL) && + (upper_comparison == VERSION_SMALLER || + upper_comparison == VERSION_EQUAL)); +} + +/*********************************************************************/ +/* Executions */ +/*********************************************************************/ + +static FnCallResult FnCallReturnsZero(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + char comm[CF_BUFSIZE]; + const char *shell_option = RlistScalarValue(finalargs->next); + ShellType shelltype = SHELL_TYPE_NONE; + bool need_executable_check = false; + + if (strcmp(shell_option, "useshell") == 0) + { + shelltype = SHELL_TYPE_USE; + } + else if (strcmp(shell_option, "powershell") == 0) + { + shelltype = SHELL_TYPE_POWERSHELL; + } + + if (IsAbsoluteFileName(RlistScalarValue(finalargs))) + { + need_executable_check = true; + } + else if (shelltype == SHELL_TYPE_NONE) + { + Log(LOG_LEVEL_ERR, "returnszero '%s' does not have an absolute path", RlistScalarValue(finalargs)); + return FnReturnContext(false); + } + + if (need_executable_check && !IsExecutable(CommandArg0(RlistScalarValue(finalargs)))) + { + Log(LOG_LEVEL_ERR, "returnszero '%s' is assumed to be executable but isn't", RlistScalarValue(finalargs)); + return FnReturnContext(false); + } + + snprintf(comm, CF_BUFSIZE, "%s", RlistScalarValue(finalargs)); + + if (ShellCommandReturnsZero(comm, shelltype)) + { + Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully and it returned zero", fp->name, RlistScalarValue(finalargs)); + return FnReturnContext(true); + } + else + { + Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully and it did not return zero", fp->name, RlistScalarValue(finalargs)); + return FnReturnContext(false); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallExecResult(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(fp != NULL); + + const char *const function = fp->name; + size_t args = RlistLen(finalargs); + if (args == 0) + { + FatalError(ctx, "Missing argument to %s() - Must specify command", function); + } + else if (args == 1) + { + FatalError(ctx, "Missing argument to %s() - Must specify 'noshell', 'useshell', or 'powershell'", function); + } + else if (args > 3) + { + FatalError(ctx, "Too many arguments to %s() - Maximum 3 allowed", function); + } + const char *shell_option = RlistScalarValue(finalargs->next); + ShellType shelltype = SHELL_TYPE_NONE; + bool need_executable_check = false; + + if (strcmp(shell_option, "useshell") == 0) + { + shelltype = SHELL_TYPE_USE; + } + else if (strcmp(shell_option, "powershell") == 0) + { + shelltype = SHELL_TYPE_POWERSHELL; + } + + const char *const command = RlistScalarValue(finalargs); + if (IsAbsoluteFileName(command)) + { + need_executable_check = true; + } + else if (shelltype == SHELL_TYPE_NONE) + { + Log(LOG_LEVEL_ERR, "%s '%s' does not have an absolute path", fp->name, command); + return FnFailure(); + } + + if (need_executable_check && !IsExecutable(CommandArg0(command))) + { + Log(LOG_LEVEL_ERR, "%s '%s' is assumed to be executable but isn't", fp->name, command); + return FnFailure(); + } + + size_t buffer_size = CF_EXPANDSIZE; + char *buffer = xcalloc(1, buffer_size); + + OutputSelect output_select = OUTPUT_SELECT_BOTH; + + if (args >= 3) + { + const char *output = RlistScalarValue(finalargs->next->next); + if (StringEqual(output, "stderr")) + { + output_select = OUTPUT_SELECT_STDERR; + } + else if (StringEqual(output, "stdout")) + { + output_select = OUTPUT_SELECT_STDOUT; + } + else + { + assert(StringEqual(output, "both")); + assert(output_select == OUTPUT_SELECT_BOTH); + } + } + + int exit_code; + + if (GetExecOutput(command, &buffer, &buffer_size, shelltype, output_select, &exit_code)) + { + Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully", fp->name, command); + if (StringEqual(function, "execresult")) + { + FnCallResult res = FnReturn(buffer); + free(buffer); + return res; + } + else + { + assert(StringEqual(function, "execresult_as_data")); + JsonElement *result = JsonObjectCreate(2); + JsonObjectAppendInteger(result, "exit_code", exit_code); + JsonObjectAppendString(result, "output", buffer); + free(buffer); + return FnReturnContainerNoCopy(result); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "%s could not run '%s' successfully", fp->name, command); + free(buffer); + return FnFailure(); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallUseModule(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) + /* usemodule("/programpath",varargs) */ +{ + char modulecmd[CF_BUFSIZE]; + struct stat statbuf; + + char *command = RlistScalarValue(finalargs); + char *args = RlistScalarValue(finalargs->next); + const char* const workdir = GetWorkDir(); + + snprintf(modulecmd, CF_BUFSIZE, "\"%s%cmodules%c%s\"", + workdir, FILE_SEPARATOR, FILE_SEPARATOR, command); + + if (stat(CommandArg0(modulecmd), &statbuf) == -1) + { + Log(LOG_LEVEL_ERR, "Plug-in module '%s' not found", modulecmd); + return FnFailure(); + } + + if ((statbuf.st_uid != 0) && (statbuf.st_uid != getuid())) + { + Log(LOG_LEVEL_ERR, "Module '%s' was not owned by uid %ju who is executing agent", modulecmd, (uintmax_t)getuid()); + return FnFailure(); + } + + snprintf(modulecmd, CF_BUFSIZE, "\"%s%cmodules%c%s\" %s", + workdir, FILE_SEPARATOR, FILE_SEPARATOR, command, args); + + Log(LOG_LEVEL_VERBOSE, "Executing and using module [%s]", modulecmd); + + if (!ExecModule(ctx, modulecmd)) + { + return FnFailure(); + } + + return FnReturnContext(true); +} + +/*********************************************************************/ +/* Misc */ +/*********************************************************************/ + +static FnCallResult FnCallSplayClass(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + char class_name[CF_MAXVARSIZE]; + + Interval splay_policy = IntervalFromString(RlistScalarValue(finalargs->next)); + + if (splay_policy == INTERVAL_HOURLY) + { + /* 12 5-minute slots in hour */ + int slot = StringHash(RlistScalarValue(finalargs), 0); + slot &= (SPLAY_PSEUDO_RANDOM_CONSTANT - 1); + slot = slot * 12 / SPLAY_PSEUDO_RANDOM_CONSTANT; + snprintf(class_name, CF_MAXVARSIZE, "Min%02d_%02d", slot * 5, ((slot + 1) * 5) % 60); + } + else + { + /* 12*24 5-minute slots in day */ + int dayslot = StringHash(RlistScalarValue(finalargs), 0); + dayslot &= (SPLAY_PSEUDO_RANDOM_CONSTANT - 1); + dayslot = dayslot * 12 * 24 / SPLAY_PSEUDO_RANDOM_CONSTANT; + int hour = dayslot / 12; + int slot = dayslot % 12; + + snprintf(class_name, CF_MAXVARSIZE, "Min%02d_%02d.Hr%02d", slot * 5, ((slot + 1) * 5) % 60, hour); + } + + Log(LOG_LEVEL_VERBOSE, "Computed context for '%s' splayclass: '%s'", RlistScalarValue(finalargs), class_name); + return FnReturnContext(IsDefinedClass(ctx, class_name)); +} + +/*********************************************************************/ + +#ifdef HAVE_LIBCURL +struct _curl_userdata +{ + const FnCall *fp; + const char *desc; + size_t max_size; + Buffer* content; +}; + +static size_t cfengine_curl_write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + struct _curl_userdata *options = (struct _curl_userdata*) userdata; + unsigned int old = BufferSize(options->content); + size_t requested = size*nmemb; + size_t granted = requested; + + if (old + requested > options->max_size) + { + granted = options->max_size - old; + Log(LOG_LEVEL_VERBOSE, + "%s: while receiving %s, current %u + requested %zu bytes would be over the maximum %zu; only accepting %zu bytes", + options->fp->name, options->desc, old, requested, options->max_size, granted); + } + + BufferAppend(options->content, ptr, granted); + + // `written` is actually (BufferSize(options->content) - old) but + // libcurl doesn't like that + size_t written = requested; + + // extra caution + BufferTrimToMaxLength(options->content, options->max_size); + return written; +} + +static void CurlCleanup() +{ + if (CURL_CACHE == NULL) + { + JsonElement *temp = CURL_CACHE; + CURL_CACHE = NULL; + JsonDestroy(temp); + } + + if (CURL_INITIALIZED) + { + curl_global_cleanup(); + CURL_INITIALIZED = false; + } + +} +#endif + +static FnCallResult FnCallUrlGet(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *finalargs) +{ + +#ifdef HAVE_LIBCURL + + char *url = RlistScalarValue(finalargs); + bool allocated = false; + JsonElement *options = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated); + + if (options == NULL) + { + return FnFailure(); + } + + if (JsonGetElementType(options) != JSON_ELEMENT_TYPE_CONTAINER || + JsonGetContainerType(options) != JSON_CONTAINER_TYPE_OBJECT) + { + JsonDestroyMaybe(options, allocated); + return FnFailure(); + } + + Writer *cache_w = StringWriter(); + WriterWriteF(cache_w, "url = %s; options = ", url); + JsonWriteCompact(cache_w, options); + + if (CURL_CACHE == NULL) + { + CURL_CACHE = JsonObjectCreate(10); + atexit(&CurlCleanup); + } + + JsonElement *old_result = JsonObjectGetAsObject(CURL_CACHE, StringWriterData(cache_w)); + + if (old_result != NULL) + { + Log(LOG_LEVEL_VERBOSE, "%s: found cached request for %s", fp->name, url); + WriterClose(cache_w); + JsonDestroyMaybe(options, allocated); + return FnReturnContainer(old_result); + } + + if (!CURL_INITIALIZED && curl_global_init(CURL_GLOBAL_DEFAULT) != 0) + { + Log(LOG_LEVEL_ERR, "%s: libcurl initialization failed, sorry", fp->name); + + WriterClose(cache_w); + JsonDestroyMaybe(options, allocated); + return FnFailure(); + } + + CURL_INITIALIZED = true; + + CURL *curl = curl_easy_init(); + if (!curl) + { + Log(LOG_LEVEL_ERR, "%s: libcurl easy_init failed, sorry", fp->name); + + WriterClose(cache_w); + JsonDestroyMaybe(options, allocated); + return FnFailure(); + } + + Buffer *content = BufferNew(); + Buffer *headers = BufferNew(); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // do not use signals + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); // set default timeout + curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); + curl_easy_setopt(curl, + CURLOPT_PROTOCOLS_STR, + // Allowed protocols + "file,ftp,ftps,http,https"); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cfengine_curl_write_callback); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, cfengine_curl_write_callback); + + size_t max_content = 4096; + size_t max_headers = 4096; + JsonIterator iter = JsonIteratorInit(options); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + const char *key = JsonIteratorCurrentKey(&iter); + const char *value = JsonPrimitiveGetAsString(e); + + if (strcmp(key, "url.timeout") == 0) + { + Log(LOG_LEVEL_VERBOSE, "%s: setting timeout to %ld seconds", fp->name, IntFromString(value)); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, IntFromString(value)); + } + else if (strcmp(key, "url.verbose") == 0) + { + Log(LOG_LEVEL_VERBOSE, "%s: setting verbosity to %ld", fp->name, IntFromString(value)); + curl_easy_setopt(curl, CURLOPT_VERBOSE, IntFromString(value)); + } + else if (strcmp(key, "url.header") == 0) + { + Log(LOG_LEVEL_VERBOSE, "%s: setting inline headers to %ld", fp->name, IntFromString(value)); + curl_easy_setopt(curl, CURLOPT_HEADER, IntFromString(value)); + } + else if (strcmp(key, "url.referer") == 0) + { + Log(LOG_LEVEL_VERBOSE, "%s: setting referer to %s", fp->name, value); + curl_easy_setopt(curl, CURLOPT_REFERER, value); + } + else if (strcmp(key, "url.user-agent") == 0) + { + Log(LOG_LEVEL_VERBOSE, "%s: setting user agent string to %s", fp->name, value); + curl_easy_setopt(curl, CURLOPT_USERAGENT, value); + } + else if (strcmp(key, "url.max_content") == 0) + { + Log(LOG_LEVEL_VERBOSE, "%s: setting max contents to %ld", fp->name, IntFromString(value)); + max_content = IntFromString(value); + } + else if (strcmp(key, "url.max_headers") == 0) + { + Log(LOG_LEVEL_VERBOSE, "%s: setting max headers to %ld", fp->name, IntFromString(value)); + max_headers = IntFromString(value); + } + else + { + Log(LOG_LEVEL_INFO, "%s: unknown option %s", fp->name, key); + } + } + + struct _curl_userdata data = { fp, "content", max_content, content }; + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); + + struct _curl_userdata header_data = { fp, "headers", max_headers, headers }; + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_data); + + JsonElement *options_headers = JsonObjectGetAsArray(options, "url.headers"); + struct curl_slist *header_list = NULL; + + if (options_headers != NULL) + { + iter = JsonIteratorInit(options_headers); + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + header_list = curl_slist_append(header_list, JsonPrimitiveGetAsString(e)); + } + } + + if (header_list != NULL) + { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list); + } + + JsonElement *result = JsonObjectCreate(10); + CURLcode res = curl_easy_perform(curl); + if (header_list != NULL) + { + curl_slist_free_all(header_list); + header_list = NULL; + } + + long returncode = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &returncode); + JsonObjectAppendInteger(result, "returncode", returncode); + + curl_easy_cleanup(curl); + + JsonObjectAppendInteger(result, "rc", 0+res); + + bool success = (CURLE_OK == res); + JsonObjectAppendBool(result, "success", success); + + if (!success) + { + JsonObjectAppendString(result, "error_message", curl_easy_strerror(res)); + } + + + + BufferTrimToMaxLength(content, max_content); + JsonObjectAppendString(result, "content", BufferData(content)); + BufferDestroy(content); + + BufferTrimToMaxLength(headers, max_headers); + JsonObjectAppendString(result, "headers", BufferData(headers)); + BufferDestroy(headers); + + JsonObjectAppendObject(CURL_CACHE, StringWriterData(cache_w), JsonCopy(result)); + WriterClose(cache_w); + + JsonDestroyMaybe(options, allocated); + return FnReturnContainerNoCopy(result); + +#else + + UNUSED(finalargs); /* suppress unused parameter warning */ + Log(LOG_LEVEL_ERR, + "%s: libcurl integration is not compiled into CFEngine, sorry", fp->name); + return FnFailure(); + +#endif +} + +/*********************************************************************/ + +/* ReadTCP(localhost,80,'GET index.html',1000) */ +static FnCallResult FnCallReadTcp(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + char *hostnameip = RlistScalarValue(finalargs); + char *port = RlistScalarValue(finalargs->next); + char *sendstring = RlistScalarValue(finalargs->next->next); + ssize_t maxbytes = IntFromString(RlistScalarValue(finalargs->next->next->next)); + + if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON) + { + return FnFailure(); + } + + if (maxbytes < 0 || maxbytes > CF_BUFSIZE - 1) + { + Log(LOG_LEVEL_VERBOSE, + "readtcp: invalid number of bytes %zd to read, defaulting to %d", + maxbytes, CF_BUFSIZE - 1); + maxbytes = CF_BUFSIZE - 1; + } + + char txtaddr[CF_MAX_IP_LEN] = ""; + int sd = SocketConnect(hostnameip, port, CONNTIMEOUT, false, + txtaddr, sizeof(txtaddr)); + if (sd == -1) + { + Log(LOG_LEVEL_INFO, "readtcp: Couldn't connect. (socket: %s)", + GetErrorStr()); + return FnFailure(); + } + + if (strlen(sendstring) > 0) + { + int sent = 0; + int result = 0; + size_t length = strlen(sendstring); + do + { + result = send(sd, sendstring, length, 0); + if (result < 0) + { + cf_closesocket(sd); + return FnFailure(); + } + else + { + sent += result; + } + } while ((size_t) sent < length); + } + + char recvbuf[CF_BUFSIZE]; + ssize_t n_read = recv(sd, recvbuf, maxbytes, 0); + cf_closesocket(sd); + + if (n_read < 0) + { + Log(LOG_LEVEL_INFO, "readtcp: Error while receiving (%s)", + GetErrorStr()); + return FnFailure(); + } + + assert((size_t) n_read < sizeof(recvbuf)); + recvbuf[n_read] = '\0'; + + Log(LOG_LEVEL_VERBOSE, + "readtcp: requested %zd maxbytes, got %zd bytes from %s", + maxbytes, n_read, txtaddr); + + return FnReturn(recvbuf); +} + +/*********************************************************************/ + +/** + * Look for the indices of a variable in #finalargs if it is an array. + * + * @return *Always* return an slist of the indices; if the variable is not an + * array or does not resolve at all, return an empty slist. + * + * @NOTE + * This is needed for literally one acceptance test: + * 01_vars/02_functions/getindices_returns_expected_list_from_array.cf + * + * The case is that we have a[x] = "1" AND a[x][y] = "2" which is + * ambiguous, but classic CFEngine arrays allow it. So we want the + * classic getindices("a[x]") to return "y" in this case. + */ +static FnCallResult FnCallGetIndicesClassic(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + VarRef *ref = VarRefParse(RlistScalarValue(finalargs)); + if (!VarRefIsQualified(ref)) + { + if (fp->caller) + { + const Bundle *caller_bundle = PromiseGetBundle(fp->caller); + VarRefQualify(ref, caller_bundle->ns, caller_bundle->name); + } + else + { + Log(LOG_LEVEL_WARNING, + "Function '%s' was given an unqualified variable reference, " + "and it was not called from a promise. " + "No way to automatically qualify the reference '%s'", + fp->name, RlistScalarValue(finalargs)); + VarRefDestroy(ref); + return FnFailure(); + } + } + + Rlist *keys = NULL; + + VariableTableIterator *iter = EvalContextVariableTableFromRefIteratorNew(ctx, ref); + const Variable *itervar; + while ((itervar = VariableTableIteratorNext(iter)) != NULL) + { + const VarRef *itervar_ref = VariableGetRef(itervar); + /* + Log(LOG_LEVEL_DEBUG, + "%s(%s): got itervar->ref->num_indices %zu while ref->num_indices is %zu", + fp->name, RlistScalarValue(finalargs), + itervar->ref->num_indices, ref->num_indices); + */ + /* Does the variable we found have more indices than the one we + * requested? For example, if we requested the variable "blah", it has + * 0 indices, so a found variable blah[i] will be acceptable. */ + if (itervar_ref->num_indices > ref->num_indices) + { + RlistAppendScalarIdemp(&keys, itervar_ref->indices[ref->num_indices]); + } + } + + VariableTableIteratorDestroy(iter); + VarRefDestroy(ref); + + return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallGetIndices(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + const char *name_str = RlistScalarValueSafe(finalargs); + bool allocated = false; + JsonElement *json = NULL; + + // Protect against collected args (their rval type will be data + // container). This is a special case to preserve legacy behavior + // for array lookups that requires a scalar in finalargs. + if (RlistValueIsType(finalargs, RVAL_TYPE_SCALAR)) + { + VarRef *ref = ResolveAndQualifyVarName(fp, name_str); + DataType type; + EvalContextVariableGet(ctx, ref, &type); + + /* A variable holding a data container. */ + if (type == CF_DATA_TYPE_CONTAINER) + { + json = VarRefValueToJson(ctx, fp, ref, NULL, 0, true, &allocated); + } + /* Resolves to a different type or does not resolve at all. It's + * normal not to resolve, for example "blah" will not resolve if the + * variable table only contains "blah[1]"; we have to go through + * FnCallGetIndicesClassic() to extract these indices. */ + else + { + JsonParseError res = JsonParseWithLookup(ctx, &LookupVarRefToJson, &name_str, &json); + if (res == JSON_PARSE_OK) + { + if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + // VarNameOrInlineToJson() would now look up this primitive + // in the variable table, returning a JSON container for + // whatever type it is, but since we already know that it's + // not a native container type (thanks to the + // CF_DATA_TYPE_CONTAINER check above) we skip that, and + // stick to the legacy data types. + JsonDestroy(json); + VarRefDestroy(ref); + return FnCallGetIndicesClassic(ctx, policy, fp, finalargs); + } + else + { + // Inline JSON of some sort. + allocated = true; + } + } + else + { + /* Invalid inline JSON. */ + VarRefDestroy(ref); + return FnCallGetIndicesClassic(ctx, policy, fp, finalargs); + } + } + + VarRefDestroy(ref); + } + else + { + json = VarNameOrInlineToJson(ctx, fp, finalargs, true, &allocated); + } + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + + Rlist *keys = NULL; + + if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + JsonDestroyMaybe(json, allocated); + return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } }; + } + + if (JsonGetContainerType(json) == JSON_CONTAINER_TYPE_OBJECT) + { + JsonIterator iter = JsonIteratorInit(json); + const char *key; + while ((key = JsonIteratorNextKey(&iter))) + { + RlistAppendScalar(&keys, key); + } + } + else + { + for (size_t i = 0; i < JsonLength(json); i++) + { + Rval key = (Rval) { StringFromLong(i), RVAL_TYPE_SCALAR }; + RlistAppendRval(&keys, key); + } + } + + JsonDestroyMaybe(json, allocated); + return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +void CollectContainerValues(EvalContext *ctx, Rlist **values, const JsonElement *container) +{ + if (JsonGetElementType(container) == JSON_ELEMENT_TYPE_CONTAINER) + { + JsonIterator iter = JsonIteratorInit(container); + const JsonElement *el; + while ((el = JsonIteratorNextValue(&iter))) + { + if (JsonGetElementType(el) == JSON_ELEMENT_TYPE_CONTAINER) + { + CollectContainerValues(ctx, values, el); + } + else + { + char *value = JsonPrimitiveToString(el); + if (value != NULL) + { + RlistAppendScalar(values, value); + free(value); + } + } + } + } + else if (JsonGetElementType(container) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + char *value = JsonPrimitiveToString(container); + if (value != NULL) + { + RlistAppendScalar(values, value); + free(value); + } + } +} + +/*********************************************************************/ + +static FnCallResult FnCallGetValues(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, true, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + /* CFE-2479: Inexistent variable, return an empty slist. */ + Log(LOG_LEVEL_DEBUG, "getvalues('%s'):" + " unresolvable variable, returning an empty list", + RlistScalarValueSafe(finalargs)); + return (FnCallResult) { FNCALL_SUCCESS, { NULL, RVAL_TYPE_LIST } }; + } + + Rlist *values = NULL; /* start with an empty Rlist */ + CollectContainerValues(ctx, &values, json); + + JsonDestroyMaybe(json, allocated); + return (FnCallResult) { FNCALL_SUCCESS, { values, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallGrep(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + return FilterInternal(ctx, + fp, + RlistScalarValue(finalargs), // regex + finalargs->next, // list identifier + 1, // regex match = TRUE + 0, // invert matches = FALSE + LONG_MAX); // max results = max int +} + +/*********************************************************************/ + +static FnCallResult FnCallRegList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + return FilterInternal(ctx, + fp, + RlistScalarValue(finalargs->next), // regex or string + finalargs, // list identifier + 1, + 0, + LONG_MAX); +} + +/*********************************************************************/ + +static FnCallResult JoinContainer(const JsonElement *container, const char *delimiter) +{ + JsonIterator iter = JsonIteratorInit(container); + const JsonElement *e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true); + if (!e) + { + return FnReturn(""); + } + + Buffer *result = BufferNew(); + BufferAppendString(result, JsonPrimitiveGetAsString(e)); + + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + BufferAppendString(result, delimiter); + BufferAppendString(result, JsonPrimitiveGetAsString(e)); + } + + return FnReturnBuffer(result); +} + +static FnCallResult FnCallJoin(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + const char *delimiter = RlistScalarValue(finalargs); + const char *name_str = RlistScalarValueSafe(finalargs->next); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + FnCallResult result = JoinContainer(json, delimiter); + JsonDestroyMaybe(json, allocated); + return result; + +} + +/*********************************************************************/ + +static FnCallResult FnCallGetFields(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *finalargs) +{ + Regex *rx = CompileRegex(RlistScalarValue(finalargs)); + if (!rx) + { + return FnFailure(); + } + + const char *filename = RlistScalarValue(finalargs->next); + const char *split = RlistScalarValue(finalargs->next->next); + const char *array_lval = RlistScalarValue(finalargs->next->next->next); + + FILE *fin = safe_fopen(filename, "rt"); + if (!fin) + { + Log(LOG_LEVEL_ERR, "File '%s' could not be read in getfields(). (fopen: %s)", filename, GetErrorStr()); + RegexDestroy(rx); + return FnFailure(); + } + + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(CF_BUFSIZE); + + int line_count = 0; + + while (CfReadLine(&line, &line_size, fin) != -1) + { + if (!StringMatchFullWithPrecompiledRegex(rx, line)) + { + continue; + } + + if (line_count == 0) + { + Rlist *newlist = RlistFromSplitRegex(line, split, 31, true); + int vcount = 1; + + for (const Rlist *rp = newlist; rp != NULL; rp = rp->next) + { + char name[CF_MAXVARSIZE]; + snprintf(name, CF_MAXVARSIZE - 1, "%s[%d]", array_lval, vcount); + VarRef *ref = VarRefParse(name); + if (!VarRefIsQualified(ref)) + { + if (fp->caller) + { + const Bundle *caller_bundle = PromiseGetBundle(fp->caller); + VarRefQualify(ref, caller_bundle->ns, caller_bundle->name); + } + else + { + Log(LOG_LEVEL_WARNING, + "Function '%s' was given an unqualified variable reference, " + "and it was not called from a promise. No way to automatically qualify the reference '%s'.", + fp->name, RlistScalarValue(finalargs)); + VarRefDestroy(ref); + free(line); + RlistDestroy(newlist); + RegexDestroy(rx); + return FnFailure(); + } + } + + EvalContextVariablePut(ctx, ref, RlistScalarValue(rp), CF_DATA_TYPE_STRING, "source=function,function=getfields"); + VarRefDestroy(ref); + Log(LOG_LEVEL_VERBOSE, "getfields: defining '%s' => '%s'", name, RlistScalarValue(rp)); + vcount++; + } + + RlistDestroy(newlist); + } + + line_count++; + } + + RegexDestroy(rx); + free(line); + + if (!feof(fin)) + { + Log(LOG_LEVEL_ERR, "Unable to read data from file '%s'. (fgets: %s)", filename, GetErrorStr()); + fclose(fin); + return FnFailure(); + } + + fclose(fin); + + return FnReturnF("%d", line_count); +} + +/*********************************************************************/ + +static FnCallResult FnCallCountLinesMatching(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + Regex *rx = CompileRegex(RlistScalarValue(finalargs)); + if (!rx) + { + return FnFailure(); + } + + char *filename = RlistScalarValue(finalargs->next); + + FILE *fin = safe_fopen(filename, "rt"); + if (!fin) + { + Log(LOG_LEVEL_ERR, "File '%s' could not be read in countlinesmatching(). (fopen: %s)", filename, GetErrorStr()); + RegexDestroy(rx); + return FnReturn("0"); + } + + int lcount = 0; + { + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + + while (CfReadLine(&line, &line_size, fin) != -1) + { + if (StringMatchFullWithPrecompiledRegex(rx, line)) + { + lcount++; + Log(LOG_LEVEL_VERBOSE, "countlinesmatching: matched '%s'", line); + continue; + } + } + + free(line); + } + + RegexDestroy(rx); + + if (!feof(fin)) + { + Log(LOG_LEVEL_ERR, "Unable to read data from file '%s'. (fgets: %s)", filename, GetErrorStr()); + fclose(fin); + return FnFailure(); + } + + fclose(fin); + + return FnReturnF("%d", lcount); +} + +/*********************************************************************/ + +static FnCallResult FnCallLsDir(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + Rlist *newlist = NULL; + + char *dirname = RlistScalarValue(finalargs); + char *regex = RlistScalarValue(finalargs->next); + int includepath = BooleanFromString(RlistScalarValue(finalargs->next->next)); + + Dir *dirh = DirOpen(dirname); + if (dirh == NULL) + { + Log(LOG_LEVEL_ERR, "Directory '%s' could not be accessed in lsdir(), (opendir: %s)", dirname, GetErrorStr()); + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; + } + + const struct dirent *dirp; + for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh)) + { + if (strlen(regex) == 0 || StringMatchFull(regex, dirp->d_name)) + { + if (includepath) + { + char line[CF_BUFSIZE]; + snprintf(line, CF_BUFSIZE, "%s/%s", dirname, dirp->d_name); + MapName(line); + RlistPrepend(&newlist, line, RVAL_TYPE_SCALAR); + } + else + { + RlistPrepend(&newlist, dirp->d_name, RVAL_TYPE_SCALAR); + } + } + } + DirClose(dirh); + + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +bool EvalContextVariablePutSpecialEscaped(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags, bool escape) +{ + if (escape) + { + char *escaped = EscapeCharCopy(value, '"', '\\'); + bool ret = EvalContextVariablePutSpecial(ctx, scope, lval, escaped, type, tags); + free(escaped); + return ret; + } + + return EvalContextVariablePutSpecial(ctx, scope, lval, value, type, tags); +} + +/*********************************************************************/ + +static JsonElement* ExecJSON_Pipe(const char *cmd, JsonElement *container) +{ + IOData io = cf_popen_full_duplex(cmd, false, false); + + if (io.write_fd == -1 || io.read_fd == -1) + { + Log(LOG_LEVEL_INFO, "An error occurred while communicating with '%s'", cmd); + + return NULL; + } + + Log(LOG_LEVEL_DEBUG, "Opened fds %d and %d for command '%s'.", + io.read_fd, io.write_fd, cmd); + + // write the container to a string + Writer *w = StringWriter(); + JsonWrite(w, container, 0); + char *container_str = StringWriterClose(w); + + ssize_t written = PipeWrite(&io, container_str); + if (written < 0) + { + Log(LOG_LEVEL_ERR, "Failed to write to pipe (fd = %d): %s", + io.write_fd, GetErrorStr()); + free(container_str); + + container_str = NULL; + } + else if ((size_t) written != strlen(container_str)) + { + Log(LOG_LEVEL_VERBOSE, "Couldn't send whole container data to '%s'.", cmd); + free(container_str); + + container_str = NULL; + } + + Rlist *returnlist = NULL; + if (container_str) + { + free(container_str); + /* We can have some error message here. */ + returnlist = PipeReadData(&io, 5, 5); + } + + /* If script returns non 0 status */ + int close = cf_pclose_full_duplex(&io); + if (close != EXIT_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "%s returned with non zero return code: %d", + cmd, close); + } + + // Exit if no data was obtained from the pipe + if (returnlist == NULL) + { + return NULL; + } + + JsonElement *returnjq = JsonArrayCreate(5); + + Buffer *buf = BufferNew(); + for (const Rlist *rp = returnlist; rp != NULL; rp = rp->next) + { + const char *data = RlistScalarValue(rp); + + if (BufferSize(buf) != 0) + { + // simulate the newline + BufferAppendString(buf, "\n"); + } + + BufferAppendString(buf, data); + const char *bufdata = BufferData(buf); + JsonElement *parsed = NULL; + if (JsonParse(&bufdata, &parsed) == JSON_PARSE_OK) + { + JsonArrayAppendElement(returnjq, parsed); + BufferClear(buf); + } + else + { + Log(LOG_LEVEL_DEBUG, "'%s' generated invalid JSON '%s', appending next line", cmd, data); + } + } + + BufferDestroy(buf); + + RlistDestroy(returnlist); + + return returnjq; +} + +/*********************************************************************/ + +static FnCallResult FnCallMapData(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, ARG_UNUSED const Rlist *finalargs) +{ + if (!fp->caller) + { + Log(LOG_LEVEL_ERR, "Function '%s' must be called from a promise", fp->name); + return FnFailure(); + } + + bool mapdatamode = (strcmp(fp->name, "mapdata") == 0); + Rlist *returnlist = NULL; + + // This is a delayed evaluation function, so we have to resolve arguments ourselves + // We resolve them once now, to get the second or third argument with the iteration data + Rlist *expargs = NewExpArgs(ctx, policy, fp, NULL); + + Rlist *varpointer = NULL; + const char* conversion = NULL; + + if (mapdatamode) + { + if (expargs == NULL || RlistIsUnresolved(expargs->next->next)) + { + RlistDestroy(expargs); + return FnFailure(); + } + + conversion = RlistScalarValue(expargs); + varpointer = expargs->next->next; + } + else + { + if (expargs == NULL || RlistIsUnresolved(expargs->next)) + { + RlistDestroy(expargs); + return FnFailure(); + } + + conversion = "none"; + varpointer = expargs->next; + } + + const char* varname = RlistScalarValueSafe(varpointer); + + bool jsonmode = (strcmp(conversion, "json") == 0); + bool canonifymode = (strcmp(conversion, "canonify") == 0); + bool json_pipemode = (strcmp(conversion, "json_pipe") == 0); + + bool allocated = false; + JsonElement *container = VarNameOrInlineToJson(ctx, fp, varpointer, false, &allocated); + + if (container == NULL) + { + RlistDestroy(expargs); + return FnFailure(); + } + + if (JsonGetElementType(container) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_ERR, "Function '%s' got an unexpected non-container from argument '%s'", fp->name, varname); + JsonDestroyMaybe(container, allocated); + + RlistDestroy(expargs); + return FnFailure(); + } + + if (mapdatamode && json_pipemode) + { + JsonElement *returnjson_pipe = ExecJSON_Pipe(RlistScalarValue(expargs->next), container); + + RlistDestroy(expargs); + + if (returnjson_pipe == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s failed to get output from 'json_pipe' execution", fp->name); + return FnFailure(); + } + + JsonDestroyMaybe(container, allocated); + + return FnReturnContainerNoCopy(returnjson_pipe); + } + + Buffer *expbuf = BufferNew(); + if (JsonGetContainerType(container) != JSON_CONTAINER_TYPE_OBJECT) + { + JsonElement *temp = JsonObjectCreate(0); + JsonElement *temp2 = JsonMerge(temp, container); + JsonDestroy(temp); + JsonDestroyMaybe(container, allocated); + + container = temp2; + allocated = true; + } + + JsonIterator iter = JsonIteratorInit(container); + const JsonElement *e; + + while ((e = JsonIteratorNextValue(&iter)) != NULL) + { + EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "k", JsonGetPropertyAsString(e), + CF_DATA_TYPE_STRING, "source=function,function=maparray", + jsonmode); + + switch (JsonGetElementType(e)) + { + case JSON_ELEMENT_TYPE_PRIMITIVE: + BufferClear(expbuf); + EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "v", JsonPrimitiveGetAsString(e), + CF_DATA_TYPE_STRING, "source=function,function=maparray", + jsonmode); + + // This is a delayed evaluation function, so we have to resolve arguments ourselves + // We resolve them every time now, to get the arg_map argument + Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL); + const char *arg_map = RlistScalarValueSafe(mapdatamode ? local_expargs->next : local_expargs); + ExpandScalar(ctx, PromiseGetBundle(fp->caller)->ns, PromiseGetBundle(fp->caller)->name, arg_map, expbuf); + RlistDestroy(local_expargs); + + if (strstr(BufferData(expbuf), "$(this.k)") || strstr(BufferData(expbuf), "${this.k}") || + strstr(BufferData(expbuf), "$(this.v)") || strstr(BufferData(expbuf), "${this.v}")) + { + RlistDestroy(returnlist); + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k"); + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v"); + BufferDestroy(expbuf); + JsonDestroyMaybe(container, allocated); + RlistDestroy(expargs); + return FnFailure(); + } + + if (canonifymode) + { + BufferCanonify(expbuf); + } + + RlistAppendScalar(&returnlist, BufferData(expbuf)); + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v"); + + break; + + case JSON_ELEMENT_TYPE_CONTAINER: + { + const JsonElement *e2; + JsonIterator iter2 = JsonIteratorInit(e); + while ((e2 = JsonIteratorNextValueByType(&iter2, JSON_ELEMENT_TYPE_PRIMITIVE, true)) != NULL) + { + char *key = (char*) JsonGetPropertyAsString(e2); + bool havekey = (key != NULL); + if (havekey) + { + EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "k[1]", key, + CF_DATA_TYPE_STRING, "source=function,function=maparray", + jsonmode); + } + + BufferClear(expbuf); + + EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "v", JsonPrimitiveGetAsString(e2), + CF_DATA_TYPE_STRING, "source=function,function=maparray", + jsonmode); + + // This is a delayed evaluation function, so we have to resolve arguments ourselves + // We resolve them every time now, to get the arg_map argument + Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL); + const char *arg_map = RlistScalarValueSafe(mapdatamode ? local_expargs->next : local_expargs); + ExpandScalar(ctx, PromiseGetBundle(fp->caller)->ns, PromiseGetBundle(fp->caller)->name, arg_map, expbuf); + RlistDestroy(local_expargs); + + if (strstr(BufferData(expbuf), "$(this.k)") || strstr(BufferData(expbuf), "${this.k}") || + (havekey && (strstr(BufferData(expbuf), "$(this.k[1])") || strstr(BufferData(expbuf), "${this.k[1]}"))) || + strstr(BufferData(expbuf), "$(this.v)") || strstr(BufferData(expbuf), "${this.v}")) + { + RlistDestroy(returnlist); + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k"); + if (havekey) + { + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k[1]"); + } + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v"); + BufferDestroy(expbuf); + JsonDestroyMaybe(container, allocated); + RlistDestroy(expargs); + return FnFailure(); + } + + if (canonifymode) + { + BufferCanonify(expbuf); + } + + RlistAppendScalarIdemp(&returnlist, BufferData(expbuf)); + if (havekey) + { + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k[1]"); + } + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v"); + } + } + break; + + default: + break; + } + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k"); + } + + BufferDestroy(expbuf); + JsonDestroyMaybe(container, allocated); + RlistDestroy(expargs); + + JsonElement *returnjson = NULL; + + // this is mapdata() + if (mapdatamode) + { + returnjson = JsonArrayCreate(RlistLen(returnlist)); + for (const Rlist *rp = returnlist; rp != NULL; rp = rp->next) + { + const char *data = RlistScalarValue(rp); + if (jsonmode) + { + JsonElement *parsed = NULL; + if (JsonParseWithLookup(ctx, &LookupVarRefToJson, &data, &parsed) == JSON_PARSE_OK) + { + JsonArrayAppendElement(returnjson, parsed); + } + else + { + Log(LOG_LEVEL_VERBOSE, "Function '%s' could not parse dynamic JSON '%s', skipping it", fp->name, data); + } + } + else + { + JsonArrayAppendString(returnjson, data); + } + } + + RlistDestroy(returnlist); + return FnReturnContainerNoCopy(returnjson); + } + + // this is maparray() + return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallMapList(EvalContext *ctx, + const Policy *policy, + const FnCall *fp, + ARG_UNUSED const Rlist *finalargs) +{ + // This is a delayed evaluation function, so we have to resolve arguments ourselves + // We resolve them once now, to get the second argument + Rlist *expargs = NewExpArgs(ctx, policy, fp, NULL); + + if (expargs == NULL || RlistIsUnresolved(expargs->next)) + { + RlistDestroy(expargs); + return FnFailure(); + } + + const char *name_str = RlistScalarValueSafe(expargs->next); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, expargs->next, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + RlistDestroy(expargs); + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + RlistDestroy(expargs); + return FnFailure(); + } + + Rlist *newlist = NULL; + Buffer *expbuf = BufferNew(); + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + const char* value = JsonPrimitiveGetAsString(e); + + BufferClear(expbuf); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "this", value, CF_DATA_TYPE_STRING, "source=function,function=maplist"); + + // This is a delayed evaluation function, so we have to resolve arguments ourselves + // We resolve them every time now, to get the first argument + Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL); + const char *arg_map = RlistScalarValueSafe(local_expargs); + ExpandScalar(ctx, NULL, "this", arg_map, expbuf); + RlistDestroy(local_expargs); + + if (strstr(BufferData(expbuf), "$(this)") || strstr(BufferData(expbuf), "${this}")) + { + RlistDestroy(newlist); + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "this"); + BufferDestroy(expbuf); + JsonDestroyMaybe(json, allocated); + RlistDestroy(expargs); + return FnFailure(); + } + + RlistAppendScalar(&newlist, BufferData(expbuf)); + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "this"); + } + BufferDestroy(expbuf); + JsonDestroyMaybe(json, allocated); + RlistDestroy(expargs); + + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; +} + +/******************************************************************************/ + +static FnCallResult FnCallExpandRange(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + Rlist *newlist = NULL; + const char *template = RlistScalarValue(finalargs); + char *step = RlistScalarValue(finalargs->next); + size_t template_size = strlen(template) + 1; + char *before = xstrdup(template); + char *after = xcalloc(template_size, 1); + char *work = xstrdup(template); + int from = CF_NOINT, to = CF_NOINT, step_size = atoi(step); + + if (*template == '[') + { + *before = '\0'; + sscanf(template, "[%d-%d]%[^\n]", &from, &to, after); + } + else + { + sscanf(template, "%[^[\[][%d-%d]%[^\n]", before, &from, &to, after); + } + + if (step_size < 1 || abs(from-to) < step_size) + { + FatalError(ctx, "EXPANDRANGE Step size cannot be less than 1 or greater than the interval"); + } + + if (from == CF_NOINT || to == CF_NOINT) + { + FatalError(ctx, "EXPANDRANGE malformed range expression"); + } + + if (from > to) + { + for (int i = from; i >= to; i -= step_size) + { + xsnprintf(work, template_size, "%s%d%s",before,i,after);; + RlistAppendScalar(&newlist, work); + } + } + else + { + for (int i = from; i <= to; i += step_size) + { + xsnprintf(work, template_size, "%s%d%s",before,i,after);; + RlistAppendScalar(&newlist, work); + } + } + + free(before); + free(after); + free(work); + + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; +} + +/*****************************************************************************/ + +static FnCallResult FnCallMergeData(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + if (RlistLen(args) == 0) + { + Log(LOG_LEVEL_ERR, "%s needs at least one argument, a reference to a container variable", fp->name); + return FnFailure(); + } + + for (const Rlist *arg = args; arg; arg = arg->next) + { + if (args->val.type != RVAL_TYPE_SCALAR && + args->val.type != RVAL_TYPE_CONTAINER) + { + Log(LOG_LEVEL_ERR, "%s: argument is not a variable reference", fp->name); + return FnFailure(); + } + } + + Seq *containers = SeqNew(10, &JsonDestroy); + + for (const Rlist *arg = args; arg; arg = arg->next) + { + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, arg, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + SeqDestroy(containers); + + return FnFailure(); + } + + // Fail on json primitives, only merge containers + if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + if (allocated) + { + JsonDestroy(json); + } + char *const as_string = RvalToString(arg->val); + Log(LOG_LEVEL_ERR, "%s is not mergeable as it it not a container", as_string); + free(as_string); + SeqDestroy(containers); + return FnFailure(); + } + + // This can be optimized better + if (allocated) + { + SeqAppend(containers, json); + } + else + { + SeqAppend(containers, JsonCopy(json)); + } + + } // end of args loop + + if (SeqLength(containers) == 1) + { + JsonElement *first = JsonCopy(SeqAt(containers, 0)); + SeqDestroy(containers); + return FnReturnContainerNoCopy(first); + } + else + { + JsonElement *first = SeqAt(containers, 0); + JsonElement *second = SeqAt(containers, 1); + JsonElement *result = JsonMerge(first, second); + + for (size_t i = 2; i < SeqLength(containers); i++) + { + JsonElement *cur = SeqAt(containers, i); + JsonElement *tmp = JsonMerge(result, cur); + JsonDestroy(result); + result = tmp; + } + + SeqDestroy(containers); + return FnReturnContainerNoCopy(result); + } + + assert(false); +} + +JsonElement *DefaultTemplateData(const EvalContext *ctx, const char *wantbundle) +{ + JsonElement *hash = JsonObjectCreate(30); + JsonElement *classes = NULL; + JsonElement *bundles = NULL; + + bool want_all_bundles = (wantbundle == NULL); + + if (want_all_bundles) // no specific bundle + { + classes = JsonObjectCreate(50); + bundles = JsonObjectCreate(50); + JsonObjectAppendObject(hash, "classes", classes); + JsonObjectAppendObject(hash, "vars", bundles); + + ClassTableIterator *it = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true); + Class *cls; + while ((cls = ClassTableIteratorNext(it))) + { + char *key = ClassRefToString(cls->ns, cls->name); + JsonObjectAppendBool(classes, key, true); + free(key); + } + ClassTableIteratorDestroy(it); + + it = EvalContextClassTableIteratorNewLocal(ctx); + while ((cls = ClassTableIteratorNext(it))) + { + char *key = ClassRefToString(cls->ns, cls->name); + JsonObjectAppendBool(classes, key, true); + free(key); + } + ClassTableIteratorDestroy(it); + } + + { + VariableTableIterator *it = EvalContextVariableTableIteratorNew(ctx, NULL, NULL, NULL); + Variable *var; + while ((var = VariableTableIteratorNext(it))) + { + const VarRef *var_ref = VariableGetRef(var); + // TODO: need to get a CallRef, this is bad + char *scope_key = ClassRefToString(var_ref->ns, var_ref->scope); + + JsonElement *scope_obj = NULL; + if (want_all_bundles) + { + scope_obj = JsonObjectGetAsObject(bundles, scope_key); + if (!scope_obj) + { + scope_obj = JsonObjectCreate(50); + JsonObjectAppendObject(bundles, scope_key, scope_obj); + } + } + else if (StringEqual(scope_key, wantbundle)) + { + scope_obj = hash; + } + + free(scope_key); + + if (scope_obj != NULL) + { + char *lval_key = VarRefToString(var_ref, false); + Rval var_rval = VariableGetRval(var, true); + // don't collect mangled refs + if (strchr(lval_key, CF_MANGLED_SCOPE) == NULL) + { + JsonObjectAppendElement(scope_obj, lval_key, RvalToJson(var_rval)); + } + free(lval_key); + } + } + VariableTableIteratorDestroy(it); + } + + Writer *w = StringWriter(); + JsonWrite(w, hash, 0); + Log(LOG_LEVEL_DEBUG, "Generated DefaultTemplateData '%s'", StringWriterData(w)); + WriterClose(w); + + return hash; +} + +static FnCallResult FnCallDatastate(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + ARG_UNUSED const Rlist *args) +{ + JsonElement *state = DefaultTemplateData(ctx, NULL); + return FnReturnContainerNoCopy(state); +} + +static FnCallResult FnCallBundlestate(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + ARG_UNUSED const Rlist *args) +{ + JsonElement *state = DefaultTemplateData(ctx, RlistScalarValue(args)); + + if (state == NULL || + JsonGetElementType(state) != JSON_ELEMENT_TYPE_CONTAINER || + JsonLength(state) < 1) + { + if (state != NULL) + { + JsonDestroy(state); + } + + return FnFailure(); + } + else + { + return FnReturnContainerNoCopy(state); + } +} + + +static FnCallResult FnCallSelectServers(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *finalargs) +{ + const char *listvar = RlistScalarValue(finalargs); + const char *port = RlistScalarValue(finalargs->next); + const char *sendstring = RlistScalarValue(finalargs->next->next); + const char *regex = RlistScalarValue(finalargs->next->next->next); + ssize_t maxbytes = IntFromString(RlistScalarValue(finalargs->next->next->next->next)); + char *array_lval = xstrdup(RlistScalarValue(finalargs->next->next->next->next->next)); + + if (!IsQualifiedVariable(array_lval)) + { + if (fp->caller) + { + VarRef *ref = VarRefParseFromBundle(array_lval, PromiseGetBundle(fp->caller)); + free(array_lval); + array_lval = VarRefToString(ref, true); + VarRefDestroy(ref); + } + else + { + Log(LOG_LEVEL_ERR, "Function '%s' called with an unqualifed array reference '%s', " + "and the reference could not be automatically qualified as the function was not called from a promise.", + fp->name, array_lval); + free(array_lval); + return FnFailure(); + } + } + + char naked[CF_MAXVARSIZE] = ""; + + if (IsVarList(listvar)) + { + GetNaked(naked, listvar); + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Function selectservers was promised a list called '%s' but this was not found", + listvar); + return FnFailure(); + } + + VarRef *ref = VarRefParse(naked); + DataType value_type; + const Rlist *hostnameip = EvalContextVariableGet(ctx, ref, &value_type); + if (value_type == CF_DATA_TYPE_NONE) + { + Log(LOG_LEVEL_VERBOSE, + "Function selectservers was promised a list called '%s' but this was not found from context '%s.%s'", + listvar, ref->scope, naked); + VarRefDestroy(ref); + free(array_lval); + return FnFailure(); + } + VarRefDestroy(ref); + + if (DataTypeToRvalType(value_type) != RVAL_TYPE_LIST) + { + Log(LOG_LEVEL_VERBOSE, + "Function selectservers was promised a list called '%s' but this variable is not a list", + listvar); + free(array_lval); + return FnFailure(); + } + + if (maxbytes < 0 || maxbytes > CF_BUFSIZE - 1) + { + Log(LOG_LEVEL_VERBOSE, + "selectservers: invalid number of bytes %zd to read, defaulting to %d", + maxbytes, CF_BUFSIZE - 1); + maxbytes = CF_BUFSIZE - 1; + } + + if (THIS_AGENT_TYPE != AGENT_TYPE_AGENT) + { + free(array_lval); + return FnReturnF("%d", 0); + } + + Policy *select_server_policy = PolicyNew(); + { + Bundle *bp = PolicyAppendBundle(select_server_policy, NamespaceDefault(), + "select_server_bundle", "agent", NULL, NULL); + BundleSection *sp = BundleAppendSection(bp, "select_server"); + + BundleSectionAppendPromise(sp, "function", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, NULL, NULL); + } + + size_t count = 0; + for (const Rlist *rp = hostnameip; rp != NULL; rp = rp->next) + { + const char *host = RlistScalarValue(rp); + Log(LOG_LEVEL_DEBUG, "Want to read %zd bytes from %s port %s", + maxbytes, host, port); + + char txtaddr[CF_MAX_IP_LEN] = ""; + int sd = SocketConnect(host, port, CONNTIMEOUT, false, + txtaddr, sizeof(txtaddr)); + if (sd == -1) + { + continue; + } + + if (strlen(sendstring) > 0) + { + if (SendSocketStream(sd, sendstring, strlen(sendstring)) != -1) + { + char recvbuf[CF_BUFSIZE]; + ssize_t n_read = recv(sd, recvbuf, maxbytes, 0); + + if (n_read >= 0) + { + /* maxbytes was checked earlier, but just make sure... */ + assert((size_t) n_read < sizeof(recvbuf)); + recvbuf[n_read] = '\0'; + + if (strlen(regex) == 0 || StringMatchFull(regex, recvbuf)) + { + Log(LOG_LEVEL_VERBOSE, + "selectservers: Got matching reply from host %s address %s", + host, txtaddr); + + char buffer[CF_MAXVARSIZE] = ""; + snprintf(buffer, sizeof(buffer), "%s[%zu]", array_lval, count); + VarRef *ref = VarRefParse(buffer); + EvalContextVariablePut(ctx, ref, host, CF_DATA_TYPE_STRING, + "source=function,function=selectservers"); + VarRefDestroy(ref); + + count++; + } + } + } + } + else /* If query is empty, all hosts are added */ + { + Log(LOG_LEVEL_VERBOSE, + "selectservers: Got reply from host %s address %s", + host, txtaddr); + + char buffer[CF_MAXVARSIZE] = ""; + snprintf(buffer, sizeof(buffer), "%s[%zu]", array_lval, count); + VarRef *ref = VarRefParse(buffer); + EvalContextVariablePut(ctx, ref, host, CF_DATA_TYPE_STRING, + "source=function,function=selectservers"); + VarRefDestroy(ref); + + count++; + } + + cf_closesocket(sd); + } + + PolicyDestroy(select_server_policy); + free(array_lval); + + Log(LOG_LEVEL_VERBOSE, "selectservers: found %zu servers", count); + return FnReturnF("%zu", count); +} + + +static FnCallResult FnCallShuffle(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + const char *seed_str = RlistScalarValue(finalargs->next); + + const char *name_str = RlistScalarValueSafe(finalargs); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + Seq *seq = SeqNew(100, NULL); + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + SeqAppend(seq, (void*)JsonPrimitiveGetAsString(e)); + } + + SeqShuffle(seq, StringHash(seed_str, 0)); + + Rlist *shuffled = NULL; + for (size_t i = 0; i < SeqLength(seq); i++) + { + RlistPrepend(&shuffled, SeqAt(seq, i), RVAL_TYPE_SCALAR); + } + + SeqDestroy(seq); + JsonDestroyMaybe(json, allocated); + return (FnCallResult) { FNCALL_SUCCESS, (Rval) { shuffled, RVAL_TYPE_LIST } }; +} + +static FnCallResult FnCallInt(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + assert(finalargs != NULL); + + char *str = RlistScalarValueSafe(finalargs); + + double val; + bool ok = DoubleFromString(str, &val); + if (!ok) + { + // Log from DoubleFromString + return FnFailure(); + } + + return FnReturnF("%jd", (intmax_t) val); // Discard decimals +} + +static FnCallResult FnCallIsNewerThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + struct stat frombuf, tobuf; + + if (stat(RlistScalarValue(finalargs), &frombuf) == -1 || + stat(RlistScalarValue(finalargs->next), &tobuf) == -1) + { + return FnFailure(); + } + + return FnReturnContext(frombuf.st_mtime > tobuf.st_mtime); +} + +/*********************************************************************/ + +static FnCallResult FnCallIsAccessedBefore(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + struct stat frombuf, tobuf; + + if (stat(RlistScalarValue(finalargs), &frombuf) == -1 || + stat(RlistScalarValue(finalargs->next), &tobuf) == -1) + { + return FnFailure(); + } + + return FnReturnContext(frombuf.st_atime < tobuf.st_atime); +} + +/*********************************************************************/ + +static FnCallResult FnCallIsChangedBefore(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + struct stat frombuf, tobuf; + + if (stat(RlistScalarValue(finalargs), &frombuf) == -1 || + stat(RlistScalarValue(finalargs->next), &tobuf) == -1) + { + return FnFailure(); + } + + return FnReturnContext(frombuf.st_ctime > tobuf.st_ctime); +} + +/*********************************************************************/ + +static FnCallResult FnCallFileStat(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + char *path = RlistScalarValue(finalargs); + struct stat statbuf; + + if (lstat(path, &statbuf) == -1) + { + if (StringEqual(fp->name, "filesize")) + { + return FnFailure(); + } + return FnReturnContext(false); + } + + if (!strcmp(fp->name, "isexecutable")) + { + if (S_ISLNK(statbuf.st_mode) && stat(path, &statbuf) == -1) + { + // stat on link target failed - probably broken link + return FnReturnContext(false); + } + return FnReturnContext(IsExecutable(path)); + } + if (!strcmp(fp->name, "isdir")) + { + return FnReturnContext(S_ISDIR(statbuf.st_mode)); + } + if (!strcmp(fp->name, "islink")) + { + return FnReturnContext(S_ISLNK(statbuf.st_mode)); + } + if (!strcmp(fp->name, "isplain")) + { + return FnReturnContext(S_ISREG(statbuf.st_mode)); + } + if (!strcmp(fp->name, "fileexists")) + { + return FnReturnContext(true); + } + if (!strcmp(fp->name, "filesize")) + { + return FnReturnF("%ju", (uintmax_t) statbuf.st_size); + } + + ProgrammingError("Unexpected function name in FnCallFileStat: %s", fp->name); +} + +/*********************************************************************/ + +static FnCallResult FnCallFileStatDetails(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *finalargs) +{ + char buffer[CF_BUFSIZE]; + const char *path = RlistScalarValue(finalargs); + char *detail = RlistScalarValue(finalargs->next); + struct stat statbuf; + + buffer[0] = '\0'; + + if (lstat(path, &statbuf) == -1) + { + return FnFailure(); + } + else if (!strcmp(detail, "xattr")) + { +#if defined(WITH_XATTR) + // Extended attributes include both POSIX ACLs and SELinux contexts. + char attr_raw_names[CF_BUFSIZE]; + ssize_t attr_raw_names_size = llistxattr(path, attr_raw_names, sizeof(attr_raw_names)); + + if (attr_raw_names_size < 0) + { + if (errno != ENOTSUP && errno != ENODATA) + { + Log(LOG_LEVEL_ERR, "Can't read extended attributes of '%s'. (llistxattr: %s)", + path, GetErrorStr()); + } + } + else + { + Buffer *printattr = BufferNew(); + for (int pos = 0; pos < attr_raw_names_size;) + { + const char *current = attr_raw_names + pos; + pos += strlen(current) + 1; + + if (!StringIsPrintable(current)) + { + Log(LOG_LEVEL_INFO, "Skipping extended attribute of '%s', it has a non-printable name: '%s'", + path, current); + continue; + } + + char data[CF_BUFSIZE]; + int datasize = lgetxattr(path, current, data, sizeof(data)); + if (datasize < 0) + { + if (errno == ENOTSUP) + { + continue; + } + else + { + Log(LOG_LEVEL_ERR, "Can't read extended attribute '%s' of '%s'. (lgetxattr: %s)", + path, current, GetErrorStr()); + } + } + else + { + if (!StringIsPrintable(data)) + { + Log(LOG_LEVEL_INFO, "Skipping extended attribute of '%s', it has non-printable data: '%s=%s'", + path, current, data); + continue; + } + + BufferPrintf(printattr, "%s=%s", current, data); + + // Append a newline for multiple attributes. + if (attr_raw_names_size > 0) + { + BufferAppendChar(printattr, '\n'); + } + } + } + + snprintf(buffer, CF_MAXVARSIZE, "%s", BufferData(printattr)); + BufferDestroy(printattr); + } +#else // !WITH_XATTR + // do nothing, leave the buffer empty +#endif + } + else if (!strcmp(detail, "size")) + { + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_size); + } + else if (!strcmp(detail, "gid")) + { + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_gid); + } + else if (!strcmp(detail, "uid")) + { + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_uid); + } + else if (!strcmp(detail, "ino")) + { + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_ino); + } + else if (!strcmp(detail, "nlink")) + { + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_nlink); + } + else if (!strcmp(detail, "ctime")) + { + snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_ctime); + } + else if (!strcmp(detail, "mtime")) + { + snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_mtime); + } + else if (!strcmp(detail, "atime")) + { + snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_atime); + } + else if (!strcmp(detail, "permstr")) + { + snprintf(buffer, CF_MAXVARSIZE, + "%c%c%c%c%c%c%c%c%c%c", + S_ISDIR(statbuf.st_mode) ? 'd' : '-', + (statbuf.st_mode & S_IRUSR) ? 'r' : '-', + (statbuf.st_mode & S_IWUSR) ? 'w' : '-', + (statbuf.st_mode & S_IXUSR) ? 'x' : '-', + (statbuf.st_mode & S_IRGRP) ? 'r' : '-', + (statbuf.st_mode & S_IWGRP) ? 'w' : '-', + (statbuf.st_mode & S_IXGRP) ? 'x' : '-', + (statbuf.st_mode & S_IROTH) ? 'r' : '-', + (statbuf.st_mode & S_IWOTH) ? 'w' : '-', + (statbuf.st_mode & S_IXOTH) ? 'x' : '-'); + } + else if (!strcmp(detail, "permoct")) + { + snprintf(buffer, CF_MAXVARSIZE, "%jo", (uintmax_t) (statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))); + } + else if (!strcmp(detail, "modeoct")) + { + snprintf(buffer, CF_MAXVARSIZE, "%jo", (uintmax_t) statbuf.st_mode); + } + else if (!strcmp(detail, "mode")) + { + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_mode); + } + else if (!strcmp(detail, "type")) + { + switch (statbuf.st_mode & S_IFMT) + { + case S_IFBLK: snprintf(buffer, CF_MAXVARSIZE, "%s", "block device"); break; + case S_IFCHR: snprintf(buffer, CF_MAXVARSIZE, "%s", "character device"); break; + case S_IFDIR: snprintf(buffer, CF_MAXVARSIZE, "%s", "directory"); break; + case S_IFIFO: snprintf(buffer, CF_MAXVARSIZE, "%s", "FIFO/pipe"); break; + case S_IFLNK: snprintf(buffer, CF_MAXVARSIZE, "%s", "symlink"); break; + case S_IFREG: snprintf(buffer, CF_MAXVARSIZE, "%s", "regular file"); break; + case S_IFSOCK: snprintf(buffer, CF_MAXVARSIZE, "%s", "socket"); break; + default: snprintf(buffer, CF_MAXVARSIZE, "%s", "unknown"); break; + } + } + else if (!strcmp(detail, "dev_minor")) + { +#if !defined(__MINGW32__) + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) minor(statbuf.st_dev) ); +#else + snprintf(buffer, CF_MAXVARSIZE, "Not available on Windows"); +#endif + } + else if (!strcmp(detail, "dev_major")) + { +#if !defined(__MINGW32__) + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) major(statbuf.st_dev) ); +#else + snprintf(buffer, CF_MAXVARSIZE, "Not available on Windows"); +#endif + } + else if (!strcmp(detail, "devno")) + { +#if !defined(__MINGW32__) + snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_dev ); +#else + snprintf(buffer, CF_MAXVARSIZE, "%c:", statbuf.st_dev + 'A'); +#endif + } + else if (!strcmp(detail, "dirname")) + { + snprintf(buffer, CF_MAXVARSIZE, "%s", path); + ChopLastNode(buffer); + MapName(buffer); + } + else if (!strcmp(detail, "basename")) + { + snprintf(buffer, CF_MAXVARSIZE, "%s", ReadLastNode(path)); + } + else if (!strcmp(detail, "linktarget") || !strcmp(detail, "linktarget_shallow")) + { +#if !defined(__MINGW32__) + char path_buffer[CF_BUFSIZE]; + bool recurse = !strcmp(detail, "linktarget"); + int cycles = 0; + int max_cycles = 30; // This allows for up to 31 levels of indirection. + + strlcpy(path_buffer, path, CF_MAXVARSIZE); + + // Iterate while we're looking at a link. + while (S_ISLNK(statbuf.st_mode)) + { + if (cycles > max_cycles) + { + Log(LOG_LEVEL_INFO, + "%s bailing on link '%s' (original '%s') because %d cycles were chased", + fp->name, path_buffer, path, cycles + 1); + break; + } + + Log(LOG_LEVEL_VERBOSE, "%s cycle %d, resolving link: %s", fp->name, cycles+1, path_buffer); + + /* Note we subtract 1 since we may need an extra char for '\0'. */ + ssize_t got = readlink(path_buffer, buffer, CF_BUFSIZE - 1); + if (got < 0) + { + // An error happened. Empty the buffer (don't keep the last target). + Log(LOG_LEVEL_ERR, "%s could not readlink '%s'", fp->name, path_buffer); + path_buffer[0] = '\0'; + break; + } + buffer[got] = '\0'; /* readlink() doesn't terminate */ + + /* If it is a relative path, then in order to follow it further we + * need to prepend the directory. */ + if (!IsAbsoluteFileName(buffer) && + strcmp(detail, "linktarget") == 0) + { + DeleteSlash(path_buffer); + ChopLastNode(path_buffer); + AddSlash(path_buffer); + strlcat(path_buffer, buffer, sizeof(path_buffer)); + /* Use buffer again as a tmp buffer. */ + CompressPath(buffer, sizeof(buffer), path_buffer); + } + + // We got a good link target into buffer. Copy it to path_buffer. + strlcpy(path_buffer, buffer, CF_MAXVARSIZE); + + Log(LOG_LEVEL_VERBOSE, "Link resolved to: %s", path_buffer); + + if (!recurse) + { + Log(LOG_LEVEL_VERBOSE, + "%s bailing on link '%s' (original '%s') because linktarget_shallow was requested", + fp->name, path_buffer, path); + break; + } + else if (lstat(path_buffer, &statbuf) == -1) + { + Log(LOG_LEVEL_INFO, + "%s bailing on link '%s' (original '%s') because it could not be read", + fp->name, path_buffer, path); + break; + } + + // At this point we haven't bailed, path_buffer has the link target + cycles++; + } + + // Get the path_buffer back into buffer. + strlcpy(buffer, path_buffer, CF_MAXVARSIZE); +#else + // Always return the original path on W32. + strlcpy(buffer, path, CF_MAXVARSIZE); +#endif + } + + return FnReturn(buffer); +} + +/*********************************************************************/ + +static FnCallResult FnCallFindfiles(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + Rlist *returnlist = NULL; + char id[CF_BUFSIZE]; + + snprintf(id, CF_BUFSIZE, "built-in FnCall %s-arg", fp->name); + + /* We need to check all the arguments, ArgTemplate does not check varadic functions */ + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err)); + } + } + + for (const Rlist *arg = finalargs; /* Start with arg set to finalargs. */ + arg; /* We must have arg to proceed. */ + arg = arg->next) /* arg steps forward every time. */ + { + const char *pattern = RlistScalarValue(arg); + + if (!IsAbsoluteFileName(pattern)) + { + Log(LOG_LEVEL_WARNING, + "Non-absolute path in findfiles(), skipping: %s", + pattern); + continue; + } + + StringSet *found = GlobFileList(pattern); + + char fname[CF_BUFSIZE]; + + StringSetIterator it = StringSetIteratorInit(found); + const char *element = NULL; + while ((element = StringSetIteratorNext(&it))) + { + // TODO: this truncates the filename and may be removed + // if Rlist and the core are OK with that possibility + strlcpy(fname, element, CF_BUFSIZE); + Log(LOG_LEVEL_VERBOSE, "%s pattern '%s' found match '%s'", fp->name, pattern, fname); + RlistAppendScalarIdemp(&returnlist, fname); + } + StringSetDestroy(found); + } + + return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallFilter(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + return FilterInternal(ctx, + fp, + RlistScalarValue(finalargs), // regex or string + finalargs->next, // list identifier + BooleanFromString(RlistScalarValue(finalargs->next->next)), // match as regex or exactly + BooleanFromString(RlistScalarValue(finalargs->next->next->next)), // invert matches + IntFromString(RlistScalarValue(finalargs->next->next->next->next))); // max results +} + +/*********************************************************************/ + +static const Rlist *GetListReferenceArgument(const EvalContext *ctx, const FnCall *fp, const char *lval_str, DataType *datatype_out) +{ + VarRef *ref = VarRefParse(lval_str); + DataType value_type; + const Rlist *value = EvalContextVariableGet(ctx, ref, &value_type); + VarRefDestroy(ref); + + /* Error 1: variable not found. */ + if (value_type == CF_DATA_TYPE_NONE) + { + Log(LOG_LEVEL_VERBOSE, + "Could not resolve expected list variable '%s' in function '%s'", + lval_str, fp->name); + assert(value == NULL); + } + /* Error 2: variable is not a list. */ + else if (DataTypeToRvalType(value_type) != RVAL_TYPE_LIST) + { + Log(LOG_LEVEL_ERR, "Function '%s' expected a list variable," + " got variable of type '%s'", + fp->name, DataTypeToString(value_type)); + + value = NULL; + value_type = CF_DATA_TYPE_NONE; + } + + if (datatype_out) + { + *datatype_out = value_type; + } + return value; +} + +/*********************************************************************/ + +static FnCallResult FilterInternal(EvalContext *ctx, + const FnCall *fp, + const char *regex, + const Rlist* rp, + bool do_regex, + bool invert, + long max) +{ + Regex *rx = NULL; + if (do_regex) + { + rx = CompileRegex(regex); + if (!rx) + { + return FnFailure(); + } + } + + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, rp, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + RegexDestroy(rx); + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, RlistScalarValueSafe(rp)); + JsonDestroyMaybe(json, allocated); + RegexDestroy(rx); + return FnFailure(); + } + + Rlist *returnlist = NULL; + + long match_count = 0; + long total = 0; + + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *el = NULL; + while ((el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)) && + match_count < max) + { + char *val = JsonPrimitiveToString(el); + if (val != NULL) + { + bool found; + if (do_regex) + { + found = StringMatchFullWithPrecompiledRegex(rx, val); + } + else + { + found = (0==strcmp(regex, val)); + } + + if (invert ? !found : found) + { + RlistAppendScalar(&returnlist, val); + match_count++; + + if (strcmp(fp->name, "some") == 0 || + strcmp(fp->name, "regarray") == 0) + { + free(val); + break; + } + } + else if (strcmp(fp->name, "every") == 0) + { + total++; + free(val); + break; + } + + total++; + free(val); + } + } + + JsonDestroyMaybe(json, allocated); + + if (rx) + { + RegexDestroy(rx); + } + + bool contextmode = false; + bool ret = false; + if (strcmp(fp->name, "every") == 0) + { + contextmode = true; + ret = (match_count == total && total > 0); + } + else if (strcmp(fp->name, "none") == 0) + { + contextmode = true; + ret = (match_count == 0); + } + else if (strcmp(fp->name, "some") == 0 || + strcmp(fp->name, "regarray") == 0 || + strcmp(fp->name, "reglist") == 0) + { + contextmode = true; + ret = (match_count > 0); + } + else if (strcmp(fp->name, "grep") != 0 && + strcmp(fp->name, "filter") != 0) + { + ProgrammingError("built-in FnCall %s: unhandled FilterInternal() contextmode", fp->name); + } + + if (contextmode) + { + RlistDestroy(returnlist); + return FnReturnContext(ret); + } + + // else, return the list itself + return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallSublist(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + const char *name_str = RlistScalarValueSafe(finalargs); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + bool head = (strcmp(RlistScalarValue(finalargs->next), "head") == 0); // heads or tails + long max = IntFromString(RlistScalarValue(finalargs->next->next)); // max results + + Rlist *input_list = NULL; + Rlist *returnlist = NULL; + + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + RlistAppendScalar(&input_list, JsonPrimitiveGetAsString(e)); + } + + JsonDestroyMaybe(json, allocated); + + if (head) + { + long count = 0; + for (const Rlist *rp = input_list; rp != NULL && count < max; rp = rp->next) + { + RlistAppendScalar(&returnlist, RlistScalarValue(rp)); + count++; + } + } + else if (max > 0) // tail mode + { + const Rlist *rp = input_list; + int length = RlistLen((const Rlist *) rp); + + int offset = max >= length ? 0 : length-max; + + for (; rp != NULL && offset--; rp = rp->next) + { + /* skip to offset */ + } + + for (; rp != NULL; rp = rp->next) + { + RlistAppendScalar(&returnlist, RlistScalarValue(rp)); + } + } + + RlistDestroy(input_list); + return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +// TODO: This monstrosity needs refactoring +static FnCallResult FnCallSetop(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, const Rlist *finalargs) +{ + bool difference_mode = (strcmp(fp->name, "difference") == 0); + bool unique_mode = (strcmp(fp->name, "unique") == 0); + + const char *name_str = RlistScalarValueSafe(finalargs); + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + JsonElement *json_b = NULL; + bool allocated_b = false; + if (!unique_mode) + { + const char *name_str_b = RlistScalarValueSafe(finalargs->next); + json_b = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated_b); + + // we failed to produce a valid JsonElement, so give up + if (json_b == NULL) + { + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + else if (JsonGetElementType(json_b) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str_b); + JsonDestroyMaybe(json, allocated); + JsonDestroyMaybe(json_b, allocated_b); + return FnFailure(); + } + } + + StringSet *set_b = StringSetNew(); + if (!unique_mode) + { + JsonIterator iter = JsonIteratorInit(json_b); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + StringSetAdd(set_b, xstrdup(JsonPrimitiveGetAsString(e))); + } + } + + Rlist *returnlist = NULL; + + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + const char *value = JsonPrimitiveGetAsString(e); + + // Yes, this is an XOR. But it's more legible this way. + if (!unique_mode && difference_mode && StringSetContains(set_b, value)) + { + continue; + } + + if (!unique_mode && !difference_mode && !StringSetContains(set_b, value)) + { + continue; + } + + RlistAppendScalarIdemp(&returnlist, value); + } + + JsonDestroyMaybe(json, allocated); + if (json_b != NULL) + { + JsonDestroyMaybe(json_b, allocated_b); + } + + + StringSetDestroy(set_b); + + return (FnCallResult) { FNCALL_SUCCESS, (Rval) { returnlist, RVAL_TYPE_LIST } }; +} + +static FnCallResult FnCallLength(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, const Rlist *finalargs) +{ + const char *name_str = RlistScalarValueSafe(finalargs); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + size_t len = JsonLength(json); + JsonDestroyMaybe(json, allocated); + return FnReturnF("%zu", len); +} + +static FnCallResult FnCallFold(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, const Rlist *finalargs) +{ + const char *sort_type = finalargs->next ? RlistScalarValue(finalargs->next) : NULL; + + size_t count = 0; + double product = 1.0; // this could overflow + double sum = 0; // this could overflow + double mean = 0; + double M2 = 0; + char* min = NULL; + char* max = NULL; + bool variance_mode = strcmp(fp->name, "variance") == 0; + bool mean_mode = strcmp(fp->name, "mean") == 0; + bool max_mode = strcmp(fp->name, "max") == 0; + bool min_mode = strcmp(fp->name, "min") == 0; + bool sum_mode = strcmp(fp->name, "sum") == 0; + bool product_mode = strcmp(fp->name, "product") == 0; + + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + if (!json) + { + return FnFailure(); + } + + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *el; + while ((el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + char *value = JsonPrimitiveToString(el); + + if (value != NULL) + { + if (sort_type) + { + if (min_mode && (min == NULL || !GenericStringItemLess(sort_type, min, value))) + { + free(min); + min = xstrdup(value); + } + + if (max_mode && (max == NULL || GenericStringItemLess(sort_type, max, value))) + { + free(max); + max = xstrdup(value); + } + } + + count++; + + if (mean_mode || variance_mode || sum_mode || product_mode) + { + double x; + if (sscanf(value, "%lf", &x) != 1) + { + x = 0; /* treat non-numeric entries as zero */ + } + + // Welford's algorithm + double delta = x - mean; + mean += delta/count; + M2 += delta * (x - mean); + sum += x; + product *= x; + } + + free(value); + } + } + + JsonDestroyMaybe(json, allocated); + + if (mean_mode) + { + return count == 0 ? FnFailure() : FnReturnF("%lf", mean); + } + else if (sum_mode) + { + return FnReturnF("%lf", sum); + } + else if (product_mode) + { + return FnReturnF("%lf", product); + } + else if (variance_mode) + { + double variance = 0; + + if (count == 0) + { + return FnFailure(); + } + + // if count is 1, variance is 0 + + if (count > 1) + { + variance = M2/(count - 1); + } + + return FnReturnF("%lf", variance); + } + else if (max_mode) + { + return max == NULL ? FnFailure() : FnReturnNoCopy(max); + } + else if (min_mode) + { + return min == NULL ? FnFailure() : FnReturnNoCopy(min); + } + + // else, we don't know this fp->name + ProgrammingError("Unknown function call %s to FnCallFold", fp->name); + return FnFailure(); +} + +/*********************************************************************/ + +static FnCallResult FnCallDatatype(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(fp != NULL); + assert(fp->name != NULL); + + if (finalargs == NULL) + { + Log(LOG_LEVEL_ERR, + "Function %s requires variable identifier as first argument", + fp->name); + return FnFailure(); + } + const char* const var_name = RlistScalarValue(finalargs); + + VarRef* const var_ref = VarRefParse(var_name); + DataType type; + const void *value = EvalContextVariableGet(ctx, var_ref, &type); + VarRefDestroy(var_ref); + + /* detail argument defaults to false */ + bool detail = false; + if (finalargs->next != NULL) + { + detail = BooleanFromString(RlistScalarValue(finalargs->next)); + } + + const char *const type_str = + (type == CF_DATA_TYPE_NONE) ? "none" : DataTypeToString(type); + + if (!detail) + { + return FnReturn(type_str); + } + + if (type == CF_DATA_TYPE_CONTAINER) + { + const char *subtype_str; + const JsonElement *const element = value; + + switch (JsonGetType(element)) + { + case JSON_CONTAINER_TYPE_OBJECT: + subtype_str = "object"; + break; + case JSON_CONTAINER_TYPE_ARRAY: + subtype_str = "array"; + break; + case JSON_PRIMITIVE_TYPE_STRING: + subtype_str = "string"; + break; + case JSON_PRIMITIVE_TYPE_INTEGER: + subtype_str = "int"; + break; + case JSON_PRIMITIVE_TYPE_REAL: + subtype_str = "real"; + break; + case JSON_PRIMITIVE_TYPE_BOOL: + subtype_str = "boolean"; + break; + case JSON_PRIMITIVE_TYPE_NULL: + subtype_str = "null"; + break; + default: + Log(LOG_LEVEL_ERR, + "Function %s failed to get subtype of type data", fp->name); + return FnFailure(); + } + + return FnReturnF("%s %s", type_str, subtype_str); + } + + return FnReturnF("policy %s", type_str); +} + +/*********************************************************************/ + +static FnCallResult FnCallNth(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + const char* const key = RlistScalarValue(finalargs->next); + + const char *name_str = RlistScalarValueSafe(finalargs); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + const char *jstring = NULL; + FnCallResult result; + if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_CONTAINER) + { + JsonContainerType ct = JsonGetContainerType(json); + JsonElement* jelement = NULL; + + if (JSON_CONTAINER_TYPE_OBJECT == ct) + { + jelement = JsonObjectGet(json, key); + } + else if (JSON_CONTAINER_TYPE_ARRAY == ct) + { + long index = IntFromString(key); + if (index < 0) + { + index += JsonLength(json); + } + + if (index >= 0 && index < (long) JsonLength(json)) + { + jelement = JsonAt(json, index); + } + } + else + { + ProgrammingError("JSON Container is neither array nor object but type %d", (int) ct); + } + + if (jelement != NULL && + JsonGetElementType(jelement) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + jstring = JsonPrimitiveGetAsString(jelement); + if (jstring != NULL) + { + result = FnReturn(jstring); + } + } + } + + JsonDestroyMaybe(json, allocated); + + if (jstring == NULL) + { + return FnFailure(); + } + + return result; +} + +/*********************************************************************/ + +static FnCallResult FnCallEverySomeNone(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + return FilterInternal(ctx, + fp, + RlistScalarValue(finalargs), // regex or string + finalargs->next, // list identifier + 1, + 0, + LONG_MAX); +} + +static FnCallResult FnCallSort(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + if (finalargs == NULL) + { + FatalError(ctx, "in built-in FnCall %s: missing first argument, a list name", fp->name); + } + + const char *sort_type = NULL; + + if (finalargs->next) + { + sort_type = RlistScalarValue(finalargs->next); // sort mode + } + else + { + sort_type = "lex"; + } + + const char *name_str = RlistScalarValueSafe(finalargs); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + Rlist *sorted = NULL; + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + RlistAppendScalar(&sorted, JsonPrimitiveGetAsString(e)); + } + JsonDestroyMaybe(json, allocated); + + if (strcmp(sort_type, "int") == 0) + { + sorted = IntSortRListNames(sorted); + } + else if (strcmp(sort_type, "real") == 0) + { + sorted = RealSortRListNames(sorted); + } + else if (strcmp(sort_type, "IP") == 0 || strcmp(sort_type, "ip") == 0) + { + sorted = IPSortRListNames(sorted); + } + else if (strcmp(sort_type, "MAC") == 0 || strcmp(sort_type, "mac") == 0) + { + sorted = MACSortRListNames(sorted); + } + else // "lex" + { + sorted = AlphaSortRListNames(sorted); + } + + return (FnCallResult) { FNCALL_SUCCESS, (Rval) { sorted, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallFormat(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + const char *const id = "built-in FnCall format-arg"; + + /* We need to check all the arguments, ArgTemplate does not check varadic functions */ + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err)); + } + } + + if (finalargs == NULL) + { + return FnFailure(); + } + + char *format = RlistScalarValue(finalargs); + + if (format == NULL) + { + return FnFailure(); + } + + const Rlist *rp = finalargs->next; + + char *check = strchr(format, '%'); + char check_buffer[CF_BUFSIZE]; + Buffer *buf = BufferNew(); + + if (check != NULL) + { + BufferAppend(buf, format, check - format); + Seq *s; + + while (check != NULL && + (s = StringMatchCaptures("^(%%|%[^diouxXeEfFgGaAcsCSpnm%]*?[diouxXeEfFgGaAcsCSpnm])([^%]*)(.*)$", check, false))) + { + { + if (SeqLength(s) >= 2) + { + const char *format_piece = BufferData(SeqAt(s, 1)); + bool percent = StringEqualN(format_piece, "%%", 2); + char *data = NULL; + + if (percent) + { + // "%%" in format string + } + else if (rp != NULL) + { + data = RlistScalarValue(rp); + rp = rp->next; + } + else // not %% and no data + { + Log(LOG_LEVEL_ERR, "format() didn't have enough parameters"); + BufferDestroy(buf); + SeqDestroy(s); + return FnFailure(); + } + + char piece[CF_BUFSIZE]; + memset(piece, 0, CF_BUFSIZE); + + const char bad_modifiers[] = "hLqjzt"; + const size_t length = strlen(bad_modifiers); + for (size_t b = 0; b < length; b++) + { + if (strchr(format_piece, bad_modifiers[b]) != NULL) + { + Log(LOG_LEVEL_ERR, "format() does not allow modifier character '%c' in format specifier '%s'.", + bad_modifiers[b], + format_piece); + BufferDestroy(buf); + SeqDestroy(s); + return FnFailure(); + } + } + + if (strrchr(format_piece, 'd') != NULL || strrchr(format_piece, 'o') != NULL || strrchr(format_piece, 'x') != NULL) + { + long x = 0; + sscanf(data, "%ld", &x); + snprintf(piece, CF_BUFSIZE, format_piece, x); + BufferAppend(buf, piece, strlen(piece)); + } + else if (percent) + { + // "%%" -> "%" + BufferAppend(buf, "%", 1); + } + else if (strrchr(format_piece, 'f') != NULL) + { + double x = 0; + sscanf(data, "%lf", &x); + snprintf(piece, CF_BUFSIZE, format_piece, x); + BufferAppend(buf, piece, strlen(piece)); + } + else if (strrchr(format_piece, 's') != NULL) + { + BufferAppendF(buf, format_piece, data); + } + else if (strrchr(format_piece, 'S') != NULL) + { + char *found_format_spec = NULL; + char format_rewrite[CF_BUFSIZE]; + + strlcpy(format_rewrite, format_piece, CF_BUFSIZE); + found_format_spec = strrchr(format_rewrite, 'S'); + + if (found_format_spec != NULL) + { + *found_format_spec = 's'; + } + else + { + ProgrammingError("Couldn't find the expected S format spec in %s", format_piece); + } + + const char* const varname = data; + VarRef *ref = VarRefParse(varname); + DataType type; + const void *value = EvalContextVariableGet(ctx, ref, &type); + VarRefDestroy(ref); + + if (type == CF_DATA_TYPE_CONTAINER) + { + Writer *w = StringWriter(); + JsonWriteCompact(w, value); + BufferAppendF(buf, format_rewrite, StringWriterData(w)); + WriterClose(w); + } + else // it might be a list reference + { + DataType data_type; + const Rlist *list = GetListReferenceArgument(ctx, fp, varname, &data_type); + if (data_type == CF_DATA_TYPE_STRING_LIST) + { + Writer *w = StringWriter(); + WriterWrite(w, "{ "); + for (const Rlist *rp = list; rp; rp = rp->next) + { + char *escaped = EscapeCharCopy(RlistScalarValue(rp), '"', '\\'); + WriterWriteF(w, "\"%s\"", escaped); + free(escaped); + + if (rp != NULL && rp->next != NULL) + { + WriterWrite(w, ", "); + } + } + WriterWrite(w, " }"); + + BufferAppendF(buf, format_rewrite, StringWriterData(w)); + WriterClose(w); + } + else // whatever this is, it's not a list reference or a data container + { + Log(LOG_LEVEL_VERBOSE, "format() with %%S specifier needs a data container or a list instead of '%s'.", + varname); + BufferDestroy(buf); + SeqDestroy(s); + return FnFailure(); + } + } + } + else + { + char error[] = "(unhandled format)"; + BufferAppend(buf, error, strlen(error)); + } + } + else + { + check = NULL; + } + } + + { + if (SeqLength(s) >= 3) + { + BufferAppend(buf, BufferData(SeqAt(s, 2)), BufferSize(SeqAt(s, 2))); + } + else + { + check = NULL; + } + } + + { + if (SeqLength(s) >= 4) + { + strlcpy(check_buffer, BufferData(SeqAt(s, 3)), CF_BUFSIZE); + check = check_buffer; + } + else + { + check = NULL; + } + } + + SeqDestroy(s); + } + } + else + { + BufferAppend(buf, format, strlen(format)); + } + + return FnReturnBuffer(buf); +} + +/*********************************************************************/ + +static FnCallResult FnCallIPRange(EvalContext *ctx, ARG_UNUSED const Policy *policy, + const FnCall *fp, const Rlist *finalargs) +{ + const char *range = RlistScalarValue(finalargs); + const Rlist *ifaces = finalargs->next; + + if (!FuzzyMatchParse(range)) + { + Log(LOG_LEVEL_VERBOSE, + "%s(%s): argument is not a valid address range", + fp->name, range); + return FnFailure(); + } + + for (const Item *ip = EvalContextGetIpAddresses(ctx); + ip != NULL; + ip = ip->next) + { + if (FuzzySetMatch(range, ip->name) == 0) + { + /* + * MODE1: iprange(range) + * Match range on the address of any interface. + */ + if (ifaces == NULL) + { + Log(LOG_LEVEL_DEBUG, "%s(%s): Match on IP '%s'", + fp->name, range, ip->name); + return FnReturnContext(true); + } + /* + * MODE2: iprange(range, args...) + * Match range only on the addresses of args interfaces. + */ + else + { + for (const Rlist *i = ifaces; i != NULL; i = i->next) + { + char *iface = xstrdup(RlistScalarValue(i)); + CanonifyNameInPlace(iface); + + const char *ip_iface = ip->classes; + + if (ip_iface != NULL && + strcmp(iface, ip_iface) == 0) + { + Log(LOG_LEVEL_DEBUG, + "%s(%s): Match on IP '%s' interface '%s'", + fp->name, range, ip->name, ip->classes); + + free(iface); + return FnReturnContext(true); + } + free(iface); + } + } + } + } + + Log(LOG_LEVEL_DEBUG, "%s(%s): no match", fp->name, range); + return FnReturnContext(false); +} + +static FnCallResult FnCallIsIpInSubnet(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, const Rlist *finalargs) +{ + const char *range = RlistScalarValue(finalargs); + const Rlist *ips = finalargs->next; + + if (!FuzzyMatchParse(range)) + { + Log(LOG_LEVEL_VERBOSE, + "%s(%s): argument is not a valid address range", + fp->name, range); + return FnFailure(); + } + + for (const Rlist *ip = ips; ip != NULL; ip = ip->next) + { + const char *ip_s = RlistScalarValue(ip); + + if (FuzzySetMatch(range, ip_s) == 0) + { + Log(LOG_LEVEL_DEBUG, "%s(%s): Match on IP '%s'", + fp->name, range, ip_s); + return FnReturnContext(true); + } + } + + Log(LOG_LEVEL_DEBUG, "%s(%s): no match", fp->name, range); + return FnReturnContext(false); +} +/*********************************************************************/ + +static FnCallResult FnCallHostRange(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *prefix = RlistScalarValue(finalargs); + char *range = RlistScalarValue(finalargs->next); + + if (!FuzzyHostParse(range)) + { + return FnFailure(); + } + + return FnReturnContext(FuzzyHostMatch(prefix, range, VUQNAME) == 0); +} + +/*********************************************************************/ + +FnCallResult FnCallHostInNetgroup(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + setnetgrent(RlistScalarValue(finalargs)); + + bool found = false; + char *host, *user, *domain; + while (getnetgrent(&host, &user, &domain)) + { + if (host == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Matched '%s' in netgroup '%s'", + VFQNAME, RlistScalarValue(finalargs)); + found = true; + break; + } + + if (strcmp(host, VFQNAME) == 0 || + strcmp(host, VUQNAME) == 0) + { + Log(LOG_LEVEL_VERBOSE, "Matched '%s' in netgroup '%s'", + host, RlistScalarValue(finalargs)); + found = true; + break; + } + } + + endnetgrent(); + + return FnReturnContext(found); +} + +/*********************************************************************/ + +static FnCallResult FnCallIsVariable(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + const char *lval = RlistScalarValue(finalargs); + bool found = false; + + if (lval) + { + VarRef *ref = VarRefParse(lval); + DataType value_type; + EvalContextVariableGet(ctx, ref, &value_type); + if (value_type != CF_DATA_TYPE_NONE) + { + found = true; + } + VarRefDestroy(ref); + } + + return FnReturnContext(found); +} + +/*********************************************************************/ + +static FnCallResult FnCallStrCmp(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + return FnReturnContext(strcmp(RlistScalarValue(finalargs), RlistScalarValue(finalargs->next)) == 0); +} + +/*********************************************************************/ + +static FnCallResult FnCallTranslatePath(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char buffer[MAX_FILENAME]; + + strlcpy(buffer, RlistScalarValue(finalargs), sizeof(buffer)); + MapName(buffer); + + return FnReturn(buffer); +} + +/*********************************************************************/ + +#if defined(__MINGW32__) + +static FnCallResult FnCallRegistryValue(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char buffer[CF_BUFSIZE] = ""; + + const char *const key = RlistScalarValue(finalargs); + const char *const value = RlistScalarValue(finalargs->next); + if (GetRegistryValue(key, value, buffer, sizeof(buffer))) + { + return FnReturn(buffer); + } + + Log(LOG_LEVEL_ERR, "Could not read existing registry data for key '%s' and value '%s'.", + key, value); + return FnFailure(); +} + +#else /* !__MINGW32__ */ + +static FnCallResult FnCallRegistryValue(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs) +{ + return FnFailure(); +} + +#endif /* !__MINGW32__ */ + +/*********************************************************************/ + +static FnCallResult FnCallRemoteScalar(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + char *handle = RlistScalarValue(finalargs); + char *server = RlistScalarValue(finalargs->next); + int encrypted = BooleanFromString(RlistScalarValue(finalargs->next->next)); + + if (strcmp(server, "localhost") == 0) + { + /* The only reason for this is testing... */ + server = "127.0.0.1"; + } + + if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON) + { + return FnReturn(""); + } + else + { + char buffer[CF_BUFSIZE]; + buffer[0] = '\0'; + + char *ret = GetRemoteScalar(ctx, "VAR", handle, server, + encrypted, buffer); + if (ret == NULL) + { + return FnFailure(); + } + + if (strncmp(buffer, "BAD:", 4) == 0) + { + if (RetrieveUnreliableValue("remotescalar", handle, buffer) == 0) + { + // This function should never fail + buffer[0] = '\0'; + } + } + else + { + CacheUnreliableValue("remotescalar", handle, buffer); + } + + return FnReturn(buffer); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallHubKnowledge(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + char *handle = RlistScalarValue(finalargs); + + if (THIS_AGENT_TYPE != AGENT_TYPE_AGENT) + { + return FnReturn(""); + } + else + { + char buffer[CF_BUFSIZE]; + buffer[0] = '\0'; + + Log(LOG_LEVEL_VERBOSE, "Accessing hub knowledge base for '%s'", handle); + char *ret = GetRemoteScalar(ctx, "VAR", handle, PolicyServerGetIP(), + true, buffer); + if (ret == NULL) + { + return FnFailure(); + } + + + // This should always be successful - and this one doesn't cache + + if (strncmp(buffer, "BAD:", 4) == 0) + { + return FnReturn("0"); + } + + return FnReturn(buffer); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallRemoteClassesMatching(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + char *regex = RlistScalarValue(finalargs); + char *server = RlistScalarValue(finalargs->next); + int encrypted = BooleanFromString(RlistScalarValue(finalargs->next->next)); + char *prefix = RlistScalarValue(finalargs->next->next->next); + + if (strcmp(server, "localhost") == 0) + { + /* The only reason for this is testing... */ + server = "127.0.0.1"; + } + + if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON) + { + return FnReturn("remote_classes"); + } + else + { + char buffer[CF_BUFSIZE]; + buffer[0] = '\0'; + + char *ret = GetRemoteScalar(ctx, "CONTEXT", regex, server, + encrypted, buffer); + if (ret == NULL) + { + return FnFailure(); + } + + if (strncmp(buffer, "BAD:", 4) == 0) + { + return FnFailure(); + } + + Rlist *classlist = RlistFromSplitString(buffer, ','); + if (classlist) + { + for (const Rlist *rp = classlist; rp != NULL; rp = rp->next) + { + char class_name[CF_MAXVARSIZE]; + snprintf(class_name, sizeof(class_name), "%s_%s", + prefix, RlistScalarValue(rp)); + EvalContextClassPutSoft(ctx, class_name, CONTEXT_SCOPE_BUNDLE, + "source=function,function=remoteclassesmatching"); + } + RlistDestroy(classlist); + } + + return FnReturnContext(true); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallPeers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + int maxent = 100000, maxsize = 100000; + + char *filename = RlistScalarValue(finalargs); + char *comment = RlistScalarValue(finalargs->next); + int groupsize = IntFromString(RlistScalarValue(finalargs->next->next)); + + if (2 > groupsize) + { + Log(LOG_LEVEL_WARNING, "Function %s: called with a nonsensical group size of %d, failing", fp->name, groupsize); + return FnFailure(); + } + + char *file_buffer = CfReadFile(filename, maxsize); + + if (file_buffer == NULL) + { + return FnFailure(); + } + + file_buffer = StripPatterns(file_buffer, comment, filename); + + Rlist *const newlist = + file_buffer ? RlistFromSplitRegex(file_buffer, "\n", maxent, true) : NULL; + + /* Slice up the list and discard everything except our slice */ + + int i = 0; + bool found = false; + Rlist *pruned = NULL; + + for (const Rlist *rp = newlist; rp != NULL; rp = rp->next) + { + const char *s = RlistScalarValue(rp); + if (EmptyString(s)) + { + continue; + } + + if (strcmp(s, VFQNAME) == 0 || strcmp(s, VUQNAME) == 0) + { + found = true; + } + else + { + RlistPrepend(&pruned, s, RVAL_TYPE_SCALAR); + } + + if (i++ % groupsize == groupsize - 1) + { + if (found) + { + break; + } + else + { + RlistDestroy(pruned); + pruned = NULL; + } + } + } + + RlistDestroy(newlist); + free(file_buffer); // OK if it's NULL + + if (pruned && found) + { + RlistReverse(&pruned); + } + else + { + RlistDestroy(pruned); + pruned = NULL; + } + return (FnCallResult) { FNCALL_SUCCESS, { pruned, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallPeerLeader(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + int maxent = 100000, maxsize = 100000; + + char *filename = RlistScalarValue(finalargs); + char *comment = RlistScalarValue(finalargs->next); + int groupsize = IntFromString(RlistScalarValue(finalargs->next->next)); + + if (2 > groupsize) + { + Log(LOG_LEVEL_WARNING, "Function %s: called with a nonsensical group size of %d, failing", fp->name, groupsize); + return FnFailure(); + } + + char *file_buffer = CfReadFile(filename, maxsize); + if (file_buffer == NULL) + { + return FnFailure(); + } + + file_buffer = StripPatterns(file_buffer, comment, filename); + + Rlist *const newlist = + file_buffer ? RlistFromSplitRegex(file_buffer, "\n", maxent, true) : NULL; + + /* Slice up the list and discard everything except our slice */ + + int i = 0; + bool found = false; + char buffer[CF_MAXVARSIZE]; + buffer[0] = '\0'; + + for (const Rlist *rp = newlist; !found && rp != NULL; rp = rp->next) + { + const char *s = RlistScalarValue(rp); + if (EmptyString(s)) + { + continue; + } + + found = (strcmp(s, VFQNAME) == 0 || strcmp(s, VUQNAME) == 0); + if (i % groupsize == 0) + { + strlcpy(buffer, found ? "localhost" : s, CF_MAXVARSIZE); + } + + i++; + } + + RlistDestroy(newlist); + free(file_buffer); + + if (found) + { + return FnReturn(buffer); + } + + return FnFailure(); +} + +/*********************************************************************/ + +static FnCallResult FnCallPeerLeaders(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + int maxent = 100000, maxsize = 100000; + + char *filename = RlistScalarValue(finalargs); + char *comment = RlistScalarValue(finalargs->next); + int groupsize = IntFromString(RlistScalarValue(finalargs->next->next)); + + if (2 > groupsize) + { + Log(LOG_LEVEL_WARNING, "Function %s: called with a nonsensical group size of %d, failing", fp->name, groupsize); + return FnFailure(); + } + + char *file_buffer = CfReadFile(filename, maxsize); + if (file_buffer == NULL) + { + return FnFailure(); + } + + file_buffer = StripPatterns(file_buffer, comment, filename); + + Rlist *const newlist = + file_buffer ? RlistFromSplitRegex(file_buffer, "\n", maxent, true) : NULL; + + /* Slice up the list and discard everything except our slice */ + + int i = 0; + Rlist *pruned = NULL; + + for (const Rlist *rp = newlist; rp != NULL; rp = rp->next) + { + const char *s = RlistScalarValue(rp); + if (EmptyString(s)) + { + continue; + } + + if (i % groupsize == 0) + { + if (strcmp(s, VFQNAME) == 0 || strcmp(s, VUQNAME) == 0) + { + RlistPrepend(&pruned, "localhost", RVAL_TYPE_SCALAR); + } + else + { + RlistPrepend(&pruned, s, RVAL_TYPE_SCALAR); + } + } + + i++; + } + + RlistDestroy(newlist); + free(file_buffer); + + RlistReverse(&pruned); + return (FnCallResult) { FNCALL_SUCCESS, { pruned, RVAL_TYPE_LIST } }; + +} + +/*********************************************************************/ + +static FnCallResult FnCallRegCmp(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *argv0 = RlistScalarValue(finalargs); + char *argv1 = RlistScalarValue(finalargs->next); + + return FnReturnContext(StringMatchFull(argv0, argv1)); +} + +/*********************************************************************/ + +static FnCallResult FnCallRegReplace(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + const char *data = RlistScalarValue(finalargs); + const char *regex = RlistScalarValue(finalargs->next); + const char *replacement = RlistScalarValue(finalargs->next->next); + const char *options = RlistScalarValue(finalargs->next->next->next); + + Buffer *rewrite = BufferNewFrom(data, strlen(data)); + const char* error = BufferSearchAndReplace(rewrite, regex, replacement, options); + + if (error) + { + BufferDestroy(rewrite); + Log(LOG_LEVEL_ERR, "%s: couldn't use regex '%s', replacement '%s', and options '%s': error=%s", + fp->name, regex, replacement, options, error); + return FnFailure(); + } + + return FnReturnBuffer(rewrite); +} + +/*********************************************************************/ + +static FnCallResult FnCallRegExtract(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + const bool container_mode = strcmp(fp->name, "data_regextract") == 0; + + const char *regex = RlistScalarValue(finalargs); + const char *data = RlistScalarValue(finalargs->next); + char *arrayname = NULL; + + if (!container_mode) + { + arrayname = xstrdup(RlistScalarValue(finalargs->next->next)); + + if (!IsQualifiedVariable(arrayname)) + { + if (fp->caller) + { + VarRef *ref = VarRefParseFromBundle(arrayname, PromiseGetBundle(fp->caller)); + free(arrayname); + arrayname = VarRefToString(ref, true); + VarRefDestroy(ref); + } + else + { + Log(LOG_LEVEL_ERR, "Function '%s' called with an unqualifed array reference '%s', " + "and the reference could not be automatically qualified as the function was not called from a promise.", + fp->name, arrayname); + free(arrayname); + return FnFailure(); + } + } + } + + Seq *s = StringMatchCaptures(regex, data, true); + + if (!s || SeqLength(s) == 0) + { + SeqDestroy(s); + free(arrayname); + return container_mode ? FnFailure() : FnReturnContext(false); + } + + JsonElement *json = NULL; + + if (container_mode) + { + json = JsonObjectCreate(SeqLength(s)/2); + } + + for (size_t i = 0; i < SeqLength(s); i+=2) + { + Buffer *key = SeqAt(s, i); + Buffer *value = SeqAt(s, i+1); + + + if (container_mode) + { + JsonObjectAppendString(json, BufferData(key), BufferData(value)); + } + else + { + char var[CF_MAXVARSIZE] = ""; + snprintf(var, CF_MAXVARSIZE - 1, "%s[%s]", arrayname, BufferData(key)); + VarRef *new_ref = VarRefParse(var); + EvalContextVariablePut(ctx, new_ref, BufferData(value), + CF_DATA_TYPE_STRING, + "source=function,function=regextract"); + VarRefDestroy(new_ref); + } + } + + free(arrayname); + SeqDestroy(s); + + if (container_mode) + { + return FnReturnContainerNoCopy(json); + } + else + { + return FnReturnContext(true); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallRegLine(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + Regex *rx = CompileRegex(RlistScalarValue(finalargs)); + if (!rx) + { + return FnFailure(); + } + + const char *arg_filename = RlistScalarValue(finalargs->next); + + FILE *fin = safe_fopen(arg_filename, "rt"); + if (!fin) + { + RegexDestroy(rx); + return FnReturnContext(false); + } + + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + + while (CfReadLine(&line, &line_size, fin) != -1) + { + if (StringMatchFullWithPrecompiledRegex(rx, line)) + { + free(line); + fclose(fin); + RegexDestroy(rx); + return FnReturnContext(true); + } + } + + RegexDestroy(rx); + free(line); + + if (!feof(fin)) + { + Log(LOG_LEVEL_ERR, "In function '%s', error reading from file. (getline: %s)", + fp->name, GetErrorStr()); + } + + fclose(fin); + return FnReturnContext(false); +} + +/*********************************************************************/ + +static FnCallResult FnCallIsLessGreaterThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + char *argv0 = RlistScalarValue(finalargs); + char *argv1 = RlistScalarValue(finalargs->next); + bool rising = (strcmp(fp->name, "isgreaterthan") == 0); + + if (IsRealNumber(argv0) && IsRealNumber(argv1)) + { + double a = 0, b = 0; + if (!DoubleFromString(argv0, &a) || + !DoubleFromString(argv1, &b)) + { + return FnFailure(); + } + + if (rising) + { + return FnReturnContext(a > b); + } + else + { + return FnReturnContext(a < b); + } + } + + if (rising) + { + return FnReturnContext(strcmp(argv0, argv1) > 0); + } + else + { + return FnReturnContext(strcmp(argv0, argv1) < 0); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallIRange(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + long from = IntFromString(RlistScalarValue(finalargs)); + long to = IntFromString(RlistScalarValue(finalargs->next)); + + if (from == CF_NOINT || to == CF_NOINT) + { + return FnFailure(); + } + + if (from > to) + { + long tmp = to; + to = from; + from = tmp; + } + + return FnReturnF("%ld,%ld", from, to); +} + +/*********************************************************************/ + +static FnCallResult FnCallRRange(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + double from = 0; + if (!DoubleFromString(RlistScalarValue(finalargs), &from)) + { + Log(LOG_LEVEL_ERR, + "Function rrange, error reading assumed real value '%s'", + RlistScalarValue(finalargs)); + return FnFailure(); + } + + double to = 0; + if (!DoubleFromString(RlistScalarValue(finalargs), &to)) + { + Log(LOG_LEVEL_ERR, + "Function rrange, error reading assumed real value '%s'", + RlistScalarValue(finalargs->next)); + return FnFailure(); + } + + if (from > to) + { + int tmp = to; + to = from; + from = tmp; + } + + return FnReturnF("%lf,%lf", from, to); +} + +static FnCallResult FnCallReverse(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + const char *name_str = RlistScalarValueSafe(finalargs); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + Rlist *returnlist = NULL; + + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + RlistPrepend(&returnlist, JsonPrimitiveGetAsString(e), RVAL_TYPE_SCALAR); + } + JsonDestroyMaybe(json, allocated); + + return (FnCallResult) { FNCALL_SUCCESS, (Rval) { returnlist, RVAL_TYPE_LIST } }; +} + + +/* Convert y/m/d/h/m/s 6-tuple */ +static struct tm FnArgsToTm(const Rlist *rp) +{ + struct tm ret = { .tm_isdst = -1 }; + /* .tm_year stores year - 1900 */ + ret.tm_year = IntFromString(RlistScalarValue(rp)) - 1900; + rp = rp->next; + /* .tm_mon counts from Jan = 0 to Dec = 11 */ + ret.tm_mon = IntFromString(RlistScalarValue(rp)); + rp = rp->next; + /* .tm_mday is day of month, 1 to 31, but we use 0 through 30 (for now) */ + ret.tm_mday = IntFromString(RlistScalarValue(rp)) + 1; + rp = rp->next; + ret.tm_hour = IntFromString(RlistScalarValue(rp)); + rp = rp->next; + ret.tm_min = IntFromString(RlistScalarValue(rp)); + rp = rp->next; + ret.tm_sec = IntFromString(RlistScalarValue(rp)); + return ret; +} + +static FnCallResult FnCallOn(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + struct tm tmv = FnArgsToTm(finalargs); + time_t cftime = mktime(&tmv); + + if (cftime == -1) + { + Log(LOG_LEVEL_INFO, "Illegal time value"); + } + + return FnReturnF("%jd", (intmax_t) cftime); +} + +/*********************************************************************/ + +static FnCallResult FnCallOr(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + char id[CF_BUFSIZE]; + + snprintf(id, CF_BUFSIZE, "built-in FnCall or-arg"); + +/* We need to check all the arguments, ArgTemplate does not check varadic functions */ + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err)); + } + } + + for (const Rlist *arg = finalargs; arg; arg = arg->next) + { + if (IsDefinedClass(ctx, RlistScalarValue(arg))) + { + return FnReturnContext(true); + } + } + + return FnReturnContext(false); +} + +/*********************************************************************/ + +static FnCallResult FnCallLaterThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + time_t now = time(NULL); + struct tm tmv = FnArgsToTm(finalargs); + /* Adjust to 1-based counting (input) for month and day of month + * (0-based in mktime): */ + tmv.tm_mon--; + tmv.tm_mday--; + time_t cftime = mktime(&tmv); + + if (cftime == -1) + { + Log(LOG_LEVEL_INFO, "Illegal time value"); + } + + return FnReturnContext(now > cftime); +} + +static FnCallResult FnCallAgoDate(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + struct tm ago = FnArgsToTm(finalargs); + time_t now = time(NULL); + struct tm t; + localtime_r(&now, &t); + + t.tm_year -= ago.tm_year + 1900; /* tm.tm_year stores year - 1900 */ + t.tm_mon -= ago.tm_mon; + t.tm_mday -= ago.tm_mday - 1; + t.tm_hour -= ago.tm_hour; + t.tm_min -= ago.tm_min; + t.tm_sec -= ago.tm_sec; + + time_t cftime = mktime(&t); + if (cftime < 0) + { + return FnReturn("0"); + } + + return FnReturnF("%jd", (intmax_t) cftime); +} + +/*********************************************************************/ + +static FnCallResult FnCallAccumulatedDate(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + struct tm tmv = FnArgsToTm(finalargs); + + intmax_t cftime = 0; + cftime = 0; + cftime += tmv.tm_sec; + cftime += (intmax_t) tmv.tm_min * 60; + cftime += (intmax_t) tmv.tm_hour * 3600; + cftime += (intmax_t) (tmv.tm_mday - 1) * 24 * 3600; + cftime += (intmax_t) tmv.tm_mon * 30 * 24 * 3600; + cftime += (intmax_t) (tmv.tm_year + 1900) * 365 * 24 * 3600; + + return FnReturnF("%jd", (intmax_t) cftime); +} + +/*********************************************************************/ + +static FnCallResult FnCallNot(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + return FnReturnContext(!IsDefinedClass(ctx, RlistScalarValue(finalargs))); +} + +/*********************************************************************/ + +static FnCallResult FnCallNow(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs) +{ + return FnReturnF("%jd", (intmax_t)CFSTARTTIME); +} + +/*********************************************************************/ + +#ifdef __sun /* Lacks %P and */ +#define STRFTIME_F_HACK +#define STRFTIME_s_HACK +#define STRFTIME_R_HACK +#endif /* http://www.unix.com/man-page/opensolaris/3c/strftime/ */ + +#ifdef __hpux /* Unknown gaps, aside from: */ +#define STRFTIME_F_HACK +#endif + +#ifdef _WIN32 /* Has non-standard %z, lacks %[CDeGghklnPrRtTuV] and: */ +#define STRFTIME_F_HACK +#define STRFTIME_R_HACK +#define STRFTIME_s_HACK +#endif /* http://msdn.microsoft.com/en-us/library/fe06s4ak.aspx */ + +bool PortablyFormatTime(char *buffer, size_t bufsiz, + const char *format, +#ifndef STRFTIME_s_HACK + ARG_UNUSED +#endif + time_t when, + const struct tm *tm) +{ + /* TODO: might be better done in a libcompat wrapper. + * + * The following GNU extensions may be worth adding at some point; + * see individual platforms for lists of which they lack. + * + * %C (century) + * %D => %m/%d/%y + * %e: as %d but s/^0/ / + * %G: like %Y but frobbed for ISO week numbers + * %g: last two digits of %G + * %h => %b + * %k: as %H but s/^0/ / + * %l: as %I but s/^0/ / + * %n => \n + * %P: as %p but lower-cased + * %r => %I:%M:%S %p + * %s: seconds since epoch + * %t => \t + * %T => %H:%M:%S + * %u: dow, {1: Mon, ..., Sun: 7} + * %V: ISO week number within year %G + * + * The "=>" ones can all be done by extending expansion[], below; + * the rest would require actually implementing GNU strftime() + * properly. + */ + +#ifdef STRFTIME_s_HACK /* %s: seconds since epoch */ + char epoch[PRINTSIZE(when)]; + xsnprintf(epoch, sizeof(epoch), "%jd", (intmax_t) when); +#endif /* STRFTIME_s_HACK */ + + typedef char * SearchReplacePair[2]; + SearchReplacePair expansion[] = + { + /* Each pair is { search, replace }. */ +#ifdef STRFTIME_F_HACK + { "%F", "%Y-%m-%d" }, +#endif +#ifdef STRFTIME_R_HACK /* %R => %H:%M:%S */ + { "%R", "%H:%M:%S" }, +#endif +#ifdef STRFTIME_s_HACK + { "%s", epoch }, +#endif + + /* Order as in GNU strftime's man page. */ + { NULL, NULL } + }; + + char *delenda = NULL; /* in need of destruction */ + /* No-op when no STRFTIME_*_HACK were defined. */ + for (size_t i = 0; expansion[i][0]; i++) + { + char *tmp = SearchAndReplace(format, expansion[i][0], expansion[i][1]); + free(delenda); + format = delenda = tmp; + } + + size_t ans = strftime(buffer, bufsiz, format, tm); + free(delenda); + return ans > 0; +} +#undef STRFTIME_F_HACK +#undef STRFTIME_R_HACK +#undef STRFTIME_s_HACK + +/*********************************************************************/ + +static FnCallResult FnCallStrftime(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *finalargs) +{ + /* begin fn-specific content */ + + char *mode = RlistScalarValue(finalargs); + char *format_string = RlistScalarValue(finalargs->next); + // this will be a problem on 32-bit systems... + const time_t when = IntFromString(RlistScalarValue(finalargs->next->next)); + + struct tm tm_value; + struct tm *tm_pointer; + + if (strcmp("gmtime", mode) == 0) + { + tm_pointer = gmtime_r(&when, &tm_value); + } + else + { + tm_pointer = localtime_r(&when, &tm_value); + } + + char buffer[CF_BUFSIZE]; + if (tm_pointer == NULL) + { + Log(LOG_LEVEL_WARNING, + "Function %s, the given time stamp '%ld' was invalid. (strftime: %s)", + fp->name, when, GetErrorStr()); + } + else if (PortablyFormatTime(buffer, sizeof(buffer), + format_string, when, tm_pointer)) + { + return FnReturn(buffer); + } + + return FnFailure(); +} + +/*********************************************************************/ + +static FnCallResult FnCallEval(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + if (finalargs == NULL) + { + FatalError(ctx, "in built-in FnCall %s: missing first argument, an evaluation input", fp->name); + } + + const char *input = RlistScalarValue(finalargs); + + const char *type = NULL; + + if (finalargs->next) + { + type = RlistScalarValue(finalargs->next); + } + else + { + type = "math"; + } + + /* Third argument can currently only be "infix". */ + // So we completely ignore finalargs->next->next + + const bool context_mode = (strcmp(type, "class") == 0); + + char failure[CF_BUFSIZE]; + memset(failure, 0, sizeof(failure)); + + double result = EvaluateMathInfix(ctx, input, failure); + if (context_mode) + { + // see CLOSE_ENOUGH in math.peg + return FnReturnContext(strlen(failure) == 0 && + !(result < 0.00000000000000001 && + result > -0.00000000000000001)); + } + + if (strlen(failure) > 0) + { + Log(LOG_LEVEL_INFO, "%s error: %s (input '%s')", fp->name, failure, input); + return FnReturn(""); /* TODO: why not FnFailure() ? */ + } + + return FnReturnF("%lf", result); +} + +/*********************************************************************/ +/* Read functions */ +/*********************************************************************/ + +static FnCallResult FnCallReadFile(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *filename = RlistScalarValue(finalargs); + const Rlist *next = finalargs->next; // max_size argument, default to inf: + long maxsize = next ? IntFromString(RlistScalarValue(next)) : IntFromString("inf"); + + if (maxsize == CF_INFINITY) /* "inf" in the policy */ + { + maxsize = 0; + } + + if (maxsize < 0) + { + Log(LOG_LEVEL_ERR, "%s: requested max size %li is less than 0", fp->name, maxsize); + return FnFailure(); + } + + // Read once to validate structure of file in itemlist + char *contents = CfReadFile(filename, maxsize); + if (contents) + { + return FnReturnNoCopy(contents); + } + + Log(LOG_LEVEL_VERBOSE, "Function '%s' failed to read file: %s", + fp->name, filename); + return FnFailure(); +} + +/*********************************************************************/ + +static FnCallResult ReadList(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const FnCall *fp, const Rlist *finalargs, DataType type) +{ + const char *filename = RlistScalarValue(finalargs); + const char *comment = RlistScalarValue(finalargs->next); + const char *split = RlistScalarValue(finalargs->next->next); + const int maxent = IntFromString(RlistScalarValue(finalargs->next->next->next)); + const int maxsize = IntFromString(RlistScalarValue(finalargs->next->next->next->next)); + + char *file_buffer = CfReadFile(filename, maxsize); + if (!file_buffer) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s' failed to read file: %s", + fp->name, filename); + return FnFailure(); + } + + bool blanks = false; + Rlist *newlist = NULL; + file_buffer = StripPatterns(file_buffer, comment, filename); + if (!file_buffer) + { + return (FnCallResult) { FNCALL_SUCCESS, { NULL, RVAL_TYPE_LIST } }; + } + else + { + newlist = RlistFromSplitRegex(file_buffer, split, maxent, blanks); + } + + bool noerrors = true; + + switch (type) + { + case CF_DATA_TYPE_STRING: + break; + + case CF_DATA_TYPE_INT: + for (Rlist *rp = newlist; rp != NULL; rp = rp->next) + { + if (IntFromString(RlistScalarValue(rp)) == CF_NOINT) + { + Log(LOG_LEVEL_ERR, "Presumed int value '%s' read from file '%s' has no recognizable value", + RlistScalarValue(rp), filename); + noerrors = false; + } + } + break; + + case CF_DATA_TYPE_REAL: + for (Rlist *rp = newlist; rp != NULL; rp = rp->next) + { + double real_value = 0; + if (!DoubleFromString(RlistScalarValue(rp), &real_value)) + { + Log(LOG_LEVEL_ERR, "Presumed real value '%s' read from file '%s' has no recognizable value", + RlistScalarValue(rp), filename); + noerrors = false; + } + } + break; + + default: + ProgrammingError("Unhandled type in switch: %d", type); + } + + free(file_buffer); + + if (noerrors) + { + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; + } + + RlistDestroy(newlist); + return FnFailure(); +} + +static FnCallResult FnCallReadStringList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ReadList(ctx, fp, args, CF_DATA_TYPE_STRING); +} + +static FnCallResult FnCallReadIntList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ReadList(ctx, fp, args, CF_DATA_TYPE_INT); +} + +static FnCallResult FnCallReadRealList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ReadList(ctx, fp, args, CF_DATA_TYPE_REAL); +} + +static FnCallResult ReadDataGeneric(const char *const fname, + const char *const input_path, + const size_t size_max, + const DataFileType requested_mode) +{ + assert(fname != NULL); + assert(input_path != NULL); + + JsonElement *json = JsonReadDataFile(fname, input_path, requested_mode, size_max); + if (json == NULL) + { + return FnFailure(); + } + + return FnReturnContainerNoCopy(json); +} + +static FnCallResult FnCallReadData(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + assert(fp != NULL); + if (args == NULL) + { + Log(LOG_LEVEL_ERR, "Function '%s' requires at least one argument", fp->name); + return FnFailure(); + } + + const char *input_path = RlistScalarValue(args); + const char *const mode_string = RlistScalarValue(args->next); + DataFileType requested_mode = DATAFILETYPE_UNKNOWN; + if (StringEqual("auto", mode_string)) + { + requested_mode = GetDataFileTypeFromSuffix(input_path); + Log(LOG_LEVEL_VERBOSE, + "%s: automatically selected data type %s from filename %s", + fp->name, DataFileTypeToString(requested_mode), input_path); + } + else + { + requested_mode = GetDataFileTypeFromString(mode_string); + } + + return ReadDataGeneric(fp->name, input_path, CF_INFINITY, requested_mode); +} + +static FnCallResult ReadGenericDataType(const FnCall *fp, + const Rlist *args, + const DataFileType requested_mode) +{ + assert(fp != NULL); + if (args == NULL) + { + Log(LOG_LEVEL_ERR, + "Function '%s' requires at least one argument", + fp->name); + return FnFailure(); + } + + const char *const input_path = RlistScalarValue(args); + size_t size_max = args->next ? + IntFromString(RlistScalarValue(args->next)) : + CF_INFINITY; + return ReadDataGeneric(fp->name, input_path, size_max, requested_mode); +} + +static FnCallResult FnCallReadCsv(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + return ReadGenericDataType(fp, args, DATAFILETYPE_CSV); +} + +static FnCallResult FnCallReadEnvFile(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + return ReadGenericDataType(fp, args, DATAFILETYPE_ENV); +} + +static FnCallResult FnCallReadYaml(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + return ReadGenericDataType(fp, args, DATAFILETYPE_YAML); +} + +static FnCallResult FnCallReadJson(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + return ReadGenericDataType(fp, args, DATAFILETYPE_JSON); +} + +static FnCallResult ValidateDataGeneric(const char *const fname, + const char *data, + const DataFileType requested_mode) +{ + assert(data != NULL); + if (requested_mode != DATAFILETYPE_JSON) + { + Log(LOG_LEVEL_ERR, + "%s: Data type %s is not supported by this function", + fname, DataFileTypeToString(requested_mode)); + return FnFailure(); + } + + JsonElement *json = NULL; + JsonParseError err = JsonParseAll(&data, &json); + if (err != JSON_PARSE_OK) + { + Log(LOG_LEVEL_VERBOSE, "%s: %s", fname, JsonParseErrorToString(err)); + } + + FnCallResult ret = FnReturnContext(json != NULL); + JsonDestroy(json); + return ret; +} + +static FnCallResult FnCallValidData(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + assert(fp != NULL); + if (args == NULL || args->next == NULL) + { + Log(LOG_LEVEL_ERR, "Function '%s' requires two arguments", fp->name); + return FnFailure(); + } + + const char *data = RlistScalarValue(args); + const char *const mode_string = RlistScalarValue(args->next); + DataFileType requested_mode = GetDataFileTypeFromString(mode_string); + + return ValidateDataGeneric(fp->name, data, requested_mode); +} + +static FnCallResult FnCallValidJson(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + assert(fp != NULL); + if (args == NULL) + { + Log(LOG_LEVEL_ERR, "Function '%s' requires one argument", fp->name); + return FnFailure(); + } + + const char *data = RlistScalarValue(args); + return ValidateDataGeneric(fp->name, data, DATAFILETYPE_JSON); +} + +static FnCallResult FnCallReadModuleProtocol( + ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *args) +{ + assert(fp != NULL); + + if (args == NULL) + { + Log(LOG_LEVEL_ERR, "Function '%s' requires at least one argument", fp->name); + return FnFailure(); + } + + const char *input_path = RlistScalarValue(args); + + char module_context[CF_BUFSIZE] = {0}; + + FILE *file = safe_fopen(input_path, "rt"); + if (file == NULL) + { + return FnReturnContext(false); + } + + StringSet *module_tags = StringSetNew(); + long persistence = 0; + + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + + bool success = true; + for (;;) + { + const ssize_t res = CfReadLine(&line, &line_size, file); + if (res == -1) + { + if (!feof(file)) + { + Log(LOG_LEVEL_ERR, "Unable to read from file '%s'. (fread: %s)", input_path, GetErrorStr()); + success = false; + } + break; + } + + ModuleProtocol(ctx, input_path, line, false, module_context, sizeof(module_context), module_tags, &persistence); + } + + StringSetDestroy(module_tags); + free(line); + fclose(file); + + return FnReturnContext(success); +} + +static int JsonPrimitiveComparator(JsonElement const *left_obj, + JsonElement const *right_obj, + void *user_data) +{ + size_t const index = *(size_t *)user_data; + + char const *left = JsonPrimitiveGetAsString(JsonAt(left_obj, index)); + char const *right = JsonPrimitiveGetAsString(JsonAt(right_obj, index)); + return StringSafeCompare(left, right); +} + +static FnCallResult FnCallClassFilterCsv(EvalContext *ctx, + ARG_UNUSED Policy const *policy, + FnCall const *fp, + Rlist const *args) +{ + if (args == NULL || args->next == NULL || args->next->next == NULL) + { + FatalError(ctx, "Function %s requires at least 3 arguments", + fp->name); + } + + char const *path = RlistScalarValue(args); + bool const has_heading = BooleanFromString(RlistScalarValue(args->next)); + size_t const class_index = IntFromString(RlistScalarValue(args->next->next)); + Rlist const *sort_arg = args->next->next->next; + + FILE *csv_file = safe_fopen(path, "r"); + if (csv_file == NULL) + { + Log(LOG_LEVEL_ERR, + "%s: Failed to read file %s: %s", + fp->name, path, GetErrorStrFromCode(errno)); + return FnFailure(); + } + + Seq *heading = NULL; + JsonElement *json = JsonArrayCreate(50); + char *line; + size_t num_columns = 0; + + // Current line number, for debugging + size_t line_number = 0; + + while ((line = GetCsvLineNext(csv_file)) != NULL) + { + ++line_number; + if (line[0] == '#') + { + Log(LOG_LEVEL_DEBUG, "%s: Ignoring comment at line %zu", + fp->name, line_number); + free(line); + continue; + } + + Seq *list = SeqParseCsvString(line); + free(line); + if (list == NULL) + { + Log(LOG_LEVEL_WARNING, + "%s: Failed to parse line %zu, line ignored.", + fp->name, line_number); + continue; + } + + if (SeqLength(list) == 1 && + strlen(SeqAt(list, 0)) == 0) + { + Log(LOG_LEVEL_DEBUG, + "%s: Found empty line at line %zu, line ignored", + fp->name, line_number); + SeqDestroy(list); + continue; + } + + if (num_columns == 0) + { + num_columns = SeqLength(list); + assert(num_columns != 0); + + if (class_index >= num_columns) + { + Log(LOG_LEVEL_ERR, + "%s: Class expression index is out of bounds. " + "Row length %zu, index %zu", + fp->name, num_columns, class_index); + SeqDestroy(list); + JsonDestroy(json); + return FnFailure(); + } + } + else if (num_columns != SeqLength(list)) + { + Log(LOG_LEVEL_WARNING, + "%s: Line %zu has incorrect amount of elements, " + "%zu instead of %zu. Line ignored.", + fp->name, line_number, SeqLength(list), num_columns); + SeqDestroy(list); + continue; + } + + // First parsed line is set to be heading if has_heading is true + if (has_heading && heading == NULL) + { + Log(LOG_LEVEL_DEBUG, "%s: Found header at line %zu", + fp->name, line_number); + heading = list; + SeqRemove(heading, class_index); + } + else + { + if (!IsDefinedClass(ctx, SeqAt(list, class_index))) + { + SeqDestroy(list); + continue; + } + + SeqRemove(list, class_index); + JsonElement *class_container = JsonObjectCreate(num_columns); + + size_t const num_fields = SeqLength(list); + for (size_t i = 0; i < num_fields; i++) + { + if (has_heading) + { + JsonObjectAppendString(class_container, + SeqAt(heading, i), + SeqAt(list, i)); + } + else + { + size_t const key_len = PRINTSIZE(size_t); + char key[key_len]; + xsnprintf(key, key_len, "%zu", i); + + JsonObjectAppendString(class_container, + key, + SeqAt(list, i)); + } + } + + JsonArrayAppendObject(json, class_container); + SeqDestroy(list); + } + } + + if (sort_arg != NULL) + { + size_t sort_index = IntFromString(RlistScalarValue(sort_arg)); + if (sort_index == class_index) + { + Log(LOG_LEVEL_WARNING, + "%s: sorting column (%zu) is the same as class " + "expression column (%zu). Not sorting data container.", + fp->name, sort_index, class_index); + } + else if (sort_index >= num_columns) + { + Log(LOG_LEVEL_WARNING, + "%s: sorting index %zu out of bounds. " + "Not sorting data container.", + fp->name, sort_index); + } + else + { + /* The sorting index needs to be decremented if it is higher than + * the class expression index, since the class column is removed + * in the data containers. */ + if (sort_index > class_index) + { + sort_index--; + } + + JsonSort(json, JsonPrimitiveComparator, &sort_index); + } + } + + fclose(csv_file); + if (heading != NULL) + { + SeqDestroy(heading); + } + + return FnReturnContainerNoCopy(json); +} + +static FnCallResult FnCallParseJson(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *args) +{ + const char *data = RlistScalarValue(args); + JsonElement *json = NULL; + bool yaml_mode = (strcmp(fp->name, "parseyaml") == 0); + const char* data_type = yaml_mode ? "YAML" : "JSON"; + JsonParseError res; + + if (yaml_mode) + { + res = JsonParseYamlString(&data, &json); + } + else + { + res = JsonParseWithLookup(ctx, &LookupVarRefToJson, &data, &json); + } + + if (res != JSON_PARSE_OK) + { + Log(LOG_LEVEL_ERR, "Error parsing %s expression '%s': %s", + data_type, data, JsonParseErrorToString(res)); + } + else if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + Log(LOG_LEVEL_ERR, "Non-container from parsing %s expression '%s'", data_type, data); + JsonDestroy(json); + } + else + { + return FnReturnContainerNoCopy(json); + } + + return FnFailure(); +} + +/*********************************************************************/ + +static FnCallResult FnCallStoreJson(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + const char *name_str = RlistScalarValueSafe(finalargs); + + // try to load directly + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + Writer *w = StringWriter(); + + JsonWrite(w, json, 0); + JsonDestroyMaybe(json, allocated); + Log(LOG_LEVEL_DEBUG, "%s: from data container %s, got JSON data '%s'", fp->name, name_str, StringWriterData(w)); + + return FnReturnNoCopy(StringWriterClose(w)); +} + + +/*********************************************************************/ + +// this function is separate so other data container readers can use it +static FnCallResult DataRead(EvalContext *ctx, const FnCall *fp, const Rlist *finalargs) +{ + /* 5 args: filename,comment_regex,split_regex,max number of entries,maxfilesize */ + + const char *filename = RlistScalarValue(finalargs); + const char *comment = RlistScalarValue(finalargs->next); + const char *split = RlistScalarValue(finalargs->next->next); + int maxent = IntFromString(RlistScalarValue(finalargs->next->next->next)); + int maxsize = IntFromString(RlistScalarValue(finalargs->next->next->next->next)); + + bool make_array = (strcmp(fp->name, "data_readstringarrayidx") == 0); + JsonElement *json = NULL; + + // Read once to validate structure of file in itemlist + char *file_buffer = CfReadFile(filename, maxsize); + if (file_buffer) + { + file_buffer = StripPatterns(file_buffer, comment, filename); + + if (file_buffer != NULL) + { + json = BuildData(ctx, file_buffer, split, maxent, make_array); + } + } + + free(file_buffer); + + if (json == NULL) + { + Log(LOG_LEVEL_ERR, "%s: error reading from file '%s'", fp->name, filename); + return FnFailure(); + } + + return FnReturnContainerNoCopy(json); +} + +/*********************************************************************/ + +static FnCallResult FnCallDataExpand(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *args) +{ + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, args, false, &allocated); + + if (json == NULL) + { + return FnFailure(); + } + + JsonElement *expanded = JsonExpandElement(ctx, json); + JsonDestroyMaybe(json, allocated); + + return FnReturnContainerNoCopy(expanded); +} + +/*********************************************************************/ + +static FnCallResult FnCallDataRead(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return DataRead(ctx, fp, args); +} + +/*********************************************************************/ + +static FnCallResult ReadArray(EvalContext *ctx, const FnCall *fp, const Rlist *finalargs, DataType type, bool int_index) +/* lval,filename,separator,comment,Max number of bytes */ +{ + if (!fp->caller) + { + Log(LOG_LEVEL_ERR, "Function '%s' can only be called from a promise", fp->name); + return FnFailure(); + } + + /* 6 args: array_lval,filename,comment_regex,split_regex,max number of entries,maxfilesize */ + + const char *array_lval = RlistScalarValue(finalargs); + const char *filename = RlistScalarValue(finalargs->next); + const char *comment = RlistScalarValue(finalargs->next->next); + const char *split = RlistScalarValue(finalargs->next->next->next); + int maxent = IntFromString(RlistScalarValue(finalargs->next->next->next->next)); + int maxsize = IntFromString(RlistScalarValue(finalargs->next->next->next->next->next)); + + // Read once to validate structure of file in itemlist + char *file_buffer = CfReadFile(filename, maxsize); + int entries = 0; + if (file_buffer) + { + file_buffer = StripPatterns(file_buffer, comment, filename); + + if (file_buffer) + { + entries = BuildLineArray(ctx, PromiseGetBundle(fp->caller), array_lval, file_buffer, split, maxent, type, int_index); + } + } + + switch (type) + { + case CF_DATA_TYPE_STRING: + case CF_DATA_TYPE_INT: + case CF_DATA_TYPE_REAL: + break; + + default: + ProgrammingError("Unhandled type in switch: %d", type); + } + + free(file_buffer); + + return FnReturnF("%d", entries); +} + +/*********************************************************************/ + +static FnCallResult FnCallReadStringArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ReadArray(ctx, fp, args, CF_DATA_TYPE_STRING, false); +} + +/*********************************************************************/ + +static FnCallResult FnCallReadStringArrayIndex(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ReadArray(ctx, fp, args, CF_DATA_TYPE_STRING, true); +} + +/*********************************************************************/ + +static FnCallResult FnCallReadIntArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ReadArray(ctx, fp, args, CF_DATA_TYPE_INT, false); +} + +/*********************************************************************/ + +static FnCallResult FnCallReadRealArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ReadArray(ctx, fp, args, CF_DATA_TYPE_REAL, false); +} + +/*********************************************************************/ + +static FnCallResult ParseArray(EvalContext *ctx, const FnCall *fp, const Rlist *finalargs, DataType type, int intIndex) +/* lval,filename,separator,comment,Max number of bytes */ +{ + if (!fp->caller) + { + Log(LOG_LEVEL_ERR, "Function '%s' can only be called from a promise", fp->name); + return FnFailure(); + } + + /* 6 args: array_lval,instring,comment_regex,split_regex,max number of entries,maxtextsize */ + + const char *array_lval = RlistScalarValue(finalargs); + int maxsize = IntFromString(RlistScalarValue(finalargs->next->next->next->next->next)); + char *instring = xstrndup(RlistScalarValue(finalargs->next), maxsize); + const char *comment = RlistScalarValue(finalargs->next->next); + const char *split = RlistScalarValue(finalargs->next->next->next); + int maxent = IntFromString(RlistScalarValue(finalargs->next->next->next->next)); + +// Read once to validate structure of file in itemlist + + Log(LOG_LEVEL_DEBUG, "Parse string data from string '%s' - , maxent %d, maxsize %d", instring, maxent, maxsize); + + int entries = 0; + if (instring) + { + instring = StripPatterns(instring, comment, "string argument 2"); + + if (instring) + { + entries = BuildLineArray(ctx, PromiseGetBundle(fp->caller), array_lval, instring, split, maxent, type, intIndex); + } + } + + switch (type) + { + case CF_DATA_TYPE_STRING: + case CF_DATA_TYPE_INT: + case CF_DATA_TYPE_REAL: + break; + + default: + ProgrammingError("Unhandled type in switch: %d", type); + } + + free(instring); + + return FnReturnF("%d", entries); +} + +/*********************************************************************/ + +static FnCallResult FnCallParseStringArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ParseArray(ctx, fp, args, CF_DATA_TYPE_STRING, false); +} + +/*********************************************************************/ + +static FnCallResult FnCallParseStringArrayIndex(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ParseArray(ctx, fp, args, CF_DATA_TYPE_STRING, true); +} + +/*********************************************************************/ + +static FnCallResult FnCallParseIntArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ParseArray(ctx, fp, args, CF_DATA_TYPE_INT, false); +} + +/*********************************************************************/ + +static FnCallResult FnCallParseRealArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args) +{ + return ParseArray(ctx, fp, args, CF_DATA_TYPE_REAL, false); +} + +/*********************************************************************/ + +static FnCallResult FnCallStringMustache(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + if (!finalargs) + { + return FnFailure(); + } + + const char* const mustache_template = RlistScalarValue(finalargs); + JsonElement *json = NULL; + bool allocated = false; + + if (finalargs->next) // we have a variable name... + { + // try to load directly + json = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + } + else + { + allocated = true; + json = DefaultTemplateData(ctx, NULL); + } + + Buffer *result = BufferNew(); + bool success = MustacheRender(result, mustache_template, json); + + JsonDestroyMaybe(json, allocated); + + if (success) + { + return FnReturnBuffer(result); + } + else + { + BufferDestroy(result); + return FnFailure(); + } +} + +/*********************************************************************/ + +static FnCallResult FnCallSplitString(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + /* 2args: string,split_regex,max */ + + char *string = RlistScalarValue(finalargs); + char *split = RlistScalarValue(finalargs->next); + int max = IntFromString(RlistScalarValue(finalargs->next->next)); + + // Read once to validate structure of file in itemlist + Rlist *newlist = RlistFromSplitRegex(string, split, max, true); + + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallStringSplit(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + ARG_UNUSED const FnCall *fp, + const Rlist *finalargs) +{ + /* 3 args: string, split_regex, max */ + char *string = RlistScalarValue(finalargs); + char *split = RlistScalarValue(finalargs->next); + int max = IntFromString(RlistScalarValue(finalargs->next->next)); + + if (max < 1) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s' called with invalid maxent argument: '%d' (should be > 0).", fp->name, max); + return FnFailure(); + } + + Rlist *newlist = RlistFromRegexSplitNoOverflow(string, split, max); + + if (newlist == NULL) + { + /* We are logging error in RlistFromRegexSplitNoOverflow() so no need to do it here as well. */ + return FnFailure(); + } + + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + +static FnCallResult FnCallStringReplace(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED Policy const *policy, + ARG_UNUSED FnCall const *fp, + Rlist const *finalargs) +{ + if (finalargs->next == NULL || finalargs->next->next == NULL) + { + Log(LOG_LEVEL_WARNING, + "Incorrect number of arguments for function '%s'", + fp->name); + return FnFailure(); + } + + char *string = RlistScalarValue(finalargs); + char *match = RlistScalarValue(finalargs->next); + char *substitute = RlistScalarValue(finalargs->next->next); + + char *ret = SearchAndReplace(string, match, substitute); + + if (ret == NULL) + { + Log(LOG_LEVEL_WARNING, + "Failed to replace with function '%s', string: '%s', match: '%s', " + "substitute: '%s'", + fp->name, string, match, substitute); + return FnFailure(); + } + + return FnReturnNoCopy(ret); +} + +/*********************************************************************/ + +static FnCallResult FnCallStringTrim(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *string = RlistScalarValue(finalargs); + + return FnReturn(TrimWhitespace(string)); +} + +/*********************************************************************/ + +static FnCallResult FnCallFileSexist(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + Log(LOG_LEVEL_VERBOSE, + "Cannot produce valid JSON from the argument '%s' of the function '%s'", + fp->name, RlistScalarValueSafe(finalargs)); + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, RlistScalarValueSafe(finalargs)); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true); + + /* no elements mean 'false' should be returned, otherwise let's see if the files exist */ + bool file_found = el != NULL; + + while (file_found && (el != NULL)) + { + char *val = JsonPrimitiveToString(el); + struct stat sb; + if (stat(val, &sb) == -1) + { + file_found = false; + } + free(val); + el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true); + } + + JsonDestroyMaybe(json, allocated); + return FnReturnContext(file_found); +} + +/*********************************************************************/ +/* LDAP Nova features */ +/*********************************************************************/ + +static FnCallResult FnCallLDAPValue(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char buffer[CF_BUFSIZE], handle[CF_BUFSIZE]; + + char *uri = RlistScalarValue(finalargs); + char *dn = RlistScalarValue(finalargs->next); + char *filter = RlistScalarValue(finalargs->next->next); + char *name = RlistScalarValue(finalargs->next->next->next); + char *scope = RlistScalarValue(finalargs->next->next->next->next); + char *sec = RlistScalarValue(finalargs->next->next->next->next->next); + + snprintf(handle, CF_BUFSIZE, "%s_%s_%s_%s", dn, filter, name, scope); + + void *newval = CfLDAPValue(uri, dn, filter, name, scope, sec); + if (newval) + { + CacheUnreliableValue("ldapvalue", handle, newval); + } + else if (RetrieveUnreliableValue("ldapvalue", handle, buffer) > 0) + { + newval = xstrdup(buffer); + } + + if (newval) + { + return FnReturnNoCopy(newval); + } + + return FnFailure(); +} + +/*********************************************************************/ + +static FnCallResult FnCallLDAPArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + if (!fp->caller) + { + Log(LOG_LEVEL_ERR, "Function '%s' can only be called from a promise", fp->name); + return FnFailure(); + } + + char *array = RlistScalarValue(finalargs); + char *uri = RlistScalarValue(finalargs->next); + char *dn = RlistScalarValue(finalargs->next->next); + char *filter = RlistScalarValue(finalargs->next->next->next); + char *scope = RlistScalarValue(finalargs->next->next->next->next); + char *sec = RlistScalarValue(finalargs->next->next->next->next->next); + + void *newval = CfLDAPArray(ctx, PromiseGetBundle(fp->caller), array, uri, dn, filter, scope, sec); + if (newval) + { + return FnReturnNoCopy(newval); + } + + return FnFailure(); +} + +/*********************************************************************/ + +static FnCallResult FnCallLDAPList(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *uri = RlistScalarValue(finalargs); + char *dn = RlistScalarValue(finalargs->next); + char *filter = RlistScalarValue(finalargs->next->next); + char *name = RlistScalarValue(finalargs->next->next->next); + char *scope = RlistScalarValue(finalargs->next->next->next->next); + char *sec = RlistScalarValue(finalargs->next->next->next->next->next); + + void *newval = CfLDAPList(uri, dn, filter, name, scope, sec); + if (newval) + { + return (FnCallResult) { FNCALL_SUCCESS, { newval, RVAL_TYPE_LIST } }; + } + + return FnFailure(); +} + +/*********************************************************************/ + +static FnCallResult FnCallRegLDAP(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *uri = RlistScalarValue(finalargs); + char *dn = RlistScalarValue(finalargs->next); + char *filter = RlistScalarValue(finalargs->next->next); + char *name = RlistScalarValue(finalargs->next->next->next); + char *scope = RlistScalarValue(finalargs->next->next->next->next); + char *regex = RlistScalarValue(finalargs->next->next->next->next->next); + char *sec = RlistScalarValue(finalargs->next->next->next->next->next->next); + + void *newval = CfRegLDAP(ctx, uri, dn, filter, name, scope, regex, sec); + if (newval) + { + return FnReturnNoCopy(newval); + } + + return FnFailure(); +} + +/*********************************************************************/ + +#define KILOBYTE 1024 + +static FnCallResult FnCallDiskFree(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + off_t df = GetDiskUsage(RlistScalarValue(finalargs), CF_SIZE_ABS); + + if (df == CF_INFINITY) + { + df = 0; + } + + return FnReturnF("%jd", (intmax_t) (df / KILOBYTE)); +} + + +static FnCallResult FnCallMakerule(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + const char *target = RlistScalarValue(finalargs); + + const char *name_str = RlistScalarValueSafe(finalargs->next); + + time_t target_time = 0; + bool stale = false; + struct stat statbuf; + if (lstat(target, &statbuf) == -1) + { + stale = true; + } + else + { + if (!S_ISREG(statbuf.st_mode)) + { + Log(LOG_LEVEL_WARNING, "Function '%s' target-file '%s' exists and is not a plain file", fp->name, target); + // Not a probe's responsibility to fix - but have this for debugging + } + + target_time = statbuf.st_mtime; + } + + // Check if the file name (which should be a scalar if given directly) is explicit + if (RlistValueIsType(finalargs->next, RVAL_TYPE_SCALAR) && + lstat(name_str, &statbuf) != -1) + { + if (statbuf.st_mtime > target_time) + { + stale = true; + } + } + else + { + // try to load directly from a container of collected values + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated); + + // we failed to produce a valid JsonElement, so give up + if (json == NULL) + { + return FnFailure(); + } + else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list", + fp->name, name_str); + JsonDestroyMaybe(json, allocated); + return FnFailure(); + } + + JsonIterator iter = JsonIteratorInit(json); + const JsonElement *e; + while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + const char *value = JsonPrimitiveGetAsString(e); + if (lstat(value, &statbuf) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Function '%s', source dependency %s was not (yet) readable", fp->name, value); + JsonDestroyMaybe(json, allocated); + return FnReturnContext(false); + } + else + { + if (statbuf.st_mtime > target_time) + { + stale = true; + } + } + } + + JsonDestroyMaybe(json, allocated); + } + + return stale ? FnReturnContext(true) : FnReturnContext(false); +} + + +#if !defined(__MINGW32__) + +FnCallResult FnCallUserExists(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *arg = RlistScalarValue(finalargs); + + if (StringIsNumeric(arg)) + { + uid_t uid = Str2Uid(arg, NULL, NULL); + if (uid == CF_SAME_OWNER || uid == CF_UNKNOWN_OWNER) + { + return FnFailure(); + } + + if (!GetUserName(uid, NULL, 0, LOG_LEVEL_VERBOSE)) + { + return FnReturnContext(false); + } + } + else if (!GetUserID(arg, NULL, LOG_LEVEL_VERBOSE)) + { + return FnReturnContext(false); + } + + return FnReturnContext(true); +} + +/*********************************************************************/ + +FnCallResult FnCallGroupExists(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + char *arg = RlistScalarValue(finalargs); + + if (StringIsNumeric(arg)) + { + gid_t gid = Str2Gid(arg, NULL, NULL); + if (gid == CF_SAME_GROUP || gid == CF_UNKNOWN_GROUP) + { + return FnFailure(); + } + + if (!GetGroupName(gid, NULL, 0, LOG_LEVEL_VERBOSE)) + { + return FnReturnContext(false); + } + } + else if (!GetGroupID(arg, NULL, LOG_LEVEL_VERBOSE)) + { + return FnReturnContext(false); + } + + return FnReturnContext(true); +} + +#endif /* !defined(__MINGW32__) */ + +static bool SingleLine(const char *s) +{ + size_t length = strcspn(s, "\n\r"); +#ifdef __MINGW32__ /* Treat a CRLF as a single line-ending: */ + if (s[length] == '\r' && s[length + 1] == '\n') + { + length++; + } +#endif + /* [\n\r] followed by EOF */ + return s[length] && !s[length+1]; +} + +static char *CfReadFile(const char *filename, size_t maxsize) +{ + /* TODO remove this stat() call, it's a remnant from old code + that examined sb.st_size. */ + struct stat sb; + if (stat(filename, &sb) == -1) + { + if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON) + { + Log(LOG_LEVEL_ERR, "Could not examine file '%s'", filename); + } + else + { + if (IsCf3VarString(filename)) + { + Log(LOG_LEVEL_VERBOSE, "Cannot converge/reduce variable '%s' yet .. assuming it will resolve later", + filename); + } + else + { + Log(LOG_LEVEL_ERR, "CfReadFile: Could not examine file '%s' (stat: %s)", + filename, GetErrorStr()); + } + } + return NULL; + } + + /* 0 means 'read until the end of file' */ + size_t limit = maxsize > 0 ? maxsize : SIZE_MAX; + bool truncated = false; + Writer *w = NULL; + int fd = safe_open(filename, O_RDONLY | O_TEXT); + if (fd >= 0) + { + w = FileReadFromFd(fd, limit, &truncated); + close(fd); + } + + if (!w) + { + Log(LOG_LEVEL_ERR, "CfReadFile: Error while reading file '%s' (%s)", + filename, GetErrorStr()); + return NULL; + } + + if (truncated) + { + Log(LOG_LEVEL_VERBOSE, "CfReadFile: Truncating file '%s' to %zu bytes as " + "requested by maxsize parameter", filename, maxsize); + } + + size_t size = StringWriterLength(w); + char *result = StringWriterClose(w); + + /* FIXME: Is it necessary here? Move to caller(s) */ + if (SingleLine(result)) + { + StripTrailingNewline(result, size); + } + return result; +} + +/*********************************************************************/ + +static char *StripPatterns(char *file_buffer, const char *pattern, const char *filename) +{ + if (NULL_OR_EMPTY(pattern)) + { + return file_buffer; + } + + Regex *rx = CompileRegex(pattern); + if (!rx) + { + return file_buffer; + } + + size_t start, end, count = 0; + const size_t original_length = strlen(file_buffer); + while (StringMatchWithPrecompiledRegex(rx, file_buffer, &start, &end)) + { + StringCloseHole(file_buffer, start, end); + + if (start == end) + { + Log(LOG_LEVEL_WARNING, + "Comment regex '%s' matched empty string in '%s'", + pattern, + filename); + break; + } + assert(start < end); + if (count++ > original_length) + { + debug_abort_if_reached(); + Log(LOG_LEVEL_ERR, + "Comment regex '%s' was irreconcilable reading input '%s' probably because it legally matches nothing", + pattern, filename); + break; + } + } + + RegexDestroy(rx); + return file_buffer; +} + +/*********************************************************************/ + +static JsonElement* BuildData(ARG_UNUSED EvalContext *ctx, const char *file_buffer, const char *split, int maxent, bool make_array) +{ + JsonElement *ret = make_array ? JsonArrayCreate(10) : JsonObjectCreate(10); + Seq *lines = SeqStringFromString(file_buffer, '\n'); + + char *line; + int hcount = 0; + + for (size_t i = 0; i < SeqLength(lines) && hcount < maxent; i++) + { + line = (char*) SeqAt(lines, i); + size_t line_len = strlen(line); + + if (line_len == 0 || (line_len == 1 && line[0] == '\r')) + { + continue; + } + + if (line[line_len - 1] == '\r') + { + line[line_len - 1] = '\0'; + } + + Rlist *tokens = RlistFromSplitRegex(line, split, 99999, true); + JsonElement *linearray = JsonArrayCreate(10); + + for (const Rlist *rp = tokens; rp; rp = rp->next) + { + const char *token = RlistScalarValue(rp); + JsonArrayAppendString(linearray, token); + } + + RlistDestroy(tokens); + + if (JsonLength(linearray) > 0) + { + if (make_array) + { + JsonArrayAppendArray(ret, linearray); + } + else + { + char *key = xstrdup(JsonArrayGetAsString(linearray, 0)); + JsonArrayRemoveRange(linearray, 0, 0); + JsonObjectAppendArray(ret, key, linearray); + free(key); + } + + // only increase hcount if we actually got something + hcount++; + } + } + + SeqDestroy(lines); + + return ret; +} + +/*********************************************************************/ + +static int BuildLineArray(EvalContext *ctx, const Bundle *bundle, + const char *array_lval, const char *file_buffer, + const char *split, int maxent, DataType type, + bool int_index) +{ + Rlist *lines = RlistFromSplitString(file_buffer, '\n'); + int hcount = 0; + + for (Rlist *it = lines; it && hcount < maxent; it = it->next) + { + char *line = RlistScalarValue(it); + size_t line_len = strlen(line); + + if (line_len == 0 || (line_len == 1 && line[0] == '\r')) + { + continue; + } + + if (line[line_len - 1] == '\r') + { + line[line_len - 1] = '\0'; + } + + char* first_index = NULL; + int vcount = 0; + + Rlist *tokens = RlistFromSplitRegex(line, split, 99999, true); + + for (const Rlist *rp = tokens; rp; rp = rp->next) + { + const char *token = RlistScalarValue(rp); + char *converted = NULL; + + switch (type) + { + case CF_DATA_TYPE_STRING: + converted = xstrdup(token); + break; + + case CF_DATA_TYPE_INT: + { + long value = IntFromString(token); + if (value == CF_NOINT) + { + FatalError(ctx, "Could not convert token to int"); + } + converted = StringFormat("%ld", value); + } + break; + + case CF_DATA_TYPE_REAL: + { + double real_value = 0; + if (!DoubleFromString(token, &real_value)) + { + FatalError(ctx, "Could not convert token to double"); + } + converted = xstrdup(token); + } + break; + + default: + ProgrammingError("Unhandled type in switch: %d", type); + } + + if (first_index == NULL) + { + first_index = xstrdup(converted); + } + + char *name; + if (int_index) + { + xasprintf(&name, "%s[%d][%d]", array_lval, hcount, vcount); + } + else + { + xasprintf(&name, "%s[%s][%d]", array_lval, first_index, vcount); + } + + VarRef *ref = VarRefParseFromBundle(name, bundle); + EvalContextVariablePut(ctx, ref, converted, type, "source=function,function=buildlinearray"); + VarRefDestroy(ref); + + free(name); + free(converted); + + vcount++; + } + + free(first_index); + RlistDestroy(tokens); + + hcount++; + line++; + } + + RlistDestroy(lines); + + return hcount; +} + +/*********************************************************************/ + +static FnCallResult FnCallProcessExists(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + char *regex = RlistScalarValue(finalargs); + + const bool is_context_processexists = strcmp(fp->name, "processexists") == 0; + + if (!LoadProcessTable()) + { + Log(LOG_LEVEL_ERR, "%s: could not load the process table?!?!", fp->name); + return FnFailure(); + } + + ProcessSelect ps = PROCESS_SELECT_INIT; + ps.owner = NULL; + ps.process_result = ""; + + // ps is unused because attrselect = false below + Item *matched = SelectProcesses(regex, &ps, false); + ClearProcessTable(); + + if (is_context_processexists) + { + const bool ret = (matched != NULL); + DeleteItemList(matched); + return FnReturnContext(ret); + } + + JsonElement *json = JsonArrayCreate(50); + // we're in process gathering mode + for (Item *ip = matched; ip != NULL; ip = ip->next) + { + // we only have the ps line and PID + + // TODO: this properly, by including more properties of the + // processes, when the underlying code stops using a plain + // ItemList + JsonElement *pobj = JsonObjectCreate(2); + JsonObjectAppendString(pobj, "line", ip->name); + JsonObjectAppendInteger(pobj, "pid", ip->counter); + + JsonArrayAppendObject(json, pobj); + } + DeleteItemList(matched); + + return FnReturnContainerNoCopy(json); +} + +/*********************************************************************/ + +static FnCallResult FnCallNetworkConnections(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs) +{ + JsonElement *json = GetNetworkingConnections(ctx); + + if (json == NULL) + { + // nothing was collected, this is a failure + return FnFailure(); + } + + return FnReturnContainerNoCopy(json); +} + +/*********************************************************************/ + +struct IsReadableThreadData +{ + pthread_t thread; + pthread_cond_t cond; + pthread_mutex_t mutex; + const char *path; + FnCallResult result; +}; + +static void *IsReadableThreadRoutine(void *data) +{ + assert(data != NULL); + + struct IsReadableThreadData *const thread_data = data; + + // Give main thread time to call pthread_cond_timedwait(3) + int ret = pthread_mutex_lock(&thread_data->mutex); + if (ret != 0) + { + ProgrammingError("Failed to lock mutex: %s", + GetErrorStrFromCode(ret)); + } + + thread_data->result = FnReturnContext(false); + + // Allow main thread to require lock on pthread_cond_timedwait(3) + ret = pthread_mutex_unlock(&thread_data->mutex); + if (ret != 0) + { + ProgrammingError("Failed to unlock mutex: %s", + GetErrorStrFromCode(ret)); + } + + char buf[1]; + const int fd = open(thread_data->path, O_RDONLY); + if (fd < 0) { + Log(LOG_LEVEL_DEBUG, "Failed to open file '%s': %s", + thread_data->path, GetErrorStr()); + } + else if (read(fd, buf, sizeof(buf)) < 0) + { + Log(LOG_LEVEL_DEBUG, "Failed to read from file '%s': %s", + thread_data->path, GetErrorStr()); + close(fd); + } + else + { + close(fd); + thread_data->result = FnReturnContext(true); + } + + ret = pthread_cond_signal(&(thread_data->cond)); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to signal waiting thread: %s", + GetErrorStrFromCode(ret)); + } + + return NULL; +} + +static FnCallResult FnCallIsReadable(ARG_UNUSED EvalContext *const ctx, + ARG_UNUSED const Policy *const policy, + const FnCall *const fp, + const Rlist *finalargs) +{ + assert(fp != NULL); + assert(fp->name != NULL); + + if (finalargs == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s requires path as first argument", + fp->name); + return FnFailure(); + } + const char *const path = RlistScalarValue(finalargs); + + long timeout = (finalargs->next == NULL) ? 3L /* default timeout */ + : IntFromString(RlistScalarValue(finalargs->next)); + assert(timeout >= 0); + + if (timeout == 0) // Try read in main thread, possibly blocking forever + { + Log(LOG_LEVEL_DEBUG, + "Checking if file '%s' is readable in main thread, " + "possibly blocking forever.", path); + + char buf[1]; + const int fd = open(path, O_RDONLY); + if (fd < 0) + { + Log(LOG_LEVEL_DEBUG, "Failed to open file '%s': %s", path, + GetErrorStr()); + return FnReturnContext(false); + } + else if (read(fd, buf, sizeof(buf)) < 0) + { + Log(LOG_LEVEL_DEBUG, "Failed to read from file '%s': %s", path, + GetErrorStr()); + close(fd); + return FnReturnContext(false); + } + + close(fd); + return FnReturnContext(true); + } + + // Else try read in separate thread, possibly blocking for N seconds + Log(LOG_LEVEL_DEBUG, + "Checking if file '%s' is readable in separate thread, " + "possibly blocking for %ld seconds.", path, timeout); + + struct IsReadableThreadData thread_data = {0}; + thread_data.path = path; + + int ret = pthread_mutex_init(&thread_data.mutex, NULL); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to initialize mutex: %s", + GetErrorStrFromCode(ret)); + return FnFailure(); + } + + ret = pthread_cond_init(&thread_data.cond, NULL); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to initialize condition: %s", + GetErrorStrFromCode(ret)); + return FnFailure(); + } + + // Keep thread from doing its thing until we call + // pthread_cond_timedwait(3) + ret = pthread_mutex_lock(&thread_data.mutex); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to lock mutex: %s", + GetErrorStrFromCode(ret)); + return FnFailure(); + } + + ret = pthread_create(&thread_data.thread, NULL, &IsReadableThreadRoutine, + &thread_data); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to create thread: %s", + GetErrorStrFromCode(ret)); + return FnFailure(); + } + + FnCallResult result; + // Wait on thread to finish or timeout + ret = ThreadWait(&thread_data.cond, &thread_data.mutex, timeout); + switch (ret) + { + case 0: // Thread finished in time + result = thread_data.result; + break; + + case ETIMEDOUT: // Thread timed out + Log(LOG_LEVEL_DEBUG, "File '%s' is not readable: " + "Read operation timed out, exceeded %ld seconds.", path, + timeout); + + ret = pthread_cancel(thread_data.thread); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to cancel thread"); + return FnFailure(); + } + + result = FnReturnContext(false); + break; + + default: + Log(LOG_LEVEL_ERR, "Failed to wait for condition: %s", + GetErrorStrFromCode(ret)); + return FnFailure(); + + } + + ret = pthread_mutex_unlock(&thread_data.mutex); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to unlock mutex"); + return FnFailure(); + } + + void *status; + ret = pthread_join(thread_data.thread, &status); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Failed to join thread"); + return FnFailure(); + } + + if (status == PTHREAD_CANCELED) + { + Log(LOG_LEVEL_DEBUG, "Thread was canceled"); + } + + return result; +} + +/*********************************************************************/ + +static FnCallResult FnCallFindfilesUp(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(fp != NULL); + assert(fp->name != NULL); + + const Rlist *const path_arg = finalargs; + if (path_arg == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s requires path as first argument", + fp->name); + return FnFailure(); + } + + const Rlist *const glob_arg = finalargs->next; + if (glob_arg == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s requires glob as second argument", + fp->name); + return FnFailure(); + } + + const Rlist *const level_arg = finalargs->next->next; + + char path[PATH_MAX]; + size_t copied = strlcpy(path, RlistScalarValue(path_arg), sizeof(path)); + if (copied >= sizeof(path)) + { + Log(LOG_LEVEL_ERR, "Function %s, path is too long (%zu > %zu)", + fp->name, copied, sizeof(path)); + return FnFailure(); + } + + if (!IsAbsoluteFileName(path)) + { + Log(LOG_LEVEL_ERR, "Function %s, not an absolute path '%s'", + fp->name, path); + return FnFailure(); + } + + if (!IsDir(path)) + { + Log(LOG_LEVEL_ERR, "Function %s, path '%s' is not a directory", + fp->name, path); + return FnFailure(); + } + + MapName(path); // Makes sure we get host native path separators + DeleteRedundantSlashes(path); + + size_t len = strlen(path); + if (path[len - 1] == FILE_SEPARATOR) + { + path[len - 1] = '\0'; + } + + char glob[PATH_MAX]; + copied = strlcpy(glob, RlistScalarValue(glob_arg), sizeof(glob)); + if (copied >= sizeof(glob)) + { + Log(LOG_LEVEL_ERR, "Function %s, glob is too long (%zu > %zu)", + fp->name, copied, sizeof(glob)); + return FnFailure(); + } + + if (IsAbsoluteFileName(glob)) + { + Log(LOG_LEVEL_ERR, + "Function %s, glob '%s' cannot be an absolute path", fp->name, + glob); + return FnFailure(); + } + + DeleteRedundantSlashes(glob); + + /* level defaults to inf */ + long level = IntFromString("inf"); + if (level_arg != NULL) + { + level = IntFromString(RlistScalarValue(level_arg)); + } + + JsonElement *json = JsonArrayCreate(1); + + while (level-- >= 0) + { + char filepath[PATH_MAX]; + copied = strlcpy(filepath, path, sizeof(filepath)); + assert(copied < sizeof(path)); + if (JoinPaths(filepath, sizeof(filepath), glob) == NULL) + { + Log(LOG_LEVEL_ERR, + "Function %s, failed to join path '%s' and glob '%s'", + fp->name, path, glob); + } + + StringSet *matches = GlobFileList(filepath); + JsonElement *matches_json = StringSetToJson(matches); + JsonArrayExtend(json, matches_json); + StringSetDestroy(matches); + + char *sep = strrchr(path, FILE_SEPARATOR); + if (sep == NULL) + { + /* don't search beyond root directory */ + break; + } + *sep = '\0'; + } + + return FnReturnContainerNoCopy(json); +} + +/*********************************************************************/ + +static bool ExecModule(EvalContext *ctx, char *command) +{ + FILE *pp = cf_popen(command, "rt", true); + if (!pp) + { + Log(LOG_LEVEL_ERR, "Couldn't open pipe from '%s'. (cf_popen: %s)", command, GetErrorStr()); + return false; + } + + char context[CF_BUFSIZE] = ""; + StringSet *tags = StringSetNew(); + long persistence = 0; + + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + + while (CfReadLine(&line, &line_size, pp) != -1) + { + bool print = false; + for (const char *sp = line; *sp != '\0'; sp++) + { + if (!isspace((unsigned char) *sp)) + { + print = true; + break; + } + } + + ModuleProtocol(ctx, command, line, print, context, sizeof(context), tags, &persistence); + } + bool atend = feof(pp); + cf_pclose(pp); + free(line); + StringSetDestroy(tags); + + if (!atend) + { + Log(LOG_LEVEL_ERR, "Unable to read output from '%s'. (fread: %s)", command, GetErrorStr()); + return false; + } + + return true; +} + +void ModuleProtocol(EvalContext *ctx, const char *command, const char *line, int print, char* context, size_t context_size, StringSet *tags, long *persistence) +{ + assert(tags); + + StringSetAdd(tags, xstrdup("source=module")); + StringSetAddF(tags, "derived_from=%s", command); + + // see the sscanf() limit below + if(context_size < 51) + { + ProgrammingError("ModuleProtocol: context_size too small (%zu)", + context_size); + } + + if (*context == '\0') + { + /* Infer namespace from script name */ + char arg0[CF_BUFSIZE]; + strlcpy(arg0, CommandArg0(command), CF_BUFSIZE); + char *filename = basename(arg0); + + /* Canonicalize filename into acceptable namespace name */ + CanonifyNameInPlace(filename); + strlcpy(context, filename, context_size); + Log(LOG_LEVEL_VERBOSE, "Module context '%s'", context); + } + + char name[CF_BUFSIZE], content[CF_BUFSIZE]; + name[0] = content[0] = '\0'; + + size_t length = strlen(line); + switch (*line) + { + case '^': + // Allow modules to set their variable context (up to 50 characters) + if (sscanf(line + 1, "context=%50[^\n]", content) == 1 && + content[0] != '\0') + { + /* Symbol ID without \200 to \377: */ + Regex *context_name_rx = CompileRegex("[a-zA-Z0-9_]+"); + if (!context_name_rx) + { + Log(LOG_LEVEL_ERR, + "Internal error compiling module protocol context regex, aborting!!!"); + } + else if (StringMatchFullWithPrecompiledRegex(context_name_rx, content)) + { + Log(LOG_LEVEL_VERBOSE, "Module changed variable context from '%s' to '%s'", context, content); + strlcpy(context, content, context_size); + } + else + { + Log(LOG_LEVEL_ERR, + "Module protocol was given an unacceptable ^context directive '%s', skipping", content); + } + + if (context_name_rx) + { + RegexDestroy(context_name_rx); + } + } + else if (sscanf(line + 1, "meta=%1024[^\n]", content) == 1 && + content[0] != '\0') + { + Log(LOG_LEVEL_VERBOSE, "Module set meta tags to '%s'", content); + StringSetClear(tags); + + StringSetAddSplit(tags, content, ','); + StringSetAdd(tags, xstrdup("source=module")); + StringSetAddF(tags, "derived_from=%s", command); + } + else if (sscanf(line + 1, "persistence=%ld", persistence) == 1) + { + Log(LOG_LEVEL_VERBOSE, "Module set persistence to %ld minutes", *persistence); + } + else + { + Log(LOG_LEVEL_INFO, "Unknown extended module command '%s'", line); + } + break; + + case '+': + if (length > CF_MAXVARSIZE) + { + Log(LOG_LEVEL_ERR, + "Module protocol was given an overlong +class line (%zu bytes), skipping", + length); + break; + } + + // the class name will fit safely inside CF_MAXVARSIZE - 1 bytes + sscanf(line + 1, "%1023[^\n]", content); + Log(LOG_LEVEL_VERBOSE, "Activating classes from module protocol: '%s'", content); + if (CheckID(content)) + { + Buffer *tagbuf = StringSetToBuffer(tags, ','); + EvalContextClassPutSoft(ctx, content, CONTEXT_SCOPE_NAMESPACE, BufferData(tagbuf)); + if (*persistence > 0) + { + Log(LOG_LEVEL_VERBOSE, "Module set persistent class '%s' for %ld minutes", content, *persistence); + EvalContextHeapPersistentSave(ctx, content, *persistence, CONTEXT_STATE_POLICY_PRESERVE, BufferData(tagbuf)); + } + + BufferDestroy(tagbuf); + } + else + { + Log(LOG_LEVEL_VERBOSE, "Automatically canonifying '%s'", content); + CanonifyNameInPlace(content); + Log(LOG_LEVEL_VERBOSE, "Automatically canonified to '%s'", content); + Buffer *tagbuf = StringSetToBuffer(tags, ','); + EvalContextClassPutSoft(ctx, content, CONTEXT_SCOPE_NAMESPACE, BufferData(tagbuf)); + BufferDestroy(tagbuf); + } + break; + case '-': + if (length > CF_MAXVARSIZE) + { + Log(LOG_LEVEL_ERR, + "Module protocol was given an overlong -class line (%zu bytes), skipping", + length); + break; + } + + // the class name(s) will fit safely inside CF_MAXVARSIZE - 1 bytes + sscanf(line + 1, "%1023[^\n]", content); + Log(LOG_LEVEL_VERBOSE, "Deactivating classes from module protocol: '%s'", content); + if (CheckID(content)) + { + if (content[0] != '\0') + { + StringSet *negated = StringSetFromString(content, ','); + StringSetIterator it = StringSetIteratorInit(negated); + const char *negated_context = NULL; + while ((negated_context = StringSetIteratorNext(&it))) + { + Class *cls = EvalContextClassGet(ctx, NULL, negated_context); + if (cls && !cls->is_soft) + { + FatalError(ctx, "Cannot negate the reserved class '%s'", negated_context); + } + + ClassRef ref = ClassRefParse(negated_context); + EvalContextClassRemove(ctx, ref.ns, ref.name); + ClassRefDestroy(ref); + } + StringSetDestroy(negated); + } + } + break; + case '=': + if (length > CF_BUFSIZE + 256) + { + Log(LOG_LEVEL_ERR, + "Module protocol was given an overlong variable =line (%zu bytes), skipping", + length); + break; + } + + // TODO: the variable name is limited to 256 to accommodate the + // context name once it's in the vartable. Maybe this can be relaxed. + sscanf(line + 1, "%256[^=]=%4095[^\n]", name, content); + + if (CheckID(name)) + { + Log(LOG_LEVEL_VERBOSE, "Defined variable '%s' in context '%s' with value '%s'", name, context, content); + VarRef *ref = VarRefParseFromScope(name, context); + + Buffer *tagbuf = StringSetToBuffer(tags, ','); + EvalContextVariablePut(ctx, ref, content, CF_DATA_TYPE_STRING, BufferData(tagbuf)); + BufferDestroy(tagbuf); + + VarRefDestroy(ref); + } + break; + + case '&': + // TODO: the variable name is limited to 256 to accommodate the + // context name once it's in the vartable. Maybe this can be relaxed. + // &policy_varname=/path/to/file can be json/env/yaml/csv type, default: json + sscanf(line + 1, "%256[^=]=%4095[^\n]", name, content); + + if (CheckID(name)) + { + if (FileCanOpen(content, "r")) + { + const int size_max = IntFromString("inf"); + const DataFileType requested_mode = GetDataFileTypeFromSuffix(content); + + Log(LOG_LEVEL_DEBUG, "Module protocol parsing %s file '%s'", + DataFileTypeToString(requested_mode), content); + + JsonElement *json = JsonReadDataFile("module file protocol", content, requested_mode, size_max); + if (json != NULL) + { + Buffer *tagbuf = StringSetToBuffer(tags, ','); + VarRef *ref = VarRefParseFromScope(name, context); + + EvalContextVariablePut(ctx, ref, json, CF_DATA_TYPE_CONTAINER, BufferData(tagbuf)); + VarRefDestroy(ref); + BufferDestroy(tagbuf); + JsonDestroy(json); + } + else + { + // reading / parsing failed, error message printed by JsonReadDataFile, + // variable will not be created + } + } + else + { + Log(LOG_LEVEL_ERR, "could not load module file '%s'", content); + } + } + break; + + case '%': + // TODO: the variable name is limited to 256 to accommodate the + // context name once it's in the vartable. Maybe this can be relaxed. + sscanf(line + 1, "%256[^=]=", name); + + if (CheckID(name)) + { + JsonElement *json = NULL; + Buffer *holder = BufferNewFrom(line+strlen(name)+1+1, + length - strlen(name) - 1 - 1); + const char *hold = BufferData(holder); + Log(LOG_LEVEL_DEBUG, "Module protocol parsing JSON %s", content); + + JsonParseError res = JsonParse(&hold, &json); + if (res != JSON_PARSE_OK) + { + Log(LOG_LEVEL_INFO, + "Failed to parse JSON '%s' for module protocol: %s", + content, JsonParseErrorToString(res)); + } + else + { + if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + Log(LOG_LEVEL_INFO, + "Module protocol JSON '%s' should be object or array; wasn't", + content); + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Defined data container variable '%s' in context '%s' with value '%s'", + name, context, BufferData(holder)); + + Buffer *tagbuf = StringSetToBuffer(tags, ','); + VarRef *ref = VarRefParseFromScope(name, context); + + EvalContextVariablePut(ctx, ref, json, CF_DATA_TYPE_CONTAINER, BufferData(tagbuf)); + VarRefDestroy(ref); + BufferDestroy(tagbuf); + } + + JsonDestroy(json); + } + + BufferDestroy(holder); + } + break; + + case '@': + // TODO: should not need to exist. entry size matters, not line size. bufferize module protocol + if (length > CF_BUFSIZE + 256 - 1) + { + Log(LOG_LEVEL_ERR, + "Module protocol was given an overlong variable @line (%zu bytes), skipping", + length); + break; + } + + sscanf(line + 1, "%256[^=]=%4095[^\n]", name, content); + + if (CheckID(name)) + { + Rlist *list = RlistParseString(content); + if (!list) + { + Log(LOG_LEVEL_ERR, "Module protocol could not parse variable %s's data content %s", name, content); + } + else + { + bool has_oversize_entry = false; + for (const Rlist *rp = list; rp; rp = rp->next) + { + size_t entry_size = strlen(RlistScalarValue(rp)); + if (entry_size > CF_MAXVARSIZE) + { + has_oversize_entry = true; + break; + } + } + + if (has_oversize_entry) + { + Log(LOG_LEVEL_ERR, "Module protocol was given a variable @ line with an oversize entry, skipping"); + RlistDestroy(list); + break; + } + + Log(LOG_LEVEL_VERBOSE, "Defined variable '%s' in context '%s' with value '%s'", name, context, content); + + VarRef *ref = VarRefParseFromScope(name, context); + + Buffer *tagbuf = StringSetToBuffer(tags, ','); + EvalContextVariablePut(ctx, ref, list, CF_DATA_TYPE_STRING_LIST, BufferData(tagbuf)); + BufferDestroy(tagbuf); + + VarRefDestroy(ref); + RlistDestroy(list); + } + } + break; + + case '\0': + break; + + default: + if (print) + { + Log(LOG_LEVEL_INFO, "M '%s': %s", command, line); + } + break; + } +} + +/*********************************************************************/ + +static FnCallResult FnCallCFEngineCallers(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, ARG_UNUSED const Rlist *finalargs) +{ + bool promisersmode = (strcmp(fp->name, "callstack_promisers") == 0); + + if (promisersmode) + { + Rlist *returnlist = EvalContextGetPromiseCallerMethods(ctx); + return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } }; + } + + JsonElement *callers = EvalContextGetPromiseCallers(ctx); + return FnReturnContainerNoCopy(callers); +} + +static FnCallResult FnCallString(EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *finalargs) +{ + assert(finalargs != NULL); + + char *ret = RvalToString(finalargs->val); + if (StringStartsWith(ret, "@(") || StringStartsWith(ret, "@{")) + { + bool allocated = false; + JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); + + if (json != NULL) + { + Writer *w = StringWriter(); + JsonWriteCompact(w, json); + ret = StringWriterClose(w); + if (allocated) + { + JsonDestroy(json); + } + } + } + + return FnReturnNoCopy(ret); +} + +/*********************************************************************/ +/* Level */ +/*********************************************************************/ + +static bool CheckIDChar(const char ch) +{ + return isalnum((int) ch) || (ch == '.') || (ch == '-') || (ch == '_') || + (ch == '[') || (ch == ']') || (ch == '/') || + (ch == '@'); +} + +static bool CheckID(const char *id) +{ + for (const char *sp = id; *sp != '\0'; sp++) + { + if (!CheckIDChar(*sp)) + { + Log(LOG_LEVEL_VERBOSE, + "Module protocol contained an illegal character '%c' in class/variable identifier '%s'.", *sp, + id); + } + } + + return true; +} + +/*********************************************************/ +/* Function prototypes */ +/*********************************************************/ + +static const FnCallArg ACCESSEDBEFORE_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Newer filename"}, + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Older filename"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg ACCUM_ARGS[] = +{ + {"0,1000", CF_DATA_TYPE_INT, "Years"}, + {"0,1000", CF_DATA_TYPE_INT, "Months"}, + {"0,1000", CF_DATA_TYPE_INT, "Days"}, + {"0,1000", CF_DATA_TYPE_INT, "Hours"}, + {"0,1000", CF_DATA_TYPE_INT, "Minutes"}, + {"0,40000", CF_DATA_TYPE_INT, "Seconds"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg AND_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg AGO_ARGS[] = +{ + {"0,1000", CF_DATA_TYPE_INT, "Years"}, + {"0,1000", CF_DATA_TYPE_INT, "Months"}, + {"0,1000", CF_DATA_TYPE_INT, "Days"}, + {"0,1000", CF_DATA_TYPE_INT, "Hours"}, + {"0,1000", CF_DATA_TYPE_INT, "Minutes"}, + {"0,40000", CF_DATA_TYPE_INT, "Seconds"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg LATERTHAN_ARGS[] = +{ + {"0,10000", CF_DATA_TYPE_INT, "Years"}, + {"0,1000", CF_DATA_TYPE_INT, "Months"}, + {"0,1000", CF_DATA_TYPE_INT, "Days"}, + {"0,1000", CF_DATA_TYPE_INT, "Hours"}, + {"0,1000", CF_DATA_TYPE_INT, "Minutes"}, + {"0,40000", CF_DATA_TYPE_INT, "Seconds"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg BASENAME_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "File path"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Optional suffix"}, + {NULL, CF_DATA_TYPE_NONE, NULL}, +}; + +static const FnCallArg DATE_ARGS[] = /* for on() */ +{ + {"1970,3000", CF_DATA_TYPE_INT, "Year"}, + {"0,1000", CF_DATA_TYPE_INT, "Month (January = 0)"}, + {"0,1000", CF_DATA_TYPE_INT, "Day (First day of month = 0)"}, + {"0,1000", CF_DATA_TYPE_INT, "Hour"}, + {"0,1000", CF_DATA_TYPE_INT, "Minute"}, + {"0,1000", CF_DATA_TYPE_INT, "Second"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CANONIFY_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String containing non-identifier characters"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CHANGEDBEFORE_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Newer filename"}, + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Older filename"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CFVERSION_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine version number to compare against"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CFVERSIONBETWEEN_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Lower CFEngine version number to compare against"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Upper CFEngine version number to compare against"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg VERSION_COMPARE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "First version to compare"}, + {"=,==,!=,>,<,>=,<=", CF_DATA_TYPE_OPTION, "Operator to use in comparison"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Second version to compare"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CLASSIFY_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CLASSMATCH_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CONCAT_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg COUNTLINESMATCHING_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Filename"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg DIRNAME_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "File path"}, + {NULL, CF_DATA_TYPE_NONE, NULL}, +}; + +static const FnCallArg DISKFREE_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File system directory"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg ESCAPE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "IP address or string to escape"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg EXECRESULT_ARGS[] = +{ + {CF_PATHRANGE, CF_DATA_TYPE_STRING, "Fully qualified command path"}, + {"noshell,useshell,powershell", CF_DATA_TYPE_OPTION, "Shell encapsulation option"}, + {"both,stdout,stderr", CF_DATA_TYPE_OPTION, "Which output to return; stdout or stderr"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +// fileexists, isdir,isplain,islink + +static const FnCallArg FILESTAT_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File object name"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg FILESTAT_DETAIL_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File object name"}, + {"size,gid,uid,ino,nlink,ctime,atime,mtime,xattr,mode,modeoct,permstr,permoct,type,devno,dev_minor,dev_major,basename,dirname,linktarget,linktarget_shallow", CF_DATA_TYPE_OPTION, "stat() field to get"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg FILESEXIST_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg FINDFILES_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg FILTER_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression or string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {CF_BOOL, CF_DATA_TYPE_OPTION, "Match as regular expression if true, as exact string otherwise"}, + {CF_BOOL, CF_DATA_TYPE_OPTION, "Invert matches"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of matches to return"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETFIELDS_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression to match line"}, + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Filename to read"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression to split fields"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Return array name"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETINDICES_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETUSERS_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comma separated list of User names"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comma separated list of UserID numbers"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETENV_ARGS[] = +{ + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Name of environment variable"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of characters to read "}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETGID_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Group name in text"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETUID_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "User name in text"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETUSERINFO_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "User name in text"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GREP_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GROUPEXISTS_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Group name or identifier"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HASH_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input text"}, + {"md5,sha1,sha256,sha384,sha512", CF_DATA_TYPE_OPTION, "Hash or digest algorithm"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg FILE_HASH_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File object name"}, + {"md5,sha1,sha256,sha384,sha512", CF_DATA_TYPE_OPTION, "Hash or digest algorithm"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HASHMATCH_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Filename to hash"}, + {"md5,sha1,sha256,sha384,sha512", CF_DATA_TYPE_OPTION, "Hash or digest algorithm"}, + {CF_IDRANGE, CF_DATA_TYPE_STRING, "ASCII representation of hash for comparison"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HOST2IP_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Host name in ascii"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg IP2HOST_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "IP address (IPv4 or IPv6)"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HOSTINNETGROUP_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Netgroup name"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HOSTRANGE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Hostname prefix"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Enumerated range"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HOSTSSEEN_ARGS[] = +{ + {CF_VALRANGE, CF_DATA_TYPE_INT, "Horizon since last seen in hours"}, + {"lastseen,notseen", CF_DATA_TYPE_OPTION, "Complements for selection policy"}, + {"name,address,hostkey", CF_DATA_TYPE_OPTION, "Type of return value desired"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HOSTSWITHCLASS_ARGS[] = +{ + {"[a-zA-Z0-9_]+", CF_DATA_TYPE_STRING, "Class name to look for"}, + {"name,address", CF_DATA_TYPE_OPTION, "Type of return value desired"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg IFELSE_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg INT_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Numeric string to convert to integer"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg IPRANGE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "IP address range syntax"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg IRANGE_ARGS[] = +{ + {CF_INTRANGE, CF_DATA_TYPE_INT, "Integer start of range"}, + {CF_INTRANGE, CF_DATA_TYPE_INT, "Integer end of range"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg ISGREATERTHAN_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Larger string or value"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Smaller string or value"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg ISLESSTHAN_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Smaller string or value"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Larger string or value"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg ISNEWERTHAN_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Newer file name"}, + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Older file name"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg ISVARIABLE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Variable identifier"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg JOIN_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Join glue-string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg LASTNODE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Link separator, e.g. /,:"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg LDAPARRAY_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Array name"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URI"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Distinguished name"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter"}, + {"subtree,onelevel,base", CF_DATA_TYPE_OPTION, "Search scope policy"}, + {"none,ssl,sasl", CF_DATA_TYPE_OPTION, "Security level"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg LDAPLIST_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URI"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Distinguished name"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Record name"}, + {"subtree,onelevel,base", CF_DATA_TYPE_OPTION, "Search scope policy"}, + {"none,ssl,sasl", CF_DATA_TYPE_OPTION, "Security level"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg LDAPVALUE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URI"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Distinguished name"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Record name"}, + {"subtree,onelevel,base", CF_DATA_TYPE_OPTION, "Search scope policy"}, + {"none,ssl,sasl", CF_DATA_TYPE_OPTION, "Security level"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg LSDIRLIST_ARGS[] = +{ + {CF_PATHRANGE, CF_DATA_TYPE_STRING, "Path to base directory"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression to match files or blank"}, + {CF_BOOL, CF_DATA_TYPE_OPTION, "Include the base path in the list"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg MAPLIST_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Pattern based on $(this) as original text"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg EXPANDRANGE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String containing numerical range e.g. string[13-47]"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Step size of numerical increments"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg MAPARRAY_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Pattern based on $(this.k) and $(this.v) as original text"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON, the array variable to map"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg MAPDATA_ARGS[] = +{ + {"none,canonify,json,json_pipe", CF_DATA_TYPE_OPTION, "Conversion to apply to the mapped string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Pattern based on $(this.k) and $(this.v) as original text"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg MERGEDATA_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg NOT_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Class value"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg NOW_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg OR_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SUM_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg PRODUCT_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg PACKAGESMATCHING_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression (unanchored) to match package name"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression (unanchored) to match package version"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression (unanchored) to match package architecture"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression (unanchored) to match package method"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg PEERS_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name of host list"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comment regex pattern"}, + {"2,64", CF_DATA_TYPE_INT, "Peer group size"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg PEERLEADER_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name of host list"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comment regex pattern"}, + {"2,64", CF_DATA_TYPE_INT, "Peer group size"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg PEERLEADERS_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name of host list"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comment regex pattern"}, + {"2,64", CF_DATA_TYPE_INT, "Peer group size"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg RANDOMINT_ARGS[] = +{ + {CF_INTRANGE, CF_DATA_TYPE_INT, "Lower inclusive bound"}, + {CF_INTRANGE, CF_DATA_TYPE_INT, "Upper exclusive bound"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HASH_TO_INT_ARGS[] = +{ + {CF_INTRANGE, CF_DATA_TYPE_INT, "Lower inclusive bound"}, + {CF_INTRANGE, CF_DATA_TYPE_INT, "Upper exclusive bound"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string to hash"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg STRING_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Convert argument to string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg READFILE_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of bytes to read"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg VALIDDATATYPE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String to validate as JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CLASSFILTERCSV_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name"}, + {CF_BOOL, CF_DATA_TYPE_OPTION, "CSV file has heading"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Column index to filter by, " + "contains classes"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Column index to sort by"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg READSTRINGARRAY_ARGS[] = +{ + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Array identifier to populate"}, + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex matching comments"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split data"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of entries to read"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum bytes to read"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg PARSESTRINGARRAY_ARGS[] = +{ + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Array identifier to populate"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A string to parse for input data"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex matching comments"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split data"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of entries to read"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum bytes to read"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg READSTRINGLIST_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex matching comments"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split data"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of entries to read"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum bytes to read"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg READDATA_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read"}, + {"CSV,YAML,JSON,ENV,auto", CF_DATA_TYPE_OPTION, "Type of data to read"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg VALIDDATA_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String to validate as JSON"}, + {"JSON", CF_DATA_TYPE_OPTION, "Type of data to validate"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg READMODULE_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read and parse from"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg PARSEJSON_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "JSON string to parse"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg STOREJSON_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg READTCP_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Host name or IP address of server socket"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Port number or service name"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Protocol query string"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of bytes to read"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REGARRAY_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REGCMP_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Match string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REGEXTRACT_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Match string"}, + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Identifier for back-references"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg DATA_REGEXTRACT_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Match string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REGEX_REPLACE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression pattern"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Replacement string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "sed/Perl-style options: gmsixUT"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REGISTRYVALUE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Windows registry key"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Windows registry value-id"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REGLINE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filename to search"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REGLIST_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg MAKERULE_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Target filename"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source filename or CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REGLDAP_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URI"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Distinguished name"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Record name"}, + {"subtree,onelevel,base", CF_DATA_TYPE_OPTION, "Search scope policy"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to match results"}, + {"none,ssl,sasl", CF_DATA_TYPE_OPTION, "Security level"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REMOTESCALAR_ARGS[] = +{ + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Variable identifier"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Hostname or IP address of server"}, + {CF_BOOL, CF_DATA_TYPE_OPTION, "Use enryption"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg HUB_KNOWLEDGE_ARGS[] = +{ + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Variable identifier"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REMOTECLASSESMATCHING_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Server name or address"}, + {CF_BOOL, CF_DATA_TYPE_OPTION, "Use encryption"}, + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Return class prefix"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg RETURNSZERO_ARGS[] = +{ + {CF_PATHRANGE, CF_DATA_TYPE_STRING, "Command path"}, + {"noshell,useshell,powershell", CF_DATA_TYPE_OPTION, "Shell encapsulation option"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg RRANGE_ARGS[] = +{ + {CF_REALRANGE, CF_DATA_TYPE_REAL, "Real number, start of range"}, + {CF_REALRANGE, CF_DATA_TYPE_REAL, "Real number, end of range"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SELECTSERVERS_ARGS[] = +{ + {CF_NAKEDLRANGE, CF_DATA_TYPE_STRING, "CFEngine list identifier, the list of hosts or addresses to contact"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Port number or service name."}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A query string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A regular expression to match success"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of bytes to read from server"}, + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Name for array of results"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SPLAYCLASS_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string for classification"}, + {"daily,hourly", CF_DATA_TYPE_OPTION, "Splay time policy"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SPLITSTRING_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A data string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split on"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of pieces"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg STRCMP_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg STRFTIME_ARGS[] = +{ + {"gmtime,localtime", CF_DATA_TYPE_OPTION, "Use GMT or local time"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A format string"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "The time as a Unix epoch offset"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg STRING_REPLACE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String to replace"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Replacement string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg STRING_TRIM_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SUBLIST_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {"head,tail", CF_DATA_TYPE_OPTION, "Whether to return elements from the head or from the tail of the list"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of elements to return"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg TRANSLATEPATH_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Unix style path"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg USEMODULE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Name of module command"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Argument string for the module"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg UNIQUE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg NTH_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Offset or key of element to return"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg EVERY_SOME_NONE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression or string"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg USEREXISTS_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "User name or identifier"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SORT_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {"lex,int,real,IP,ip,MAC,mac", CF_DATA_TYPE_OPTION, "Sorting method: lex or int or real (floating point) or IPv4/IPv6 or MAC address"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg REVERSE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SHUFFLE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Any seed string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg STAT_FOLD_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SETOP_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine base variable identifier or inline JSON"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine filter variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg FORMAT_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine format string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg EVAL_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"}, + {"math,class", CF_DATA_TYPE_OPTION, "Evaluation type"}, + {"infix", CF_DATA_TYPE_OPTION, "Evaluation options"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg BUNDLESMATCHING_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg XFORM_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg XFORM_SUBSTR_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"}, + {CF_INTRANGE, CF_DATA_TYPE_INT, "Maximum number of characters to return"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg DATASTATE_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg BUNDLESTATE_ARGS[] = +{ + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Bundle name"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETCLASSMETATAGS_ARGS[] = +{ + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Class identifier"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg GETVARIABLEMETATAGS_ARGS[] = +{ + {CF_IDRANGE, CF_DATA_TYPE_STRING, "Variable identifier"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg DATA_READSTRINGARRAY_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex matching comments"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split data"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of entries to read"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum bytes to read"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg DATA_EXPAND_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg STRING_MUSTACHE_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CURL_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URL to retrieve"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON, can be blank"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg PROCESSEXISTS_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression to match process name"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg NETWORK_CONNECTIONS_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CFENGINE_PROMISERS_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg CFENGINE_CALLERS_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg SYSCTLVALUE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "sysctl key"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg DATA_SYSCTLVALUES_ARGS[] = +{ + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg FINDFILES_UP_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Path to search from"}, + {CF_PATHRANGE, CF_DATA_TYPE_STRING, "Glob pattern to match files"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Number of levels to search"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg ISREADABLE_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Path to file"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Timeout interval"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + +static const FnCallArg DATATYPE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Variable identifier"}, + {CF_BOOL, CF_DATA_TYPE_OPTION, "Enable detailed type decription"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + + +/*********************************************************/ +/* FnCalls are rvalues in certain promise constraints */ +/*********************************************************/ + +/* see fncall.h enum FnCallType */ + +const FnCallType CF_FNCALL_TYPES[] = +{ + FnCallTypeNew("accessedbefore", CF_DATA_TYPE_CONTEXT, ACCESSEDBEFORE_ARGS, &FnCallIsAccessedBefore, "True if arg1 was accessed before arg2 (atime)", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("accumulated", CF_DATA_TYPE_INT, ACCUM_ARGS, &FnCallAccumulatedDate, "Convert an accumulated amount of time into a system representation", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("ago", CF_DATA_TYPE_INT, AGO_ARGS, &FnCallAgoDate, "Convert a time relative to now to an integer system representation", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("and", CF_DATA_TYPE_CONTEXT, AND_ARGS, &FnCallAnd, "Calculate whether all arguments evaluate to true", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("basename", CF_DATA_TYPE_STRING, BASENAME_ARGS, &FnCallBasename, "Retrieves the basename of a filename.", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("bundlesmatching", CF_DATA_TYPE_STRING_LIST, BUNDLESMATCHING_ARGS, &FnCallBundlesMatching, "Find all the bundles that match a regular expression and tags.", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("bundlestate", CF_DATA_TYPE_CONTAINER, BUNDLESTATE_ARGS, &FnCallBundlestate, "Construct a container of the variables in a bundle and the global class state", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("canonify", CF_DATA_TYPE_STRING, CANONIFY_ARGS, &FnCallCanonify, "Convert an abitrary string into a legal class name", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("canonifyuniquely", CF_DATA_TYPE_STRING, CANONIFY_ARGS, &FnCallCanonify, "Convert an abitrary string into a unique legal class name", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("concat", CF_DATA_TYPE_STRING, CONCAT_ARGS, &FnCallConcat, "Concatenate all arguments into string", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("cf_version_minimum", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionMinimum, "True if local CFEngine version is newer than or equal to input", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("cf_version_after", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionAfter, "True if local CFEngine version is newer than input", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("cf_version_maximum", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionMaximum, "True if local CFEngine version is older than or equal to input", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("cf_version_before", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionBefore, "True if local CFEngine version is older than input", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("cf_version_at", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionAt, "True if local CFEngine version is the same as input", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("cf_version_between", CF_DATA_TYPE_CONTEXT, CFVERSIONBETWEEN_ARGS, &FnCallVersionBetween, "True if local CFEngine version is between two input versions (inclusive)", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("changedbefore", CF_DATA_TYPE_CONTEXT, CHANGEDBEFORE_ARGS, &FnCallIsChangedBefore, "True if arg1 was changed before arg2 (ctime)", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("classify", CF_DATA_TYPE_CONTEXT, CLASSIFY_ARGS, &FnCallClassify, "True if the canonicalization of the argument is a currently defined class", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("classmatch", CF_DATA_TYPE_CONTEXT, CLASSMATCH_ARGS, &FnCallClassesMatching, "True if the regular expression matches any currently defined class", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("classesmatching", CF_DATA_TYPE_STRING_LIST, CLASSMATCH_ARGS, &FnCallClassesMatching, "List the defined classes matching regex arg1 and tag regexes arg2,arg3,...", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("classfiltercsv", CF_DATA_TYPE_CONTAINER, CLASSFILTERCSV_ARGS, &FnCallClassFilterCsv, "Parse a CSV file and create data container filtered by defined classes", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("countclassesmatching", CF_DATA_TYPE_INT, CLASSMATCH_ARGS, &FnCallClassesMatching, "Count the number of defined classes matching regex arg1", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("countlinesmatching", CF_DATA_TYPE_INT, COUNTLINESMATCHING_ARGS, &FnCallCountLinesMatching, "Count the number of lines matching regex arg1 in file arg2", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("url_get", CF_DATA_TYPE_CONTAINER, CURL_ARGS, &FnCallUrlGet, "Retrieve the contents at a URL with libcurl", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("datastate", CF_DATA_TYPE_CONTAINER, DATASTATE_ARGS, &FnCallDatastate, "Construct a container of the variable and class state", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("difference", CF_DATA_TYPE_STRING_LIST, SETOP_ARGS, &FnCallSetop, "Returns all the unique elements of list or array or data container arg1 that are not in list or array or data container arg2", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("dirname", CF_DATA_TYPE_STRING, DIRNAME_ARGS, &FnCallDirname, "Return the parent directory name for given path", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("diskfree", CF_DATA_TYPE_INT, DISKFREE_ARGS, &FnCallDiskFree, "Return the free space (in KB) available on the directory's current partition (0 if not found)", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("escape", CF_DATA_TYPE_STRING, ESCAPE_ARGS, &FnCallEscape, "Escape regular expression characters in a string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("eval", CF_DATA_TYPE_STRING, EVAL_ARGS, &FnCallEval, "Evaluate a mathematical expression", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("every", CF_DATA_TYPE_CONTEXT, EVERY_SOME_NONE_ARGS, &FnCallEverySomeNone, "True if every element in the list or array or data container matches the given regular expression", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("execresult", CF_DATA_TYPE_STRING, EXECRESULT_ARGS, &FnCallExecResult, "Execute named command and assign output to variable", + FNCALL_OPTION_CACHED | FNCALL_OPTION_VARARG | FNCALL_OPTION_UNSAFE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("execresult_as_data", CF_DATA_TYPE_CONTAINER, EXECRESULT_ARGS, &FnCallExecResult, "Execute command and return exit code and output in data container", + FNCALL_OPTION_CACHED | FNCALL_OPTION_VARARG | FNCALL_OPTION_UNSAFE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("file_hash", CF_DATA_TYPE_STRING, FILE_HASH_ARGS, &FnCallHandlerHash, "Return the hash of file arg1, type arg2 and assign to a variable", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("expandrange", CF_DATA_TYPE_STRING_LIST, EXPANDRANGE_ARGS, &FnCallExpandRange, "Expand a name as a list of names numered according to a range", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("fileexists", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named file can be accessed", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("filesexist", CF_DATA_TYPE_CONTEXT, FILESEXIST_ARGS, &FnCallFileSexist, "True if the named list of files can ALL be accessed", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("filesize", CF_DATA_TYPE_INT, FILESTAT_ARGS, &FnCallFileStat, "Returns the size in bytes of the file", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("filestat", CF_DATA_TYPE_STRING, FILESTAT_DETAIL_ARGS, &FnCallFileStatDetails, "Returns stat() details of the file", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("filter", CF_DATA_TYPE_STRING_LIST, FILTER_ARGS, &FnCallFilter, "Similarly to grep(), filter the list or array or data container arg2 for matches to arg2. The matching can be as a regular expression or exactly depending on arg3. The matching can be inverted with arg4. A maximum on the number of matches returned can be set with arg5.", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("findfiles", CF_DATA_TYPE_STRING_LIST, FINDFILES_ARGS, &FnCallFindfiles, "Find files matching a shell glob pattern", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("findprocesses", CF_DATA_TYPE_CONTAINER, PROCESSEXISTS_ARGS, &FnCallProcessExists, "Returns data container of processes matching the regular expression", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("format", CF_DATA_TYPE_STRING, FORMAT_ARGS, &FnCallFormat, "Applies a list of string values in arg2,arg3... to a string format in arg1 with sprintf() rules", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getclassmetatags", CF_DATA_TYPE_STRING_LIST, GETCLASSMETATAGS_ARGS, &FnCallGetMetaTags, "Collect the class arg1's meta tags into an slist, optionally collecting only tag key arg2", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getenv", CF_DATA_TYPE_STRING, GETENV_ARGS, &FnCallGetEnv, "Return the environment variable named arg1, truncated at arg2 characters", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getfields", CF_DATA_TYPE_INT, GETFIELDS_ARGS, &FnCallGetFields, "Get an array of fields in the lines matching regex arg1 in file arg2, split on regex arg3 as array name arg4", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getgid", CF_DATA_TYPE_INT, GETGID_ARGS, &FnCallGetGid, "Return the integer group id of the named group on this host", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getindices", CF_DATA_TYPE_STRING_LIST, GETINDICES_ARGS, &FnCallGetIndices, "Get a list of keys to the list or array or data container whose id is the argument and assign to variable", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getuid", CF_DATA_TYPE_INT, GETUID_ARGS, &FnCallGetUid, "Return the integer user id of the named user on this host", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getusers", CF_DATA_TYPE_STRING_LIST, GETUSERS_ARGS, &FnCallGetUsers, "Get a list of all system users defined, minus those names defined in arg1 and uids in arg2", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getuserinfo", CF_DATA_TYPE_CONTAINER, GETUSERINFO_ARGS, &FnCallGetUserInfo, "Get a data container describing user arg1, defaulting to current user", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getvalues", CF_DATA_TYPE_STRING_LIST, GETINDICES_ARGS, &FnCallGetValues, "Get a list of values in the list or array or data container arg1", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getvariablemetatags", CF_DATA_TYPE_STRING_LIST, GETVARIABLEMETATAGS_ARGS, &FnCallGetMetaTags, "Collect the variable arg1's meta tags into an slist, optionally collecting only tag key arg2", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("grep", CF_DATA_TYPE_STRING_LIST, GREP_ARGS, &FnCallGrep, "Extract the sub-list if items matching the regular expression in arg1 of the list or array or data container arg2", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("groupexists", CF_DATA_TYPE_CONTEXT, GROUPEXISTS_ARGS, &FnCallGroupExists, "True if group or numerical id exists on this host", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("hash", CF_DATA_TYPE_STRING, HASH_ARGS, &FnCallHandlerHash, "Return the hash of arg1, type arg2 and assign to a variable", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("hashmatch", CF_DATA_TYPE_CONTEXT, HASHMATCH_ARGS, &FnCallHashMatch, "Compute the hash of arg1, of type arg2 and test if it matches the value in arg3", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("host2ip", CF_DATA_TYPE_STRING, HOST2IP_ARGS, &FnCallHost2IP, "Returns the primary name-service IP address for the named host", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("ip2host", CF_DATA_TYPE_STRING, IP2HOST_ARGS, &FnCallIP2Host, "Returns the primary name-service host name for the IP address", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("hostinnetgroup", CF_DATA_TYPE_CONTEXT, HOSTINNETGROUP_ARGS, &FnCallHostInNetgroup, "True if the current host is in the named netgroup", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("hostrange", CF_DATA_TYPE_CONTEXT, HOSTRANGE_ARGS, &FnCallHostRange, "True if the current host lies in the range of enumerated hostnames specified", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("hostsseen", CF_DATA_TYPE_STRING_LIST, HOSTSSEEN_ARGS, &FnCallHostsSeen, "Extract the list of hosts last seen/not seen within the last arg1 hours", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("hostswithclass", CF_DATA_TYPE_STRING_LIST, HOSTSWITHCLASS_ARGS, &FnCallHostsWithClass, "Extract the list of hosts with the given class set from the hub database (enterprise extension)", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("hubknowledge", CF_DATA_TYPE_STRING, HUB_KNOWLEDGE_ARGS, &FnCallHubKnowledge, "Read global knowledge from the hub host by id (enterprise extension)", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("ifelse", CF_DATA_TYPE_STRING, IFELSE_ARGS, &FnCallIfElse, "Do If-ElseIf-ElseIf-...-Else evaluation of arguments", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("int", CF_DATA_TYPE_INT, INT_ARGS, &FnCallInt, "Convert numeric string to int", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("intersection", CF_DATA_TYPE_STRING_LIST, SETOP_ARGS, &FnCallSetop, "Returns all the unique elements of list or array or data container arg1 that are also in list or array or data container arg2", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("iprange", CF_DATA_TYPE_CONTEXT, IPRANGE_ARGS, &FnCallIPRange, "True if the current host lies in the range of IP addresses specified in arg1 (can be narrowed to specific interfaces with arg2).", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isipinsubnet", CF_DATA_TYPE_CONTEXT, IPRANGE_ARGS, &FnCallIsIpInSubnet, "True if an IP address specified in arg2, arg3, ... lies in the range of IP addresses specified in arg1", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("irange", CF_DATA_TYPE_INT_RANGE, IRANGE_ARGS, &FnCallIRange, "Define a range of integer values for cfengine internal use", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isdir", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object is a directory", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isexecutable", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object has execution rights for the current user", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isgreaterthan", CF_DATA_TYPE_CONTEXT, ISGREATERTHAN_ARGS, &FnCallIsLessGreaterThan, "True if arg1 is numerically greater than arg2, else compare strings like strcmp", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("islessthan", CF_DATA_TYPE_CONTEXT, ISLESSTHAN_ARGS, &FnCallIsLessGreaterThan, "True if arg1 is numerically less than arg2, else compare strings like NOT strcmp", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("islink", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object is a symbolic link", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isnewerthan", CF_DATA_TYPE_CONTEXT, ISNEWERTHAN_ARGS, &FnCallIsNewerThan, "True if arg1 is newer (modified later) than arg2 (mtime)", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isplain", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object is a plain/regular file", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isvariable", CF_DATA_TYPE_CONTEXT, ISVARIABLE_ARGS, &FnCallIsVariable, "True if the named variable is defined", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("join", CF_DATA_TYPE_STRING, JOIN_ARGS, &FnCallJoin, "Join the items of list or array or data container arg2 into a string, using the conjunction in arg1", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("lastnode", CF_DATA_TYPE_STRING, LASTNODE_ARGS, &FnCallLastNode, "Extract the last of a separated string, e.g. filename from a path", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("laterthan", CF_DATA_TYPE_CONTEXT, LATERTHAN_ARGS, &FnCallLaterThan, "True if the current time is later than the given date", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("ldaparray", CF_DATA_TYPE_CONTEXT, LDAPARRAY_ARGS, &FnCallLDAPArray, "Extract all values from an ldap record", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("ldaplist", CF_DATA_TYPE_STRING_LIST, LDAPLIST_ARGS, &FnCallLDAPList, "Extract all named values from multiple ldap records", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("ldapvalue", CF_DATA_TYPE_STRING, LDAPVALUE_ARGS, &FnCallLDAPValue, "Extract the first matching named value from ldap", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("lsdir", CF_DATA_TYPE_STRING_LIST, LSDIRLIST_ARGS, &FnCallLsDir, "Return a list of files in a directory matching a regular expression", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("makerule", CF_DATA_TYPE_STRING, MAKERULE_ARGS, &FnCallMakerule, "True if the target file arg1 does not exist or a source file arg2 or the list or array or data container arg2 is newer", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("maparray", CF_DATA_TYPE_STRING_LIST, MAPARRAY_ARGS, &FnCallMapData, "Return a list with each element mapped from a list or array or data container by a pattern based on $(this.k) and $(this.v)", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("mapdata", CF_DATA_TYPE_CONTAINER, MAPDATA_ARGS, &FnCallMapData, "Return a data container with each element parsed from a JSON string applied to every key-value pair of the list or array or data container, given as $(this.k) and $(this.v)", + FNCALL_OPTION_COLLECTING|FNCALL_OPTION_DELAYED_EVALUATION, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("maplist", CF_DATA_TYPE_STRING_LIST, MAPLIST_ARGS, &FnCallMapList, "Return a mapping of the list or array or data container with each element modified by a pattern based $(this)", + FNCALL_OPTION_COLLECTING|FNCALL_OPTION_DELAYED_EVALUATION, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("mergedata", CF_DATA_TYPE_CONTAINER, MERGEDATA_ARGS, &FnCallMergeData, "Merge two or more items, each a list or array or data container", + FNCALL_OPTION_COLLECTING|FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("none", CF_DATA_TYPE_CONTEXT, EVERY_SOME_NONE_ARGS, &FnCallEverySomeNone, "True if no element in the list or array or data container matches the given regular expression", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("not", CF_DATA_TYPE_CONTEXT, NOT_ARGS, &FnCallNot, "Calculate whether argument is false", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("now", CF_DATA_TYPE_INT, NOW_ARGS, &FnCallNow, "Convert the current time into system representation", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("nth", CF_DATA_TYPE_STRING, NTH_ARGS, &FnCallNth, "Get the element at arg2 in list or array or data container arg1", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("on", CF_DATA_TYPE_INT, DATE_ARGS, &FnCallOn, "Convert an exact date/time to an integer system representation", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("or", CF_DATA_TYPE_CONTEXT, OR_ARGS, &FnCallOr, "Calculate whether any argument evaluates to true", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("packagesmatching", CF_DATA_TYPE_CONTAINER, PACKAGESMATCHING_ARGS, &FnCallPackagesMatching, "List the installed packages (\"name,version,arch,manager\") matching regex arg1=name,arg2=version,arg3=arch,arg4=method", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("packageupdatesmatching", CF_DATA_TYPE_CONTAINER, PACKAGESMATCHING_ARGS, &FnCallPackagesMatching, "List the available patches (\"name,version,arch,manager\") matching regex arg1=name,arg2=version,arg3=arch,arg4=method. Enterprise only.", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("parseintarray", CF_DATA_TYPE_INT, PARSESTRINGARRAY_ARGS, &FnCallParseIntArray, "Read an array of integers from a string, indexing by first entry on line and sequentially within each line; return line count", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("parsejson", CF_DATA_TYPE_CONTAINER, PARSEJSON_ARGS, &FnCallParseJson, "Parse a JSON data container from a string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("parserealarray", CF_DATA_TYPE_INT, PARSESTRINGARRAY_ARGS, &FnCallParseRealArray, "Read an array of real numbers from a string, indexing by first entry on line and sequentially within each line; return line count", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("parsestringarray", CF_DATA_TYPE_INT, PARSESTRINGARRAY_ARGS, &FnCallParseStringArray, "Read an array of strings from a string, indexing by first word on line and sequentially within each line; return line count", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("parsestringarrayidx", CF_DATA_TYPE_INT, PARSESTRINGARRAY_ARGS, &FnCallParseStringArrayIndex, "Read an array of strings from a string, indexing by line number and sequentially within each line; return line count", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("parseyaml", CF_DATA_TYPE_CONTAINER, PARSEJSON_ARGS, &FnCallParseJson, "Parse a data container from a YAML string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("peers", CF_DATA_TYPE_STRING_LIST, PEERS_ARGS, &FnCallPeers, "Get a list of peers (not including ourself) from the partition to which we belong", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("peerleader", CF_DATA_TYPE_STRING, PEERLEADER_ARGS, &FnCallPeerLeader, "Get the assigned peer-leader of the partition to which we belong", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("peerleaders", CF_DATA_TYPE_STRING_LIST, PEERLEADERS_ARGS, &FnCallPeerLeaders, "Get a list of peer leaders from the named partitioning", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("processexists", CF_DATA_TYPE_CONTEXT, PROCESSEXISTS_ARGS, &FnCallProcessExists, "True if the regular expression matches a process", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("randomint", CF_DATA_TYPE_INT, RANDOMINT_ARGS, &FnCallRandomInt, "Generate a random integer between the given limits, excluding the upper", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("hash_to_int", CF_DATA_TYPE_INT, HASH_TO_INT_ARGS, &FnCallHashToInt, "Generate an integer in given range based on string hash", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + + FnCallTypeNew("string", CF_DATA_TYPE_STRING, STRING_ARGS, &FnCallString, "Convert argument to string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + // read functions for reading from file + FnCallTypeNew("readdata", CF_DATA_TYPE_CONTAINER, READDATA_ARGS, &FnCallReadData, "Parse a YAML, JSON, CSV, etc. file and return a JSON data container with the contents", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readfile", CF_DATA_TYPE_STRING, READFILE_ARGS, &FnCallReadFile, "Read max number of bytes from named file and assign to variable", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readcsv", CF_DATA_TYPE_CONTAINER, READFILE_ARGS, &FnCallReadCsv, "Parse a CSV file and return a JSON data container with the contents", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readenvfile", CF_DATA_TYPE_CONTAINER, READFILE_ARGS, &FnCallReadEnvFile, "Parse a ENV-style file and return a JSON data container with the contents", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readjson", CF_DATA_TYPE_CONTAINER, READFILE_ARGS, &FnCallReadJson, "Read a JSON data container from a file", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readyaml", CF_DATA_TYPE_CONTAINER, READFILE_ARGS, &FnCallReadYaml, "Read a data container from a YAML file", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("read_module_protocol", CF_DATA_TYPE_CONTEXT, READMODULE_ARGS, &FnCallReadModuleProtocol, "Parse a file containing module protocol output (for cached modules)", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readintarray", CF_DATA_TYPE_INT, READSTRINGARRAY_ARGS, &FnCallReadIntArray, "Read an array of integers from a file, indexed by first entry on line and sequentially on each line; return line count", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readintlist", CF_DATA_TYPE_INT_LIST, READSTRINGLIST_ARGS, &FnCallReadIntList, "Read and assign a list variable from a file of separated ints", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readrealarray", CF_DATA_TYPE_INT, READSTRINGARRAY_ARGS, &FnCallReadRealArray, "Read an array of real numbers from a file, indexed by first entry on line and sequentially on each line; return line count", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readreallist", CF_DATA_TYPE_REAL_LIST, READSTRINGLIST_ARGS, &FnCallReadRealList, "Read and assign a list variable from a file of separated real numbers", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readstringarray", CF_DATA_TYPE_INT, READSTRINGARRAY_ARGS, &FnCallReadStringArray, "Read an array of strings from a file, indexed by first entry on line and sequentially on each line; return line count", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readstringarrayidx", CF_DATA_TYPE_INT, READSTRINGARRAY_ARGS, &FnCallReadStringArrayIndex, "Read an array of strings from a file, indexed by line number and sequentially on each line; return line count", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readstringlist", CF_DATA_TYPE_STRING_LIST, READSTRINGLIST_ARGS, &FnCallReadStringList, "Read and assign a list variable from a file of separated strings", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("readtcp", CF_DATA_TYPE_STRING, READTCP_ARGS, &FnCallReadTcp, "Connect to tcp port, send string and assign result to variable", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + + // reg functions for regex + FnCallTypeNew("regarray", CF_DATA_TYPE_CONTEXT, REGARRAY_ARGS, &FnCallRegList, "True if the regular expression in arg1 matches any item in the list or array or data container arg2", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("regcmp", CF_DATA_TYPE_CONTEXT, REGCMP_ARGS, &FnCallRegCmp, "True if arg1 is a regular expression matching that matches string arg2", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("regextract", CF_DATA_TYPE_CONTEXT, REGEXTRACT_ARGS, &FnCallRegExtract, "True if the regular expression in arg 1 matches the string in arg2 and sets a non-empty array of backreferences named arg3", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("registryvalue", CF_DATA_TYPE_STRING, REGISTRYVALUE_ARGS, &FnCallRegistryValue, "Returns a value for an MS-Win registry key,value pair", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("regline", CF_DATA_TYPE_CONTEXT, REGLINE_ARGS, &FnCallRegLine, "True if the regular expression in arg1 matches a line in file arg2", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("reglist", CF_DATA_TYPE_CONTEXT, REGLIST_ARGS, &FnCallRegList, "True if the regular expression in arg2 matches any item in the list or array or data container whose id is arg1", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("regldap", CF_DATA_TYPE_CONTEXT, REGLDAP_ARGS, &FnCallRegLDAP, "True if the regular expression in arg6 matches a value item in an ldap search", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("remotescalar", CF_DATA_TYPE_STRING, REMOTESCALAR_ARGS, &FnCallRemoteScalar, "Read a scalar value from a remote cfengine server", + FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + + FnCallTypeNew("remoteclassesmatching", CF_DATA_TYPE_CONTEXT, REMOTECLASSESMATCHING_ARGS, &FnCallRemoteClassesMatching, "Read persistent classes matching a regular expression from a remote cfengine server and add them into local context with prefix", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("returnszero", CF_DATA_TYPE_CONTEXT, RETURNSZERO_ARGS, &FnCallReturnsZero, "True if named shell command has exit status zero", + FNCALL_OPTION_CACHED | FNCALL_OPTION_UNSAFE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("rrange", CF_DATA_TYPE_REAL_RANGE, RRANGE_ARGS, &FnCallRRange, "Define a range of real numbers for cfengine internal use", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("reverse", CF_DATA_TYPE_STRING_LIST, REVERSE_ARGS, &FnCallReverse, "Reverse a list or array or data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("selectservers", CF_DATA_TYPE_INT, SELECTSERVERS_ARGS, &FnCallSelectServers, "Select tcp servers which respond correctly to a query and return their number, set array of names", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("shuffle", CF_DATA_TYPE_STRING_LIST, SHUFFLE_ARGS, &FnCallShuffle, "Shuffle the items in a list or array or data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("some", CF_DATA_TYPE_CONTEXT, EVERY_SOME_NONE_ARGS, &FnCallEverySomeNone, "True if an element in the list or array or data container matches the given regular expression", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("sort", CF_DATA_TYPE_STRING_LIST, SORT_ARGS, &FnCallSort, "Sort a list or array or data container", + FNCALL_OPTION_COLLECTING | FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("splayclass", CF_DATA_TYPE_CONTEXT, SPLAYCLASS_ARGS, &FnCallSplayClass, "True if the first argument's time-slot has arrived, according to a policy in arg2", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("splitstring", CF_DATA_TYPE_STRING_LIST, SPLITSTRING_ARGS, &FnCallSplitString, "Convert a string in arg1 into a list of max arg3 strings by splitting on a regular expression in arg2", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_DEPRECATED), + FnCallTypeNew("storejson", CF_DATA_TYPE_STRING, STOREJSON_ARGS, &FnCallStoreJson, "Convert a list or array or data container to a JSON string", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("strcmp", CF_DATA_TYPE_CONTEXT, STRCMP_ARGS, &FnCallStrCmp, "True if the two strings match exactly", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("strftime", CF_DATA_TYPE_STRING, STRFTIME_ARGS, &FnCallStrftime, "Format a date and time string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("sublist", CF_DATA_TYPE_STRING_LIST, SUBLIST_ARGS, &FnCallSublist, "Returns arg3 element from either the head or the tail (according to arg2) of list or array or data container arg1.", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("sysctlvalue", CF_DATA_TYPE_STRING, SYSCTLVALUE_ARGS, &FnCallSysctlValue, "Returns a value for sysctl key arg1 pair", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("data_sysctlvalues", CF_DATA_TYPE_CONTAINER, DATA_SYSCTLVALUES_ARGS, &FnCallSysctlValue, "Returns a data container map of all the sysctl key,value pairs", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("translatepath", CF_DATA_TYPE_STRING, TRANSLATEPATH_ARGS, &FnCallTranslatePath, "Translate path separators from Unix style to the host's native", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("unique", CF_DATA_TYPE_STRING_LIST, UNIQUE_ARGS, &FnCallSetop, "Returns all the unique elements of list or array or data container arg1", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("usemodule", CF_DATA_TYPE_CONTEXT, USEMODULE_ARGS, &FnCallUseModule, "Execute cfengine module script and set class if successful", + FNCALL_OPTION_NONE | FNCALL_OPTION_UNSAFE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("userexists", CF_DATA_TYPE_CONTEXT, USEREXISTS_ARGS, &FnCallUserExists, "True if user name or numerical id exists on this host", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("validdata", CF_DATA_TYPE_CONTEXT, VALIDDATA_ARGS, &FnCallValidData, "Check for errors in JSON or YAML data", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("validjson", CF_DATA_TYPE_CONTEXT, VALIDDATATYPE_ARGS, &FnCallValidJson, "Check for errors in JSON data", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("variablesmatching", CF_DATA_TYPE_STRING_LIST, CLASSMATCH_ARGS, &FnCallVariablesMatching, "List the variables matching regex arg1 and tag regexes arg2,arg3,...", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("version_compare", CF_DATA_TYPE_CONTEXT, VERSION_COMPARE_ARGS, &FnCallVersionCompare, "Compare two version numbers with a specified operator", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + + // Functions section following new naming convention + FnCallTypeNew("string_mustache", CF_DATA_TYPE_STRING, STRING_MUSTACHE_ARGS, &FnCallStringMustache, "Expand a Mustache template from arg1 into a string using the optional data container in arg2 or datastate()", + FNCALL_OPTION_COLLECTING|FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("string_split", CF_DATA_TYPE_STRING_LIST, SPLITSTRING_ARGS, &FnCallStringSplit, "Convert a string in arg1 into a list of at most arg3 strings by splitting on a regular expression in arg2", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("string_replace", CF_DATA_TYPE_STRING, STRING_REPLACE_ARGS, &FnCallStringReplace, "Search through arg1, replacing occurences of arg2 with arg3.", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("string_trim", CF_DATA_TYPE_STRING, STRING_TRIM_ARGS, &FnCallStringTrim, "Trim whitespace from beginning and end of string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("regex_replace", CF_DATA_TYPE_STRING, REGEX_REPLACE_ARGS, &FnCallRegReplace, "Replace occurrences of arg1 in arg2 with arg3, allowing backreferences. Perl-style options accepted in arg4.", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + + // Text xform functions + FnCallTypeNew("string_downcase", CF_DATA_TYPE_STRING, XFORM_ARGS, &FnCallTextXform, "Convert a string to lowercase", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("string_head", CF_DATA_TYPE_STRING, XFORM_SUBSTR_ARGS, &FnCallTextXform, "Extract characters from the head of the string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("string_reverse", CF_DATA_TYPE_STRING, XFORM_ARGS, &FnCallTextXform, "Reverse a string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("string_length", CF_DATA_TYPE_INT, XFORM_ARGS, &FnCallTextXform, "Return the length of a string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("string_tail", CF_DATA_TYPE_STRING, XFORM_SUBSTR_ARGS, &FnCallTextXform, "Extract characters from the tail of the string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("string_upcase", CF_DATA_TYPE_STRING, XFORM_ARGS, &FnCallTextXform, "Convert a string to UPPERCASE", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + + // List folding functions + FnCallTypeNew("length", CF_DATA_TYPE_INT, STAT_FOLD_ARGS, &FnCallLength, "Return the length of a list or array or data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("max", CF_DATA_TYPE_STRING, SORT_ARGS, &FnCallFold, "Return the maximum value in a list or array or data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("mean", CF_DATA_TYPE_REAL, STAT_FOLD_ARGS, &FnCallFold, "Return the mean (average) in a list or array or data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("min", CF_DATA_TYPE_STRING, SORT_ARGS, &FnCallFold, "Return the minimum in a list or array or data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("product", CF_DATA_TYPE_REAL, PRODUCT_ARGS, &FnCallFold, "Return the product of a list or array or data container of reals", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("sum", CF_DATA_TYPE_REAL, SUM_ARGS, &FnCallFold, "Return the sum of a list or array or data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("variance", CF_DATA_TYPE_REAL, STAT_FOLD_ARGS, &FnCallFold, "Return the variance of a list or array or data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + + // CFEngine internal functions + FnCallTypeNew("callstack_promisers", CF_DATA_TYPE_STRING_LIST, CFENGINE_PROMISERS_ARGS, &FnCallCFEngineCallers, "Get the list of promisers to the current promise execution path", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_INTERNAL, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("callstack_callers", CF_DATA_TYPE_CONTAINER, CFENGINE_CALLERS_ARGS, &FnCallCFEngineCallers, "Get the current promise execution stack in detail", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_INTERNAL, SYNTAX_STATUS_NORMAL), + + // Data container functions + FnCallTypeNew("data_regextract", CF_DATA_TYPE_CONTAINER, DATA_REGEXTRACT_ARGS, &FnCallRegExtract, "Matches the regular expression in arg 1 against the string in arg2 and returns a data container holding the backreferences by name", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("data_expand", CF_DATA_TYPE_CONTAINER, DATA_EXPAND_ARGS, &FnCallDataExpand, "Expands any CFEngine variables in a list or array or data container, converting to a data container", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("variablesmatching_as_data", CF_DATA_TYPE_CONTAINER, CLASSMATCH_ARGS, &FnCallVariablesMatching, "Capture the variables matching regex arg1 and tag regexes arg2,arg3,... with their data", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + + // File parsing functions that output a data container + FnCallTypeNew("data_readstringarray", CF_DATA_TYPE_CONTAINER, DATA_READSTRINGARRAY_ARGS, &FnCallDataRead, "Read an array of strings from a file into a data container map, using the first element as a key", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("data_readstringarrayidx", CF_DATA_TYPE_CONTAINER, DATA_READSTRINGARRAY_ARGS, &FnCallDataRead, "Read an array of strings from a file into a data container array", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), + + // Network probe functions + FnCallTypeNew("network_connections", CF_DATA_TYPE_CONTAINER, NETWORK_CONNECTIONS_ARGS, &FnCallNetworkConnections, "Get the full list of TCP, TCP6, UDP, and UDP6 connections from /proc/net", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL), + + // Files functions + FnCallTypeNew("findfiles_up", CF_DATA_TYPE_CONTAINER, FINDFILES_UP_ARGS, &FnCallFindfilesUp, "Find files matching a glob pattern by searching up the directory three from a given point in the tree structure", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("search_up", CF_DATA_TYPE_CONTAINER, FINDFILES_UP_ARGS, &FnCallFindfilesUp, "Hush... This is a super secret alias name for function 'findfiles_up'", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isreadable", CF_DATA_TYPE_CONTEXT, ISREADABLE_ARGS, &FnCallIsReadable, "Check if file is readable. Timeout immediately or after optional timeout interval", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + + // Datatype functions + FnCallTypeNew("type", CF_DATA_TYPE_STRING, DATATYPE_ARGS, &FnCallDatatype, "Get type description as string", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + + FnCallTypeNewNull() +}; diff --git a/libpromises/evalfunction.h b/libpromises/evalfunction.h new file mode 100644 index 0000000000..853656d459 --- /dev/null +++ b/libpromises/evalfunction.h @@ -0,0 +1,44 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_EVALFUNCTION_H +#define CFENGINE_EVALFUNCTION_H + +#include +#include +#include +#include + +FnCallResult FnCallHostInNetgroup(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *finalargs); + +int FnNumArgs(const FnCallType *call_type); + +void ModuleProtocol(EvalContext *ctx, const char *command, const char *line, int print, char* context, size_t context_size, StringSet *tags, long *persistence); + +/* Implemented in Nova for Win32 */ +FnCallResult FnCallGroupExists(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *finalargs); +FnCallResult FnCallUserExists(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *finalargs); + +JsonElement *DefaultTemplateData(const EvalContext *ctx, const char *wantbundle); +#endif diff --git a/libpromises/exec_tools.c b/libpromises/exec_tools.c new file mode 100644 index 0000000000..e64eecbd7a --- /dev/null +++ b/libpromises/exec_tools.c @@ -0,0 +1,349 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include // CloseLog + +/********************************************************************/ + +bool GetExecOutput(const char *command, char **buffer, size_t *buffer_size, ShellType shell, OutputSelect output_select, int *ret_out) +/* Buffer initially contains whole exec string */ +{ + FILE *pp; + + if (shell == SHELL_TYPE_USE) + { + pp = cf_popen_sh_select(command, "rt", output_select); + } + else if (shell == SHELL_TYPE_POWERSHELL) + { +#ifdef __MINGW32__ + pp = cf_popen_powershell_select(command, "rt", output_select); +#else // !__MINGW32__ + Log(LOG_LEVEL_ERR, "Powershell is only supported on Windows"); + return false; +#endif // __MINGW32__ + } + else + { + pp = cf_popen_select(command, "rt", output_select); + } + + if (pp == NULL) + { + Log(LOG_LEVEL_ERR, "Couldn't open pipe to command '%s'. (cf_popen: %s)", command, GetErrorStr()); + return false; + } + + size_t offset = 0; + size_t line_size = CF_EXPANDSIZE; + size_t attempted_size = 0; + char *line = xcalloc(1, line_size); + + while (*buffer_size < CF_MAXSIZE) + { + ssize_t res = CfReadLine(&line, &line_size, pp); + if (res == -1) + { + if (!feof(pp)) + { + Log(LOG_LEVEL_ERR, "Unable to read output of command '%s'. (fread: %s)", command, GetErrorStr()); + cf_pclose(pp); + free(line); + return false; + } + else + { + break; + } + } + + if ((attempted_size = snprintf(*buffer + offset, *buffer_size - offset, "%s\n", line)) >= *buffer_size - offset) + { + *buffer_size += (attempted_size > CF_EXPANDSIZE ? attempted_size : CF_EXPANDSIZE); + *buffer = xrealloc(*buffer, *buffer_size); + snprintf(*buffer + offset, *buffer_size - offset, "%s\n", line); + } + + offset += strlen(line) + 1; + } + + if (offset > 0) + { + if (Chop(*buffer, *buffer_size) == -1) + { + Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator"); + } + } + + Log(LOG_LEVEL_DEBUG, "GetExecOutput got '%s'", *buffer); + + if (ret_out != NULL) + { + *ret_out = cf_pclose(pp); + } + else + { + cf_pclose(pp); + } + + free(line); + return true; +} + +/**********************************************************************/ + +void ActAsDaemon() +{ + int fd; + +#ifdef HAVE_SETSID + if (setsid() == (pid_t) -1) + { + Log(LOG_LEVEL_WARNING, + "Failed to become a session leader while daemonising (setsid: %s)", + GetErrorStr()); + } +#endif + + CloseNetwork(); + CloseLog(); + + fflush(NULL); + + /* Close descriptors 0,1,2 and reopen them with /dev/null. */ + + fd = open(NULLFILE, O_RDWR, 0); + if (fd == -1) + { + Log(LOG_LEVEL_WARNING, "Could not open '%s', " + "stdin/stdout/stderr are still open (open: %s)", + NULLFILE, GetErrorStr()); + } + else + { + if (dup2(fd, STDIN_FILENO) == -1) + { + Log(LOG_LEVEL_WARNING, + "Could not close stdin while daemonising (dup2: %s)", + GetErrorStr()); + } + + if (dup2(fd, STDOUT_FILENO) == -1) + { + Log(LOG_LEVEL_WARNING, + "Could not close stdout while daemonising (dup2: %s)", + GetErrorStr()); + } + + if (dup2(fd, STDERR_FILENO) == -1) + { + Log(LOG_LEVEL_WARNING, + "Could not close stderr while daemonising (dup2: %s)", + GetErrorStr()); + } + + if (fd > STDERR_FILENO) + { + close(fd); + } + } + + if (chdir("/")) + { + Log(LOG_LEVEL_WARNING, + "Failed to chdir into '/' directory while daemonising (chdir: %s)", + GetErrorStr()); + } +} + +/**********************************************************************/ + +/** + * Split the command string like "/bin/echo -n Hi!" in two parts -- the + * executable ("/bin/echo") and the arguments for the executable ("-n Hi!"). + * + * @param[in] comm the whole command to split + * @param[out] exec pointer to **a newly allocated** string with the executable + * @param[out] args pointer to **a newly allocated** string with the args + * + * @note Whitespace between the executable and the arguments is skipped. + */ +void ArgGetExecutableAndArgs(const char *comm, char **exec, char **args) +{ + const char *s = comm; + while (*s != '\0') + { + const char *end = NULL; + + if (isspace((int)*s)) /* Skip whitespace */ + { + s++; + continue; + } + + switch (*s) + { + case '"': /* Look for matching quote */ + case '\'': + case '`': + { + char delim = *(s++); /* Skip first delimeter */ + + end = strchr(s, delim); + break; + } + default: /* Look for whitespace */ + end = strpbrk(s, " \f\n\r\t\v"); + break; + } + + if (end == NULL) /* Delimeter was not found, remaining string is the executable */ + { + *exec = xstrdup(s); + *args = NULL; + return; + } + else + { + assert(end > s); + const size_t length = end - s; + *exec = xstrndup(s, length); + + const char *args_start = end; + if (*(args_start + 1) != '\0') + { + args_start++; /* Skip second delimeter */ + args_start += strspn(args_start, " \f\n\r\t\v"); /* Skip whitespace */ + *args = xstrdup(args_start); + } + else + { + *args = NULL; + } + return; + } + } + + /* was not able to parse/split the command */ + *exec = NULL; + *args = NULL; + return; +} + +#define INITIAL_ARGS 8 + +char **ArgSplitCommand(const char *comm, const Seq *arglist) +{ + const char *s = comm; + + int argc = 0; + int argslen = INITIAL_ARGS; + char **args = xmalloc(argslen * sizeof(char *)); + + while (*s != '\0') + { + const char *end; + char *arg; + + if (isspace((int)*s)) /* Skip whitespace */ + { + s++; + continue; + } + + switch (*s) + { + case '"': /* Look for matching quote */ + case '\'': + case '`': + { + char delim = *s++; /* Skip first delimeter */ + + end = strchr(s, delim); + break; + } + default: /* Look for whitespace */ + end = strpbrk(s, " \f\n\r\t\v"); + break; + } + + if (end == NULL) /* Delimeter was not found, remaining string is the argument */ + { + arg = xstrdup(s); + s += strlen(arg); + } + else + { + arg = xstrndup(s, end - s); + s = end; + if ((*s == '"') || (*s == '\'') || (*s == '`')) /* Skip second delimeter */ + s++; + } + + /* Argument */ + + if (argc == argslen) + { + argslen *= 2; + args = xrealloc(args, argslen * sizeof(char *)); + } + + args[argc++] = arg; + } + + size_t extra = (arglist == NULL) ? 0 : SeqLength(arglist); + if (argc + extra + 1 /* NULL */ > argslen) + { + args = xrealloc(args, (argc + extra + 1) * sizeof(char *)); + } + + for (size_t i = 0; i < extra; i++) { + args[argc++] = xstrdup(SeqAt(arglist, i)); + } + args[argc] = NULL; + + return args; +} + +/**********************************************************************/ + +void ArgFree(char **args) +{ + if (args != NULL) + { + for (char **arg = args; *arg; ++arg) + { + free(*arg); + } + free(args); + } +} diff --git a/libpromises/exec_tools.h b/libpromises/exec_tools.h new file mode 100644 index 0000000000..5c44d9fcfb --- /dev/null +++ b/libpromises/exec_tools.h @@ -0,0 +1,48 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_EXEC_TOOLS_H +#define CFENGINE_EXEC_TOOLS_H + +#include +#include +#include // OutputSelect + +bool IsExecutable(const char *file); +bool ShellCommandReturnsZero(const char *command, ShellType shell); +bool GetExecOutput(const char *command, char **buffer, size_t *buffer_size, ShellType shell, OutputSelect output_select, int *ret_out); +void ActAsDaemon(); +void ArgGetExecutableAndArgs(const char *comm, char **exec, char **args); + +/** + * Create a argument list as expected by execv(3) + * @param command is split on spaces, unless quoted + * @param arglist is not split (can have embedded spaces) + * @return null-terminated argument list + */ +char **ArgSplitCommand(const char *command, const Seq *arglist); + +void ArgFree(char **args); + +#endif diff --git a/libpromises/expand.c b/libpromises/expand.c new file mode 100644 index 0000000000..b45be40a97 --- /dev/null +++ b/libpromises/expand.c @@ -0,0 +1,1322 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * VARIABLES AND PROMISE EXPANSION + * + * Expanding variables is easy -- expanding lists automagically requires + * some thought. Remember that + * + * promiser <=> RVAL_TYPE_SCALAR + * promisee <=> RVAL_TYPE_LIST + * + * For bodies we have + * + * lval <=> RVAL_TYPE_LIST | RVAL_TYPE_SCALAR + * + * Any list or container variable occurring within a scalar or in place of a + * scalar is assumed to be iterated i.e. $(name). See comments in iteration.c. + * + * Any list variable @(name) is *not iterated*, but dropped into place (see + * DeRefCopyPromise()). + * + * Please note that bodies cannot contain iterators. + * + * The full process of promise and variable expansion is mostly outlined in + * ExpandPromise() and ExpandPromiseAndDo() and the basic steps are: + * + * + Skip everything if the class guard is not defined. + * + * + DeRefCopyPromise(): *Copy the promise* while expanding '@' slists and body + * arguments and handling body inheritance. This requires one round of + * expansion with scopeid "body". + * + * + Push promise frame + * + * + MapIteratorsFromRval(): Parse all strings (promiser-promisee-constraints), + * find all unexpanded variables, mangle them if needed (if they are + * namespaced/scoped), and *initialise the wheels* in the iteration engine + * (iterctx) to iterate over iterable variables (slists and containers). See + * comments in iteration.c for further details. + * + * + For every iteration: + * + * - Push iteration frame + * + * - EvalContextStackPushPromiseIterationFrame()->ExpandDeRefPromise(): Make + * another copy of the promise with all constraints evaluated and variables + * expanded. + * + * -- NOTE: As a result all *functions are also evaluated*, even if they are + * not to be used immediately (for example promises that the actuator skips + * because of ifvarclass, see promises.c:ExpandDeRefPromise() ). + * + * -- (TODO IS IT CORRECT?) In a sub-bundle, create a new context and make + * hashes of the the transferred variables in the temporary context + * + * - Run the actuator (=act_on_promise= i.e. =VerifyWhateverPromise()=) + * + * - Pop iteration frame + * + * + Pop promise frame + * + */ + +static inline char opposite(char c); + +static void PutHandleVariable(EvalContext *ctx, const Promise *pp) +{ + char *handle_s; + const char *existing_handle = PromiseGetHandle(pp); + + if (existing_handle != NULL) + { + // This ordering is necessary to get automated canonification + handle_s = ExpandScalar(ctx, NULL, "this", existing_handle, NULL); + CanonifyNameInPlace(handle_s); + } + else + { + handle_s = xstrdup(PromiseID(pp)); /* default handle */ + } + + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, + "handle", handle_s, + CF_DATA_TYPE_STRING, "source=promise"); + free(handle_s); +} + +/** + * Recursively go down the #rval and run PromiseIteratorPrepare() to take note + * of all iterables and mangle all rvals than need to be mangled before + * iterating. + */ +static void MapIteratorsFromRval(EvalContext *ctx, + PromiseIterator *iterctx, + Rval rval) +{ + switch (rval.type) + { + + case RVAL_TYPE_SCALAR: + PromiseIteratorPrepare(iterctx, ctx, RvalScalarValue(rval)); + break; + + case RVAL_TYPE_LIST: + for (const Rlist *rp = RvalRlistValue(rval); + rp != NULL; rp = rp->next) + { + MapIteratorsFromRval(ctx, iterctx, rp->val); + } + break; + + case RVAL_TYPE_FNCALL: + { + char *fn_name = RvalFnCallValue(rval)->name; + + /* Check function name. */ + PromiseIteratorPrepare(iterctx, ctx, fn_name); + + /* Check each of the function arguments. */ + /* EXCEPT on functions that use special variables: the mangled + * variables would never be resolved if they contain inner special + * variables (for example "$(bundle.A[$(this.k)])" and the returned + * slist would contained mangled vars like "bundle#A[1]" which would + * never resolve in future iterations. By skipping the iteration + * engine for now, the function returns an slist with unmangled + * entries, and the iteration engine works correctly on the next + * pass! */ + if (strcmp(fn_name, "maplist") != 0 && + strcmp(fn_name, "mapdata") != 0 && + strcmp(fn_name, "maparray")!= 0) + { + for (Rlist *rp = RvalFnCallValue(rval)->args; + rp != NULL; rp = rp->next) + { + MapIteratorsFromRval(ctx, iterctx, rp->val); + } + } + break; + } + + case RVAL_TYPE_CONTAINER: + case RVAL_TYPE_NOPROMISEE: + break; + } +} + +static PromiseResult ExpandPromiseAndDo(EvalContext *ctx, PromiseIterator *iterctx, + PromiseActuator *act_on_promise, void *param, + bool actuate_ifelse) +{ + PromiseResult result = PROMISE_RESULT_SKIPPED; + + /* In the case of ifelse() we must always include an extra round of "actuation" + * in the while loop below. PromiseIteratorNext() will return false in the case + * that there are doubly-unresolved Rvals like $($(missing)). + * We can't add an empty wheel because that is skipped as well as noted in + * libpromises/iteration.c ShouldAddVariableAsIterationWheel(). */ + bool ifelse_actuated = !actuate_ifelse; + + /* TODO this loop could be completely skipped for for non vars/classes if + * act_on_promise is CommonEvalPromise(). */ + while (PromiseIteratorNext(iterctx, ctx) || !ifelse_actuated) + { + /* + * ACTUAL WORK PART 1: Get a (another) copy of the promise. + * + * Basically this evaluates all constraints. As a result it evaluates + * all functions, even if they are not to be used immediately (for + * example promises that the actuator skips because of ifvarclass). + */ + const Promise *pexp = /* expanded promise */ + EvalContextStackPushPromiseIterationFrame(ctx, iterctx); + if (pexp == NULL) /* is the promise excluded? */ + { + result = PromiseResultUpdate(result, PROMISE_RESULT_SKIPPED); + ifelse_actuated = true; + continue; + } + + /* ACTUAL WORK PART 2: run the actuator */ + PromiseResult iteration_result = act_on_promise(ctx, pexp, param); + + /* iteration_result is always NOOP for PRE-EVAL. */ + result = PromiseResultUpdate(result, iteration_result); + + /* Redmine#6484: Do not store promise handles during PRE-EVAL, to + * avoid package promise always running. */ + if (act_on_promise != &CommonEvalPromise) + { + NotifyDependantPromises(ctx, pexp, iteration_result); + } + + /* EVALUATE VARS PROMISES again, allowing redefinition of + * variables. The theory behind this is that the "sampling rate" of + * vars promise needs to be double than the rest. */ + if (strcmp(PromiseGetPromiseType(pexp), "vars") == 0 || + strcmp(PromiseGetPromiseType(pexp), "meta") == 0) + { + if (act_on_promise != &VerifyVarPromise) + { + VerifyVarPromise(ctx, pexp, NULL); + } + } + + /* Why do we push/pop an iteration frame, if all iterated variables + * are Put() on the previous scope? */ + EvalContextStackPopFrame(ctx); + ifelse_actuated = true; + } + + return result; +} + +PromiseResult ExpandPromise(EvalContext *ctx, const Promise *pp, + PromiseActuator *act_on_promise, void *param) +{ + assert(pp != NULL); + + if (!IsDefinedClass(ctx, pp->classes)) + { + Log(LOG_LEVEL_DEBUG, + "Skipping %s promise expansion with promiser '%s' due to class guard '%s::' (pass %d)", + PromiseGetPromiseType(pp), + pp->promiser, + pp->classes, + EvalContextGetPass(ctx)); + return PROMISE_RESULT_SKIPPED; + } + + /* 1. Copy the promise while expanding '@' slists and body arguments + * (including body inheritance). */ + Promise *pcopy = DeRefCopyPromise(ctx, pp); + + EvalContextStackPushPromiseFrame(ctx, pcopy); + PromiseIterator *iterctx = PromiseIteratorNew(pcopy); + + /* 2. Parse all strings (promiser-promisee-constraints), find all + unexpanded variables, mangle them if needed (if they are + namespaced/scoped), and start the iteration engine (iterctx) to + iterate over slists and containers. */ + + MapIteratorsFromRval(ctx, iterctx, + (Rval) { pcopy->promiser, RVAL_TYPE_SCALAR }); + + if (pcopy->promisee.item != NULL) + { + MapIteratorsFromRval(ctx, iterctx, pcopy->promisee); + } + + bool actuate_ifelse = false; + for (size_t i = 0; i < SeqLength(pcopy->conlist); i++) + { + Constraint *cp = SeqAt(pcopy->conlist, i); + if (cp->rval.type == RVAL_TYPE_FNCALL && + strcmp(RvalFnCallValue(cp->rval)->name, "ifelse") == 0) + { + actuate_ifelse = true; + } + MapIteratorsFromRval(ctx, iterctx, cp->rval); + } + + /* 3. GO! */ + PutHandleVariable(ctx, pcopy); + PromiseResult result = ExpandPromiseAndDo(ctx, iterctx, + act_on_promise, param, actuate_ifelse); + + EvalContextStackPopFrame(ctx); + PromiseIteratorDestroy(iterctx); + PromiseDestroy(pcopy); + + return result; +} + + +/*********************************************************************/ +/*********************************************************************/ + +Rval ExpandPrivateRval(const EvalContext *ctx, + const char *ns, const char *scope, + const void *rval_item, RvalType rval_type) +{ + Rval returnval; + returnval.item = NULL; + returnval.type = RVAL_TYPE_NOPROMISEE; + + switch (rval_type) + { + case RVAL_TYPE_SCALAR: + returnval.item = ExpandScalar(ctx, ns, scope, rval_item, NULL); + returnval.type = RVAL_TYPE_SCALAR; + break; + case RVAL_TYPE_LIST: + returnval.item = ExpandList(ctx, ns, scope, rval_item, true); + returnval.type = RVAL_TYPE_LIST; + break; + + case RVAL_TYPE_FNCALL: + returnval.item = ExpandFnCall(ctx, ns, scope, rval_item); + returnval.type = RVAL_TYPE_FNCALL; + break; + + case RVAL_TYPE_CONTAINER: + returnval = RvalNew(rval_item, RVAL_TYPE_CONTAINER); + break; + + case RVAL_TYPE_NOPROMISEE: + break; + } + + return returnval; +} + +/** + * Detects a variable expansion inside of a data/list reference, for example + * "@(${container_name})" or "@(prefix${container_name})" or + * "@(nspace:${container_name})" or "@(container_name[${field}])". + * + * @note This function doesn't have to be bullet-proof, it only needs to + * properly detect valid cases. The rest is left to the parser and code + * expanding variables. + */ +static inline bool VariableDataOrListReference(const char *str) +{ + assert(str != NULL); + + size_t len = strlen(str); + + /* at least '@($(X))' is needed */ + if (len < 7) + { + return false; + } + + if (!((str[0] == '@') && + ((str[1] == '{') || (str[1] == '(')))) + { + return false; + } + + /* Check if, after '@(', there are only + * - characters allowed in data/list names or + * - ':' to separate namespace from the name or + * - '.' to separate bundle and variable name or, + * - '[' for data/list field/index specification, + * followed by "$(" or "${" with a matching close bracket somewhere. */ + for (size_t i = 2; i < len; i++) + { + if (!(isalnum((int) str[i]) || (str[i] == '_') || + (str[i] == ':') || (str[i] == '$') || (str[i] == '.') || (str[i] == '['))) + { + return false; + } + + if (str[i] == '$') + { + if (((i + 1) < len) && ((str[i + 1] == '{') || (str[i + 1] == '('))) + { + int close_bracket = (int) opposite(str[i+1]); + return (strchr(str + i + 2, close_bracket) != NULL); + } + else + { + return false; + } + } + } + + return false; +} + +static Rval ExpandListEntry(const EvalContext *ctx, + const char *ns, const char *scope, + int expandnaked, Rval entry) +{ + Rval expanded_data_list = {0}; + /* If rval is something like '@($(container_name).field)', we need to expand + * the nested variable first. */ + if (entry.type == RVAL_TYPE_SCALAR && + VariableDataOrListReference(entry.item)) + { + entry = ExpandPrivateRval(ctx, ns, scope, entry.item, entry.type); + expanded_data_list = entry; + } + + if (entry.type == RVAL_TYPE_SCALAR && + IsNakedVar(entry.item, '@')) + { + if (expandnaked) + { + char naked[CF_MAXVARSIZE]; + GetNaked(naked, entry.item); + + if (IsExpandable(naked)) + { + char *exp = ExpandScalar(ctx, ns, scope, naked, NULL); + strlcpy(naked, exp, sizeof(naked)); /* TODO err */ + free(exp); + } + + /* Check again, it might have changed. */ + if (!IsExpandable(naked)) + { + VarRef *ref = VarRefParseFromScope(naked, scope); + + DataType value_type; + const void *value = EvalContextVariableGet(ctx, ref, &value_type); + VarRefDestroy(ref); + + if (value_type != CF_DATA_TYPE_NONE) /* variable found? */ + { + Rval ret = ExpandPrivateRval(ctx, ns, scope, value, + DataTypeToRvalType(value_type)); + RvalDestroy(expanded_data_list); + return ret; + } + } + } + else + { + Rval ret = RvalNew(entry.item, RVAL_TYPE_SCALAR); + RvalDestroy(expanded_data_list); + return ret; + } + } + + Rval ret = ExpandPrivateRval(ctx, ns, scope, entry.item, entry.type); + RvalDestroy(expanded_data_list); + return ret; +} + +Rlist *ExpandList(const EvalContext *ctx, + const char *ns, const char *scope, + const Rlist *list, int expandnaked) +{ + Rlist *start = NULL; + + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + Rval returnval = ExpandListEntry(ctx, ns, scope, expandnaked, rp->val); + RlistAppend(&start, returnval.item, returnval.type); + RvalDestroy(returnval); + } + + return start; +} + +/*********************************************************************/ + +Rval ExpandBundleReference(EvalContext *ctx, + const char *ns, const char *scope, + Rval rval) +{ + // Allocates new memory for the copy + switch (rval.type) + { + case RVAL_TYPE_SCALAR: + return (Rval) { ExpandScalar(ctx, ns, scope, RvalScalarValue(rval), NULL), + RVAL_TYPE_SCALAR }; + + case RVAL_TYPE_FNCALL: + return (Rval) { ExpandFnCall(ctx, ns, scope, RvalFnCallValue(rval)), + RVAL_TYPE_FNCALL}; + + case RVAL_TYPE_CONTAINER: + case RVAL_TYPE_LIST: + case RVAL_TYPE_NOPROMISEE: + return RvalNew(NULL, RVAL_TYPE_NOPROMISEE); + } + + assert(false); + return RvalNew(NULL, RVAL_TYPE_NOPROMISEE); +} + +/** + * Expand a #string into Buffer #out, returning the pointer to the string + * itself, inside the Buffer #out. If #out is NULL then the buffer will be + * created and destroyed internally. + * + * @retval NULL something went wrong + */ +char *ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, + const char *string, Buffer *out) +{ + bool out_belongs_to_us = false; + + if (out == NULL) + { + out = BufferNew(); + out_belongs_to_us = true; + } + + assert(string != NULL); + assert(out != NULL); + Buffer *current_item = BufferNew(); + + for (const char *sp = string; *sp != '\0'; sp++) + { + BufferClear(current_item); + ExtractScalarPrefix(current_item, sp, strlen(sp)); + + BufferAppend(out, BufferData(current_item), BufferSize(current_item)); + sp += BufferSize(current_item); + if (*sp == '\0') + { + break; + } + + BufferClear(current_item); + char varstring = sp[1]; + ExtractScalarReference(current_item, sp, strlen(sp), true); + sp += BufferSize(current_item) + 2; + + if (IsCf3VarString(BufferData(current_item))) + { + Buffer *temp = BufferCopy(current_item); + BufferClear(current_item); + ExpandScalar(ctx, ns, scope, BufferData(temp), current_item); + BufferDestroy(temp); + } + + if (!IsExpandable(BufferData(current_item))) + { + VarRef *ref = VarRefParseFromNamespaceAndScope( + BufferData(current_item), + ns, scope, CF_NS, '.'); + DataType value_type; + const void *value = EvalContextVariableGet(ctx, ref, &value_type); + VarRefDestroy(ref); + + switch (DataTypeToRvalType(value_type)) + { + case RVAL_TYPE_SCALAR: + assert(value != NULL); + BufferAppendString(out, value); + continue; + break; + + case RVAL_TYPE_CONTAINER: + { + assert(value != NULL); + const JsonElement *jvalue = value; /* instead of casts */ + if (JsonGetElementType(jvalue) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + BufferAppendString(out, JsonPrimitiveGetAsString(jvalue)); + continue; + } + break; + } + default: + /* TODO Log() */ + break; + } + } + + if (varstring == '{') + { + BufferAppendF(out, "${%s}", BufferData(current_item)); + } + else + { + BufferAppendF(out, "$(%s)", BufferData(current_item)); + } + } + + BufferDestroy(current_item); + + LogDebug(LOG_MOD_EXPAND, + "Expanded scalar '%s' to '%s' using %s namespace and %s scope.", + string, BufferData(out), (ns == NULL) ? "current" : ns, + (scope == NULL) ? "current" : scope); + + return out_belongs_to_us ? BufferClose(out) : BufferGet(out); +} + +/*********************************************************************/ + +Rval EvaluateFinalRval(EvalContext *ctx, const Policy *policy, + const char *ns, const char *scope, + Rval rval, bool forcelist, const Promise *pp) +{ + assert(ctx); + assert(policy); + Rval returnval; + + /* Treat lists specially. */ + if (rval.type == RVAL_TYPE_SCALAR && IsNakedVar(rval.item, '@')) + { + char naked[CF_MAXVARSIZE]; + GetNaked(naked, rval.item); + + if (IsExpandable(naked)) /* example: @(blah_$(blue)) */ + { + returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); + } + else + { + VarRef *ref = VarRefParseFromScope(naked, scope); + DataType value_type; + const void *value = EvalContextVariableGet(ctx, ref, &value_type); + VarRefDestroy(ref); + + if (DataTypeToRvalType(value_type) == RVAL_TYPE_LIST) + { + returnval.item = ExpandList(ctx, ns, scope, value, true); + returnval.type = RVAL_TYPE_LIST; + } + else + { + returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); + } + } + } + else if (forcelist) /* We are replacing scalar @(name) with list */ + { + returnval = ExpandPrivateRval(ctx, ns, scope, rval.item, rval.type); + } + else if (FnCallIsBuiltIn(rval)) + { + returnval = RvalCopy(rval); + } + else + { + returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); + } + + switch (returnval.type) + { + case RVAL_TYPE_SCALAR: + case RVAL_TYPE_CONTAINER: + break; + + case RVAL_TYPE_LIST: + for (Rlist *rp = RvalRlistValue(returnval); rp; rp = rp->next) + { + switch (rp->val.type) + { + case RVAL_TYPE_FNCALL: + { + FnCall *fp = RlistFnCallValue(rp); + rp->val = FnCallEvaluate(ctx, policy, fp, pp).rval; + FnCallDestroy(fp); + break; + } + case RVAL_TYPE_SCALAR: + if (EvalContextStackCurrentPromise(ctx) && + IsCf3VarString(RlistScalarValue(rp))) + { + void *prior = rp->val.item; + rp->val = ExpandPrivateRval(ctx, NULL, "this", + prior, RVAL_TYPE_SCALAR); + free(prior); + } + /* else: returnval unchanged. */ + break; + default: + assert(!"Bad type for entry in Rlist"); + } + } + break; + + case RVAL_TYPE_FNCALL: + if (FnCallIsBuiltIn(returnval)) + { + FnCall *fp = RvalFnCallValue(returnval); + returnval = FnCallEvaluate(ctx, policy, fp, pp).rval; + FnCallDestroy(fp); + } + break; + + default: + assert(returnval.item == NULL); /* else we're leaking it */ + returnval.item = NULL; + returnval.type = RVAL_TYPE_NOPROMISEE; + break; + } + + return returnval; +} + +/*********************************************************************/ + +void BundleResolvePromiseType(EvalContext *ctx, const Bundle *bundle, const char *type, PromiseActuator *actuator) +{ + for (size_t j = 0; j < SeqLength(bundle->sections); j++) + { + BundleSection *section = SeqAt(bundle->sections, j); + + if (strcmp(section->promise_type, type) == 0) + { + EvalContextStackPushBundleSectionFrame(ctx, section); + for (size_t i = 0; i < SeqLength(section->promises); i++) + { + Promise *pp = SeqAt(section->promises, i); + ExpandPromise(ctx, pp, actuator, NULL); + } + EvalContextStackPopFrame(ctx); + } + } +} + +static int PointerCmp(const void *a, const void *b, ARG_UNUSED void *user_data) +{ + if (a < b) + { + return -1; + } + else if (a == b) + { + return 0; + } + else + { + return 1; + } +} + +static void RemoveRemotelyInjectedVars(const EvalContext *ctx, const Bundle *bundle) +{ + const Seq *remote_var_promises = EvalContextGetRemoteVarPromises(ctx, bundle->name); + if ((remote_var_promises == NULL) || SeqLength(remote_var_promises) == 0) + { + /* nothing to do here */ + return; + } + + size_t promises_length = SeqLength(remote_var_promises); + Seq *remove_vars = SeqNew(promises_length, NULL); + + /* remove variables that have been attempted to be inserted into this + * bundle */ + /* TODO: this is expensive and should be removed! */ + for (size_t i = 0; i < promises_length; i++) + { + const Promise *pp = (Promise *) SeqAt(remote_var_promises, i); + + VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, bundle->name, NULL); + const Variable *var = VariableTableIteratorNext(iter); + while (var != NULL) + { + /* variables are stored together with their original promises (org_pp) */ + const Promise *var_promise = VariableGetPromise(var); + const VarRef *var_ref = VariableGetRef(var); + if (var_promise && var_promise->org_pp == pp) + { + Log(LOG_LEVEL_ERR, "Ignoring remotely-injected variable '%s'", + var_ref->lval); + /* avoid modifications of the variable table being iterated + * over and avoid trying to remove the same variable twice */ + SeqAppendOnce(remove_vars, (void *) var, PointerCmp); + } + var = VariableTableIteratorNext(iter); + } + VariableTableIteratorDestroy(iter); + } + + /* iteration over the variable table done, time to remove the variables */ + size_t remove_vars_length = SeqLength(remove_vars); + for (size_t i = 0; i < remove_vars_length; i++) + { + Variable *var = (Variable *) SeqAt(remove_vars, i); + const VarRef *var_ref = VariableGetRef(var); + if (var_ref != NULL) + { + EvalContextVariableRemove(ctx, var_ref); + } + } + SeqDestroy(remove_vars); +} + +void BundleResolve(EvalContext *ctx, const Bundle *bundle) +{ + Log(LOG_LEVEL_DEBUG, + "Resolving classes and variables in 'bundle %s %s'", + bundle->type, bundle->name); + + /* first check if some variables were injected remotely into this bundle and + * remove them (CFE-1915) */ + RemoveRemotelyInjectedVars(ctx, bundle); + + /* PRE-EVAL: evaluate classes of common bundles. */ + if (strcmp(bundle->type, "common") == 0) + { + /* Necessary to parse vars *before* classes for cases like this: + * 00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf.sub + * -- see bundle "classify". */ + BundleResolvePromiseType(ctx, bundle, "vars", VerifyVarPromise); + + BundleResolvePromiseType(ctx, bundle, "classes", VerifyClassPromise); + } + + /* Necessary to also parse vars *after* classes, + * because "inputs" might be affected in cases like: + * 00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf */ + BundleResolvePromiseType(ctx, bundle, "vars", VerifyVarPromise); +} + +/** + * Evaluate the relevant control body, and set the + * relevant fields in #ctx and #config. + */ +static void ResolveControlBody(EvalContext *ctx, GenericAgentConfig *config, + const Body *control_body) +{ + const char *filename = control_body->source_path; + + assert(CFG_CONTROLBODY[COMMON_CONTROL_MAX].lval == NULL); + + const ConstraintSyntax *body_syntax = NULL; + for (int i = 0; CONTROL_BODIES[i].constraints != NULL; i++) + { + body_syntax = CONTROL_BODIES[i].constraints; + + if (strcmp(control_body->type, CONTROL_BODIES[i].body_type) == 0) + { + break; + } + } + if (body_syntax == NULL) + { + FatalError(ctx, "Unknown control body: %s", control_body->type); + } + + char *scope; + assert(strcmp(control_body->name, "control") == 0); + xasprintf(&scope, "control_%s", control_body->type); + + Log(LOG_LEVEL_DEBUG, "Initiate control variable convergence for scope '%s'", scope); + + EvalContextStackPushBodyFrame(ctx, NULL, control_body, NULL); + + for (size_t i = 0; i < SeqLength(control_body->conlist); i++) + { + const char *lval; + Rval evaluated_rval; + size_t lineno; + + /* Use nested scope to constrain cp. */ + { + Constraint *cp = SeqAt(control_body->conlist, i); + lval = cp->lval; + lineno = cp->offset.line; + + if (!IsDefinedClass(ctx, cp->classes)) + { + continue; + } + + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_BUNDLESEQUENCE].lval) == 0) + { + evaluated_rval = ExpandPrivateRval(ctx, NULL, scope, + cp->rval.item, cp->rval.type); + } + else + { + evaluated_rval = EvaluateFinalRval(ctx, control_body->parent_policy, + NULL, scope, cp->rval, + true, NULL); + } + + } /* Close scope: assert we only use evaluated_rval, not cp->rval. */ + + VarRef *ref = VarRefParseFromScope(lval, scope); + EvalContextVariableRemove(ctx, ref); + + DataType rval_proper_datatype = + ConstraintSyntaxGetDataType(body_syntax, lval); + if (evaluated_rval.type != DataTypeToRvalType(rval_proper_datatype)) + { + Log(LOG_LEVEL_ERR, + "Attribute '%s' in %s:%zu is of wrong type, skipping", + lval, filename, lineno); + VarRefDestroy(ref); + RvalDestroy(evaluated_rval); + continue; + } + + bool success = EvalContextVariablePut( + ctx, ref, evaluated_rval.item, rval_proper_datatype, + "source=promise"); + if (!success) + { + Log(LOG_LEVEL_ERR, + "Attribute '%s' in %s:%zu can't be added, skipping", + lval, filename, lineno); + VarRefDestroy(ref); + RvalDestroy(evaluated_rval); + continue; + } + + VarRefDestroy(ref); + + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_OUTPUT_PREFIX].lval) == 0) + { + strlcpy(VPREFIX, RvalScalarValue(evaluated_rval), + sizeof(VPREFIX)); + } + + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_DOMAIN].lval) == 0) + { + strlcpy(VDOMAIN, RvalScalarValue(evaluated_rval), + sizeof(VDOMAIN)); + Log(LOG_LEVEL_VERBOSE, "SET domain = %s", VDOMAIN); + + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "domain"); + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost"); + + // We don't expect hostname or domain name longer than 255, + // warnings are printed in sysinfo.c. + // Here we support up to 511 bytes, just in case, because we can: + snprintf(VFQNAME, CF_MAXVARSIZE, "%511s.%511s", VUQNAME, VDOMAIN); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost", + VFQNAME, CF_DATA_TYPE_STRING, + "inventory,source=agent,attribute_name=Host name"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "domain", + VDOMAIN, CF_DATA_TYPE_STRING, + "source=agent"); + EvalContextClassPutHard(ctx, VDOMAIN, "source=agent"); + } + + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_INPUTS].lval) == 0) + { + Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_inputs %s", + RvalScalarValue(evaluated_rval)); + if (StringIsBoolean(RvalScalarValue(evaluated_rval))) + { + config->ignore_missing_inputs = BooleanFromString( + RvalScalarValue(evaluated_rval)); + } + } + + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_BUNDLES].lval) == 0) + { + Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_bundles %s", + RvalScalarValue(evaluated_rval)); + if (StringIsBoolean(RvalScalarValue(evaluated_rval))) + { + config->ignore_missing_bundles = BooleanFromString( + RvalScalarValue(evaluated_rval)); + } + } + + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_CACHE_SYSTEM_FUNCTIONS].lval) == 0) + { + Log(LOG_LEVEL_VERBOSE, "SET cache_system_functions %s", + RvalScalarValue(evaluated_rval)); + bool cache_system_functions = BooleanFromString( + RvalScalarValue(evaluated_rval)); + EvalContextSetEvalOption(ctx, EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS, + cache_system_functions); + } + + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PROTOCOL_VERSION].lval) == 0) + { + config->protocol_version = ProtocolVersionParse( + RvalScalarValue(evaluated_rval)); + Log(LOG_LEVEL_VERBOSE, "SET common protocol_version: %s", + ProtocolVersionString(config->protocol_version)); + } + + /* Those are package_inventory and package_module common control body options */ + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_INVENTORY].lval) == 0) + { + AddDefaultInventoryToContext(ctx, RvalRlistValue(evaluated_rval)); + Log(LOG_LEVEL_VERBOSE, "SET common package_inventory list"); + } + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_MODULE].lval) == 0) + { + AddDefaultPackageModuleToContext(ctx, RvalScalarValue(evaluated_rval)); + Log(LOG_LEVEL_VERBOSE, "SET common package_module: %s", + RvalScalarValue(evaluated_rval)); + } + + if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_GOALPATTERNS].lval) == 0) + { + /* Ignored */ + } + + RvalDestroy(evaluated_rval); + } + + EvalContextStackPopFrame(ctx); + free(scope); +} + +static void ResolvePackageManagerBody(EvalContext *ctx, const Body *pm_body) +{ + PackageModuleBody *new_manager = xcalloc(1, sizeof(PackageModuleBody)); + new_manager->name = SafeStringDuplicate(pm_body->name); + + for (size_t i = 0; i < SeqLength(pm_body->conlist); i++) + { + Constraint *cp = SeqAt(pm_body->conlist, i); + + Rval returnval = {0}; + + if (IsDefinedClass(ctx, cp->classes)) + { + returnval = ExpandPrivateRval(ctx, NULL, "body", + cp->rval.item, cp->rval.type); + } + + if (returnval.item == NULL || returnval.type == RVAL_TYPE_NOPROMISEE) + { + Log(LOG_LEVEL_VERBOSE, "have invalid constraint while resolving" + "package promise body: %s", cp->lval); + + RvalDestroy(returnval); + continue; + } + + if (strcmp(cp->lval, "query_installed_ifelapsed") == 0) + { + new_manager->installed_ifelapsed = + (int)IntFromString(RvalScalarValue(returnval)); + } + else if (strcmp(cp->lval, "query_updates_ifelapsed") == 0) + { + new_manager->updates_ifelapsed = + (int)IntFromString(RvalScalarValue(returnval)); + } + else if (strcmp(cp->lval, "default_options") == 0) + { + new_manager->options = RlistCopy(RvalRlistValue(returnval)); + } + else if (strcmp(cp->lval, "interpreter") == 0) + { + assert(new_manager->interpreter == NULL); + new_manager->interpreter = SafeStringDuplicate(RvalScalarValue(returnval)); + } + else if (strcmp(cp->lval, "module_path") == 0) + { + assert(new_manager->module_path == NULL); + new_manager->module_path = SafeStringDuplicate(RvalScalarValue(returnval)); + } + else + { + /* This should be handled by the parser. */ + assert(0); + } + RvalDestroy(returnval); + } + AddPackageModuleToContext(ctx, new_manager); +} + +void PolicyResolve(EvalContext *ctx, const Policy *policy, + GenericAgentConfig *config) +{ + /* PRE-EVAL: common bundles: classes,vars. */ + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + Bundle *bundle = SeqAt(policy->bundles, i); + if (strcmp("common", bundle->type) == 0) + { + EvalContextStackPushBundleFrame(ctx, bundle, NULL, false); + BundleResolve(ctx, bundle); /* PRE-EVAL classes,vars */ + EvalContextStackPopFrame(ctx); + } + } + +/* + * HACK: yet another pre-eval pass here, WHY? TODO remove, but test fails: + * 00_basics/03_bodies/dynamic_inputs_findfiles.cf + */ +#if 1 + + /* PRE-EVAL: non-common bundles: only vars. */ + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + Bundle *bundle = SeqAt(policy->bundles, i); + if (strcmp("common", bundle->type) != 0) + { + EvalContextStackPushBundleFrame(ctx, bundle, NULL, false); + BundleResolve(ctx, bundle); /* PRE-EVAL vars */ + EvalContextStackPopFrame(ctx); + } + } + +#endif + + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + Body *bdp = SeqAt(policy->bodies, i); + + if (strcmp(bdp->name, "control") == 0) + { + ResolveControlBody(ctx, config, bdp); + } + /* Collect all package managers data from policy as we don't know yet + * which ones we will use. */ + else if (strcmp(bdp->type, "package_module") == 0) + { + ResolvePackageManagerBody(ctx, bdp); + } + } +} + +bool IsExpandable(const char *str) +{ + char left = 'x', right = 'x'; + int dollar = false; + int bracks = 0, vars = 0; + + for (const char *sp = str; *sp != '\0'; sp++) /* check for varitems */ + { + switch (*sp) + { + case '$': + if (*(sp + 1) == '{' || *(sp + 1) == '(') + { + dollar = true; + } + break; + case '(': + case '{': + if (dollar) + { + left = *sp; + bracks++; + } + break; + case ')': + case '}': + if (dollar) + { + bracks--; + right = *sp; + } + break; + } + + if (left == '(' && right == ')' && dollar && (bracks == 0)) + { + vars++; + dollar = false; + } + + if (left == '{' && right == '}' && dollar && (bracks == 0)) + { + vars++; + dollar = false; + } + } + + if (bracks != 0) + { + Log(LOG_LEVEL_DEBUG, "If this is an expandable variable string then it contained syntax errors"); + return false; + } + + if (vars > 0) + { + Log(LOG_LEVEL_DEBUG, + "Expanding variable '%s': found %d variables", str, vars); + } + return (vars > 0); +} + +/*********************************************************************/ + +static inline char opposite(char c) +{ + switch (c) + { + case '(': return ')'; + case '{': return '}'; + default : ProgrammingError("Was expecting '(' or '{' but got: '%c'", c); + } + return 0; +} + +/** + * Check if #str contains one and only one variable expansion of #vtype kind + * (it's usually either '$' or '@'). It can contain nested expansions which + * are not checked properly. Examples: + * true: "$(whatever)", "${whatever}", "$(blah$(blue))" + * false: "$(blah)blue", "blah$(blue)", "$(blah)$(blue)", "$(blah}" + */ +bool IsNakedVar(const char *str, char vtype) +{ + size_t len = strlen(str); + char last = len > 0 ? str[len-1] : '\0'; + + if (len < 3 + || str[0] != vtype + || (str[1] != '(' && str[1] != '{') + || last != opposite(str[1])) + { + return false; + } + + /* TODO check if nesting happens correctly? Is it needed? */ + size_t count = 0; + for (const char *sp = str; *sp != '\0'; sp++) + { + switch (*sp) + { + case '(': + case '{': + count++; + break; + case ')': + case '}': + count--; + + /* Make sure the end of the variable is the last character. */ + if (count == 0 && sp[1] != '\0') + { + return false; + } + + break; + } + } + + if (count != 0) + { + return false; + } + + return true; +} + +/*********************************************************************/ + +/** + * Copy @(listname) -> listname. + * + * This function performs no validations, it is necessary to call the + * validation functions before calling this function. + * + * @NOTE make sure sizeof(dst) >= sizeof(s) + */ +void GetNaked(char *dst, const char *s) +{ + size_t s_len = strlen(s); + + if (s_len < 4 || s_len + 3 >= CF_MAXVARSIZE) + { + Log(LOG_LEVEL_ERR, + "@(variable) expected, but got malformed: %s", s); + strlcpy(dst, s, CF_MAXVARSIZE); + return; + } + + memcpy(dst, &s[2], s_len - 3); + dst[s_len - 3] = '\0'; +} + +/*********************************************************************/ + +/** + * Checks if a variable is an @-list and returns true or false. + */ +bool IsVarList(const char *var) +{ + if ('@' != var[0]) + { + return false; + } + /* + * Minimum size for a list is 4: + * '@' + '(' + name + ')' + */ + if (strlen(var) < 4) + { + return false; + } + return true; +} + +PromiseResult CommonEvalPromise(EvalContext *ctx, const Promise *pp, + ARG_UNUSED void *param) +{ + assert(param == NULL); + + PromiseRecheckAllConstraints(ctx, pp); + + return PROMISE_RESULT_NOOP; +} diff --git a/libpromises/expand.h b/libpromises/expand.h new file mode 100644 index 0000000000..6a35c26ede --- /dev/null +++ b/libpromises/expand.h @@ -0,0 +1,59 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_EXPAND_H +#define CFENGINE_EXPAND_H + + +#include +#include +#include + + +PromiseResult CommonEvalPromise(EvalContext *ctx, const Promise *pp, void *param); + +PromiseResult ExpandPromise(EvalContext *ctx, const Promise *pp, PromiseActuator *ActOnPromise, void *param); + +Rval ExpandDanglers(EvalContext *ctx, const char *ns, const char *scope, Rval rval, const Promise *pp); + +bool IsExpandable(const char *str); + +char *ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, + const char *string, Buffer *out); +Rval ExpandBundleReference(EvalContext *ctx, const char *ns, const char *scope, Rval rval); +Rval ExpandPrivateRval(const EvalContext *ctx, const char *ns, const char *scope, const void *rval_item, RvalType rval_type); +Rlist *ExpandList(const EvalContext *ctx, const char *ns, const char *scope, const Rlist *list, int expandnaked); +Rval EvaluateFinalRval(EvalContext *ctx, const Policy *policy, const char *ns, const char *scope, Rval rval, bool forcelist, const Promise *pp); + +void BundleResolve(EvalContext *ctx, const Bundle *bundle); +void PolicyResolve(EvalContext *ctx, const Policy *policy, GenericAgentConfig *config); +void BundleResolvePromiseType(EvalContext *ctx, const Bundle *bundle, const char *type, PromiseActuator *actuator); + +bool IsNakedVar(const char *str, char vtype); +void GetNaked(char *dst, const char *s); +bool IsVarList(const char *var); + +#include // ProtocolVersionParse() TODO: Remove + +#endif diff --git a/libpromises/extensions.c b/libpromises/extensions.c new file mode 100644 index 0000000000..fb415831a1 --- /dev/null +++ b/libpromises/extensions.c @@ -0,0 +1,187 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#include /* sscanf() */ +#include /* getenv() */ +#include /* strcmp() */ + +#include +#include + +#include + +/* + * A note regarding the loading of the extension plugins: + * + * The extension plugin was originally statically linked into each agent, + * but was then refactored into plugins. + * Therefore, since it hasn't been written according to plugin guidelines, + * it is not safe to assume that we can unload it once we have + * loaded it, since it may allocate resources that are not freed. It is also + * not safe to assume that we can load it after initialization is complete + * (for example if the plugin is dropped into the directory after cf-serverd + * has already been running for some time), because some data structures may + * not have been initialized. + * + * Therefore, the load strategy is as follows: + * + * - If we find the plugin immediately, load it, and keep it loaded. + * + * - If we don't find the plugin, NEVER attempt to load it again afterwards. + * + * - Never unload the plugin. + * + * - Any installation/upgrade/removal of the plugin requires daemon restarts. + * + * - The exception is for testing (see getenv below). + */ + +#ifndef BUILTIN_EXTENSIONS + +static bool enable_extension_libraries = true; /* GLOBAL_X */ +static bool attempted_loading = false; /* GLOBAL_X */ + +void extension_libraries_disable() +{ + if (attempted_loading) + { + ProgrammingError("extension_libraries_disable() MUST be called before any call to extension functions"); + } + enable_extension_libraries = false; +} + +void *extension_library_open(const char *name) +{ + if (!enable_extension_libraries) + { + return NULL; + } + + if (getenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DO_CLOSE") == NULL) + { + // Only do loading checks if we are not doing tests. + attempted_loading = true; + } + + const char *dirs_to_try[3] = { NULL }; + const char *dir = getenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR"); + char lib[] = "/lib"; + if (dir) + { + lib[0] = '\0'; + dirs_to_try[0] = dir; + } + else + { + dirs_to_try[0] = GetWorkDir(); + if (strcmp(WORKDIR, dirs_to_try[0]) != 0) + { + // try to load from the real WORKDIR in case GetWorkDir returned the local workdir to the user + // We try this because enterprise "make install" is in WORKDIR, not per user + dirs_to_try[1] = WORKDIR; + } + } + + void *handle = NULL; + for (int i = 0; dirs_to_try[i]; i++) + { + char path[strlen(dirs_to_try[i]) + strlen(lib) + strlen(name) + 2]; + xsnprintf(path, sizeof(path), "%s%s/%s", dirs_to_try[i], lib, name); + + Log(LOG_LEVEL_DEBUG, "Trying to shlib_open extension plugin '%s' from '%s'", name, path); + + handle = shlib_open(path); + if (handle) + { + Log(LOG_LEVEL_VERBOSE, "Successfully opened extension plugin '%s' from '%s'", name, path); + break; + } + else + { + const char *error; + if (errno == ENOENT) + { + error = "(not installed)"; + } + else + { + error = GetErrorStr(); + } + Log(LOG_LEVEL_VERBOSE, "Could not open extension plugin '%s' from '%s': %s", name, path, error); + } + } + + if (!handle) + { + return handle; + } + + // Version check, to avoid binary incompatible plugins. + const char * (*GetExtensionLibraryVersion)() = shlib_load(handle, "GetExtensionLibraryVersion"); + if (!GetExtensionLibraryVersion) + { + Log(LOG_LEVEL_ERR, "Could not retrieve version from extension plugin (%s). Not loading the plugin.", name); + goto close_and_fail; + } + + const char *plugin_version = GetExtensionLibraryVersion(); + unsigned int bin_major, bin_minor, bin_patch; + unsigned int plug_major, plug_minor, plug_patch; + if (sscanf(VERSION, "%u.%u.%u", &bin_major, &bin_minor, &bin_patch) != 3) + { + Log(LOG_LEVEL_ERR, "Not able to extract version number from binary (%s). Not loading extension plugin.", name); + goto close_and_fail; + } + if (sscanf(plugin_version, "%u.%u.%u", &plug_major, &plug_minor, &plug_patch) != 3) + { + Log(LOG_LEVEL_ERR, "Not able to extract version number from plugin (%s). Not loading extension plugin.", name); + goto close_and_fail; + } + + if (bin_major != plug_major || bin_minor != plug_minor || bin_patch != plug_patch) + { + Log(LOG_LEVEL_ERR, "Extension plugin version does not match CFEngine Community version " + "(CFEngine Community v%u.%u.%u, Extension (%s) v%u.%u.%u). Refusing to load it.", + bin_major, bin_minor, bin_patch, name, plug_major, plug_minor, plug_patch); + goto close_and_fail; + } + + Log(LOG_LEVEL_VERBOSE, "Successfully loaded extension plugin '%s'", name); + + return handle; + +close_and_fail: + shlib_close(handle); + return NULL; +} + +void extension_library_close(void *handle) +{ + shlib_close(handle); +} + +#endif // !BUILTIN_EXTENSIONS diff --git a/libpromises/extensions.h b/libpromises/extensions.h new file mode 100644 index 0000000000..010172647b --- /dev/null +++ b/libpromises/extensions.h @@ -0,0 +1,40 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#ifdef BUILTIN_EXTENSIONS + +// No effect if using builtin extensions. +static inline void extension_libraries_disable() +{ +} + +#else // !BUILTIN_EXTENSIONS + +void extension_libraries_disable(); +void *extension_library_open(const char *name); +void extension_library_close(void *handle); + +#endif // !BUILTIN_EXTENSIONS diff --git a/libpromises/extensions_template.c.pre b/libpromises/extensions_template.c.pre new file mode 100644 index 0000000000..e06ededf9c --- /dev/null +++ b/libpromises/extensions_template.c.pre @@ -0,0 +1,75 @@ +/* + Copyright 2021 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +BEGIN_MARKER // Do not include this file directly! Build process should replace XextensionX occurrences. + +#include + +#include /* getenv() */ +#include /* strerror() */ + +#include + +#include + +#ifndef BUILTIN_EXTENSIONS + +static pthread_once_t XextensionX_library_once = PTHREAD_ONCE_INIT; +static void *XextensionX_library_handle = NULL; + +static void XextensionX_library_assign(); + +void *XextensionX_library_open() +{ + if (getenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DO_CLOSE") != NULL) + { + return extension_library_open(xEXTENSIONx_LIBRARY_NAME); + } + + int ret = pthread_once(&XextensionX_library_once, &XextensionX_library_assign); + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "Could not initialize Extension Library: %s: %s", xEXTENSIONx_LIBRARY_NAME, strerror(ret)); + return NULL; + } + return XextensionX_library_handle; +} + +static void XextensionX_library_assign() +{ + XextensionX_library_handle = extension_library_open(xEXTENSIONx_LIBRARY_NAME); +} + +void XextensionX_library_close(void *handle) +{ + if (getenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DO_CLOSE") != NULL) + { + return extension_library_close(handle); + } + + // Normally we don't ever close the extension library, because we may have + // pointer references to it. +} + +#endif // BUILTIN_EXTENSIONS diff --git a/libpromises/extensions_template.h.pre b/libpromises/extensions_template.h.pre new file mode 100644 index 0000000000..228f3b256c --- /dev/null +++ b/libpromises/extensions_template.h.pre @@ -0,0 +1,1484 @@ +/* + Copyright 2021 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +BEGIN_MARKER // Do not include this file directly! Build process should replace XextensionX occurrences. + +/******************************************************************************* + * Note: This shared library calling mechanism should only be used for private + * shared libraries, such as the Enterprise plugins for CFEngine. + ******************************************************************************/ + +/******************************************************************************* + * How to use the Extension calling API: + * + * In the application: + * + * - Declare function prototypes using the xEXTENSIONx_FUNC_xARG_DECLARE macros. + * Replace x with the number of arguments to the function. + * + * - Define a stub function using the xEXTENSIONx_FUNC_xARG_DEFINE_STUB macros. + * + * In the xExtensionX plugin: + * + * - Include the same prototype as you used in the application. + * + * - Define the xExtensionX function with the xEXTENSIONx_FUNC_xARG_DEFINE + * macros. + * + * IMPORTANT: + * + * - For functions returning void, you need to use the VOID_FUNC version of the + * macros. + * + * - Due to macro limitations, for each function argument, you need to put the + * type and the name as separate arguments, so instead of (int par), use + * (int, par). + * + * - You can use the function normally in your code. If the xExtensionX plugin + * is available, it will call that function, if not, it will call the stub + * function. + * + * - The lookup is more expensive than a normal function call. Don't use it in + * a tight loop. + * + * - Be careful when changing the function signature. The plugin needs + * to stay binary compatible, otherwise the it will not work, and may even + * crash. Signature changes should only happen between major releases. + * + * Examples: + * + * - int xExtensionXOnlyInt(const char *str, int num) becomes + * xEXTENSIONx_FUNC_2ARG_DECLARE(int, xExtensionXOnlyInt, + * const char *, str, int, num) + * xEXTENSIONx_FUNC_2ARG_DEFINE_STUB(int, xExtensionXOnlyInt, + * const char *, str, int, num) + * xEXTENSIONx_FUNC_2ARG_DEFINE(int, xExtensionXOnlyInt, + * const char *, str, int, num) + * + * - void xExtensionXFunc(int num) becomes + * xEXTENSIONx_VOID_FUNC_1ARG_DECLARE(void, xExtensionXFunc, int, num) + * xEXTENSIONx_VOID_FUNC_1ARG_DEFINE_STUB(void, xExtensionXFunc, int, num) + * xEXTENSIONx_VOID_FUNC_1ARG_DEFINE(void, xExtensionXFunc, int, num) + ******************************************************************************/ + + +/******************************************************************************* + * It may be easier to understand the implementation below by looking at the + * output it actually produces. gcc -E is your friend. + ******************************************************************************/ + +#ifndef xEXTENSIONx_EXTENSION_H +#define xEXTENSIONx_EXTENSION_H + +#include +#include + +#include + +#ifndef BUILTIN_EXTENSIONS + +#define xEXTENSIONx_CANARY_VALUE 0x10203040 +#define xEXTENSIONx_LIBRARY_NAME "cfengine-XextensionX.so" + +void *XextensionX_library_open(); +void XextensionX_library_close(void *handle); + +#ifndef BUILDING_xEXTENSIONx_EXTENSION +# define xEXTENSIONx_FUNC_0ARG_WRAPPER_SIGNATURE(__ret, __func) \ + __ret __func() +# define xEXTENSIONx_FUNC_1ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1) \ + __ret __func(__t1 __p1) +# define xEXTENSIONx_FUNC_2ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + __ret __func(__t1 __p1, __t2 __p2) +# define xEXTENSIONx_FUNC_3ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3) +# define xEXTENSIONx_FUNC_4ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4) +# define xEXTENSIONx_FUNC_5ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5) +# define xEXTENSIONx_FUNC_6ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6) +# define xEXTENSIONx_FUNC_7ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7) +# define xEXTENSIONx_FUNC_8ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8) +# define xEXTENSIONx_FUNC_9ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9) +# define xEXTENSIONx_FUNC_10ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10) +# define xEXTENSIONx_FUNC_11ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11) +# define xEXTENSIONx_FUNC_12ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12) +# define xEXTENSIONx_FUNC_13ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13) +# define xEXTENSIONx_FUNC_14ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14) +# define xEXTENSIONx_FUNC_15ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, __t15 __p15) + +# define xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) \ + __ret __func##__stub() +# define xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) \ + __ret __func##__stub(__t1 __p1) +# define xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + __ret __func##__stub(__t1 __p1, __t2 __p2) +# define xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3) +# define xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4) +# define xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5) +# define xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6) +# define xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7) +# define xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8) +# define xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9) +# define xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10) +# define xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11) +# define xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12) +# define xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13) +# define xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14) +# define xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, __t15 __p15) + +#else // BUILDING_xEXTENSIONx_EXTENSION + +# define xEXTENSIONx_FUNC_0ARG_WRAPPER_SIGNATURE(__ret, __func) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, int32_t __end_canary) +# define xEXTENSIONx_FUNC_1ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, int32_t __end_canary) +# define xEXTENSIONx_FUNC_2ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, int32_t __end_canary) +# define xEXTENSIONx_FUNC_3ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, int32_t __end_canary) +# define xEXTENSIONx_FUNC_4ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, int32_t __end_canary) +# define xEXTENSIONx_FUNC_5ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, int32_t __end_canary) +# define xEXTENSIONx_FUNC_6ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, int32_t __end_canary) +# define xEXTENSIONx_FUNC_7ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, int32_t __end_canary) +# define xEXTENSIONx_FUNC_8ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, int32_t __end_canary) +# define xEXTENSIONx_FUNC_9ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, int32_t __end_canary) +# define xEXTENSIONx_FUNC_10ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, int32_t __end_canary) +# define xEXTENSIONx_FUNC_11ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, int32_t __end_canary) +# define xEXTENSIONx_FUNC_12ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, int32_t __end_canary) +# define xEXTENSIONx_FUNC_13ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, int32_t __end_canary) +# define xEXTENSIONx_FUNC_14ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, int32_t __end_canary) +# define xEXTENSIONx_FUNC_15ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + __ret __func##__wrapper(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, __t15 __p15, int32_t __end_canary) + +# define xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) \ + __ret __func##__real() +# define xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) \ + __ret __func##__real(__t1 __p1) +# define xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + __ret __func##__real(__t1 __p1, __t2 __p2) +# define xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3) +# define xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4) +# define xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5) +# define xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6) +# define xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7) +# define xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8) +# define xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9) +# define xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10) +# define xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11) +# define xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12) +# define xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13) +# define xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14) +# define xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + __ret __func##__real(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, __t15 __p15) + +# define xEXTENSIONx_FUNC_0ARG_INLINE_SIGNATURE(__ret, __func) \ + inline static __ret __func() +# define xEXTENSIONx_FUNC_1ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1) \ + inline static __ret __func(__t1 __p1) +# define xEXTENSIONx_FUNC_2ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + inline static __ret __func(__t1 __p1, __t2 __p2) +# define xEXTENSIONx_FUNC_3ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3) +# define xEXTENSIONx_FUNC_4ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4) +# define xEXTENSIONx_FUNC_5ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5) +# define xEXTENSIONx_FUNC_6ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6) +# define xEXTENSIONx_FUNC_7ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7) +# define xEXTENSIONx_FUNC_8ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8) +# define xEXTENSIONx_FUNC_9ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9) +# define xEXTENSIONx_FUNC_10ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10) +# define xEXTENSIONx_FUNC_11ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11) +# define xEXTENSIONx_FUNC_12ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12) +# define xEXTENSIONx_FUNC_13ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13) +# define xEXTENSIONx_FUNC_14ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14) +# define xEXTENSIONx_FUNC_15ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + inline static __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, __t15 __p15) + +#endif // BUILDING_xEXTENSIONx_EXTENSION + +#ifndef BUILDING_xEXTENSIONx_EXTENSION + +# define xEXTENSIONx_FUNC_0ARG_DECLARE_IMPL(__ret, __func) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, int32_t __end_canary); \ + xEXTENSIONx_FUNC_0ARG_WRAPPER_SIGNATURE(__ret, __func); \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) + +# define xEXTENSIONx_FUNC_1ARG_DECLARE_IMPL(__ret, __func, __t1, __p1) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, int32_t __end_canary); \ + xEXTENSIONx_FUNC_1ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1); \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) + +# define xEXTENSIONx_FUNC_2ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, int32_t __end_canary); \ + xEXTENSIONx_FUNC_2ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2); \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) + +# define xEXTENSIONx_FUNC_3ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, int32_t __end_canary); \ + xEXTENSIONx_FUNC_3ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3); \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) + +# define xEXTENSIONx_FUNC_4ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, int32_t __end_canary); \ + xEXTENSIONx_FUNC_4ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4); \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) + +# define xEXTENSIONx_FUNC_5ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, int32_t __end_canary); \ + xEXTENSIONx_FUNC_5ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5); \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) + +# define xEXTENSIONx_FUNC_6ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, int32_t __end_canary); \ + xEXTENSIONx_FUNC_6ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6); \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) + +# define xEXTENSIONx_FUNC_7ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, int32_t __end_canary); \ + xEXTENSIONx_FUNC_7ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7); \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) + +# define xEXTENSIONx_FUNC_8ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, int32_t __end_canary); \ + xEXTENSIONx_FUNC_8ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8); \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) + +# define xEXTENSIONx_FUNC_9ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, int32_t __end_canary); \ + xEXTENSIONx_FUNC_9ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9); \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +# define xEXTENSIONx_FUNC_10ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, int32_t __end_canary); \ + xEXTENSIONx_FUNC_10ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10); \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +# define xEXTENSIONx_FUNC_11ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, int32_t __end_canary); \ + xEXTENSIONx_FUNC_11ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11); \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +# define xEXTENSIONx_FUNC_12ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, int32_t __end_canary); \ + xEXTENSIONx_FUNC_12ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12); \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +# define xEXTENSIONx_FUNC_13ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, int32_t __end_canary); \ + xEXTENSIONx_FUNC_13ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13); \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +# define xEXTENSIONx_FUNC_14ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, int32_t __end_canary); \ + xEXTENSIONx_FUNC_14ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14); \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +# define xEXTENSIONx_FUNC_15ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + typedef __ret (*__func##__type)(int32_t __start_canary, int *__successful, __t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, __t15 __p15, int32_t __end_canary); \ + xEXTENSIONx_FUNC_15ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15); \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + + +# define xEXTENSIONx_FUNC_0ARG_DECLARE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_DECLARE_IMPL(__ret, __func) +# define xEXTENSIONx_FUNC_1ARG_DECLARE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_DECLARE_IMPL(__ret, __func, __t1, __p1) +# define xEXTENSIONx_FUNC_2ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2) +# define xEXTENSIONx_FUNC_3ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +# define xEXTENSIONx_FUNC_4ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +# define xEXTENSIONx_FUNC_5ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +# define xEXTENSIONx_FUNC_6ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +# define xEXTENSIONx_FUNC_7ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +# define xEXTENSIONx_FUNC_8ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +# define xEXTENSIONx_FUNC_9ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +# define xEXTENSIONx_FUNC_10ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +# define xEXTENSIONx_FUNC_11ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +# define xEXTENSIONx_FUNC_12ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +# define xEXTENSIONx_FUNC_13ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +# define xEXTENSIONx_FUNC_14ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +# define xEXTENSIONx_FUNC_15ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +# define xEXTENSIONx_VOID_FUNC_0ARG_DECLARE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_DECLARE_IMPL(__ret, __func) +# define xEXTENSIONx_VOID_FUNC_1ARG_DECLARE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_DECLARE_IMPL(__ret, __func, __t1, __p1) +# define xEXTENSIONx_VOID_FUNC_2ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2) +# define xEXTENSIONx_VOID_FUNC_3ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +# define xEXTENSIONx_VOID_FUNC_4ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +# define xEXTENSIONx_VOID_FUNC_5ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +# define xEXTENSIONx_VOID_FUNC_6ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +# define xEXTENSIONx_VOID_FUNC_7ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +# define xEXTENSIONx_VOID_FUNC_8ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +# define xEXTENSIONx_VOID_FUNC_9ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +# define xEXTENSIONx_VOID_FUNC_10ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +# define xEXTENSIONx_VOID_FUNC_11ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +# define xEXTENSIONx_VOID_FUNC_12ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +# define xEXTENSIONx_VOID_FUNC_13ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +# define xEXTENSIONx_VOID_FUNC_14ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +# define xEXTENSIONx_VOID_FUNC_15ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_DECLARE_IMPL(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#else // BUILDING_xEXTENSIONx_EXTENSION + +# define xEXTENSIONx_FUNC_0ARG_DECLARE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func); \ + xEXTENSIONx_FUNC_0ARG_INLINE_SIGNATURE(__ret, __func) \ + { \ + return __func##__real(); \ + } \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) +# define xEXTENSIONx_FUNC_1ARG_DECLARE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1); \ + xEXTENSIONx_FUNC_1ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1) \ + { \ + return __func##__real(__p1); \ + } \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) +# define xEXTENSIONx_FUNC_2ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2); \ + xEXTENSIONx_FUNC_2ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + { \ + return __func##__real(__p1, __p2); \ + } \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) +# define xEXTENSIONx_FUNC_3ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3); \ + xEXTENSIONx_FUNC_3ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + { \ + return __func##__real(__p1, __p2, __p3); \ + } \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +# define xEXTENSIONx_FUNC_4ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4); \ + xEXTENSIONx_FUNC_4ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4); \ + } \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +# define xEXTENSIONx_FUNC_5ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5); \ + xEXTENSIONx_FUNC_5ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5); \ + } \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +# define xEXTENSIONx_FUNC_6ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6); \ + xEXTENSIONx_FUNC_6ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6); \ + } \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +# define xEXTENSIONx_FUNC_7ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7); \ + xEXTENSIONx_FUNC_7ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7); \ + } \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +# define xEXTENSIONx_FUNC_8ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8); \ + xEXTENSIONx_FUNC_8ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8); \ + } \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +# define xEXTENSIONx_FUNC_9ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9); \ + xEXTENSIONx_FUNC_9ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9); \ + } \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +# define xEXTENSIONx_FUNC_10ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10); \ + xEXTENSIONx_FUNC_10ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10); \ + } \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +# define xEXTENSIONx_FUNC_11ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11); \ + xEXTENSIONx_FUNC_11ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11); \ + } \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +# define xEXTENSIONx_FUNC_12ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12); \ + xEXTENSIONx_FUNC_12ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12); \ + } \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +# define xEXTENSIONx_FUNC_13ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13); \ + xEXTENSIONx_FUNC_13ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13); \ + } \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +# define xEXTENSIONx_FUNC_14ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14); \ + xEXTENSIONx_FUNC_14ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14); \ + } \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +# define xEXTENSIONx_FUNC_15ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15); \ + xEXTENSIONx_FUNC_15ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14, __p15); \ + } \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + + +# define xEXTENSIONx_VOID_FUNC_0ARG_DECLARE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func); \ + xEXTENSIONx_FUNC_0ARG_INLINE_SIGNATURE(__ret, __func) \ + { \ + return __func##__real(); \ + } \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) +# define xEXTENSIONx_VOID_FUNC_1ARG_DECLARE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1); \ + xEXTENSIONx_FUNC_1ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1) \ + { \ + return __func##__real(__p1); \ + } \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) +# define xEXTENSIONx_VOID_FUNC_2ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2); \ + xEXTENSIONx_FUNC_2ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + { \ + return __func##__real(__p1, __p2); \ + } \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) +# define xEXTENSIONx_VOID_FUNC_3ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3); \ + xEXTENSIONx_FUNC_3ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + { \ + return __func##__real(__p1, __p2, __p3); \ + } \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +# define xEXTENSIONx_VOID_FUNC_4ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4); \ + xEXTENSIONx_FUNC_4ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4); \ + } \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +# define xEXTENSIONx_VOID_FUNC_5ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5); \ + xEXTENSIONx_FUNC_5ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5); \ + } \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +# define xEXTENSIONx_VOID_FUNC_6ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6); \ + xEXTENSIONx_FUNC_6ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6); \ + } \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +# define xEXTENSIONx_VOID_FUNC_7ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7); \ + xEXTENSIONx_FUNC_7ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7); \ + } \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +# define xEXTENSIONx_VOID_FUNC_8ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8); \ + xEXTENSIONx_FUNC_8ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8); \ + } \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +# define xEXTENSIONx_VOID_FUNC_9ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9); \ + xEXTENSIONx_FUNC_9ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9); \ + } \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +# define xEXTENSIONx_VOID_FUNC_10ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10); \ + xEXTENSIONx_FUNC_10ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10); \ + } \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +# define xEXTENSIONx_VOID_FUNC_11ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11); \ + xEXTENSIONx_FUNC_11ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11); \ + } \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +# define xEXTENSIONx_VOID_FUNC_12ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12); \ + xEXTENSIONx_FUNC_12ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12); \ + } \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +# define xEXTENSIONx_VOID_FUNC_13ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13); \ + xEXTENSIONx_FUNC_13ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13); \ + } \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +# define xEXTENSIONx_VOID_FUNC_14ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14); \ + xEXTENSIONx_FUNC_14ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14); \ + } \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +# define xEXTENSIONx_VOID_FUNC_15ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15); \ + xEXTENSIONx_FUNC_15ARG_INLINE_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + { \ + return __func##__real(__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14, __p15); \ + } \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#endif // BUILDING_xEXTENSIONx_EXTENSION + + +#ifndef BUILDING_xEXTENSIONx_EXTENSION + +// The __ret__assign and __ret_ref parameters are to work around functions returning void. +#define xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, __real__func__par, __stub__func__par) \ + { \ + void *__handle = XextensionX_library_open(); \ + if (__handle) \ + { \ + static __func##__type func_ptr = NULL; \ + if (!func_ptr) \ + { \ + func_ptr = shlib_load(__handle, #__func "__wrapper"); \ + } \ + if (func_ptr) \ + { \ + int __successful = 0; \ + __ret__assign func_ptr __real__func__par; \ + if (__successful) \ + { \ + XextensionX_library_close(__handle); \ + return __ret__ref; \ + } \ + } \ + XextensionX_library_close(__handle); \ + } \ + return __func##__stub __stub__func__par; \ + } + +# define xEXTENSIONx_FUNC_0ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref) \ + xEXTENSIONx_FUNC_0ARG_WRAPPER_SIGNATURE(__ret, __func) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, xEXTENSIONx_CANARY_VALUE), \ + ()) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) + +# define xEXTENSIONx_FUNC_1ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, xEXTENSIONx_CANARY_VALUE), \ + (__p1)) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) + +# define xEXTENSIONx_FUNC_2ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2)) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) + +# define xEXTENSIONx_FUNC_3ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3)) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) + +# define xEXTENSIONx_FUNC_4ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4)) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) + +# define xEXTENSIONx_FUNC_5ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5)) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) + +# define xEXTENSIONx_FUNC_6ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6)) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) + +# define xEXTENSIONx_FUNC_7ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7)) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) + +# define xEXTENSIONx_FUNC_8ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8)) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) + +# define xEXTENSIONx_FUNC_9ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9)) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +# define xEXTENSIONx_FUNC_10ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10)) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +# define xEXTENSIONx_FUNC_11ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11)) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +# define xEXTENSIONx_FUNC_12ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12)) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +# define xEXTENSIONx_FUNC_13ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13)) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +# define xEXTENSIONx_FUNC_14ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14)) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +# define xEXTENSIONx_FUNC_15ARG_DEFINE_STUB_IMPL(__ret, __func, __ret__assign, __ret__ref, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_IMPL_LOADER(__ret, __func, __ret__assign, __ret__ref, \ + (xEXTENSIONx_CANARY_VALUE, &__successful, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14, __p15, xEXTENSIONx_CANARY_VALUE), \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14, __p15)) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#define xEXTENSIONx_FUNC_0ARG_DEFINE_STUB(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value) +#define xEXTENSIONx_FUNC_1ARG_DEFINE_STUB(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1) +#define xEXTENSIONx_FUNC_2ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2) +#define xEXTENSIONx_FUNC_3ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3) +#define xEXTENSIONx_FUNC_4ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +#define xEXTENSIONx_FUNC_5ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +#define xEXTENSIONx_FUNC_6ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +#define xEXTENSIONx_FUNC_7ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +#define xEXTENSIONx_FUNC_8ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +#define xEXTENSIONx_FUNC_9ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +#define xEXTENSIONx_FUNC_10ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +#define xEXTENSIONx_FUNC_11ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +#define xEXTENSIONx_FUNC_12ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +#define xEXTENSIONx_FUNC_13ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +#define xEXTENSIONx_FUNC_14ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +#define xEXTENSIONx_FUNC_15ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_DEFINE_STUB_IMPL(__ret, __func, __ret __ret_value =, __ret_value, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#define xEXTENSIONx_VOID_FUNC_0ARG_DEFINE_STUB(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_DEFINE_STUB_IMPL(__ret, __func, , ) +#define xEXTENSIONx_VOID_FUNC_1ARG_DEFINE_STUB(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1) +#define xEXTENSIONx_VOID_FUNC_2ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2) +#define xEXTENSIONx_VOID_FUNC_3ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3) +#define xEXTENSIONx_VOID_FUNC_4ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +#define xEXTENSIONx_VOID_FUNC_5ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +#define xEXTENSIONx_VOID_FUNC_6ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +#define xEXTENSIONx_VOID_FUNC_7ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +#define xEXTENSIONx_VOID_FUNC_8ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +#define xEXTENSIONx_VOID_FUNC_9ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +#define xEXTENSIONx_VOID_FUNC_10ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +#define xEXTENSIONx_VOID_FUNC_11ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +#define xEXTENSIONx_VOID_FUNC_12ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +#define xEXTENSIONx_VOID_FUNC_13ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +#define xEXTENSIONx_VOID_FUNC_14ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +#define xEXTENSIONx_VOID_FUNC_15ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_DEFINE_STUB_IMPL(__ret, __func, , , __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#define xEXTENSIONx_FUNC_DEFINE_REAL_INVALID In_core_code_you_need_to_use_DEFINE_STUB func() + +#define xEXTENSIONx_FUNC_0ARG_DEFINE(__ret, __func) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_1ARG_DEFINE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_2ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_3ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_4ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_5ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_6ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_7ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_8ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_9ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_10ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_11ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_12ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_13ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_14ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_FUNC_15ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID + +#define xEXTENSIONx_VOID_FUNC_0ARG_DEFINE(__ret, __func) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_1ARG_DEFINE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_2ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_3ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_4ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_5ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_6ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_7ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_8ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_9ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_10ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_11ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_12ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_13ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_14ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID +#define xEXTENSIONx_VOID_FUNC_15ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_DEFINE_REAL_INVALID + +#else // BUILDING_xEXTENSIONx_EXTENSION + +#define xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL_RET_VALUE(__ret, __func, __real__func__par, __ret__expr) \ + { \ + if (__start_canary != xEXTENSIONx_CANARY_VALUE || __end_canary != xEXTENSIONx_CANARY_VALUE) \ + { \ + Log(LOG_LEVEL_ERR, "Function '%s %s%s' failed stack consistency check. Most likely this means the plugin containing the " \ + "function is incompatible with this version of CFEngine.", #__ret, #__func, #__real__func__par); \ + __ret__expr; \ + } \ + *__successful = 1; \ + return __func __real__func__par; \ + } + +#define xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, __real__func__par) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL_RET_VALUE(__ret, __func, __real__func__par, \ + __ret __ret_value = (__ret)0; \ + (void)__ret_value; \ + return __ret_value) + +#define xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, __real__func__par) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL_RET_VALUE(__ret, __func, __real__func__par, return) + +#define xEXTENSIONx_FUNC_DEFINE_INLINE_IMPL(__ret, __func, __real__func__par) \ + { \ + return __func##__real __real__func__par; \ + } + +#define xEXTENSIONx_FUNC_0ARG_DEFINE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_WRAPPER_SIGNATURE(__ret, __func) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + ()) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) + +#define xEXTENSIONx_FUNC_1ARG_DEFINE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1)) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) + +#define xEXTENSIONx_FUNC_2ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2)) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) + +#define xEXTENSIONx_FUNC_3ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3)) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) + +#define xEXTENSIONx_FUNC_4ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4)) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) + +#define xEXTENSIONx_FUNC_5ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5)) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) + +#define xEXTENSIONx_FUNC_6ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6)) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) + +#define xEXTENSIONx_FUNC_7ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7)) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) + +#define xEXTENSIONx_FUNC_8ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8)) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) + +#define xEXTENSIONx_FUNC_9ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9)) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) + +#define xEXTENSIONx_FUNC_10ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10)) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) + +#define xEXTENSIONx_FUNC_11ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11)) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) + +#define xEXTENSIONx_FUNC_12ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12)) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) + +#define xEXTENSIONx_FUNC_13ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13)) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) + +#define xEXTENSIONx_FUNC_14ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14)) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) + +#define xEXTENSIONx_FUNC_15ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14, __p15)) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + + +#define xEXTENSIONx_VOID_FUNC_0ARG_DEFINE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_WRAPPER_SIGNATURE(__ret, __func) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + ()) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) + +#define xEXTENSIONx_VOID_FUNC_1ARG_DEFINE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1)) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) + +#define xEXTENSIONx_VOID_FUNC_2ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2)) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) + +#define xEXTENSIONx_VOID_FUNC_3ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3)) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) + +#define xEXTENSIONx_VOID_FUNC_4ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4)) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) + +#define xEXTENSIONx_VOID_FUNC_5ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5)) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) + +#define xEXTENSIONx_VOID_FUNC_6ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6)) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) + +#define xEXTENSIONx_VOID_FUNC_7ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7)) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) + +#define xEXTENSIONx_VOID_FUNC_8ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8)) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) + +#define xEXTENSIONx_VOID_FUNC_9ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9)) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) + +#define xEXTENSIONx_VOID_FUNC_10ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10)) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) + +#define xEXTENSIONx_VOID_FUNC_11ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11)) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) + +#define xEXTENSIONx_VOID_FUNC_12ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12)) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) + +#define xEXTENSIONx_VOID_FUNC_13ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13)) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) + +#define xEXTENSIONx_VOID_FUNC_14ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14)) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) + +#define xEXTENSIONx_VOID_FUNC_15ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_WRAPPER_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_VOID_FUNC_DEFINE_WRAPPER_IMPL(__ret, __func, \ + (__p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9, __p10, __p11, __p12, __p13, __p14, __p15)) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + + +#define xEXTENSIONx_FUNC_DEFINE_STUB_INVALID In_extension_code_you_cannot_define_a_STUB func() + +#define xEXTENSIONx_FUNC_0ARG_DEFINE_STUB(__ret, __func) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_1ARG_DEFINE_STUB(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_2ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_3ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_4ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_5ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_6ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_7ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_8ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_9ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_10ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_11ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_12ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_13ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_14ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_FUNC_15ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID + +#define xEXTENSIONx_VOID_FUNC_0ARG_DEFINE_STUB(__ret, __func) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_1ARG_DEFINE_STUB(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_2ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_3ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_4ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_5ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_6ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_7ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_8ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_9ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_10ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_11ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_12ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_13ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_14ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID +#define xEXTENSIONx_VOID_FUNC_15ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_DEFINE_STUB_INVALID + +#endif // BUILDING_xEXTENSIONx_EXTENSION + + +#else // BUILTIN_EXTENSIONS +// In this case just map to real function calls. +// BUILTIN_EXTENSIONS is for Windows binaries and debugging. + +# define xEXTENSIONx_FUNC_0ARG_STUB_SIGNATURE(__ret, __func) \ + __ret __func##__stub() +# define xEXTENSIONx_FUNC_1ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1) \ + __ret __func##__stub(__t1 __p1) +# define xEXTENSIONx_FUNC_2ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + __ret __func##__stub(__t1 __p1, __t2 __p2) +# define xEXTENSIONx_FUNC_3ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3) +# define xEXTENSIONx_FUNC_4ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4) +# define xEXTENSIONx_FUNC_5ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5) +# define xEXTENSIONx_FUNC_6ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6) +# define xEXTENSIONx_FUNC_7ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7) +# define xEXTENSIONx_FUNC_8ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8) +# define xEXTENSIONx_FUNC_9ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9) +# define xEXTENSIONx_FUNC_10ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10) +# define xEXTENSIONx_FUNC_11ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11) +# define xEXTENSIONx_FUNC_12ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12) +# define xEXTENSIONx_FUNC_13ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13) +# define xEXTENSIONx_FUNC_14ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14) +# define xEXTENSIONx_FUNC_15ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + __ret __func##__stub(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, __t15 __p15) + +# define xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) \ + __ret __func() +# define xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) \ + __ret __func(__t1 __p1) +# define xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) \ + __ret __func(__t1 __p1, __t2 __p2) +# define xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3) +# define xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4) +# define xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5) +# define xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6) +# define xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7) +# define xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8) +# define xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9) +# define xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10) +# define xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11) +# define xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12) +# define xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13) +# define xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14) +# define xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + __ret __func(__t1 __p1, __t2 __p2, __t3 __p3, __t4 __p4, __t5 __p5, __t6 __p6, __t7 __p7, __t8 __p8, __t9 __p9, __t10 __p10, __t11 __p11, __t12 __p12, __t13 __p13, __t14 __p14, __t15 __p15) + + +#define xEXTENSIONx_FUNC_0ARG_DECLARE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) +#define xEXTENSIONx_FUNC_1ARG_DECLARE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) +#define xEXTENSIONx_FUNC_2ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) +#define xEXTENSIONx_FUNC_3ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +#define xEXTENSIONx_FUNC_4ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +#define xEXTENSIONx_FUNC_5ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +#define xEXTENSIONx_FUNC_6ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +#define xEXTENSIONx_FUNC_7ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +#define xEXTENSIONx_FUNC_8ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +#define xEXTENSIONx_FUNC_9ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +#define xEXTENSIONx_FUNC_10ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +#define xEXTENSIONx_FUNC_11ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +#define xEXTENSIONx_FUNC_12ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +#define xEXTENSIONx_FUNC_13ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +#define xEXTENSIONx_FUNC_14ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +#define xEXTENSIONx_FUNC_15ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#define xEXTENSIONx_VOID_FUNC_0ARG_DECLARE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) +#define xEXTENSIONx_VOID_FUNC_1ARG_DECLARE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) +#define xEXTENSIONx_VOID_FUNC_2ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) +#define xEXTENSIONx_VOID_FUNC_3ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +#define xEXTENSIONx_VOID_FUNC_4ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +#define xEXTENSIONx_VOID_FUNC_5ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +#define xEXTENSIONx_VOID_FUNC_6ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +#define xEXTENSIONx_VOID_FUNC_7ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +#define xEXTENSIONx_VOID_FUNC_8ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +#define xEXTENSIONx_VOID_FUNC_9ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +#define xEXTENSIONx_VOID_FUNC_10ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +#define xEXTENSIONx_VOID_FUNC_11ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +#define xEXTENSIONx_VOID_FUNC_12ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +#define xEXTENSIONx_VOID_FUNC_13ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +#define xEXTENSIONx_VOID_FUNC_14ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +#define xEXTENSIONx_VOID_FUNC_15ARG_DECLARE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#define xEXTENSIONx_FUNC_0ARG_DEFINE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) +#define xEXTENSIONx_FUNC_1ARG_DEFINE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) +#define xEXTENSIONx_FUNC_2ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) +#define xEXTENSIONx_FUNC_3ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +#define xEXTENSIONx_FUNC_4ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +#define xEXTENSIONx_FUNC_5ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +#define xEXTENSIONx_FUNC_6ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +#define xEXTENSIONx_FUNC_7ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +#define xEXTENSIONx_FUNC_8ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +#define xEXTENSIONx_FUNC_9ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +#define xEXTENSIONx_FUNC_10ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +#define xEXTENSIONx_FUNC_11ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +#define xEXTENSIONx_FUNC_12ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +#define xEXTENSIONx_FUNC_13ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +#define xEXTENSIONx_FUNC_14ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +#define xEXTENSIONx_FUNC_15ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#define xEXTENSIONx_VOID_FUNC_0ARG_DEFINE(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_REAL_SIGNATURE(__ret, __func) +#define xEXTENSIONx_VOID_FUNC_1ARG_DEFINE(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1) +#define xEXTENSIONx_VOID_FUNC_2ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) +#define xEXTENSIONx_VOID_FUNC_3ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +#define xEXTENSIONx_VOID_FUNC_4ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +#define xEXTENSIONx_VOID_FUNC_5ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +#define xEXTENSIONx_VOID_FUNC_6ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +#define xEXTENSIONx_VOID_FUNC_7ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +#define xEXTENSIONx_VOID_FUNC_8ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +#define xEXTENSIONx_VOID_FUNC_9ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +#define xEXTENSIONx_VOID_FUNC_10ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +#define xEXTENSIONx_VOID_FUNC_11ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +#define xEXTENSIONx_VOID_FUNC_12ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +#define xEXTENSIONx_VOID_FUNC_13ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +#define xEXTENSIONx_VOID_FUNC_14ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +#define xEXTENSIONx_VOID_FUNC_15ARG_DEFINE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_REAL_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#define xEXTENSIONx_FUNC_0ARG_DEFINE_STUB(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_STUB_SIGNATURE(__ret, __func) +#define xEXTENSIONx_FUNC_1ARG_DEFINE_STUB(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1) +#define xEXTENSIONx_FUNC_2ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) +#define xEXTENSIONx_FUNC_3ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +#define xEXTENSIONx_FUNC_4ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +#define xEXTENSIONx_FUNC_5ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +#define xEXTENSIONx_FUNC_6ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +#define xEXTENSIONx_FUNC_7ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +#define xEXTENSIONx_FUNC_8ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +#define xEXTENSIONx_FUNC_9ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +#define xEXTENSIONx_FUNC_10ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +#define xEXTENSIONx_FUNC_11ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +#define xEXTENSIONx_FUNC_12ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +#define xEXTENSIONx_FUNC_13ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +#define xEXTENSIONx_FUNC_14ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +#define xEXTENSIONx_FUNC_15ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#define xEXTENSIONx_VOID_FUNC_0ARG_DEFINE_STUB(__ret, __func) \ + xEXTENSIONx_FUNC_0ARG_STUB_SIGNATURE(__ret, __func) +#define xEXTENSIONx_VOID_FUNC_1ARG_DEFINE_STUB(__ret, __func, __t1, __p1) \ + xEXTENSIONx_FUNC_1ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1) +#define xEXTENSIONx_VOID_FUNC_2ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2) \ + xEXTENSIONx_FUNC_2ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2) +#define xEXTENSIONx_VOID_FUNC_3ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) \ + xEXTENSIONx_FUNC_3ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3) +#define xEXTENSIONx_VOID_FUNC_4ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) \ + xEXTENSIONx_FUNC_4ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4) +#define xEXTENSIONx_VOID_FUNC_5ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) \ + xEXTENSIONx_FUNC_5ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5) +#define xEXTENSIONx_VOID_FUNC_6ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) \ + xEXTENSIONx_FUNC_6ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6) +#define xEXTENSIONx_VOID_FUNC_7ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) \ + xEXTENSIONx_FUNC_7ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7) +#define xEXTENSIONx_VOID_FUNC_8ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) \ + xEXTENSIONx_FUNC_8ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8) +#define xEXTENSIONx_VOID_FUNC_9ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) \ + xEXTENSIONx_FUNC_9ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9) +#define xEXTENSIONx_VOID_FUNC_10ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) \ + xEXTENSIONx_FUNC_10ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10) +#define xEXTENSIONx_VOID_FUNC_11ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) \ + xEXTENSIONx_FUNC_11ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11) +#define xEXTENSIONx_VOID_FUNC_12ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) \ + xEXTENSIONx_FUNC_12ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12) +#define xEXTENSIONx_VOID_FUNC_13ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) \ + xEXTENSIONx_FUNC_13ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13) +#define xEXTENSIONx_VOID_FUNC_14ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) \ + xEXTENSIONx_FUNC_14ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14) +#define xEXTENSIONx_VOID_FUNC_15ARG_DEFINE_STUB(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) \ + xEXTENSIONx_FUNC_15ARG_STUB_SIGNATURE(__ret, __func, __t1, __p1, __t2, __p2, __t3, __p3, __t4, __p4, __t5, __p5, __t6, __p6, __t7, __p7, __t8, __p8, __t9, __p9, __t10, __p10, __t11, __p11, __t12, __p12, __t13, __p13, __t14, __p14, __t15, __p15) + +#endif // BUILTIN_EXTENSIONS + +#endif // xEXTENSIONx_EXTENSION_H diff --git a/libpromises/failsafe.cf b/libpromises/failsafe.cf new file mode 100644 index 0000000000..272a1dc98d --- /dev/null +++ b/libpromises/failsafe.cf @@ -0,0 +1,517 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. + +########## CFEngine Bootstrap / Failsafe Policy ############################## +# This file (failsafe.cf) is re-generated inside "inputs" directory every time +# you bootstrap. This means that custom changes will be overwritten. +# +# The role of this standalone policy file is to fetch the main promises from +# the policy hub for the first time when bootstrapping, and to recover the +# system by fetching policies in case the standard agent run fails. +############################################################################## + +body agent control +{ + # Bootstrapping can't continue without keys + abortclasses => { "no_ppkeys_ABORT_kept" }; + # Make sure that running failsafe many times in a row does not + # change functionality + ifelapsed => "0"; +} + +################################################################################ + +bundle agent main +{ + meta: + + "description" + string => "Perform bootstrap or failsafe recovery operations."; + + vars: + # In order to preserve the log level used during bootstrap we build the + # string to set log level on any direct sub-agent calls based on classes + # that are defined when the options are set. + + # --log-level, -g value - Specify how detailed logs should be. + # Possible values: 'error', 'warning', 'notice', 'info', 'verbose', 'debug' + + "log_level" + string => ifelse("debug_mode", "--log-level=debug", + "verbose_mode", "--log-level=verbose", + "info_mode", "--log-level=info", + # CFE-4121 - Not yet implemented + # "notice_mode", "--log-level=notice", + # "warning_mode", "--log-level=warning", + # "error_mode", "--log-level=error", + ""); + + methods: + + "Check Keys" + usebundle => failsafe_cfe_internal_checkkeys, + comment => "Without a valid keypair we aren't going to be able + to establish trust"; + + "Fetch Inputs" + usebundle => failsafe_cfe_internal_update, + comment => "We need to fetch policy from upstream if we are + bootstrapping or if we are performing failsafe + recovery."; + + "Actuate Update Policy" + usebundle => failsafe_cfe_internal_call_update, + comment => "In order to speed up convergence and reporting we + trigger the update policy right after initial + bootstrap. This allows the first scheduled run to + happen with the most up to date and complete + information."; + + "Trigger Policy" + usebundle => failsafe_cfe_internal_trigger_policy, + comment => "In order to speed up convergence and reporting we + trigger the whole policy right after initial + bootstrap. This allows the first report to provide + more complete data."; + + "Report" + usebundle => failsafe_cfe_internal_report, + comment => "It's important to let the user know what happened + as the result of the bootstrap or failsafe + operation."; +} + +bundle agent failsafe_cfe_internal_checkkeys +{ + classes: + "have_ppkeys" + expression => fileexists("$(sys.workdir)/ppkeys/localhost.pub"), + handle => "failsafe_cfe_internal_bootstrap_checkkeys_classes_have_ppkeys"; + + reports: + !have_ppkeys:: + "No public/private key pair is loaded, please create one by running \"cf-key\"" + classes => failsafe_results("namespace", "no_ppkeys_ABORT"); +} + +################################################################################ + +bundle agent failsafe_cfe_internal_update +{ + vars: + + # A policy server cannot use the shortcut feature to resolve + # masterfiles since cf-serverd is potentially not yet up and + # running. + + # The unqualified path is used for non policy servers so that + # the policy server can use a shortcut to decide on behalf of + # the client which policy to serve by default. This is useful + # when running binaires from mixed sources (for example CFEngine + # produced binaries vs packages from the debian repository). + + "masterfiles_dir_remote" + string => ifelse( "policy_server", $(sys.masterdir), + "masterfiles" ); + + files: + + "$(sys.inputdir)" + handle => "failsafe_cfe_internal_bootstrap_update_files_sys_workdir_inputs_shortcut", + copy_from => failsafe_scp("$(masterfiles_dir_remote)"), + depth_search => failsafe_u_infinite_client_policy, + file_select => failsafe_exclude_vcs_files, + classes => failsafe_results("namespace", "inputdir_update"); + + !policy_server:: + + "$(sys.workdir)/modules" + handle => "failsafe_cfe_internal_bootstrap_update_files_sys_workdir_modules_shortcut", + copy_from => failsafe_scp("modules"), + depth_search => failsafe_recurse("inf"), + file_select => failsafe_exclude_vcs_files, + classes => failsafe_results("namespace", "modulesdir_update"); + + !windows.inputdir_update_error:: + + # When running on a *nix platform with homogeneous packages + # $(sys.masterdir) is a good guess. This is never the case for + # windows, and might be a poor guess if mixing packages from + # different sources (for example debian repositories and + # CFEngine produced packages). + "$(sys.inputdir)" + handle => "failsafe_cfe_internal_bootstrap_update_files_sys_workdir_inputs_not_windows", + copy_from => failsafe_scp("$(sys.masterdir)"), + depth_search => failsafe_recurse("inf"), + file_select => failsafe_exclude_vcs_files, + classes => failsafe_results("namespace", "inputdir_update"), + comment => "If we failed to fetch policy we try again using + the legacy default in case we are fetching policy + from a hub that is not serving mastefiles via a + shortcut."; + + windows.inputdir_update_error:: + + # Note: Windows can't use $(sys.masterdir) because no one runs a + # hub on windows and the copy_from needs the remote path. + "$(sys.inputdir)" + handle => "failsafe_cfe_internal_bootstrap_update_files_sys_workdir_inputs_windows", + copy_from => failsafe_scp("/var/cfengine/masterfiles"), + depth_search => failsafe_recurse("inf"), + file_select => failsafe_exclude_vcs_files, + classes => failsafe_results("namespace", "inputdir_update"), + comment => "If we failed to fetch policy we try again using + the legacy default in case we are fetching policy + from a hub that is not serving mastefiles via a + shortcut."; + + windows:: + + # TODO: Remove the use of bin-twin ref: Redmine #7364 + "$(sys.workdir)\\bin-twin\\." + handle => "failsafe_cfe_internal_bootstrap_update_files_sys_workdir_bin_twin_windows", + copy_from => failsafe_cp("$(sys.workdir)\\bin\\."), + depth_search => failsafe_recurse("1"), + file_select => failsafe_exclude_vcs_files, + comment => "Make sure we maintain a clone of the binaries and + libraries for updating"; + + + processes: + + # TODO: Decide if this class guard is appropriate. Should we + # guard checking of cf-execd process running to when inputs are + # repaired + !windows.inputdir_update_repaired:: + + # We need to know when cf-execd is not running so that we can + # start it when necessary. Windows and systemd hosts uses the service + # manager instead of keying on individual processes. + + "cf-execd" restart_class => "cf_execd_not_running", + handle => "failsafe_cfe_internal_bootstrap_update_processes_start_cf_execd"; + + any:: + + # We need to know if cf-serverd isn't running so that we can + # start it when necessary. + + "cf-serverd" restart_class => "cf_serverd_not_running", + handle => "failsafe_cfe_internal_bootstrap_update_processes_start_cf_serverd"; + + commands: + + cf_execd_not_running.!(windows|systemd):: + + # Windows and systemd do not launch cf-execd directly and are + # handeled separately. + + "$(sys.cf_execd)" + handle => "failsafe_cfe_internal_bootstrap_update_commands_check_sys_cf_execd_start", + classes => failsafe_results("namespace", "cf_execd_running"); + + cf_serverd_not_running.!(windows|systemd):: + + # cf-serverd is not launched directly on Windows and systemd and is + # handled separately. + + "$(sys.cf_serverd)" + handle => "failsafe_cfe_internal_bootstrap_update_commands_check_sys_cf_serverd_start", + action => failsafe_ifwin_bg, + classes => failsafe_results("namespace", "cf_serverd_running"), + comment => "cf-serverd is needed on policy hubs or remote + clients will not be able to get policy. Clients do + not have a strong dependency on cf-serverd and if + the component is necessay it is expected to be + started by a separate policy."; + + cf_execd_not_running.systemd:: + + # We explicitly use "restart", because it is possible that cf-serverd + # is running, even if cf-execd isn't, for example. Here we want to be + # sure we relaunch everything. + + "/bin/systemctl restart cfengine3" -> { "CFE-1459" } + handle => "failsafe_cfe_internal_bootstrap_update_commands_systemd_cfe_start", + contain => bootstrap_command_silent, + classes => failsafe_results("namespace", "systemctl_restart_cfengine3"); + + services: + + # TODO: Is this restriction to only promise the service running + # when inputs are repaired appropriate? Perhaps it should always + # be checked. + windows.inputdir_update_repaired:: + + "CfengineNovaExec" + handle => "failsafe_cfe_internal_bootstrap_update_services_windows_executor", + service_policy => "start", + service_method => failsafe_bootstart, + classes => failsafe_results("namespace", "cf_execd_running"); +} + +################################################################################ + +bundle agent failsafe_cfe_internal_report +{ + meta: + + "description" + string => "Report the outcome of the embedded + bootstrap/failsafe operation."; + + classes: + + # TODO: Determine if this is necessary and/or useful. Pre-eval + # might resolve this before policy update occurs, and this is + # probably most useful after policy update has been attempted. + + "have_promises_cf" + scope => "bundle", + expression => fileexists("$(sys.inputdir)/promises.cf"), + handle => "failsafe_cfe_internal_bootstrap_update_classes_have_promises_cf", + comment => "We expect to find promises.cf after policy has + been successfully copied from the policy + server. If promises.cf is missing, then the + bootstrap or failsafe recovery has likely + failed."; + + reports: + + !bootstrap_mode:: + + "Built-in failsafe policy triggered" + handle => "failsafe_cfe_internal_bootstrap_update_reports_failsafe_notification", + comment => "Be sure to inform the user that the failsafe policy has + been triggered. This typically indicates that the agent has + received broken policy. It may also indicate legacy + configuration in body executor control."; + + bootstrap_mode:: + + "Bootstrapping from host '$(sys.policy_hub)' via built-in policy '$(this.promise_filename)'" + handle => "failsafe_cfe_internal_bootstrap_update_reports_bootstrap_notification", + comment => "Be sure to inform the user that they have triggerd a bootstrap."; + + bootstrap_mode.policy_server:: + + "This host assumes the role of policy server" + handle => "failsafe_cfe_internal_bootstrap_update_reports_assume_policy_hub"; + + bootstrap_mode.!policy_server:: + + "This autonomous node assumes the role of voluntary client" + handle => "failsafe_cfe_internal_bootstrap_update_reports_assume_voluntary_client"; + + inputdir_update_repaired:: + + "Updated local policy from policy server" + handle => "failsafe_cfe_internal_bootstrap_update_reports_inputdir_update_repaired"; + + + inputdir_update_repaired.!have_promises_cf:: + + # We used to display this report when we have fetched new + # policy, but still can not find promises.cf in + # sys.inputdir. However if the hub being bootstrapped to is down + # we may never repair inputs and this may not be triggered + # + # TODO: Come up with better conditions. These seem weak. + # - Potentially use returnszero() with cf-promises? + + "Failed to copy policy from policy server at $(sys.policy_hub):$(sys.masterdir) + Please check + * cf-serverd is running on $(sys.policy_hub) + * CFEngine version on the policy hub is 3.6.0 or latest - otherwise you need to tweak the protocol_version setting + * network connectivity to $(sys.policy_hub) on port $(sys.policy_hub_port) + * masterfiles 'body server control' - in particular allowconnects, trustkeysfrom and skipverify + * masterfiles 'bundle server' -> access: -> masterfiles -> admit/deny + It is often useful to restart cf-serverd in verbose mode (cf-serverd -v) on $(sys.policy_hub) to diagnose connection issues. + When updating masterfiles, wait (usually 5 minutes) for files to propagate to inputs on $(sys.policy_hub) before retrying." + handle => "failsafe_cfe_internal_bootstrap_update_reports_did_not_get_policy"; + + trigger_policy_repaired:: + "Triggered an initial run of the policy" + handle => "failsafe_cfe_internal_bootstrap_trigger_policy_passed"; + + trigger_policy_failed:: + "Initial run of the policy failed" + handle => "failsafe_cfe_internal_bootstrap_trigger_policy_failed"; + + systemctl_restart_cfengine3_repaired:: + + "Restarted systemd unit cfengine3" + handle => "failsafe_cfe_intrnal_bootstrap_update_reports_systemd_unit_restarted"; + + systemctl_restart_cfengine3_error:: + + "Error restarting systemd unit cfengine3" + handle => "failsafe_cfe_intrnal_bootstrap_update_reports_systemd_unit_restarted"; + + cf_serverd_running_repaired:: + + "Started the server" + handle => "failsafe_cfe_internal_bootstrap_update_reports_started_serverd"; + + cf_serverd_running_failed:: + + "Failed to start the server" + handle => "failsafe_cfe_internal_bootstrap_update_reports_failed_to_start_serverd"; + + cf_execd_running_repaired:: + + "Started the scheduler" + handle => "failsafe_cfe_internal_bootstrap_update_reports_started_execd"; + + cf_execd_running_failed:: + + "Failed to start the scheduler" + handle => "failsafe_cfe_internal_bootstrap_update_reports_failed_to_start_execd"; +} + +################################################################################ + +bundle agent failsafe_cfe_internal_call_update +{ + vars: + + "mode" string => ifelse("bootstrap_mode", "bootstrap_mode", "failsafe_mode"); + + commands: + + # On Windows we need cf-execd to call update.cf, otherwise the daemons will + # not run under the SYSTEM account. + !windows.!skip_policy_on_bootstrap:: + "$(sys.cf_agent) -f $(sys.update_policy_path) --define $(mode) $(main.log_level)" + handle => "failsafe_cfe_internal_call_update_commands_call_update_cf", + if => fileexists( $(sys.update_policy_path) ), + comment => "We run update.cf in order to prepare system information for + collection into CFEngine Enterprise more quickly."; +} + +################################################################################ + +bundle agent failsafe_cfe_internal_trigger_policy +{ + commands: + + bootstrap_mode.!skip_policy_on_bootstrap:: + "$(sys.cf_agent) --define bootstrap_mode $(main.log_level)" + handle => "failsafe_cfe_internal_trigger_policy_commands_call_promises_cf", + if => fileexists( $(sys.default_policy_path) ), + classes => failsafe_results("namespace", "trigger_policy"), + comment => "We run promises.cf in order to prepare system information for + collection into CFEngine Enterprise more quickly."; +} + +############################################ +body copy_from failsafe_scp(from) +{ + source => "$(from)"; + compare => "digest"; + # This class is always set when bootstrapping. You can deactivate + # this class with --trust-server=no when bootstrapping + trust_server:: + trustkey => "true"; + !policy_server:: + servers => { "$(sys.policy_hub)" }; + portnumber => "$(sys.policy_hub_port)"; +} +############################################ +body depth_search failsafe_u_infinite_client_policy +# @brief Search recursively for files excluding vcs related files and .no-distrib directories +# @param d Maximum depth to search recursively +# Duplicated in update policy +{ + depth => "inf"; + exclude_dirs => { "\.svn", "\.git", "git-core", "\.no-distrib" }; +} +############################################ +body depth_search failsafe_recurse(d) +{ + depth => "$(d)"; + exclude_dirs => { "\.svn", "\.git" }; +} +############################################ +body file_select failsafe_exclude_vcs_files +{ + leaf_name => { "\.git.*", "\.mailmap" }; + file_result => "!leaf_name"; +} +############################################ +body service_method failsafe_bootstart +{ + service_autostart_policy => "boot_time"; +} +############################################ +body action failsafe_ifwin_bg +{ + windows:: + background => "true"; +} +############################################ +body copy_from failsafe_cp(from) +{ + source => "$(from)"; + compare => "digest"; + copy_backup => "false"; +} + +############################################ +body classes failsafe_results(scope, class_prefix) +# @brief Define classes prefixed with `class_prefix` and suffixed with +# appropriate outcomes: _kept, _repaired, _not_kept, _error, _failed, +# _denied, _timeout, _reached +# +# @param scope The scope in which the class should be defined (`bundle` or `namespace`) +# @param class_prefix The prefix for the classes defined +{ + scope => "$(scope)"; + + promise_kept => { "$(class_prefix)_reached", + "$(class_prefix)_kept" }; + + promise_repaired => { "$(class_prefix)_reached", + "$(class_prefix)_repaired" }; + + repair_failed => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_failed" }; + + repair_denied => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_denied" }; + + repair_timeout => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_timeout" }; +} + +body contain bootstrap_command_silent +# @brief Suppress command output +{ + no_output => "true"; +} diff --git a/libpromises/feature.c b/libpromises/feature.c new file mode 100644 index 0000000000..484a639bb4 --- /dev/null +++ b/libpromises/feature.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include + +static const char* features[] = { +#ifdef HAVE_LIBYAML + "yaml", +#endif +#ifdef HAVE_LIBXML2 + "xml", +#endif +#ifdef HAVE_LIBCURL + "curl", +#endif + "tls_1_0", /* we require versions of OpenSSL that support + * at least TLS 1.0 */ +#ifdef HAVE_TLS_1_1 + "tls_1_1", +#endif +#ifdef HAVE_TLS_1_2 + "tls_1_2", +#endif +#ifdef HAVE_TLS_1_3 + "tls_1_3", +#endif + "def_json_preparse", + "host_specific_data_load", + "copyfrom_restrict_keys", + NULL +}; + +int KnownFeature(const char *feature) +{ + // dumb algorithm, but still effective for a small number of features + for(int i=0 ; features[i]!=NULL ; i++) { + int r = strcmp(feature, features[i]); + if(r==0) { + return 1; + } + } + return 0; +} + +void CreateHardClassesFromFeatures(EvalContext *ctx, char *tags) +{ + Buffer *buffer = BufferNew(); + + for(int i=0 ; features[i]!=NULL ; i++) { + BufferPrintf(buffer, "feature_%s", features[i]); + CreateHardClassesFromCanonification(ctx, BufferData(buffer), tags); + } + BufferDestroy(buffer); +} diff --git a/libpromises/feature.h b/libpromises/feature.h new file mode 100644 index 0000000000..04302e766c --- /dev/null +++ b/libpromises/feature.h @@ -0,0 +1,9 @@ +#ifndef CFENGINE_FEATURE_H +#define CFENGINE_FEATURE_H + + +int KnownFeature(const char *feature); + +void CreateHardClassesFromFeatures(EvalContext *ctx, char *tags); + +#endif diff --git a/libpromises/files_copy.c b/libpromises/files_copy.c new file mode 100644 index 0000000000..18e0119d52 --- /dev/null +++ b/libpromises/files_copy.c @@ -0,0 +1,286 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +bool CopyRegularFileDiskPerms(const char *source, const char *destination, + int mode) +{ + assert(source != NULL); + assert(destination != NULL); + + int sd = safe_open(source, O_RDONLY | O_BINARY); + if (sd == -1) + { + Log(LOG_LEVEL_INFO, "Can't copy '%s' (open: %s)", + source, GetErrorStr()); + return false; + } + + /* unlink() + safe_open(O_CREAT|O_EXCL) to avoid + symlink attacks and races. */ + unlink(destination); + + int dd = safe_open_create_perms(destination, + O_WRONLY | O_CREAT | O_EXCL | O_BINARY, + mode); + if (dd == -1) + { + Log(LOG_LEVEL_INFO, + "Unable to open destination file while copying '%s' to '%s'" + " (open: %s)", source, destination, GetErrorStr()); + close(sd); + return false; + } + + /* We need to stat the file to get the block size of the source file */ + struct stat statbuf; + if (fstat(sd, &statbuf) == -1) + { + Log(LOG_LEVEL_INFO, "Can't copy '%s' (fstat: %s)", + source, GetErrorStr()); + close(sd); + close(dd); + return false; + } + + size_t total_bytes_written; + bool last_write_was_hole; + bool ret = FileSparseCopy(sd, source, dd, destination, + ST_BLKSIZE(statbuf), + &total_bytes_written, &last_write_was_hole); + if (!ret) + { + unlink(destination); + close(sd); + close(dd); + return false; + } + + bool do_sync = false; + ret = FileSparseClose(dd, destination, do_sync, + total_bytes_written, last_write_was_hole); + if (!ret) + { + unlink(destination); + } + + close(sd); + close(dd); + return ret; +} + +bool CopyRegularFileDisk(const char *source, const char *destination) +{ + assert(source != NULL); + assert(destination != NULL); + + bool ok1 = false, ok2 = false; /* initialize before the goto end; */ + + int sd = safe_open(source, O_RDONLY | O_BINARY); + if (sd == -1) + { + Log(LOG_LEVEL_INFO, "Can't copy '%s' (open: %s)", + source, GetErrorStr()); + goto end; + } + + /* We need to stat the file to get the right source permissions. */ + struct stat statbuf; + if (fstat(sd, &statbuf) == -1) + { + Log(LOG_LEVEL_INFO, "Can't copy '%s' (fstat: %s)", + source, GetErrorStr()); + goto end; + } + + /* unlink() + safe_open(O_CREAT|O_EXCL) to avoid + symlink attacks and races. */ + unlink(destination); + + int dd = safe_open_create_perms(destination, + O_WRONLY | O_CREAT | O_EXCL | O_BINARY, + statbuf.st_mode); + if (dd == -1) + { + Log(LOG_LEVEL_INFO, + "Unable to open destination file while copying '%s' to '%s'" + " (open: %s)", source, destination, GetErrorStr()); + goto end; + } + + size_t total_bytes_written; + bool last_write_was_hole; + ok1 = FileSparseCopy(sd, source, dd, destination, + ST_BLKSIZE(statbuf), + &total_bytes_written, &last_write_was_hole); + bool do_sync = false; + ok2 = FileSparseClose(dd, destination, do_sync, + total_bytes_written, last_write_was_hole); + + if (!ok1 || !ok2) + { + unlink(destination); + } + + end: + if (sd != -1) + { + close(sd); + } + return ok1 && ok2; +} + +bool CopyFilePermissionsDisk(const char *source, const char *destination) +{ + struct stat statbuf; + + if (stat(source, &statbuf) == -1) + { + Log(LOG_LEVEL_INFO, "Can't copy permissions '%s'. (stat: %s)", source, GetErrorStr()); + return false; + } + + if (safe_chmod(destination, statbuf.st_mode) != 0) + { + Log(LOG_LEVEL_INFO, "Can't copy permissions '%s'. (chmod: %s)", source, GetErrorStr()); + return false; + } + + if (safe_chown(destination, statbuf.st_uid, statbuf.st_gid) != 0) + { + Log(LOG_LEVEL_INFO, "Can't copy permissions '%s'. (chown: %s)", source, GetErrorStr()); + return false; + } + + if (!CopyFileExtendedAttributesDisk(source, destination, NULL)) + { + return false; + } + + return true; +} + +bool CopyFileExtendedAttributesDisk(const char *source, const char *destination, bool *change) +{ +#if defined(WITH_XATTR) + // Extended attributes include both POSIX ACLs and SELinux contexts. + ssize_t attr_raw_names_size; + char attr_raw_names[CF_BUFSIZE]; + + attr_raw_names_size = llistxattr(source, attr_raw_names, sizeof(attr_raw_names)); + if (attr_raw_names_size < 0) + { + if (errno == ENOTSUP || errno == ENODATA) + { + if (change != NULL) + { + *change = false; + } + return true; + } + else + { + Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (llistxattr: %s)", + source, destination, GetErrorStr()); + return false; + } + } + + int pos; + for (pos = 0; pos < attr_raw_names_size;) + { + const char *current = attr_raw_names + pos; + pos += strlen(current) + 1; + + char src_data[CF_BUFSIZE]; + int src_datasize = lgetxattr(source, current, src_data, sizeof(src_data)); + if (src_datasize < 0) + { + if (errno == ENOTSUP) + { + continue; + } + else + { + Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (lgetxattr: %s: %s)", + source, destination, GetErrorStr(), current); + return false; + } + } + char dst_data[CF_BUFSIZE]; + int dst_datasize = lgetxattr(destination, current, dst_data, sizeof(dst_data)); + if ((dst_datasize < 0) && (errno == ENOTSUP)) + { + continue; + } + else if ((src_datasize == dst_datasize) && + (memcmp(src_data, dst_data, src_datasize) == 0)) + { + /* The value is the same, no need to overwrite it. */ + continue; + } + + int ret = lsetxattr(destination, current, src_data, src_datasize, 0); + if (ret < 0) + { + if (errno == ENOTSUP) + { + continue; + } + else + { + Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (lsetxattr: %s: %s)", + source, destination, GetErrorStr(), current); + return false; + } + } + if (change != NULL) + { + *change = true; + } + } + +#else // !WITH_XATTR + // ACLs are included in extended attributes, but fall back to CopyACLs if xattr is not available. + if (!CopyACLs(source, destination, change)) + { + return false; + } +#endif + + return true; +} diff --git a/libpromises/files_copy.h b/libpromises/files_copy.h new file mode 100644 index 0000000000..dc197e2da0 --- /dev/null +++ b/libpromises/files_copy.h @@ -0,0 +1,45 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_FILES_COPY_H +#define CFENGINE_FILES_COPY_H + +#include + +#ifdef WITH_XATTR_EXTRA_ARGS +#define llistxattr(__arg1, __arg2, __arg3) \ + llistxattr((__arg1), (__arg2), (__arg3), 0) +#define lgetxattr(__arg1, __arg2, __arg3, __arg4) \ + lgetxattr((__arg1), (__arg2), (__arg3), (__arg4), 0, 0) +#define lsetxattr(__arg1, __arg2, __arg3, __arg4, __arg5) \ + lsetxattr((__arg1), (__arg2), (__arg3), (__arg4), 0, (__arg5)) +#endif + +bool CopyRegularFileDiskPerms(const char *source, const char *destination, + const int mode); +bool CopyRegularFileDisk(const char *source, const char *destination); +bool CopyFilePermissionsDisk(const char *source, const char *destination); +bool CopyFileExtendedAttributesDisk(const char *source, const char *destination, bool *change); + +#endif diff --git a/libpromises/files_interfaces.c b/libpromises/files_interfaces.c new file mode 100644 index 0000000000..49cf7fec8d --- /dev/null +++ b/libpromises/files_interfaces.c @@ -0,0 +1,57 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* remote_stat */ + +int cf_lstat(const char *file, struct stat *buf, const FileCopy *fc, AgentConnection *conn) +{ + if (conn == NULL) + { + int ret = lstat(file, buf); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, "lstat: %s", GetErrorStr()); + } + return ret; + } + else + { + assert(fc->servers && strcmp(fc->servers->val.item, "localhost")); + return cf_remote_stat(conn, fc->encrypt, file, buf, "link"); + } +} diff --git a/libpromises/files_interfaces.h b/libpromises/files_interfaces.h new file mode 100644 index 0000000000..2fc9838110 --- /dev/null +++ b/libpromises/files_interfaces.h @@ -0,0 +1,33 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_FILES_INTERFACES_H +#define CFENGINE_FILES_INTERFACES_H + +#include +#include /* AgentConnection */ + +int cf_lstat(const char *file, struct stat *buf, const FileCopy *fc, AgentConnection *conn); + +#endif diff --git a/libpromises/files_lib.c b/libpromises/files_lib.c new file mode 100644 index 0000000000..1105d8261f --- /dev/null +++ b/libpromises/files_lib.c @@ -0,0 +1,747 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* MakingChanges(), RecordFailure() */ +#include /* PromiseResultUpdate() */ + + +static Item *ROTATED = NULL; /* GLOBAL_X */ + + +/*********************************************************************/ + +void PurgeItemList(Item **list, char *name) +{ + Item *ip, *copy = NULL; + struct stat sb; + + CopyList(©, *list); + + for (ip = copy; ip != NULL; ip = ip->next) + { + if (stat(ip->name, &sb) == -1) + { + Log(LOG_LEVEL_VERBOSE, + "Purging file '%s' from '%s' list as it no longer exists", + ip->name, name); + DeleteItemLiteral(list, ip->name); + } + } + + DeleteItemList(copy); +} + +bool FileWriteOver(char *filename, char *contents) +{ + FILE *fp = safe_fopen_create_perms(filename, "w", CF_PERMS_DEFAULT); + + if(fp == NULL) + { + return false; + } + + size_t bytes_to_write = strlen(contents); + + size_t bytes_written = fwrite(contents, 1, bytes_to_write, fp); + + bool res = true; + + if(bytes_written != bytes_to_write) + { + res = false; + } + + if(fclose(fp) != 0) + { + res = false; + } + + return res; +} + + +/*********************************************************************/ + +static bool MakeParentDirectoryImpl(EvalContext *ctx, const Promise *pp, const Attributes *attr, + PromiseResult *result, const char *parentandchild, + bool force, bool internal, bool *created, + mode_t perms_mode); + +bool MakeParentDirectory(const char *parentandchild, bool force, bool *created) +{ + /* just use the complex function with no promise info */ + return MakeParentDirectoryImpl(NULL, NULL, NULL, NULL, + parentandchild, force, false, created, DEFAULTMODE); +} + +bool MakeParentDirectoryPerms(const char *parentandchild, bool force, bool *created, mode_t perms_mode) +{ + return MakeParentDirectoryImpl(NULL, NULL, NULL, NULL, + parentandchild, force, false, created, perms_mode); +} + +bool MakeParentInternalDirectory(const char *parentandchild, bool force, bool *created) +{ + /* just use the complex function with no promise info */ + return MakeParentDirectoryImpl(NULL, NULL, NULL, NULL, + parentandchild, force, true, created, DEFAULTMODE); +} + +bool MakeParentDirectoryForPromise(EvalContext *ctx, const Promise *pp, + const Attributes *attr, + PromiseResult *result, + const char *parentandchild, + bool force, bool *created, + const mode_t perms_mode) +{ + return MakeParentDirectoryImpl(ctx, pp, attr, result, parentandchild, + force, false, created, perms_mode); +} + +static bool MakeParentDirectoryImpl(EvalContext *ctx, const Promise *pp, const Attributes *attr, + PromiseResult *result, const char *parentandchild, + bool force, bool internal, bool *created, mode_t perms_mode) +{ + char *sp; + char currentpath[CF_BUFSIZE]; + char pathbuf[CF_BUFSIZE]; + struct stat statbuf; + mode_t mask; + int rootlen; + + const char *changes_parentandchild = parentandchild; + if (!internal && ChrootChanges()) + { + changes_parentandchild = ToChangesChroot(parentandchild); + } + + const bool have_promise_info = ((ctx != NULL) && (pp != NULL) && (attr != NULL) && (result != NULL)); + + if (created != NULL) + { + *created = false; + } + +#ifdef __APPLE__ +/* Keeps track of if dealing w. resource fork */ + int rsrcfork; + + rsrcfork = 0; + + char *tmpstr; +#endif + + Log(LOG_LEVEL_DEBUG, "Trying to create a parent directory%s for: %s", + force ? " (force applied)" : "", + parentandchild); + + if (!IsAbsoluteFileName(parentandchild)) + { + Log(LOG_LEVEL_ERR, + "Will not create directories for a relative filename: %s", + parentandchild); + return false; + } + + strlcpy(pathbuf, changes_parentandchild, CF_BUFSIZE); /* local copy */ + +#ifdef __APPLE__ + if (strstr(pathbuf, _PATH_RSRCFORKSPEC) != NULL) + { + rsrcfork = 1; + } +#endif + +/* skip link name */ + + sp = (char *) LastFileSeparator(pathbuf); /* de-constify */ + + if (sp == NULL) + { + sp = pathbuf; + } + *sp = '\0'; + + DeleteSlash(pathbuf); + + if (lstat(pathbuf, &statbuf) != -1) + { + if (S_ISLNK(statbuf.st_mode)) + { + Log(LOG_LEVEL_VERBOSE, "'%s' is a symbolic link, not a directory", + pathbuf); + } + + if (force) /* force in-the-way directories aside */ + { + struct stat dir; + stat(pathbuf, &dir); + + /* If the target directory exists as a directory, no problem. */ + /* If the target directory exists but is not a directory, then + * rename it to ".cf-moved": */ + if (!S_ISDIR(dir.st_mode)) + { + struct stat sbuf; + + strcpy(currentpath, pathbuf); + DeleteSlash(currentpath); + /* TODO overflow check! */ + strlcat(currentpath, ".cf-moved", sizeof(currentpath)); + Log(LOG_LEVEL_VERBOSE, + "Moving obstructing file/link %s to %s to make directory", + pathbuf, currentpath); + + if (have_promise_info && + !MakingChanges(ctx, pp, attr, result, + "move obstructing file/link '%s' to '%s' to make directories for '%s'", + pathbuf, currentpath, parentandchild)) + { + return true; + } + + /* Remove possibly pre-existing ".cf-moved" backup object. */ + if (lstat(currentpath, &sbuf) != -1) + { + if (S_ISDIR(sbuf.st_mode)) /* directory */ + { + if (!DeleteDirectoryTree(currentpath)) + { + Log(LOG_LEVEL_WARNING, + "Failed to remove directory '%s' while trying to remove a backup", + currentpath); + } + } + else /* not a directory */ + { + if (unlink(currentpath) == -1) + { + Log(LOG_LEVEL_WARNING, + "Couldn't remove file/link '%s' while trying to remove a backup" + " (unlink: %s)", currentpath, GetErrorStr()); + } + } + } + + /* And then rename the current object to ".cf-moved". */ + if (rename(pathbuf, currentpath) == -1) + { + if (have_promise_info) + { + RecordFailure(ctx, pp, attr, + "Couldn't rename '%s' to .cf-moved (rename: %s)", + pathbuf, GetErrorStr()); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + } + else + { + Log(LOG_LEVEL_ERR, + "Couldn't rename '%s' to .cf-moved (rename: %s)", + pathbuf, GetErrorStr()); + } + return false; + } + else if (have_promise_info) + { + RecordChange(ctx, pp, attr, + "Moved obstructing file/link '%s' to '%s' to make directories for '%s'", + pathbuf, currentpath, parentandchild); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); + } + } + } + else + { + if (!S_ISLNK(statbuf.st_mode) && !S_ISDIR(statbuf.st_mode)) + { + if (have_promise_info) + { + RecordFailure(ctx, pp, attr, + "The object '%s' is not a directory." + " Cannot make a new directory without deleting it.", + pathbuf); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + } + else + { + Log(LOG_LEVEL_ERR, "The object '%s' is not a directory." + " Cannot make a new directory without deleting it.", + pathbuf); + } + return false; + } + } + } + +/* Now we make directories descending from the root folder down to the leaf */ + + currentpath[0] = '\0'; + + rootlen = RootDirLength(changes_parentandchild); + /* currentpath is not NULL terminated on purpose! */ + strncpy(currentpath, changes_parentandchild, rootlen); + + for (size_t z = rootlen; changes_parentandchild[z] != '\0'; z++) + { + const char c = changes_parentandchild[z]; + + /* Copy up to the next separator. */ + if (!IsFileSep(c)) + { + currentpath[z] = c; + continue; + } + + const char path_file_separator = c; + currentpath[z] = '\0'; + + /* currentpath is complete path for each of the parent directories. */ + + if (currentpath[0] == '\0') + { + /* We are at dir "/" of an absolute path, no need to create. */ + } + /* WARNING: on Windows stat() fails if path has a trailing slash! */ + else if (stat(currentpath, &statbuf) == -1) + { + if (!have_promise_info || + MakingChanges(ctx, pp, attr, result, + "make directory '%s' for '%s'", currentpath, parentandchild)) + { + mask = umask(0); + + if (mkdir(currentpath, perms_mode) == -1) + { + if (errno != EEXIST) + { + if (have_promise_info) + { + RecordFailure(ctx, pp, attr, "Failed to make directory: %s (mkdir: %s)", + currentpath, GetErrorStr()); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + } + else + { + Log(LOG_LEVEL_ERR, + "Failed to make directory: %s (mkdir: %s)", + currentpath, GetErrorStr()); + } + umask(mask); + return false; + } + } + else + { + if (created != NULL) + { + *created = true; + } + } + umask(mask); + } + } + else + { + if (!S_ISDIR(statbuf.st_mode)) + { +#ifdef __APPLE__ + /* Ck if rsrc fork */ + if (rsrcfork) + { + tmpstr = xmalloc(CF_BUFSIZE); + strlcpy(tmpstr, currentpath, CF_BUFSIZE); + strncat(tmpstr, _PATH_FORKSPECIFIER, CF_BUFSIZE); + + /* CFEngine removed terminating slashes */ + DeleteSlash(tmpstr); + + if (strncmp(tmpstr, pathbuf, CF_BUFSIZE) == 0) + { + free(tmpstr); + return true; + } + free(tmpstr); + } +#endif + if (have_promise_info) + { + RecordFailure(ctx, pp, attr, + "Cannot make %s - %s is not a directory!", + pathbuf, currentpath); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + } + else + { + Log(LOG_LEVEL_ERR, + "Cannot make %s - %s is not a directory!", + pathbuf, currentpath); + } + return false; + } + } + + currentpath[z] = path_file_separator; + } + + Log(LOG_LEVEL_DEBUG, "Directory for '%s' exists. Okay", parentandchild); + return true; +} + +bool LoadFileAsItemList(Item **liststart, const char *file, EditDefaults edits, bool only_checks) +{ + { + struct stat statbuf; + if (stat(file, &statbuf) == -1) + { + Log(LOG_LEVEL_VERBOSE, "The proposed file '%s' could not be loaded. (stat: %s)", file, GetErrorStr()); + return false; + } + + if (edits.maxfilesize != 0 && statbuf.st_size > edits.maxfilesize) + { + Log(LOG_LEVEL_INFO, "File '%s' is bigger than the edit limit. max_file_size = %jd > %d bytes", file, + (intmax_t) statbuf.st_size, edits.maxfilesize); + return false; + } + + if (!S_ISREG(statbuf.st_mode)) + { + Log(LOG_LEVEL_INFO, "%s is not a plain file", file); + return false; + } + } + if (only_checks) + { + /* Checks done and none of them failed and returned we can just return + * true here. */ + return true; + } + + FILE *fp = safe_fopen(file, "rt"); + if (!fp) + { + Log(LOG_LEVEL_INFO, "Couldn't read file '%s' for editing. (fopen: %s)", file, GetErrorStr()); + return false; + } + + Buffer *concat = BufferNew(); + + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + bool result = true; + + for (;;) + { + ssize_t num_read = CfReadLine(&line, &line_size, fp); + if (num_read == -1) + { + if (!feof(fp)) + { + Log(LOG_LEVEL_ERR, + "Unable to read contents of file: %s (fread: %s)", + file, GetErrorStr()); + result = false; + } + break; + } + + if (edits.joinlines && *(line + strlen(line) - 1) == '\\') + { + *(line + strlen(line) - 1) = '\0'; + + BufferAppend(concat, line, num_read); + } + else + { + BufferAppend(concat, line, num_read); + if (!feof(fp) || (BufferSize(concat) > 0)) + { + AppendItem(liststart, BufferData(concat), NULL); + } + } + + BufferClear(concat); + } + + free(line); + BufferDestroy(concat); + fclose(fp); + return result; +} + +bool TraverseDirectoryTreeInternal(const char *base_path, + const char *current_path, + int (*callback)(const char *, const struct stat *, void *), + void *user_data) +{ + Dir *dirh = DirOpen(base_path); + if (!dirh) + { + if (errno == ENOENT) + { + return true; + } + + Log(LOG_LEVEL_INFO, "Unable to open directory '%s' during traversal of directory tree '%s' (opendir: %s)", + current_path, base_path, GetErrorStr()); + return false; + } + + bool failed = false; + for (const struct dirent *dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh)) + { + if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, "..")) + { + continue; + } + + char sub_path[CF_BUFSIZE]; + snprintf(sub_path, CF_BUFSIZE, "%s" FILE_SEPARATOR_STR "%s", current_path, dirp->d_name); + + struct stat lsb; + if (lstat(sub_path, &lsb) == -1) + { + if (errno == ENOENT) + { + /* File disappeared on its own */ + continue; + } + + Log(LOG_LEVEL_VERBOSE, "Unable to stat file '%s' during traversal of directory tree '%s' (lstat: %s)", + current_path, base_path, GetErrorStr()); + failed = true; + } + else + { + if (S_ISDIR(lsb.st_mode)) + { + if (!TraverseDirectoryTreeInternal(base_path, sub_path, callback, user_data)) + { + failed = true; + } + } + else + { + if (callback(sub_path, &lsb, user_data) == -1) + { + failed = true; + } + } + } + } + + DirClose(dirh); + return !failed; +} + +bool TraverseDirectoryTree(const char *path, + int (*callback)(const char *, const struct stat *, void *), + void *user_data) +{ + return TraverseDirectoryTreeInternal(path, path, callback, user_data); +} + +typedef struct +{ + unsigned char buffer[1024]; + const char **extensions_filter; + EVP_MD_CTX *crypto_context; + unsigned char **digest; +} HashDirectoryTreeState; + +int HashDirectoryTreeCallback(const char *filename, ARG_UNUSED const struct stat *sb, void *user_data) +{ + HashDirectoryTreeState *state = user_data; + bool ignore = true; + for (size_t i = 0; state->extensions_filter[i]; i++) + { + if (StringEndsWith(filename, state->extensions_filter[i])) + { + ignore = false; + break; + } + } + + if (ignore) + { + return 0; + } + + FILE *file = fopen(filename, "rb"); + if (!file) + { + Log(LOG_LEVEL_ERR, "Cannot open file for hashing '%s'. (fopen: %s)", filename, GetErrorStr()); + return -1; + } + + size_t len = 0; + char buffer[1024]; + while ((len = fread(buffer, 1, 1024, file))) + { + EVP_DigestUpdate(state->crypto_context, state->buffer, len); + } + + fclose(file); + return 0; +} + +bool HashDirectoryTree(const char *path, + const char **extensions_filter, + EVP_MD_CTX *crypto_context) +{ + HashDirectoryTreeState state; + memset(state.buffer, 0, 1024); + state.extensions_filter = extensions_filter; + state.crypto_context = crypto_context; + + return TraverseDirectoryTree(path, HashDirectoryTreeCallback, &state); +} + +void RotateFiles(const char *name, int number) +{ + int i, fd; + struct stat statbuf; + char from[CF_BUFSIZE], to[CF_BUFSIZE]; + + if (IsItemIn(ROTATED, name)) + { + return; + } + + PrependItem(&ROTATED, name, NULL); + + if (stat(name, &statbuf) == -1) + { + Log(LOG_LEVEL_VERBOSE, "No access to file %s", name); + return; + } + + for (i = number - 1; i > 0; i--) + { + snprintf(from, CF_BUFSIZE, "%s.%d", name, i); + snprintf(to, CF_BUFSIZE, "%s.%d", name, i + 1); + + if (rename(from, to) == -1) + { + Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); + } + + snprintf(from, CF_BUFSIZE, "%s.%d.gz", name, i); + snprintf(to, CF_BUFSIZE, "%s.%d.gz", name, i + 1); + + if (rename(from, to) == -1) + { + Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); + } + + snprintf(from, CF_BUFSIZE, "%s.%d.Z", name, i); + snprintf(to, CF_BUFSIZE, "%s.%d.Z", name, i + 1); + + if (rename(from, to) == -1) + { + Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); + } + + snprintf(from, CF_BUFSIZE, "%s.%d.bz", name, i); + snprintf(to, CF_BUFSIZE, "%s.%d.bz", name, i + 1); + + if (rename(from, to) == -1) + { + Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); + } + + snprintf(from, CF_BUFSIZE, "%s.%d.bz2", name, i); + snprintf(to, CF_BUFSIZE, "%s.%d.bz2", name, i + 1); + + if (rename(from, to) == -1) + { + Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); + } + } + + snprintf(to, CF_BUFSIZE, "%s.1", name); + + if (CopyRegularFileDisk(name, to) == false) + { + Log(LOG_LEVEL_DEBUG, "Copy failed in RotateFiles '%s' -> '%s'", name, to); + return; + } + + safe_chmod(to, statbuf.st_mode); + if (safe_chown(to, statbuf.st_uid, statbuf.st_gid)) + { + UnexpectedError("Failed to chown %s", to); + } + safe_chmod(name, 0600); /* File must be writable to empty .. */ + + if ((fd = safe_creat(name, statbuf.st_mode)) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to create new '%s' in disable(rotate). (create: %s)", + name, GetErrorStr()); + } + else + { + if (safe_chown(name, statbuf.st_uid, statbuf.st_gid)) /* NT doesn't have fchown */ + { + UnexpectedError("Failed to chown '%s'", name); + } + fchmod(fd, statbuf.st_mode); + close(fd); + } +} + +#ifndef __MINGW32__ + +void CreateEmptyFile(char *name) +{ + if (unlink(name) == -1) + { + if (errno != ENOENT) + { + Log(LOG_LEVEL_DEBUG, "Unable to remove existing file '%s'. (unlink: %s)", name, GetErrorStr()); + } + } + + int tempfd = safe_open(name, O_CREAT | O_EXCL | O_WRONLY); + if (tempfd < 0) + { + Log(LOG_LEVEL_ERR, "Couldn't open a file '%s'. (open: %s)", name, GetErrorStr()); + } + + close(tempfd); +} + +#endif diff --git a/libpromises/files_lib.h b/libpromises/files_lib.h new file mode 100644 index 0000000000..b290e68583 --- /dev/null +++ b/libpromises/files_lib.h @@ -0,0 +1,83 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_FILES_LIB_H +#define CFENGINE_FILES_LIB_H + +#include +#include + +void PurgeItemList(Item **list, char *name); +bool FileWriteOver(char *filename, char *contents); + +bool LoadFileAsItemList(Item **liststart, const char *file, EditDefaults edits, bool only_checks); + +/** + * @see MakeParentDirectoryForPromise() + */ +bool MakeParentDirectory(const char *parentandchild, bool force, bool *created); + +/** + * Identical to MakeParentDirectory, but allows you to specify permissions (mode) + */ +bool MakeParentDirectoryPerms(const char *parentandchild, bool force, bool *created, mode_t perms_mode); + +/** + * Create an internal directory (never in the changes chroot). + */ +bool MakeParentInternalDirectory(const char *parentandchild, bool force, bool *created); + +/** + * @warning This function will not behave right on Windows if the path + * contains double (back)slashes! + **/ +bool MakeParentDirectoryForPromise(EvalContext *ctx, const Promise *pp, + const Attributes *attr, + PromiseResult *result, + const char *parentandchild, + bool force, bool *created, + mode_t perms_mode); + +void RotateFiles(const char *name, int number); +void CreateEmptyFile(char *name); + + +/** + * @brief This is a somewhat simpler version of nftw that support user_data. + * Callback function must return 0 to indicate success, -1 for failure. + * @param path Path to traverse + * @param user_data User data carry + * @return True if successful + */ +bool TraverseDirectoryTree(const char *path, + int (*callback)(const char *path, const struct stat *sb, void *user_data), + void *user_data); + +bool HashDirectoryTree(const char *path, + const char **extensions_filter, + EVP_MD_CTX *crypto_context); + +#include + +#endif diff --git a/libpromises/files_links.c b/libpromises/files_links.c new file mode 100644 index 0000000000..42752b6978 --- /dev/null +++ b/libpromises/files_links.c @@ -0,0 +1,875 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* PrepareChangesChroot() */ + +#if !defined(__MINGW32__) +static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, PromiseResult *result); +#endif +static char *AbsLinkPath(const char *from, const char *relto); + +/*****************************************************************************/ + +#ifdef __MINGW32__ + +PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp) +{ + RecordFailure(ctx, pp, attr, "Windows does not support symbolic links (at VerifyLink())"); + return PROMISE_RESULT_FAIL; +} + +#else + +PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp) +{ + assert(attr != NULL); + /* VerifyHardLink() is a separate function */ + assert(attr->link.link_type != FILE_LINK_TYPE_HARDLINK); + + char to[CF_BUFSIZE], linkbuf[CF_BUFSIZE], absto[CF_BUFSIZE]; + struct stat sb; + + memset(to, 0, CF_BUFSIZE); + + const bool absolute_source = IsAbsoluteFileName(source); + + if ((!absolute_source) && (*source != '.')) /* links without a directory reference */ + { + snprintf(to, CF_BUFSIZE - 1, "./%s", source); + } + else + { + strlcpy(to, source, CF_BUFSIZE); + } + + if (!absolute_source) /* relative path, must still check if exists */ + { + Log(LOG_LEVEL_DEBUG, "Relative link destination detected '%s'", to); + strcpy(absto, AbsLinkPath(destination, to)); + Log(LOG_LEVEL_DEBUG, "Absolute path to relative link '%s', '%s'", absto, destination); + } + else + { + strcpy(absto, to); + } + + /* If making changes in a chroot, we need to get the link target into the + * chroot. */ + if (ChrootChanges()) + { + PrepareChangesChroot(absto); + } + + const char *changes_absto = absto; + if (ChrootChanges()) + { + changes_absto = ToChangesChroot(absto); + } + + bool source_file_exists = true; + + if (stat(changes_absto, &sb) == -1) + { + Log(LOG_LEVEL_DEBUG, "No source file '%s'", absto); + source_file_exists = false; + } + + if ((!source_file_exists) && (attr->link.when_no_file != cfa_force) && (attr->link.when_no_file != cfa_delete)) + { + Log(LOG_LEVEL_VERBOSE, "Source '%s' for linking is absent", absto); + RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s', no source", destination, to); + return PROMISE_RESULT_FAIL; + } + + const char *changes_destination = destination; + if (ChrootChanges()) + { + changes_destination = ToChangesChroot(destination); + } + + PromiseResult result = PROMISE_RESULT_NOOP; + if ((!source_file_exists) && (attr->link.when_no_file == cfa_delete)) + { + KillGhostLink(ctx, changes_destination, attr, pp, &result); + return result; + } + + memset(linkbuf, 0, CF_BUFSIZE); + + if (readlink(changes_destination, linkbuf, CF_BUFSIZE - 1) == -1) + { + if (!MakingChanges(ctx, pp, attr, &result, "create link '%s'", destination)) + { + return result; + } + + bool dir_created = false; + if (MakeParentDirectoryForPromise(ctx, pp, attr, &result, destination, + attr->move_obstructions, + &dir_created, DEFAULTMODE)) + { + if (dir_created) + { + RecordChange(ctx, pp, attr, "Created parent directory for link '%s'", destination); + result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE); + } + if (!MoveObstruction(ctx, destination, attr, pp, &result)) + { + RecordFailure(ctx, pp, attr, + "Unable to create link '%s' -> '%s', failed to move obstruction", + destination, to); + result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); + return result; + } + + if (!MakeLink(ctx, destination, source, attr, pp, &result)) + { + RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s'", destination, to); + result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); + } + } + return result; + } + else + { + /* to == "./$source" */ + bool link_correct = (StringEqual(linkbuf, source) || StringEqual(linkbuf, to)); + + /* If making changes in chroot, the existing symlink can also be + * pointing to the changes chroot (if it's absolute). */ + if (!link_correct && absolute_source && ChrootChanges()) + { + link_correct = StringEqual(linkbuf, ToChangesChroot(source)); + } + if (link_correct) + { + RecordNoChange(ctx, pp, attr, "Link '%s' points to '%s', promise kept", destination, source); + return PROMISE_RESULT_NOOP; + } + /* else */ + if (attr->move_obstructions) + { + if (MakingChanges(ctx, pp, attr, &result, "remove incorrect link '%s'", destination)) + { + if (unlink(ToChangesPath(destination)) == -1) + { + RecordFailure(ctx, pp, attr, "Error removing link '%s' (points to '%s' not '%s')", + destination, linkbuf, to); + return PROMISE_RESULT_FAIL; + } + RecordChange(ctx, pp, attr, "Overrode incorrect link '%s'", destination); + result = PROMISE_RESULT_CHANGE; + + MakeLink(ctx, destination, source, attr, pp, &result); + return result; + } + else + { + return result; + } + } + else + { + RecordFailure(ctx, pp, attr, + "Link '%s' points to '%s' not '%s', but not moving obstructions", + destination, linkbuf, to); + return PROMISE_RESULT_FAIL; + } + } +} +#endif /* !__MINGW32__ */ + +/*****************************************************************************/ + +#ifdef __MINGW32__ +PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp) +{ + RecordFailure(ctx, pp, attr, "Windows does not support symbolic links (at VerifyAbsoluteLink())"); + return PROMISE_RESULT_FAIL; +} + +#else +PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp) +{ + assert(attr != NULL); + + char absto[CF_BUFSIZE]; + char expand[CF_BUFSIZE]; + char linkto[CF_BUFSIZE]; + + if (*source == '.') + { + strcpy(linkto, destination); + ChopLastNode(linkto); + JoinPaths(linkto, sizeof(linkto), source); + } + else + { + strcpy(linkto, source); + } + + CompressPath(absto, sizeof(absto), linkto); + + expand[0] = '\0'; + + if (attr->link.when_no_file == cfa_force) + { + bool expanded; + struct stat sb; + if (ChrootChanges() && (lstat(ToChangesChroot(absto), &sb) != -1)) + { + char chrooted_expand[sizeof(expand)]; + chrooted_expand[0] = '\0'; + expanded = ExpandLinks(chrooted_expand, ToChangesChroot(absto), 0, CF_MAXLINKLEVEL); + strlcpy(expand, ToNormalRoot(chrooted_expand), sizeof(expand)); + } + else + { + expanded = ExpandLinks(expand, absto, 0, CF_MAXLINKLEVEL); + } + if (expanded) + { + Log(LOG_LEVEL_DEBUG, "ExpandLinks returned '%s'", expand); + } + else + { + RecordFailure(ctx, pp, attr, "Failed to expand absolute link to '%s'", source); + PromiseRef(LOG_LEVEL_ERR, pp); + return PROMISE_RESULT_FAIL; + } + } + else + { + strcpy(expand, absto); + } + + CompressPath(linkto, sizeof(linkto), expand); + + return VerifyLink(ctx, destination, linkto, attr, pp); +} +#endif /* __MINGW32__ */ + +/*****************************************************************************/ + +PromiseResult VerifyRelativeLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp) +{ + char *sp, *commonto, *commonfrom; + char buff[CF_BUFSIZE], linkto[CF_BUFSIZE]; + int levels = 0; + + if (*source == '.') + { + return VerifyLink(ctx, destination, source, attr, pp); + } + + if (!CompressPath(linkto, sizeof(linkto), source)) + { + RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s'", destination, source); + return PROMISE_RESULT_INTERRUPTED; + } + + commonto = linkto; + commonfrom = destination; + + if (strcmp(commonto, commonfrom) == 0) + { + RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s', can't link file '%s' to itself", + destination, source, commonto); + return PROMISE_RESULT_INTERRUPTED; + } + + while (*commonto == *commonfrom) + { + commonto++; + commonfrom++; + } + + while (!((IsAbsoluteFileName(commonto)) && (IsAbsoluteFileName(commonfrom)))) + { + commonto--; + commonfrom--; + } + + commonto++; + + for (sp = commonfrom; *sp != '\0'; sp++) + { + if (IsFileSep(*sp)) + { + levels++; + } + } + + memset(buff, 0, CF_BUFSIZE); + + strcat(buff, "."); + strcat(buff, FILE_SEPARATOR_STR); + + while (--levels > 0) + { + const char add[] = ".." FILE_SEPARATOR_STR; + + if (!PathAppend(buff, sizeof(buff), add, FILE_SEPARATOR)) + { + RecordFailure(ctx, pp, attr, + "Internal limit reached in VerifyRelativeLink()," + " path too long: '%s' + '%s'", + buff, add); + return PROMISE_RESULT_FAIL; + } + } + + if (!PathAppend(buff, sizeof(buff), commonto, FILE_SEPARATOR)) + { + RecordFailure(ctx, pp, attr, + "Internal limit reached in VerifyRelativeLink() end," + " path too long: '%s' + '%s'", + buff, commonto); + return PROMISE_RESULT_FAIL; + } + + return VerifyLink(ctx, destination, buff, attr, pp); +} + +/*****************************************************************************/ + +PromiseResult VerifyHardLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp) +{ + char to[CF_BUFSIZE], absto[CF_BUFSIZE]; + struct stat ssb, dsb; + + memset(to, 0, CF_BUFSIZE); + + const bool absolute_source = IsAbsoluteFileName(source); + + if ((!absolute_source) && (*source != '.')) /* links without a directory reference */ + { + snprintf(to, CF_BUFSIZE - 1, ".%c%s", FILE_SEPARATOR, source); + } + else + { + strlcpy(to, source, CF_BUFSIZE); + } + + if (!absolute_source) /* relative path, must still check if exists */ + { + Log(LOG_LEVEL_DEBUG, "Relative link destination detected '%s'", to); + strcpy(absto, AbsLinkPath(destination, to)); + Log(LOG_LEVEL_DEBUG, "Absolute path to relative link '%s', '%s'", absto, destination); + } + else + { + strcpy(absto, to); + } + + /* If making changes in a chroot, we need to get the link target into the + * chroot. */ + if (ChrootChanges()) + { + PrepareChangesChroot(absto); + } + + const char *changes_absto = absto; + if (ChrootChanges()) + { + changes_absto = ToChangesChroot(absto); + } + + if (stat(changes_absto, &ssb) == -1) + { + Log(LOG_LEVEL_DEBUG, "No source file '%s'", absto); + } + + if (!S_ISREG(ssb.st_mode)) + { + RecordFailure(ctx, pp, attr, + "Source file '%s' is not a regular file, not appropriate to hard-link", to); + return PROMISE_RESULT_FAIL; + } + + Log(LOG_LEVEL_DEBUG, "Trying to hard link '%s' -> '%s'", destination, to); + + if (stat(ChrootChanges()? ToChangesChroot(destination) : destination, &dsb) == -1) + { + PromiseResult result = PROMISE_RESULT_NOOP; + MakeHardLink(ctx, destination, to, attr, pp, &result); + return result; + } + + /* both files exist, but are they the same file? POSIX says */ + /* the files could be on different devices, but unix doesn't */ + /* allow this behaviour so the tests below are theoretical... */ + + if ((dsb.st_ino != ssb.st_ino) && (dsb.st_dev != ssb.st_dev)) + { + Log(LOG_LEVEL_VERBOSE, + "If this is POSIX, unable to determine if %s is hard link is correct" + " since it points to a different filesystem", + destination); + + if ((dsb.st_mode == ssb.st_mode) && (dsb.st_size == ssb.st_size)) + { + RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' on different device appears okay", + destination, to); + return PROMISE_RESULT_NOOP; + } + } + + if ((dsb.st_ino == ssb.st_ino) && (dsb.st_dev == ssb.st_dev)) + { + RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' exists and is okay", + destination, to); + return PROMISE_RESULT_NOOP; + } + + const char *chroot_msg = ""; + if (ChrootChanges()) + { + chroot_msg = " (but hardlinks are always replicated to the changes chroot)"; + } + Log(LOG_LEVEL_INFO, "'%s' does not appear to be a hard link to '%s'%s", destination, to, chroot_msg); + + PromiseResult result = PROMISE_RESULT_NOOP; + if (!MakingChanges(ctx, pp, attr, &result, "hard link '%s' -> '%s'", destination, to)) + { + return result; + } + + if (!MoveObstruction(ctx, destination, attr, pp, &result)) + { + RecordFailure(ctx, pp, attr, + "Unable to create hard link '%s' -> '%s', failed to move obstruction", + destination, to); + result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); + return result; + } + + MakeHardLink(ctx, destination, to, attr, pp, &result); + return result; +} + +/*****************************************************************************/ +/* Level */ +/*****************************************************************************/ + +#ifdef __MINGW32__ + +bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp, PromiseResult *result) +{ + RecordFailure(ctx, pp, attr, "Cannot remove dead link '%s' (Windows does not support symbolic links)", name); + PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + return false; +} + +#else /* !__MINGW32__ */ + +bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp, + PromiseResult *result) +{ + char linkbuf[CF_BUFSIZE], tmp[CF_BUFSIZE]; + char linkpath[CF_BUFSIZE], *sp; + struct stat statbuf; + + memset(linkbuf, 0, CF_BUFSIZE); + memset(linkpath, 0, CF_BUFSIZE); + + const char *changes_name = name; + if (ChrootChanges()) + { + changes_name = ToChangesChroot(name); + } + + if (readlink(changes_name, linkbuf, CF_BUFSIZE - 1) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Can't read link '%s' while checking for deadlinks", name); + return true; /* ignore */ + } + + if (!IsAbsoluteFileName(linkbuf)) + { + strcpy(linkpath, changes_name); /* Get path to link */ + + for (sp = linkpath + strlen(linkpath); (*sp != FILE_SEPARATOR) && (sp >= linkpath); sp--) + { + *sp = '\0'; + } + } + + strcat(linkpath, linkbuf); + CompressPath(tmp, sizeof(tmp), linkpath); + + if (stat(tmp, &statbuf) == -1) /* link points nowhere */ + { + if ((attr->link.when_no_file == cfa_delete) || (attr->recursion.rmdeadlinks)) + { + Log(LOG_LEVEL_VERBOSE, + "'%s' is a link which points to '%s', but the target doesn't seem to exist", + name, linkbuf); + + if (MakingChanges(ctx, pp, attr, result, "remove dead link '%s'", name)) + { + unlink(changes_name); /* May not work on a client-mounted system ! */ + RecordChange(ctx, pp, attr, "Removed dead link '%s'", name); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); + return true; + } + else + { + return true; + } + } + } + + return false; +} +#endif /* !__MINGW32__ */ + +/*****************************************************************************/ + +#if !defined(__MINGW32__) +static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, + PromiseResult *result) +{ + if (MakingChanges(ctx, pp, attr, result, "link files '%s' -> '%s'", from, to)) + { + const char *changes_to = to; + char *chroot_to = NULL; + if (ChrootChanges()) + { + /* Create a copy because the next call to ToChangesChroot() will + * overwrite the value. */ + chroot_to = xstrdup(ToChangesChroot(to)); + changes_to = chroot_to; + } + const char *changes_from = from; + if (ChrootChanges()) + { + changes_from = ToChangesChroot(from); + } + + if (symlink(changes_to, changes_from) == -1) + { + RecordFailure(ctx, pp, attr, "Couldn't link '%s' to '%s'. (symlink: %s)", + to, from, GetErrorStr()); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + free(chroot_to); + return false; + } + else + { + RecordChange(ctx, pp, attr, "Linked files '%s' -> '%s'", from, to); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); + free(chroot_to); + return true; + } + } + else + { + return false; + } +} +#endif /* !__MINGW32__ */ + +/*****************************************************************************/ + +#ifdef __MINGW32__ + +bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, + PromiseResult *result) +{ // TODO: Implement ? + RecordFailure(ctx, pp, attr, + "Couldn't hard link '%s' to '%s' (Hard links are not yet supported on Windows)", + to, from); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + return false; +} + +#else /* !__MINGW32__ */ + +bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, + PromiseResult *result) +{ + if (MakingChanges(ctx, pp, attr, result, "hard link files '%s' -> '%s'", from, to)) + { + const char *changes_to = to; + char *chroot_to = NULL; + if (ChrootChanges()) + { + /* Create a copy because the next call to ToChangesChroot() will + * overwrite the value. */ + chroot_to = xstrdup(ToChangesChroot(to)); + changes_to = chroot_to; + } + const char *changes_from = from; + if (ChrootChanges()) + { + changes_from = ToChangesChroot(from); + } + + if (link(changes_to, changes_from) == -1) + { + RecordFailure(ctx, pp, attr, "Failed to hard link '%s' to '%s'. (link: %s)", + to, from, GetErrorStr()); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + return false; + } + else + { + RecordChange(ctx, pp, attr, "Hard linked file '%s' -> '%s'", from, to); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); + return true; + } + } + else + { + return false; + } +} + +#endif /* !__MINGW32__ */ + +/*********************************************************************/ + +/* Expand a path contaning symbolic links, up to 4 levels */ +/* of symbolic links and then beam out in a hurry ! */ + +#ifdef __MINGW32__ + +bool ExpandLinks(char *dest, const char *from, int level, int max_level) +{ + Log(LOG_LEVEL_ERR, "Windows does not support symbolic links (at ExpandLinks(%s,%s))", dest, from); + return false; +} + +#else /* !__MINGW32__ */ + +bool ExpandLinks(char *dest, const char *from, int level, int max_level) +{ + char buff[CF_BUFSIZE]; + char node[CF_MAXLINKSIZE]; + struct stat statbuf; + int lastnode = false; + + memset(dest, 0, CF_BUFSIZE); + + if (level >= CF_MAXLINKLEVEL) + { + Log(LOG_LEVEL_ERR, "Too many levels of symbolic links to evaluate absolute path"); + return false; + } + + if (level >= max_level) + { + Log(LOG_LEVEL_DEBUG, "Reached maximum level of symbolic link resolution"); + return true; + } + + const char *sp = from; + + while (*sp != '\0') + { + if (*sp == FILE_SEPARATOR) + { + sp++; + continue; + } + + sscanf(sp, "%[^/]", node); + sp += strlen(node); + + if (*sp == '\0') + { + lastnode = true; + } + + if (strcmp(node, ".") == 0) + { + continue; + } + + if (strcmp(node, "..") == 0) + { + strcat(dest, "/.."); + continue; + } + else + { + strcat(dest, "/"); + } + + strcat(dest, node); + + if (lstat(dest, &statbuf) == -1) /* File doesn't exist so we can stop here */ + { + Log(LOG_LEVEL_ERR, "Can't stat '%s' in ExpandLinks. (lstat: %s)", dest, GetErrorStr()); + return false; + } + + if (S_ISLNK(statbuf.st_mode)) + { + memset(buff, 0, CF_BUFSIZE); + + if (readlink(dest, buff, CF_BUFSIZE - 1) == -1) + { + Log(LOG_LEVEL_ERR, "Expand links can't stat '%s'. (readlink: %s)", dest, GetErrorStr()); + return false; + } + else + { + if (buff[0] == '.') + { + ChopLastNode(dest); + AddSlash(dest); + + /* TODO pass and use parameter dest_size. */ + size_t ret = strlcat(dest, buff, CF_BUFSIZE); + if (ret >= CF_BUFSIZE) + { + Log(LOG_LEVEL_ERR, + "Internal limit reached in ExpandLinks()," + " path too long: '%s' + '%s'", + dest, buff); + return false; + } + } + else if (IsAbsoluteFileName(buff)) + { + strcpy(dest, buff); + DeleteSlash(dest); + + if (strcmp(dest, from) == 0) + { + Log(LOG_LEVEL_DEBUG, "No links to be expanded"); + return true; + } + + if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1, max_level))) + { + return false; + } + } + else + { + ChopLastNode(dest); + AddSlash(dest); + + /* TODO use param dest_size. */ + size_t ret = strlcat(dest, buff, CF_BUFSIZE); + if (ret >= CF_BUFSIZE) + { + Log(LOG_LEVEL_ERR, + "Internal limit reached in ExpandLinks end," + " path too long: '%s' + '%s'", dest, buff); + return false; + } + + DeleteSlash(dest); + + if (strcmp(dest, from) == 0) + { + Log(LOG_LEVEL_DEBUG, "No links to be expanded"); + return true; + } + + memset(buff, 0, CF_BUFSIZE); + + if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1, max_level))) + { + return false; + } + } + } + } + } + + return true; +} +#endif /* !__MINGW32__ */ + +/*********************************************************************/ + +static char *AbsLinkPath(const char *from, const char *relto) +/* Take an abolute source and a relative destination object + and find the absolute name of the to object */ +{ + int pop = 1; + static char destination[CF_BUFSIZE]; /* GLOBAL_R, no need to initialize */ + + if (IsAbsoluteFileName(relto)) + { + ProgrammingError("Call to AbsLInkPath with absolute pathname"); + } + + strcpy(destination, from); /* reuse to save stack space */ + + const char *sp = NULL; + for (sp = relto; *sp != '\0'; sp++) + { + if (strncmp(sp, "../", 3) == 0) + { + pop++; + sp += 2; + continue; + } + + if (strncmp(sp, "./", 2) == 0) + { + sp += 1; + continue; + } + + break; /* real link */ + } + + while (pop > 0) + { + ChopLastNode(destination); + pop--; + } + + if (strlen(destination) == 0) + { + strcpy(destination, "/"); + } + else + { + AddSlash(destination); + } + + strcat(destination, sp); + Log(LOG_LEVEL_DEBUG, "Reconstructed absolute linkname '%s'", destination); + return destination; +} diff --git a/libpromises/files_links.h b/libpromises/files_links.h new file mode 100644 index 0000000000..8c55f1ef69 --- /dev/null +++ b/libpromises/files_links.h @@ -0,0 +1,40 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_FILES_LINKS_H +#define CFENGINE_FILES_LINKS_H + +#include + +#define CF_MAXLINKLEVEL 4 + +PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp); +PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp); +PromiseResult VerifyRelativeLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp); +PromiseResult VerifyHardLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp); +bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp, PromiseResult *result); +bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, PromiseResult *result); +bool ExpandLinks(char *dest, const char *from, int level, int max_level); + +#endif diff --git a/libpromises/files_names.c b/libpromises/files_names.c new file mode 100644 index 0000000000..e710249bef --- /dev/null +++ b/libpromises/files_names.c @@ -0,0 +1,858 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/*********************************************************************/ + +bool IsNewerFileTree(const char *dir, time_t reftime) +{ + const struct dirent *dirp; + Dir *dirh; + struct stat sb; + +// Assumes that race conditions on the file path are unlikely and unimportant + + if (lstat(dir, &sb) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to stat directory '%s' in IsNewerFileTree. (stat: %s)", dir, GetErrorStr()); + // return true to provoke update + return true; + } + + if (S_ISDIR(sb.st_mode)) + { + if (sb.st_mtime > reftime) + { + Log(LOG_LEVEL_VERBOSE, " >> Detected change in %s", dir); + return true; + } + } + + if ((dirh = DirOpen(dir)) == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to open directory '%s' in IsNewerFileTree. (opendir: %s)", dir, GetErrorStr()); + return false; + } + else + { + for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh)) + { + if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, "..")) + { + continue; + } + + char path[CF_BUFSIZE]; + size_t ret = (size_t) snprintf(path, sizeof(path), "%s%c%s", + dir, FILE_SEPARATOR, dirp->d_name); + + if (ret >= sizeof(path)) + { + Log(LOG_LEVEL_ERR, + "Internal limit reached in IsNewerFileTree()," + " path too long: '%s' + '%s'", + dir, dirp->d_name); + DirClose(dirh); + return false; + } + + if (lstat(path, &sb) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to stat directory '%s' in IsNewerFileTree. (lstat: %s)", path, GetErrorStr()); + DirClose(dirh); + // return true to provoke update + return true; + } + + if (S_ISDIR(sb.st_mode)) + { + if (sb.st_mtime > reftime) + { + Log(LOG_LEVEL_VERBOSE, " >> Detected change in %s", path); + DirClose(dirh); + return true; + } + else + { + if (IsNewerFileTree(path, reftime)) + { + DirClose(dirh); + return true; + } + } + } + } + } + + DirClose(dirh); + return false; +} + +/*********************************************************************/ + +bool IsDir(const char *path) +/* +Checks if the object pointed to by path exists and is a directory. +Returns true if so, false otherwise. +*/ +{ +#ifdef __MINGW32__ + return NovaWin_IsDir(path); +#else + struct stat sb; + + if (stat(path, &sb) != -1) + { + if (S_ISDIR(sb.st_mode)) + { + return true; + } + } + + return false; +#endif /* !__MINGW32__ */ +} + +/*********************************************************************/ + +char *JoinSuffix(char *path, size_t path_size, const char *leaf) +{ + int len = strlen(leaf); + + if (Chop(path, path_size) == -1) + { + Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator"); + } + DeleteSlash(path); + + if (strlen(path) + len + 1 > path_size) + { + Log(LOG_LEVEL_ERR, "JoinSuffix: Internal limit reached. Tried to add %s to %s", + leaf, path); + return NULL; + } + + strlcat(path, leaf, path_size); + return path; +} + +/** + * Just like the JoinSuffix() above, but makes sure there's a FILE_SEPARATOR + * between @path and @leaf_path. The only exception is the case where @path is + * "" and @leaf_path doesn't start with a FILE_SEPARATOR. In that case: + * JoinPaths("", PATH_MAX, "some_path") -> "some_path" + * + * This function is similar to Python's os.path.join() except that unlike the + * Python function this one actually joins @path and @leaf_path even if + * @leaf_path starts with a FILE_SEPARATOR. + */ +char *JoinPaths(char *path, size_t path_size, const char *leaf_path) +{ + size_t len = strlen(leaf_path); + size_t path_len = strnlen(path, path_size); + + if (Chop(path, path_size - 1) == -1) + { + Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator"); + return NULL; + } + + if (path_len + len + 1 > path_size) + { + Log(LOG_LEVEL_ERR, "JoinPaths: Internal limit reached. Tried to add %s to %s", + leaf_path, path); + return NULL; + } + + /* make sure there's a FILE_SEPARATOR between path and leaf_path */ + if ((path_len > 0 && !IsFileSep(path[path_len - 1])) && !IsFileSep(leaf_path[0])) + { + strlcat(path, FILE_SEPARATOR_STR, path_size); + } + else if ((path_len > 0 && IsFileSep(path[path_len - 1])) && IsFileSep(leaf_path[0])) + { + leaf_path += 1; + } + strlcat(path, leaf_path, path_size); + return path; +} + +bool IsAbsPath(const char *path) +{ + if (IsFileSep(*path)) + { + return true; + } + else + { + return false; + } +} + +/** + * Append a slash, of the kind that the string already has, only if the string + * doesn't end in one. + */ +void AddSlash(char *str) +{ + char *sp, *sep = FILE_SEPARATOR_STR; + int f = false, b = false; + + if (str == NULL) + { + return; + } + + if (strlen(str) == 0) + { + strcpy(str, sep); + return; + } + +/* Try to see what convention is being used for filenames + in case this is a cross-system copy from Win/Unix */ + + for (sp = str; *sp != '\0'; sp++) + { + switch (*sp) + { + case '/': + f = true; + break; + case '\\': + b = true; + break; + default: + break; + } + } + + if (f && (!b)) + { + sep = "/"; + } + else if (b && (!f)) + { + sep = "\\"; + } + + if (!IsFileSep(str[strlen(str) - 1])) + { + strcat(str, sep); + } +} + +/*********************************************************************/ + +char *GetParentDirectoryCopy(const char *path) +/** + * WARNING: Remember to free return value. + **/ +{ + assert(path); + assert(strlen(path) > 0); + + char *path_copy = xstrdup(path); + + if(strcmp(path_copy, "/") == 0) + { + return path_copy; + } + + char *sp = (char *)LastFileSeparator(path_copy); + + if(!sp) + { + Log(LOG_LEVEL_ERR, "Path %s does not contain file separators (GetParentDirectory())", path_copy); + free(path_copy); + return NULL; + } + + if(sp == FirstFileSeparator(path_copy)) // don't chop off first path separator + { + *(sp + 1) = '\0'; + } + else + { + *sp = '\0'; + } + + return path_copy; +} + +/*********************************************************************/ + +// Can remove several slashes if they are redundant. +void DeleteSlash(char *str) +{ + int size = strlen(str); + if ((size == 0) || (str == NULL)) + { + return; + } + + int root = RootDirLength(str); + while (IsFileSep(str[size - 1]) && size - 1 > root) + { + size--; + } + str[size] = '\0'; /* no-op if we didn't change size */ +} + +/*********************************************************************/ + +void DeleteRedundantSlashes(char *str) +{ + int move_from; + // Invariant: newpos <= oldpos + int oldpos = RootDirLength(str); + int newpos = oldpos; + while (str[oldpos] != '\0') + { + // Skip over subsequent separators. + while (IsFileSep(str[oldpos])) + { + oldpos++; + } + move_from = oldpos; + + // And then skip over the next path component. + while (str[oldpos] != '\0' && !IsFileSep(str[oldpos])) + { + oldpos++; + } + + // If next character is file separator, move past it, since we want to keep one. + if (IsFileSep(str[oldpos])) + { + oldpos++; + } + + int move_len = oldpos - move_from; + memmove(&str[newpos], &str[move_from], move_len); + newpos += move_len; + } + + str[newpos] = '\0'; +} + +/*********************************************************************/ + +const char *FirstFileSeparator(const char *str) +{ + assert(str); + assert(strlen(str) > 0); + + if(strncmp(str, "\\\\", 2) == 0) // windows share + { + return str + 1; + } + + for(const char *pos = str; *pos != '\0'; pos++) + { + if(IsFileSep(*pos)) + { + return pos; + } + } + + return NULL; +} + +/*********************************************************************/ + +const char *LastFileSeparator(const char *str) + /* Return pointer to last file separator in string, or NULL if + string does not contains any file separtors */ +{ + const char *sp; + +/* Walk through string backwards */ + + sp = str + strlen(str) - 1; + + while (sp >= str) + { + if (IsFileSep(*sp)) + { + return sp; + } + sp--; + } + + return NULL; +} + +/*********************************************************************/ + +bool ChopLastNode(char *str) + /* Chop off trailing node name (possible blank) starting from + last character and removing up to the first / encountered + e.g. /a/b/c -> /a/b + /a/b/ -> /a/b + Will also collapse redundant/repeating path separators. + */ +{ + char *sp; + bool ret; + + DeleteRedundantSlashes(str); + +/* Here cast is necessary and harmless, str is modifiable */ + if ((sp = (char *) LastFileSeparator(str)) == NULL) + { + int pos = RootDirLength(str); + if (str[pos] == '\0') + { + ret = false; + } + else + { + str[pos] = '.'; + str[pos + 1] = '\0'; + ret = true; + } + } + else + { + // Don't chop the root slash in an absolute path. + if (IsAbsoluteFileName(str) && FirstFileSeparator(str) == sp) + { + *(++sp) = '\0'; + } + else + { + *sp = '\0'; + } + ret = true; + } + + return ret; +} + +/*********************************************************************/ + +void TransformNameInPlace(char *s, char from, char to) +{ + for (; *s != '\0'; s++) + { + if (*s == from) + { + *s = to; + } + } +} + +/*********************************************************************/ + +/* TODO remove, kill, burn this function! Replace with BufferCanonify or CanonifyNameInPlace */ +char *CanonifyName(const char *str) +{ + static char buffer[CF_BUFSIZE]; /* GLOBAL_R, no initialization needed */ + + strlcpy(buffer, str, CF_BUFSIZE); + CanonifyNameInPlace(buffer); + return buffer; +} + +/*********************************************************************/ + +char *CanonifyChar(const char *str, char ch) +{ + static char buffer[CF_BUFSIZE]; /* GLOBAL_R, no initialization needed */ + char *sp; + + strlcpy(buffer, str, CF_BUFSIZE); + + for (sp = buffer; *sp != '\0'; sp++) + { + if (*sp == ch) + { + *sp = '_'; + } + } + + return buffer; +} + +/*********************************************************************/ + +int CompareCSVName(const char *s1, const char *s2) +{ + const char *sp1, *sp2; + char ch1, ch2; + + for (sp1 = s1, sp2 = s2; (*sp1 != '\0') || (*sp2 != '\0'); sp1++, sp2++) + { + ch1 = (*sp1 == ',') ? '_' : *sp1; + ch2 = (*sp2 == ',') ? '_' : *sp2; + + if (ch1 > ch2) + { + return 1; + } + else if (ch1 < ch2) + { + return -1; + } + } + + return 0; +} + +/*********************************************************************/ + +const char *ReadLastNode(const char *str) +/* Return the last node of a pathname string */ +{ + const char *sp; + + if ((sp = LastFileSeparator(str)) == NULL) + { + return str; + } + else + { + return sp + 1; + } +} + +/*********************************************************************/ + +bool CompressPath(char *dest, size_t dest_size, const char *src) +{ + char node[CF_BUFSIZE]; + int nodelen; + int rootlen; + + memset(dest, 0, dest_size); + + rootlen = RootDirLength(src); + + if((size_t) rootlen >= dest_size) + { + Log(LOG_LEVEL_ERR, + "Internal limit reached in CompressPath()," + "src path too long (%d bytes): '%s'", + rootlen, src); + return false; + } + + memcpy(dest, src, rootlen); + + for (const char *sp = src + rootlen; *sp != '\0'; sp++) + { + if (IsFileSep(*sp)) + { + continue; + } + + for (nodelen = 0; (sp[nodelen] != '\0') && (!IsFileSep(sp[nodelen])); nodelen++) + { + if (nodelen > CF_MAXLINKSIZE) + { + Log(LOG_LEVEL_ERR, "Link in path suspiciously large"); + return false; + } + } + + strncpy(node, sp, nodelen); + node[nodelen] = '\0'; + + sp += nodelen - 1; + + if (strcmp(node, ".") == 0) + { + continue; + } + + if (strcmp(node, "..") == 0) + { + if (!ChopLastNode(dest)) + { + Log(LOG_LEVEL_DEBUG, "used .. beyond top of filesystem!"); + return false; + } + + continue; + } + + AddSlash(dest); + + size_t ret = strlcat(dest, node, dest_size); + + if (ret >= CF_BUFSIZE) + { + Log(LOG_LEVEL_ERR, + "Internal limit reached in CompressPath()," + " path too long: '%s' + '%s'", + dest, node); + return false; + } + } + + return true; +} + +/*********************************************************************/ + +/** + * Get absolute path of @path. If @path is already an absolute path this + * function just returns a compressed (see CompressPath()) copy of it. Otherwise + * this function prepends the curent working directory before @path and returns + * the result compressed with CompressPath(). If anything goes wrong, an empty + * string is returned. + * + * WARNING: Remember to free return value. + **/ +char *GetAbsolutePath(const char *path) +{ + if (NULL_OR_EMPTY(path)) + { + return NULL; + } + char abs_path[PATH_MAX] = { 0 }; + if (IsAbsoluteFileName(path)) + { + CompressPath(abs_path, PATH_MAX, path); + return xstrdup(abs_path); + } + else + { + /* the full_path can potentially be long (with many '../' parts)*/ + char full_path[2 * PATH_MAX] = { 0 }; + if (getcwd(full_path, PATH_MAX) == NULL) + { + Log(LOG_LEVEL_WARNING, + "Could not determine current directory (getcwd: %s)", + GetErrorStr()); + } + JoinPaths(full_path, 2 * PATH_MAX, path); + CompressPath(abs_path, PATH_MAX, full_path); + return xstrdup(abs_path); + } +} + +char *GetRealPath(const char *const path) +{ + if (NULL_OR_EMPTY(path)) + { + return NULL; + } + char *const abs_path = GetAbsolutePath(path); + if (NULL_OR_EMPTY(abs_path)) + { + free(abs_path); + return NULL; + } + +#ifdef __linux__ // POSIX 2008 - could add newer versions of BSD / solaris + char *real_path = realpath(abs_path, NULL); + if (NOT_NULL_AND_EMPTY(real_path)) + { + free(real_path); + real_path = NULL; + } +#else // Pre POSIX 2008 - realpath arg cannot be NULL + char *const path_buf = xcalloc(1, PATH_MAX); + char *real_path = realpath(abs_path, path_buf); + if (NULL_OR_EMPTY(real_path)) + { + free(path_buf); + real_path = NULL; + } +#endif + + free(abs_path); + return real_path; +} + +/*********************************************************************/ + +FilePathType FilePathGetType(const char *file_path) +{ + if (IsAbsoluteFileName(file_path)) + { + return FILE_PATH_TYPE_ABSOLUTE; + } + else if (*file_path == '.') + { + return FILE_PATH_TYPE_RELATIVE; + } + else + { + return FILE_PATH_TYPE_NON_ANCHORED; + } +} + +bool IsFileOutsideDefaultRepository(const char *f) +{ + return !StringStartsWith(f, GetInputDir()); +} + +/*******************************************************************/ + +static int UnixRootDirLength(const char *f) +{ + if (IsFileSep(*f)) + { + return 1; + } + + return 0; +} + +#ifdef _WIN32 +static int NTRootDirLength(const char *f) +{ + int len; + + if (f[0] == '\\' && f[1] == '\\') + { + /* UNC style path */ + + /* Skip over host name */ + for (len = 2; f[len] != '\\'; len++) + { + if (f[len] == '\0') + { + return len; + } + } + + /* Skip over share name */ + + for (len++; f[len] != '\\'; len++) + { + if (f[len] == '\0') + { + return len; + } + } + + /* Skip over file separator */ + len++; + + return len; + } + + if (isalpha(f[0]) && f[1] == ':') + { + if (IsFileSep(f[2])) + { + return 3; + } + + return 2; + } + + return UnixRootDirLength(f); +} +#endif + +int RootDirLength(const char *f) + /* Return length of Initial directory in path - */ +{ +#ifdef _WIN32 + return NTRootDirLength(f); +#else + return UnixRootDirLength(f); +#endif +} + +/* Buffer should be at least CF_MAXVARSIZE large */ +const char *GetSoftwareCacheFilename(char *buffer) +{ + snprintf(buffer, CF_MAXVARSIZE, "%s/%s", GetStateDir(), SOFTWARE_PACKAGES_CACHE); + MapName(buffer); + return buffer; +} + +/* Buffer should be at least CF_MAXVARSIZE large */ +const char *GetSoftwarePatchesFilename(char *buffer) +{ + snprintf(buffer, CF_MAXVARSIZE, "%s/%s", GetStateDir(), SOFTWARE_PATCHES_CACHE); + MapName(buffer); + return buffer; +} + +const char *RealPackageManager(const char *manager) +{ + assert(manager); + + const char *pos = strchr(manager, ' '); + if (strncmp(manager, "env ", 4) != 0 + && (!pos || pos - manager < 4 || strncmp(pos - 4, "/env", 4) != 0)) + { + return CommandArg0(manager); + } + + // Look for variable assignments. + const char *last_pos; + bool eq_sign_found = false; + while (true) + { + if (eq_sign_found) + { + last_pos = pos + 1; + } + else + { + last_pos = pos + strspn(pos, " "); // Skip over consecutive spaces. + } + pos = strpbrk(last_pos, "= "); + if (!pos) + { + break; + } + if (*pos == '=') + { + eq_sign_found = true; + } + else if (eq_sign_found) + { + eq_sign_found = false; + } + else + { + return CommandArg0(last_pos); + } + } + + // Reached the end? Weird. Must be env command with no real command. + return CommandArg0(manager); +} diff --git a/libpromises/files_names.h b/libpromises/files_names.h new file mode 100644 index 0000000000..107c8b4997 --- /dev/null +++ b/libpromises/files_names.h @@ -0,0 +1,70 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_FILES_NAMES_H +#define CFENGINE_FILES_NAMES_H + +#include + +typedef enum +{ + FILE_PATH_TYPE_ABSOLUTE, // /foo.cf + FILE_PATH_TYPE_RELATIVE, // ./../foo.cf + FILE_PATH_TYPE_NON_ANCHORED, // foo.cf +} FilePathType; + +FilePathType FilePathGetType(const char *file_path); + +bool IsNewerFileTree(const char *dir, time_t reftime); +int CompareCSVName(const char *s1, const char *s2); +bool IsDir(const char *path); +char *JoinSuffix(char *path, size_t path_size, const char *leaf); +char *JoinPaths(char *path, size_t path_size, const char *leaf_path); +bool IsAbsPath(const char *path); +void AddSlash(char *str); +char *GetParentDirectoryCopy(const char *path); +void DeleteSlash(char *str); +void DeleteRedundantSlashes(char *str); +const char *FirstFileSeparator(const char *str); +const char *LastFileSeparator(const char *str); +bool ChopLastNode(char *str); +char *CanonifyName(const char *str); +void TransformNameInPlace(char *s, char from, char to); +char *CanonifyChar(const char *str, char ch); +const char *ReadLastNode(const char *str); +bool CompressPath(char *dest, size_t dest_size, const char *src); +char *GetAbsolutePath(const char *path); +char *GetRealPath(const char *path); +bool IsFileOutsideDefaultRepository(const char *f); +int RootDirLength(const char *f); +const char *GetSoftwareCacheFilename(char *buffer); +const char *GetSoftwarePatchesFilename(char *buffer); + +/** + * Detect whether package manager starts with an env command instead of package manager, + * and if so, return the real package manager. + */ +const char *RealPackageManager(const char *manager); + +#endif diff --git a/libpromises/files_operators.c b/libpromises/files_operators.c new file mode 100644 index 0000000000..f8d212edb3 --- /dev/null +++ b/libpromises/files_operators.c @@ -0,0 +1,476 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +bool MoveObstruction(EvalContext *ctx, char *from, const Attributes *attr, const Promise *pp, PromiseResult *result) +{ + assert(attr != NULL); + struct stat sb; + char stamp[CF_BUFSIZE], saved[CF_BUFSIZE]; + time_t now_stamp = time((time_t *) NULL); + + const char *changes_from = from; + if (ChrootChanges()) + { + changes_from = ToChangesChroot(from); + } + + if (lstat(from, &sb) == 0) + { + if (!attr->move_obstructions) + { + RecordFailure(ctx, pp, attr, "Object '%s' is obstructing promise", from); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + return false; + } + + if (!S_ISDIR(sb.st_mode)) + { + if (!MakingChanges(ctx, pp, attr, result, "move aside object '%s' obstructing promise", from)) + { + return false; + } + + saved[0] = '\0'; + strlcpy(saved, changes_from, sizeof(saved)); + + if (attr->copy.backup == BACKUP_OPTION_TIMESTAMP || attr->edits.backup == BACKUP_OPTION_TIMESTAMP) + { + snprintf(stamp, CF_BUFSIZE, "_%jd_%s", (intmax_t) CFSTARTTIME, CanonifyName(ctime(&now_stamp))); + strlcat(saved, stamp, sizeof(saved)); + } + + strlcat(saved, CF_SAVED, sizeof(saved)); + + if (rename(changes_from, saved) == -1) + { + RecordFailure(ctx, pp, attr, + "Can't rename '%s' to '%s'. (rename: %s)", from, saved, GetErrorStr()); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + return false; + } + RecordChange(ctx, pp, attr, "Moved obstructing object '%s' to '%s'", from, saved); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); + + if (ArchiveToRepository(saved, attr)) + { + RecordChange(ctx, pp, attr, "Archived '%s'", saved); + unlink(saved); + } + + return true; + } + + if (S_ISDIR(sb.st_mode)) + { + if (!MakingChanges(ctx, pp, attr, result, "move aside directory '%s' obstructing", from)) + { + return false; + } + + saved[0] = '\0'; + strlcpy(saved, changes_from, sizeof(saved)); + + snprintf(stamp, CF_BUFSIZE, "_%jd_%s", (intmax_t) CFSTARTTIME, CanonifyName(ctime(&now_stamp))); + strlcat(saved, stamp, sizeof(saved)); + strlcat(saved, CF_SAVED, sizeof(saved)); + strlcat(saved, ".dir", sizeof(saved)); + + if (stat(saved, &sb) != -1) + { + RecordFailure(ctx, pp, attr, + "Couldn't move directory '%s' aside, since '%s' exists already", + from, saved); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + return false; + } + + if (rename(changes_from, saved) == -1) + { + RecordFailure(ctx, pp, attr, "Can't rename '%s' to '%s'. (rename: %s)", + from, saved, GetErrorStr()); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); + return false; + } + RecordChange(ctx, pp, attr, "Moved directory '%s' to '%s%s'", from, from, CF_SAVED); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); + } + } + + return true; +} + +/*********************************************************************/ + +bool SaveAsFile(SaveCallbackFn callback, void *param, const char *file, const Attributes *a, NewLineMode new_line_mode) +{ + assert(a != NULL); + struct stat statbuf; + char new[CF_BUFSIZE], backup[CF_BUFSIZE]; + char stamp[CF_BUFSIZE]; + time_t stamp_now; + Buffer *deref_file = BufferNewFrom(file, strlen(file)); + Buffer *pretty_file = BufferNew(); + bool ret = false; + + BufferPrintf(pretty_file, "'%s'", file); + + stamp_now = time((time_t *) NULL); + + while (1) + { + if (lstat(BufferData(deref_file), &statbuf) == -1) + { + Log(LOG_LEVEL_ERR, "Can no longer access file %s, which needed editing. (lstat: %s)", BufferData(pretty_file), GetErrorStr()); + goto end; + } +#ifndef __MINGW32__ + if (S_ISLNK(statbuf.st_mode)) + { + char buf[statbuf.st_size + 1]; + // Careful. readlink() doesn't add '\0' byte. + ssize_t linksize = readlink(BufferData(deref_file), buf, statbuf.st_size); + if (linksize == 0) + { + Log(LOG_LEVEL_WARNING, "readlink() failed with 0 bytes. Should not happen (bug?)."); + goto end; + } + else if (linksize < 0) + { + Log(LOG_LEVEL_ERR, "Could not read link %s. (readlink: %s)", BufferData(pretty_file), GetErrorStr()); + goto end; + } + buf[linksize] = '\0'; + if (!IsAbsPath(buf)) + { + char dir[BufferSize(deref_file) + 1]; + strcpy(dir, BufferData(deref_file)); + ChopLastNode(dir); + BufferPrintf(deref_file, "%s/%s", dir, buf); + } + else + { + BufferSet(deref_file, buf, linksize); + } + BufferPrintf(pretty_file, "'%s' (from symlink '%s')", BufferData(deref_file), file); + } + else +#endif + { + break; + } + } + + strcpy(backup, BufferData(deref_file)); + + if (a->edits.backup == BACKUP_OPTION_TIMESTAMP) + { + snprintf(stamp, CF_BUFSIZE, "_%jd_%s", (intmax_t) CFSTARTTIME, CanonifyName(ctime(&stamp_now))); + strcat(backup, stamp); + } + + strcat(backup, ".cf-before-edit"); + + strcpy(new, BufferData(deref_file)); + strcat(new, ".cf-after-edit"); + unlink(new); /* Just in case of races */ + + if ((*callback)(new, param, new_line_mode) == false) + { + goto end; + } + + if (!CopyFilePermissionsDisk(BufferData(deref_file), new)) + { + Log(LOG_LEVEL_ERR, "Can't copy file permissions from %s to '%s' - so promised edits could not be moved into place.", + BufferData(pretty_file), new); + goto end; + } + + unlink(backup); +#ifndef __MINGW32__ + if (link(BufferData(deref_file), backup) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Can't link %s to '%s' - falling back to copy. (link: %s)", + BufferData(pretty_file), backup, GetErrorStr()); +#else + /* No hardlinks on Windows, go straight to copying */ + { +#endif + if (!CopyRegularFileDisk(BufferData(deref_file), backup)) + { + Log(LOG_LEVEL_ERR, "Can't copy %s to '%s' - so promised edits could not be moved into place.", + BufferData(pretty_file), backup); + goto end; + } + if (!CopyFilePermissionsDisk(BufferData(deref_file), backup)) + { + Log(LOG_LEVEL_ERR, "Can't copy permissions %s to '%s' - so promised edits could not be moved into place.", + BufferData(pretty_file), backup); + goto end; + } + } + + if (a->edits.backup == BACKUP_OPTION_ROTATE) + { + RotateFiles(backup, a->edits.rotate); + unlink(backup); + } + + if (a->edits.backup != BACKUP_OPTION_NO_BACKUP) + { + if (ArchiveToRepository(backup, a)) + { + unlink(backup); + } + } + + else + { + unlink(backup); + } + + if (rename(new, BufferData(deref_file)) == -1) + { + Log(LOG_LEVEL_ERR, "Can't rename '%s' to %s - so promised edits could not be moved into place. (rename: %s)", + new, BufferData(pretty_file), GetErrorStr()); + goto end; + } + + ret = true; + +end: + BufferDestroy(pretty_file); + BufferDestroy(deref_file); + return ret; +} + +/*********************************************************************/ + +static bool SaveItemListCallback(const char *dest_filename, void *param, NewLineMode new_line_mode) +{ + Item *liststart = param, *ip; + + //saving list to file + FILE *fp = safe_fopen( + dest_filename, (new_line_mode == NewLineMode_Native) ? "wt" : "w"); + if (fp == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to open destination file '%s' for writing. (fopen: %s)", + dest_filename, GetErrorStr()); + return false; + } + + for (ip = liststart; ip != NULL; ip = ip->next) + { + if (fprintf(fp, "%s\n", ip->name) < 0) + { + Log(LOG_LEVEL_ERR, "Unable to write into destination file '%s'. (fprintf: %s)", + dest_filename, GetErrorStr()); + fclose(fp); + return false; + } + } + + if (fclose(fp) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to close file '%s' after writing. (fclose: %s)", + dest_filename, GetErrorStr()); + return false; + } + + return true; +} + +/*********************************************************************/ + +bool SaveItemListAsFile(Item *liststart, const char *file, const Attributes *a, NewLineMode new_line_mode) +{ + assert(a != NULL); + return SaveAsFile(&SaveItemListCallback, liststart, file, a, new_line_mode); +} + +// Some complex logic here to enable warnings of diffs to be given + +static Item *NextItem(const Item *ip) +{ + if (ip) + { + return ip->next; + } + else + { + return NULL; + } +} + +static bool ItemListsEqual(EvalContext *ctx, const Item *list1, const Item *list2, int warnings, + const Attributes *a, const Promise *pp, PromiseResult *result) +{ + assert(a != NULL); + bool retval = true; + + const Item *ip1 = list1; + const Item *ip2 = list2; + + while (true) + { + if ((ip1 == NULL) && (ip2 == NULL)) + { + return retval; + } + + if ((ip1 == NULL) || (ip2 == NULL)) + { + if (warnings) + { + if ((ip1 == list1) || (ip2 == list2)) + { + cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "File content wants to change from from/to full/empty but only a warning promised"); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN); + } + else + { + if (ip1 != NULL) + { + cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, " ! edit_line change warning promised: (remove) %s", + ip1->name); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN); + } + + if (ip2 != NULL) + { + cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, " ! edit_line change warning promised: (add) %s", ip2->name); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN); + } + } + } + + if (warnings) + { + if (ip1 || ip2) + { + retval = false; + ip1 = NextItem(ip1); + ip2 = NextItem(ip2); + continue; + } + } + + return false; + } + + if (strcmp(ip1->name, ip2->name) != 0) + { + if (!warnings) + { + // No need to wait + return false; + } + else + { + // If we want to see warnings, we need to scan the whole file + + cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "edit_line warning promised: - %s", ip1->name); + *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN); + retval = false; + } + } + + ip1 = NextItem(ip1); + ip2 = NextItem(ip2); + } + + return retval; +} + +/* returns true if file on disk is identical to file in memory */ + +bool CompareToFile( + EvalContext *ctx, + const Item *liststart, + const char *file, + const Attributes *a, + const Promise *pp, + PromiseResult *result) +{ + assert(a != NULL); + struct stat statbuf; + Item *cmplist = NULL; + + if (stat(file, &statbuf) == -1) + { + return false; + } + + if ((liststart == NULL) && (statbuf.st_size == 0)) + { + return true; + } + + if (liststart == NULL) + { + return false; + } + + if (!LoadFileAsItemList(&cmplist, file, a->edits, false)) + { + return false; + } + + if (!ItemListsEqual(ctx, cmplist, liststart, (a->transaction.action == cfa_warn), a, pp, result)) + { + DeleteItemList(cmplist); + return false; + } + + DeleteItemList(cmplist); + return (true); +} diff --git a/libpromises/files_operators.h b/libpromises/files_operators.h new file mode 100644 index 0000000000..9ef4816a68 --- /dev/null +++ b/libpromises/files_operators.h @@ -0,0 +1,39 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_FILES_OPERATORS_H +#define CFENGINE_FILES_OPERATORS_H + +#include +#include + +bool MoveObstruction(EvalContext *ctx, char *from, const Attributes *attr, const Promise *pp, PromiseResult *result); + +typedef bool (*SaveCallbackFn)(const char *dest_filename, void *param, NewLineMode new_line_mode); +bool SaveAsFile(SaveCallbackFn callback, void *param, const char *file, const Attributes *a, NewLineMode new_line_mode); +bool SaveItemListAsFile(Item *liststart, const char *file, const Attributes *a, NewLineMode new_line_mode); + +bool CompareToFile(EvalContext *ctx, const Item *liststart, const char *file, const Attributes *a, const Promise *pp, PromiseResult *result); + +#endif diff --git a/libpromises/files_repository.c b/libpromises/files_repository.c new file mode 100644 index 0000000000..757f48722c --- /dev/null +++ b/libpromises/files_repository.c @@ -0,0 +1,163 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* PathAppend */ + +/*********************************************************************/ + +static Item *VREPOSLIST = NULL; /* GLOBAL_X */ +static char REPOSCHAR = '_'; /* GLOBAL_P */ +static char *VREPOSITORY = NULL; /* GLOBAL_P */ + +/*********************************************************************/ + +void SetRepositoryLocation(const char *path) +{ + VREPOSITORY = xstrdup(path); +} + +/*********************************************************************/ + +void SetRepositoryChar(char c) +{ + REPOSCHAR = c; +} + +/*********************************************************************/ + +bool GetRepositoryPath(ARG_UNUSED const char *file, const Attributes *attr, char *destination) +{ + if ((attr->repository == NULL) && (VREPOSITORY == NULL)) + { + return false; + } + + size_t repopathlen; + + if (attr->repository != NULL) + { + repopathlen = strlcpy(destination, attr->repository, CF_BUFSIZE); + } + else + { + repopathlen = strlcpy(destination, VREPOSITORY, CF_BUFSIZE); + } + + if (repopathlen >= CF_BUFSIZE) + { + Log(LOG_LEVEL_ERR, "Internal limit, buffer ran out of space for long filename"); + return false; + } + + return true; +} + +/*********************************************************************/ + +bool ArchiveToRepository(const char *file, const Attributes *attr) + /* Returns true if the file was backup up and false if not */ +{ + char destination[CF_BUFSIZE]; + struct stat sb, dsb; + + // Skip empty file name + if (file[0] == '\0') { + return false; + } + + if (!GetRepositoryPath(file, attr, destination)) + { + return false; + } + + if (attr->copy.backup == BACKUP_OPTION_NO_BACKUP) + { + return true; + } + + if (IsItemIn(VREPOSLIST, file)) + { + Log(LOG_LEVEL_INFO, + "The file '%s' has already been moved to the repository once. Multiple update will cause loss of backup.", + file); + return true; + } + + ThreadLock(cft_getaddr); + PrependItemList(&VREPOSLIST, file); + ThreadUnlock(cft_getaddr); + + if (!PathAppend(destination, sizeof(destination), + CanonifyName(file), FILE_SEPARATOR)) + { + Log(LOG_LEVEL_ERR, + "Internal limit reached in ArchiveToRepository()," + " path too long: '%s' + '%s'", + destination, CanonifyName(file)); + return false; + } + + if (!MakeParentDirectory(destination, attr->move_obstructions, NULL)) + { + // Could not create parent directory, assume this is okay, + // verbose logging in MakeParentDirectory() + Log(LOG_LEVEL_DEBUG, + "Could not create parent directory '%s'", + destination); + } + + if (stat(file, &sb) == -1) + { + Log(LOG_LEVEL_DEBUG, "File '%s' promised to archive to the repository but it disappeared!", file); + return true; + } + + stat(destination, &dsb); + + if (CopyRegularFileDisk(file, destination)) + { + Log(LOG_LEVEL_INFO, "Moved '%s' to repository location '%s'", file, destination); + return true; + } + else + { + Log(LOG_LEVEL_INFO, "Failed to move '%s' to repository location '%s'", file, destination); + return false; + } +} + +bool FileInRepository(const char *filename) +{ + return IsItemIn(VREPOSLIST, filename); +} diff --git a/libpromises/files_repository.h b/libpromises/files_repository.h new file mode 100644 index 0000000000..2a7373555b --- /dev/null +++ b/libpromises/files_repository.h @@ -0,0 +1,37 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_FILES_REPOSITORY_H +#define CFENGINE_FILES_REPOSITORY_H + +void SetRepositoryLocation(const char *path); +void SetRepositoryChar(char c); + +bool ArchiveToRepository(const char *file, const Attributes *attr); +bool FileInRepository(const char *filename); + +/* Returns false if backing up files to repository is not set up */ +bool GetRepositoryPath(const char *file, const Attributes *attr, char *destination); + +#endif diff --git a/libpromises/fncall.c b/libpromises/fncall.c new file mode 100644 index 0000000000..d17264dccc --- /dev/null +++ b/libpromises/fncall.c @@ -0,0 +1,461 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIMULATE_SAFE_META_TAG "simulate_safe" + +/******************************************************************/ +/* Argument propagation */ +/******************************************************************/ + +/* + +When formal parameters are passed, they should be literal strings, i.e. +values (check for this). But when the values are received the +receiving body should state only variable names without literal quotes. +That way we can feed in the received parameter name directly in as an lvalue + +e.g. + access => myaccess("$(person)"), + + body files myaccess(user) + +leads to Hash Association (lval,rval) => (user,"$(person)") + +*/ + +/******************************************************************/ + +Rlist *NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp, const FnCallType *fp_type) +{ + // Functions with delayed evaluation will call this themselves later + if (fp_type && fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION) + { + return RlistCopy(fp->args); + } + + const FnCallType *fn = FnCallTypeGet(fp->name); + if (fn == NULL) + { + FatalError(ctx, "Function call '%s' has unknown type", fp->name); + } + else + { + int len = RlistLen(fp->args); + + if (!(fn->options & FNCALL_OPTION_VARARG)) + { + if (len != FnNumArgs(fn)) + { + Log(LOG_LEVEL_ERR, "Arguments to function '%s' do not tally. Expected %d not %d", + fp->name, FnNumArgs(fn), len); + PromiseRef(LOG_LEVEL_ERR, fp->caller); + DoCleanupAndExit(EXIT_FAILURE); + } + } + } + + Rlist *expanded_args = NULL; + for (const Rlist *rp = fp->args; rp != NULL; rp = rp->next) + { + Rval rval; + + if (rp->val.type == RVAL_TYPE_FNCALL) + { + FnCall *subfp = RlistFnCallValue(rp); + rval = FnCallEvaluate(ctx, policy, subfp, fp->caller).rval; + } + else + { + rval = ExpandPrivateRval(ctx, NULL, NULL, rp->val.item, rp->val.type); + assert(rval.item); + } + + /* + + Collect compound values into containers only if the function + supports it. + + Functions without FNCALL_OPTION_COLLECTING don't collect + Rlist elements. So in the policy, you call + and(splitstring("a b")) and it ends up as and("a", "b"). + This expansion happens once per FnCall, not for all + arguments. + + Functions with FNCALL_OPTION_COLLECTING instead collect all + the results of a FnCall into a single JSON array object. It + requires functions to expect it, but it's the only + reasonable way to preserve backwards compatibility for + functions like and() and allow nesting of calls to functions + that take and return compound data types. + + */ + RlistAppendAllTypes(&expanded_args, rval.item, rval.type, + (fn->options & FNCALL_OPTION_COLLECTING)); + RvalDestroy(rval); + } + + return expanded_args; +} + +/*******************************************************************/ + +bool FnCallIsBuiltIn(Rval rval) +{ + FnCall *fp; + + if (rval.type != RVAL_TYPE_FNCALL) + { + return false; + } + + fp = (FnCall *) rval.item; + + if (FnCallTypeGet(fp->name)) + { + return true; + } + else + { + return false; + } +} + +/*******************************************************************/ + +FnCall *FnCallNew(const char *name, Rlist *args) +{ + FnCall *fp = xmalloc(sizeof(FnCall)); + + fp->name = xstrdup(name); + fp->args = args; + + return fp; +} + +/*******************************************************************/ + +FnCall *FnCallCopyRewriter(const FnCall *f, JsonElement *map) +{ + return FnCallNew(f->name, RlistCopyRewriter(f->args, map)); +} + +FnCall *FnCallCopy(const FnCall *f) +{ + return FnCallCopyRewriter(f, NULL); +} + +/*******************************************************************/ + +void FnCallDestroy(FnCall *fp) +{ + if (fp) + { + free(fp->name); + RlistDestroy(fp->args); + } + free(fp); +} + +unsigned FnCallHash(const FnCall *fp, unsigned seed) +{ + unsigned hash = StringHash(fp->name, seed); + return RlistHash(fp->args, hash); +} + + +FnCall *ExpandFnCall(const EvalContext *ctx, const char *ns, const char *scope, const FnCall *f) +{ + FnCall *result = NULL; + if (IsCf3VarString(f->name)) + { + // e.g. usebundle => $(m)(arg0, arg1); + Buffer *buf = BufferNewWithCapacity(CF_MAXVARSIZE); + ExpandScalar(ctx, ns, scope, f->name, buf); + + result = FnCallNew(BufferData(buf), ExpandList(ctx, ns, scope, f->args, false)); + BufferDestroy(buf); + } + else + { + result = FnCallNew(f->name, ExpandList(ctx, ns, scope, f->args, false)); + } + + return result; +} + +void FnCallWrite(Writer *writer, const FnCall *call) +{ + WriterWrite(writer, call->name); + WriterWriteChar(writer, '('); + + for (const Rlist *rp = call->args; rp != NULL; rp = rp->next) + { + switch (rp->val.type) + { + case RVAL_TYPE_SCALAR: + ScalarWrite(writer, RlistScalarValue(rp), true, false); + break; + + case RVAL_TYPE_FNCALL: + FnCallWrite(writer, RlistFnCallValue(rp)); + break; + + default: + WriterWrite(writer, "(** Unknown argument **)\n"); + break; + } + + if (rp->next != NULL) + { + WriterWriteChar(writer, ','); + } + } + + WriterWriteChar(writer, ')'); +} + +/*******************************************************************/ + +static FnCallResult CallFunction(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *expargs) +{ + const Rlist *rp = fp->args; + const FnCallType *fncall_type = FnCallTypeGet(fp->name); + if (fncall_type == NULL) + { + FatalError(ctx, "Function call '%s' has unknown type", fp->name); + } + + int argnum = 0; + for (argnum = 0; rp != NULL && fncall_type->args[argnum].pattern != NULL; argnum++) + { + if (rp->val.type != RVAL_TYPE_FNCALL) + { + /* Nested functions will not match to lval so don't bother checking */ + SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, rp->val, + fncall_type->args[argnum].dtype, + fncall_type->args[argnum].pattern, 1); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + FatalError(ctx, "In function '%s', error in variable '%s', '%s'", fp->name, (const char *)rp->val.item, SyntaxTypeMatchToString(err)); + } + } + + rp = rp->next; + } + + if (argnum != RlistLen(expargs) && !(fncall_type->options & FNCALL_OPTION_VARARG)) + { + char *args_str = RlistToString(expargs); + Log(LOG_LEVEL_ERR, "Argument template mismatch handling function %s(%s)", fp->name, args_str); + free(args_str); + + rp = expargs; + for (int i = 0; i < argnum; i++) + { + if (rp != NULL) + { + char *rval_str = RvalToString(rp->val); + Log(LOG_LEVEL_ERR, " arg[%d] range %s\t %s ", i, fncall_type->args[i].pattern, rval_str); + free(rval_str); + } + else + { + Log(LOG_LEVEL_ERR, " arg[%d] range %s\t ? ", i, fncall_type->args[i].pattern); + } + } + + FatalError(ctx, "Bad arguments"); + } + + return (*fncall_type->impl) (ctx, policy, fp, expargs); +} + +FnCallResult FnCallEvaluate(EvalContext *ctx, const Policy *policy, FnCall *fp, const Promise *caller) +{ + assert(ctx != NULL); + assert(policy != NULL); + assert(fp != NULL); + fp->caller = caller; + + if (!EvalContextGetEvalOption(ctx, EVAL_OPTION_EVAL_FUNCTIONS)) + { + Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because evaluation was turned off in the evaluator", + fp->name); + return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; + } + + const FnCallType *fp_type = FnCallTypeGet(fp->name); + + if (!fp_type) + { + if (caller) + { + Log(LOG_LEVEL_ERR, "No such FnCall '%s' in promise '%s' near line %zu", + fp->name, PromiseGetBundle(caller)->source_path, caller->offset.line); + } + else + { + Log(LOG_LEVEL_ERR, "No such FnCall '%s', context info unavailable", fp->name); + } + + return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; + } + + const bool skip_unsafe_function_calls = ((EVAL_MODE == EVAL_MODE_SIMULATE_MANIFEST) || + (EVAL_MODE == EVAL_MODE_SIMULATE_MANIFEST_FULL) || + (EVAL_MODE == EVAL_MODE_SIMULATE_DIFF)); + + Rlist *caller_meta = PromiseGetConstraintAsList(ctx, "meta", caller); + if (skip_unsafe_function_calls && + ((fp_type->options & FNCALL_OPTION_UNSAFE) != 0) && + !RlistContainsString(caller_meta, SIMULATE_SAFE_META_TAG)) + { + Log(LOG_LEVEL_WARNING, "Not calling unsafe function '%s' in simulate mode", fp->name); + return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; + } + + Rlist *expargs = NewExpArgs(ctx, policy, fp, fp_type); + + Writer *fncall_writer = NULL; + const char *fncall_string = ""; + if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) + { + fncall_writer = StringWriter(); + FnCallWrite(fncall_writer, fp); + fncall_string = StringWriterData(fncall_writer); + } + + // Check if arguments are resolved, except for delayed evaluation functions + if ( ! (fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION) && + RlistIsUnresolved(expargs)) + { + // Special case where a three argument ifelse call must + // be allowed to have undefined variables. + if (strcmp(fp->name, "ifelse") == 0 && + expargs->val.type != RVAL_TYPE_FNCALL && + RlistLen(expargs) == 3) + { + Log(LOG_LEVEL_DEBUG, "Allowing ifelse() function evaluation even" + " though its arguments contain unresolved variables: %s", + fncall_string); + } + else + { + if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) + { + Log(LOG_LEVEL_DEBUG, "Skipping function evaluation for now," + " arguments contain unresolved variables: %s", + fncall_string); + WriterClose(fncall_writer); + } + RlistDestroy(expargs); + return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; + } + } + + /* Call functions in promises with 'ifelapsed => "0"' (e.g. with + * 'action => immediate') [ENT-7478] */ + const int if_elapsed = PromiseGetConstraintAsInt(ctx, "ifelapsed", caller); + if (if_elapsed != 0) + { + Rval cached_rval; + if ((fp_type->options & FNCALL_OPTION_CACHED) && EvalContextFunctionCacheGet(ctx, fp, expargs, &cached_rval)) + { + if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) + { + Log(LOG_LEVEL_DEBUG, + "Using previously cached result for function: %s", + fncall_string); + WriterClose(fncall_writer); + } + Writer *w = StringWriter(); + FnCallWrite(w, fp); + WriterClose(w); + RlistDestroy(expargs); + + return (FnCallResult) { FNCALL_SUCCESS, RvalCopy(cached_rval) }; + } + } + + if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) + { + Log(LOG_LEVEL_DEBUG, "Evaluating function: %s%s", + fncall_string, (if_elapsed == 0) ? " (because of ifelapsed => \"0\")" : ""); + WriterClose(fncall_writer); + } + + FnCallResult result = CallFunction(ctx, policy, fp, expargs); + + if (result.status == FNCALL_FAILURE) + { + RlistDestroy(expargs); + RvalDestroy(result.rval); + return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; + } + + if (fp_type->options & FNCALL_OPTION_CACHED) + { + Writer *w = StringWriter(); + FnCallWrite(w, fp); + Log(LOG_LEVEL_VERBOSE, "Caching result for function '%s'", StringWriterData(w)); + WriterClose(w); + + EvalContextFunctionCachePut(ctx, fp, expargs, &result.rval); + } + + RlistDestroy(expargs); + + return result; +} + +/*******************************************************************/ + +const FnCallType *FnCallTypeGet(const char *name) +{ + int i; + + for (i = 0; CF_FNCALL_TYPES[i].name != NULL; i++) + { + if (strcmp(CF_FNCALL_TYPES[i].name, name) == 0) + { + return CF_FNCALL_TYPES + i; + } + } + + return NULL; +} diff --git a/libpromises/fncall.h b/libpromises/fncall.h new file mode 100644 index 0000000000..e306a7277d --- /dev/null +++ b/libpromises/fncall.h @@ -0,0 +1,112 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_FNCALL_H +#define CFENGINE_FNCALL_H + +#include + +struct FnCall_ +{ + char *name; + Rlist *args; + + const Promise *caller; +}; + +typedef enum FnCallStatus +{ + FNCALL_SUCCESS, + FNCALL_FAILURE +} FnCallStatus; + +typedef struct +{ + FnCallStatus status; + Rval rval; +} FnCallResult; + +typedef struct +{ + const char *pattern; + DataType dtype; + const char *description; +} FnCallArg; + +typedef enum +{ + FNCALL_OPTION_NONE = 0, + // Functions with variable arguments don't require a fixed number + // of arguments. + FNCALL_OPTION_VARARG = 1 << 0, + // Cached functions are evaluated once per run (memoized). The + // hash key is the function name and all the arguments. + FNCALL_OPTION_CACHED = 1 << 1, + // Collecting functions take a variable reference OR can accept a + // nested function call. Either way, those parameters are + // collected into a data container. + FNCALL_OPTION_COLLECTING = 1 << 2, + // Delayed-evaluation functions will evaluate their arguments directly, + // so they can do things like maplist(canonify($(this)), mylist) + FNCALL_OPTION_DELAYED_EVALUATION = 1 << 3, + // Unsafe functions (with side effects) that should not be evaluated in + // simulate mode(s). + FNCALL_OPTION_UNSAFE = 1 << 4, +} FnCallOption; + +typedef struct +{ + const char *name; + DataType dtype; + const FnCallArg *args; + FnCallResult (*impl)(EvalContext *, const Policy *, const FnCall *, const Rlist *); + const char *description; + FnCallOption options; + FnCallCategory category; + SyntaxStatus status; +} FnCallType; + +extern const FnCallType CF_FNCALL_TYPES[]; + +bool FnCallIsBuiltIn(Rval rval); + +FnCall *FnCallNew(const char *name, Rlist *args); +FnCall *FnCallCopy(const FnCall *f); +FnCall *FnCallCopyRewriter(const FnCall *f, JsonElement *map); +void FnCallDestroy(FnCall *fp); +unsigned FnCallHash(const FnCall *fp, unsigned seed); +void FnCallWrite(Writer *writer, const FnCall *call); + + +FnCallResult FnCallEvaluate(EvalContext *ctx, const Policy *policy, FnCall *fp, const Promise *caller); + +const FnCallType *FnCallTypeGet(const char *name); + +FnCall *ExpandFnCall(const EvalContext *ctx, const char *ns, const char *scope, const FnCall *f); +Rlist *NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp, const FnCallType *fp_type); + +// TODO: should probably demolish this eventually +void FnCallShow(FILE *fout, const char *prefix, const FnCall *fp, const Rlist *args); + +#endif diff --git a/libpromises/generic_agent.c b/libpromises/generic_agent.c new file mode 100644 index 0000000000..a8e5f294ba --- /dev/null +++ b/libpromises/generic_agent.c @@ -0,0 +1,2863 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // CompileRegex() +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* LoadCMDBData() */ + +#define AUGMENTS_VARIABLES_TAGS "tags" +#define AUGMENTS_VARIABLES_DATA "value" +#define AUGMENTS_CLASSES_TAGS "tags" +#define AUGMENTS_CLASSES_CLASS_EXPRESSIONS "class_expressions" +#define AUGMENTS_CLASSES_REGULAR_EXPRESSIONS "regular_expressions" +#define AUGMENTS_COMMENT_KEY "comment" + +static pthread_once_t pid_cleanup_once = PTHREAD_ONCE_INIT; /* GLOBAL_T */ + +static char PIDFILE[CF_BUFSIZE] = ""; /* GLOBAL_C */ + +/* Used for 'ident' argument to openlog() */ +static char CF_PROGRAM_NAME[256] = ""; + +static void CheckWorkingDirectories(EvalContext *ctx); + +static void GetAutotagDir(char *dirname, size_t max_size, const char *maybe_dirname); +static void GetPromisesValidatedFile(char *filename, size_t max_size, const GenericAgentConfig *config, const char *maybe_dirname); +static bool WriteReleaseIdFile(const char *filename, const char *dirname); +static bool GeneratePolicyReleaseIDFromGit(char *release_id_out, size_t out_size, + const char *policy_dir); +static bool GeneratePolicyReleaseID(char *release_id_out, size_t out_size, + const char *policy_dir); +static char* ReadReleaseIdFromReleaseIdFileMasterfiles(const char *maybe_dirname); + +static bool MissingInputFile(const char *input_file); + +static bool LoadAugmentsFiles(EvalContext *ctx, const char* filename); + +static void GetChangesChrootDir(char *buf, size_t buf_size); +static void DeleteChangesChroot(); +static int ParseFacility(const char *name); +static inline const char *LogFacilityToString(int facility); + +#if !defined(__MINGW32__) +static void OpenLog(int facility); +#endif + +/*****************************************************************************/ + +static void SanitizeEnvironment() +{ + /* ps(1) and other utilities invoked by CFEngine may be affected */ + unsetenv("COLUMNS"); + + /* Make sure subprocesses output is not localized */ + unsetenv("LANG"); + unsetenv("LANGUAGE"); + unsetenv("LC_MESSAGES"); +} + +/*****************************************************************************/ + +ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, GenericAgentSetDefaultDigest, HashMethod *, digest, int *, digest_len) +{ + *digest = HASH_METHOD_MD5; + *digest_len = CF_MD5_LEN; +} + +void MarkAsPolicyServer(EvalContext *ctx) +{ + EvalContextClassPutHard(ctx, "am_policy_hub", + "source=bootstrap,deprecated,alias=policy_server"); + Log(LOG_LEVEL_VERBOSE, "Additional class defined: am_policy_hub"); + EvalContextClassPutHard(ctx, "policy_server", + "inventory,attribute_name=CFEngine roles,source=bootstrap"); + Log(LOG_LEVEL_VERBOSE, "Additional class defined: policy_server"); +} + +Policy *SelectAndLoadPolicy(GenericAgentConfig *config, EvalContext *ctx, bool validate_policy, bool write_validated_file) +{ + Policy *policy = NULL; + + if (GenericAgentCheckPolicy(config, validate_policy, write_validated_file)) + { + policy = LoadPolicy(ctx, config); + } + else if (config->tty_interactive) + { + Log(LOG_LEVEL_ERR, + "Failsafe condition triggered. Interactive session detected, skipping failsafe.cf execution."); + } + else + { + Log(LOG_LEVEL_ERR, "CFEngine was not able to get confirmation of promises from cf-promises, so going to failsafe"); + EvalContextClassPutHard(ctx, "failsafe_fallback", "report,attribute_name=Errors,source=agent"); + + if (CheckAndGenerateFailsafe(GetInputDir(), "failsafe.cf")) + { + GenericAgentConfigSetInputFile(config, GetInputDir(), "failsafe.cf"); + Log(LOG_LEVEL_ERR, "CFEngine failsafe.cf: %s %s", config->input_dir, config->input_file); + policy = LoadPolicy(ctx, config); + + /* Doing failsafe, set the release_id to "failsafe" and also + * overwrite the cfe_release_id file so that sub-agent executed as + * part of failsafe can just pick it up and then rewrite it with the + * actual value from masterfiles. */ + free(policy->release_id); + policy->release_id = xstrdup("failsafe"); + + char filename[PATH_MAX]; + GetReleaseIdFile(GetInputDir(), filename, sizeof(filename)); + FILE *release_id_stream = safe_fopen_create_perms(filename, "w", + CF_PERMS_DEFAULT); + if (release_id_stream == NULL) + { + Log(LOG_LEVEL_ERR, "Failed to open the release_id file for writing during failsafe"); + } + else + { + Writer *release_id_writer = FileWriter(release_id_stream); + WriterWrite(release_id_writer, "{ releaseId: \"failsafe\" }\n"); + WriterClose(release_id_writer); + } + } + } + return policy; +} + +static bool CheckContextClassmatch(EvalContext *ctx, const char *class_str) +{ + if (StringEndsWith(class_str, "::")) // Treat as class expression, not regex + { + const size_t length = strlen(class_str); + if (length <= 2) + { + assert(length == 2); // True because StringEndsWith + Log(LOG_LEVEL_ERR, + "Invalid class expression in augments: '%s'", + class_str); + return false; + } + + char *const tmp_class_str = xstrdup(class_str); + assert(strlen(tmp_class_str) == length); + + tmp_class_str[length - 2] = '\0'; // 2 = strlen("::") + const bool found = IsDefinedClass(ctx, tmp_class_str); + + free(tmp_class_str); + return found; + } + + ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true); + StringSet *global_matches = ClassesMatching(ctx, iter, class_str, NULL, true); // returns early + + const bool found = (StringSetSize(global_matches) > 0); + + StringSetDestroy(global_matches); + ClassTableIteratorDestroy(iter); + + return found; +} + +static StringSet *GetTagsFromAugmentsTags(const char *item_type, + const char *key, + const JsonElement *json_tags, + const char *default_tag, + const char *filename) +{ + StringSet *tags = NULL; + if (JSON_NOT_NULL(json_tags)) + { + if ((JsonGetType(json_tags) != JSON_TYPE_ARRAY) || + (!JsonArrayContainsOnlyPrimitives((JsonElement*) json_tags))) + { + Log(LOG_LEVEL_ERR, + "Invalid tags information for %s '%s' in augments file '%s':" + " must be a JSON array of strings", + item_type, key, filename); + } + else + { + tags = JsonArrayToStringSet(json_tags); + if (tags == NULL) + { + Log(LOG_LEVEL_ERR, + "Invalid meta information %s '%s' in augments file '%s':" + " must be a JSON array of strings", + item_type, key, filename); + } + } + } + if (tags == NULL) + { + tags = StringSetNew(); + } + StringSetAdd(tags, xstrdup(default_tag)); + + return tags; +} + +static inline bool CanSetVariable(const EvalContext *ctx, VarRef *var_ref) +{ + assert(var_ref != NULL); + + bool null_ns = false; + if (var_ref->ns == NULL) + { + null_ns = true; + var_ref->ns = "default"; + } + StringSet *tags = EvalContextVariableTags(ctx, var_ref); + bool can_set = ((tags == NULL) || !StringSetContains(tags, CMDB_SOURCE_TAG)); + if (!can_set) + { + Log(LOG_LEVEL_VERBOSE, + "Cannot set variable %s:%s.%s from augments, already defined from host-specific data", + var_ref->ns, var_ref->scope, var_ref->lval); + } + if (null_ns) + { + var_ref->ns = NULL; + } + + return can_set; +} + +static inline bool CanSetClass(const EvalContext *ctx, const char *class_spec) +{ + char *ns = NULL; + char *ns_delim = strchr(class_spec, ':'); + if (ns_delim != NULL) + { + ns = xstrndup(class_spec, ns_delim - class_spec); + class_spec = ns_delim + 1; + } + + StringSet *tags = EvalContextClassTags(ctx, ns, class_spec); + bool can_set = ((tags == NULL) || !StringSetContains(tags, CMDB_SOURCE_TAG)); + if (!can_set) + { + Log(LOG_LEVEL_VERBOSE, + "Cannot set class %s:%s from augments, already defined from host-specific data", + ns, class_spec); + } + + return can_set; +} + +static inline const char *GetAugmentsComment(const char *item_type, const char *identifier, + const char *file_name, const JsonElement *json_object) +{ + assert(JsonGetType(json_object) == JSON_TYPE_OBJECT); + + JsonElement *json_comment = JsonObjectGet(json_object, AUGMENTS_COMMENT_KEY); + if (NULL_JSON(json_comment)) + { + return NULL; + } + + if (JsonGetType(json_comment) != JSON_TYPE_STRING) + { + Log(LOG_LEVEL_ERR, + "Invalid type of the 'comment' field for the '%s' %s in augments data in '%s', must be a string", + identifier, item_type, file_name); + return NULL; + } + + return JsonPrimitiveGetAsString(json_comment); +} + +static bool LoadAugmentsData(EvalContext *ctx, const char *filename, const JsonElement* augment) +{ + bool loaded = false; + + if (JsonGetElementType(augment) != JSON_ELEMENT_TYPE_CONTAINER || + JsonGetContainerType(augment) != JSON_CONTAINER_TYPE_OBJECT) + { + Log(LOG_LEVEL_ERR, "Invalid augments file contents in '%s', must be a JSON object", filename); + } + else + { + loaded = true; + Log(LOG_LEVEL_VERBOSE, "Loaded augments file '%s', installing contents", filename); + + JsonIterator iter = JsonIteratorInit(augment); + const char *key; + while ((key = JsonIteratorNextKey(&iter))) + { + if (!(StringEqual(key, "vars") || + StringEqual(key, "classes") || + StringEqual(key, "inputs") || + StringEqual(key, "augments"))) + { + Log(LOG_LEVEL_VERBOSE, "Unknown augments key '%s' in file '%s', skipping it", + key, filename); + } + } + + /* load variables (if any) */ + JsonElement *element = JsonObjectGet(augment, "vars"); + if (JSON_NOT_NULL(element)) + { + JsonElement* vars = JsonExpandElement(ctx, element); + + if (vars == NULL || + JsonGetElementType(vars) != JSON_ELEMENT_TYPE_CONTAINER || + JsonGetContainerType(vars) != JSON_CONTAINER_TYPE_OBJECT) + { + Log(LOG_LEVEL_ERR, "Invalid augments vars in '%s', must be a JSON object", filename); + goto vars_cleanup; + } + + JsonIterator iter = JsonIteratorInit(vars); + const char *vkey; + while ((vkey = JsonIteratorNextKey(&iter))) + { + VarRef *ref = VarRefParse(vkey); + if (ref->ns != NULL) + { + if (ref->scope == NULL) + { + Log(LOG_LEVEL_ERR, "Invalid variable specification in augments data in '%s': '%s'" + " (bundle name has to be specified if namespace is specified)", filename, vkey); + VarRefDestroy(ref); + continue; + } + } + if (ref->scope == NULL) + { + ref->scope = xstrdup("def"); + } + + JsonElement *data = JsonObjectGet(vars, vkey); + if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + char *value = JsonPrimitiveToString(data); + if ((ref->ns == NULL) && (ref->scope == NULL)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments variable '%s.%s=%s' from file '%s'", + SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, value, filename); + if (CanSetVariable(ctx, ref)) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_DEF, vkey, value, CF_DATA_TYPE_STRING, "source=augments_file"); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "Installing augments variable '%s=%s' from file '%s'", + vkey, value, filename); + if (CanSetVariable(ctx, ref)) + { + EvalContextVariablePut(ctx, ref, value, CF_DATA_TYPE_STRING, "source=augments_file"); + } + } + free(value); + } + else if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_CONTAINER && + JsonGetContainerType(data) == JSON_CONTAINER_TYPE_ARRAY && + JsonArrayContainsOnlyPrimitives(data)) + { + // map to slist if the data only has primitives + Rlist *data_as_rlist = RlistFromContainer(data); + if ((ref->ns == NULL) && (ref->scope == NULL)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments slist variable '%s.%s' from file '%s'", + SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, filename); + if (CanSetVariable(ctx, ref)) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_DEF, + vkey, data_as_rlist, + CF_DATA_TYPE_STRING_LIST, + "source=augments_file"); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "Installing augments slist variable '%s' from file '%s'", + vkey, filename); + if (CanSetVariable(ctx, ref)) + { + EvalContextVariablePut(ctx, ref, data_as_rlist, CF_DATA_TYPE_STRING_LIST, + "source=augments_file"); + } + } + + RlistDestroy(data_as_rlist); + } + else // install as a data container + { + if ((ref->ns == NULL) && (ref->scope == NULL)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments data container variable '%s.%s' from file '%s'", + SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, filename); + if (CanSetVariable(ctx, ref)) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_DEF, + vkey, data, + CF_DATA_TYPE_CONTAINER, + "source=augments_file"); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "Installing augments data container variable '%s' from file '%s'", + vkey, filename); + if (CanSetVariable(ctx, ref)) + { + EvalContextVariablePut(ctx, ref, data, + CF_DATA_TYPE_CONTAINER, + "source=augments_file"); + } + } + } + VarRefDestroy(ref); + } + + vars_cleanup: + JsonDestroy(vars); + } + + /* Uses the new format allowing metadata (CFE-3633) */ + element = JsonObjectGet(augment, "variables"); + if (JSON_NOT_NULL(element)) + { + JsonElement* variables = JsonExpandElement(ctx, element); + + if (variables == NULL || JsonGetType(variables) != JSON_TYPE_OBJECT) + { + Log(LOG_LEVEL_ERR, "Invalid augments variables in '%s', must be a JSON object", filename); + goto variables_cleanup; + } + + JsonIterator variables_iter = JsonIteratorInit(variables); + const char *vkey; + while ((vkey = JsonIteratorNextKey(&variables_iter))) + { + VarRef *ref = VarRefParse(vkey); + if (ref->ns != NULL) + { + if (ref->scope == NULL) + { + Log(LOG_LEVEL_ERR, "Invalid variable specification in augments data in '%s': '%s'" + " (bundle name has to be specified if namespace is specified)", filename, vkey); + VarRefDestroy(ref); + continue; + } + } + if (ref->scope == NULL) + { + ref->scope = xstrdup("def"); + } + + const JsonElement *const var_info = JsonObjectGet(variables, vkey); + + const JsonElement *data; + StringSet *tags; + const char *comment = NULL; + + if (JsonGetType(var_info) == JSON_TYPE_OBJECT) + { + data = JsonObjectGet(var_info, AUGMENTS_VARIABLES_DATA); + + if (NULL_JSON(data)) + { + Log(LOG_LEVEL_ERR, "Missing value for the augments variable '%s' in '%s' (value field is required)", + vkey, filename); + VarRefDestroy(ref); + continue; + } + + const JsonElement *json_tags = JsonObjectGet(var_info, AUGMENTS_VARIABLES_TAGS); + tags = GetTagsFromAugmentsTags("variable", vkey, json_tags, "source=augments_file", filename); + comment = GetAugmentsComment("variable", vkey, filename, var_info); + } + else + { + // Just a bare value, like in "vars", no metadata + data = var_info; + tags = GetTagsFromAugmentsTags("variable", vkey, NULL, "source=augments_file", filename); + } + + assert(tags != NULL); + assert(data != NULL); + + bool installed = false; + if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + char *value = JsonPrimitiveToString(data); + if ((ref->ns == NULL) && (ref->scope == NULL)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments variable '%s.%s=%s' from file '%s'", + SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, value, filename); + if (CanSetVariable(ctx, ref)) + { + installed = EvalContextVariablePutSpecialTagsSetWithComment(ctx, SPECIAL_SCOPE_DEF, vkey, value, + CF_DATA_TYPE_STRING, tags, comment); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "Installing augments variable '%s=%s' from file '%s'", + vkey, value, filename); + if (CanSetVariable(ctx, ref)) + { + installed = EvalContextVariablePutTagsSetWithComment(ctx, ref, value, CF_DATA_TYPE_STRING, + tags, comment); + } + } + free(value); + } + else if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_CONTAINER && + JsonGetContainerType(data) == JSON_CONTAINER_TYPE_ARRAY && + JsonArrayContainsOnlyPrimitives((JsonElement *) data)) + { + // map to slist if the data only has primitives + Rlist *data_as_rlist = RlistFromContainer(data); + if ((ref->ns == NULL) && (ref->scope == NULL)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments slist variable '%s.%s' from file '%s'", + SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, filename); + if (CanSetVariable(ctx, ref)) + { + installed = EvalContextVariablePutSpecialTagsSetWithComment(ctx, SPECIAL_SCOPE_DEF, + vkey, data_as_rlist, + CF_DATA_TYPE_STRING_LIST, + tags, comment); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "Installing augments slist variable '%s' from file '%s'", + vkey, filename); + if (CanSetVariable(ctx, ref)) + { + installed = EvalContextVariablePutTagsSetWithComment(ctx, ref, data_as_rlist, + CF_DATA_TYPE_STRING_LIST, + tags, comment); + } + } + + RlistDestroy(data_as_rlist); + } + else // install as a data container + { + if ((ref->ns == NULL) && (ref->scope == NULL)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments data container variable '%s.%s' from file '%s'", + SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, filename); + if (CanSetVariable(ctx, ref)) + { + installed = EvalContextVariablePutSpecialTagsSetWithComment(ctx, SPECIAL_SCOPE_DEF, + vkey, data, + CF_DATA_TYPE_CONTAINER, + tags, comment); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "Installing augments data container variable '%s' from file '%s'", + vkey, filename); + if (CanSetVariable(ctx, ref)) + { + installed = EvalContextVariablePutTagsSetWithComment(ctx, ref, data, + CF_DATA_TYPE_CONTAINER, + tags, comment); + } + } + } + VarRefDestroy(ref); + if (!installed) + { + /* EvalContextVariablePutTagsSetWithComment() and + * EvalContextVariablePutSpecialTagsSetWithComment() take + * over tags in case of success. Otherwise we have to + * destroy the set. */ + StringSetDestroy(tags); + } + } + + variables_cleanup: + JsonDestroy(variables); + } + + /* load classes (if any) */ + element = JsonObjectGet(augment, "classes"); + if (JSON_NOT_NULL(element)) + { + JsonElement* classes = JsonExpandElement(ctx, element); + + if (JsonGetElementType(classes) != JSON_ELEMENT_TYPE_CONTAINER || + JsonGetContainerType(classes) != JSON_CONTAINER_TYPE_OBJECT) + { + Log(LOG_LEVEL_ERR, "Invalid augments classes in '%s', must be a JSON object", filename); + goto classes_cleanup; + } + + const char default_tags[] = "source=augments_file"; + JsonIterator iter = JsonIteratorInit(classes); + const char *ckey; + while ((ckey = JsonIteratorNextKey(&iter))) + { + JsonElement *data = JsonObjectGet(classes, ckey); + if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + char *check = JsonPrimitiveToString(data); + // check if class is true + if (CheckContextClassmatch(ctx, check)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments class '%s' (checked '%s') from file '%s'", + ckey, check, filename); + if (CanSetClass(ctx, ckey)) + { + EvalContextClassPutSoft(ctx, ckey, CONTEXT_SCOPE_NAMESPACE, default_tags); + } + } + free(check); + } + else if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_CONTAINER && + JsonGetContainerType(data) == JSON_CONTAINER_TYPE_ARRAY && + JsonArrayContainsOnlyPrimitives(data)) + { + // check if each class is true + JsonIterator data_iter = JsonIteratorInit(data); + const JsonElement *el; + while ((el = JsonIteratorNextValueByType(&data_iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + char *check = JsonPrimitiveToString(el); + if (CheckContextClassmatch(ctx, check)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments class '%s' (checked array entry '%s') from file '%s'", + ckey, check, filename); + if (CanSetClass(ctx, ckey)) + { + EvalContextClassPutSoft(ctx, ckey, CONTEXT_SCOPE_NAMESPACE, default_tags); + } + free(check); + break; + } + + free(check); + } + } + else if (JsonGetType(data) == JSON_TYPE_OBJECT) + { + const JsonElement *class_exprs = JsonObjectGet(data, AUGMENTS_CLASSES_CLASS_EXPRESSIONS); + const JsonElement *reg_exprs = JsonObjectGet(data, AUGMENTS_CLASSES_REGULAR_EXPRESSIONS); + const JsonElement *json_tags = JsonObjectGet(data, AUGMENTS_CLASSES_TAGS); + + if ((JSON_NOT_NULL(class_exprs) && JSON_NOT_NULL(reg_exprs)) || + (NULL_JSON(class_exprs) && NULL_JSON(reg_exprs))) + { + Log(LOG_LEVEL_ERR, "Invalid augments class data for class '%s' in '%s':" + " either \"class_expressions\" or \"regular_expressions\" need to be specified", + ckey, filename); + continue; + } + + StringSet *tags = GetTagsFromAugmentsTags("class", ckey, json_tags, + "source=augments_file", filename); + const char *comment = GetAugmentsComment("class", ckey, filename, data); + bool installed = false; + JsonIterator exprs_iter = JsonIteratorInit(class_exprs ? class_exprs : reg_exprs); + const JsonElement *el; + while ((el = JsonIteratorNextValueByType(&exprs_iter, JSON_ELEMENT_TYPE_PRIMITIVE, true))) + { + char *check = JsonPrimitiveToString(el); + if (CheckContextClassmatch(ctx, check)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments class '%s' (checked array entry '%s') from file '%s'", + ckey, check, filename); + if (CanSetClass(ctx, ckey)) + { + installed = EvalContextClassPutSoftTagsSetWithComment(ctx, ckey, CONTEXT_SCOPE_NAMESPACE, + tags, comment); + } + free(check); + break; + } + + free(check); + } + if (!installed) + { + /* EvalContextClassPutSoftTagsSetWithComment() takes over tags in + * case of success. Otherwise we have to destroy the set. */ + StringSetDestroy(tags); + } + } + else + { + Log(LOG_LEVEL_ERR, "Invalid augments class data for class '%s' in '%s'", + ckey, filename); + } + } + + classes_cleanup: + JsonDestroy(classes); + } + + /* load inputs (if any) */ + element = JsonObjectGet(augment, "inputs"); + if (JSON_NOT_NULL(element)) + { + JsonElement* inputs = JsonExpandElement(ctx, element); + + if (JsonGetElementType(inputs) == JSON_ELEMENT_TYPE_CONTAINER && + JsonGetContainerType(inputs) == JSON_CONTAINER_TYPE_ARRAY && + JsonArrayContainsOnlyPrimitives(inputs)) + { + Log(LOG_LEVEL_VERBOSE, "Installing augments def.augments_inputs from file '%s'", + filename); + Rlist *rlist = RlistFromContainer(inputs); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_DEF, + "augments_inputs", rlist, + CF_DATA_TYPE_STRING_LIST, + "source=augments_file"); + RlistDestroy(rlist); + } + else + { + Log(LOG_LEVEL_ERR, "Trying to augment inputs in '%s' but the value was not a list of strings", + filename); + } + + JsonDestroy(inputs); + } + + /* load further def.json files (if any) */ + element = JsonObjectGet(augment, "augments"); + if (JSON_NOT_NULL(element)) + { + JsonElement* further_augments = element; + assert(further_augments != NULL); + + if (JsonGetElementType(further_augments) == JSON_ELEMENT_TYPE_CONTAINER && + JsonGetContainerType(further_augments) == JSON_CONTAINER_TYPE_ARRAY && + JsonArrayContainsOnlyPrimitives(further_augments)) + { + JsonIterator iter = JsonIteratorInit(further_augments); + const JsonElement *el; + while ((el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)) != NULL) + { + char *nested_filename = JsonPrimitiveToString(el); + bool further_loaded = LoadAugmentsFiles(ctx, nested_filename); + if (further_loaded) + { + Log(LOG_LEVEL_VERBOSE, "Completed augmenting from file '%s'", nested_filename); + } + else + { + Log(LOG_LEVEL_ERR, "Could not load requested further augments from file '%s'", nested_filename); + } + free(nested_filename); + } + } + else + { + Log(LOG_LEVEL_ERR, "Trying to augment inputs in '%s' but the value was not a list of strings", + filename); + } + } + } + + return loaded; +} + +static bool LoadAugmentsFiles(EvalContext *ctx, const char *unexpanded_filename) +{ + bool loaded = false; + + char *filename = ExpandScalar(ctx, NULL, "this", unexpanded_filename, NULL); + + if (strstr(filename, "/.json")) + { + Log(LOG_LEVEL_DEBUG, + "Skipping augments file '%s' because it failed to expand the base filename, resulting in '%s'", + unexpanded_filename, filename); + } + else + { + Log(LOG_LEVEL_DEBUG, "Searching for augments file '%s' (original '%s')", + filename, unexpanded_filename); + if (FileCanOpen(filename, "r")) + { + // 5 MB should be enough for most reasonable def.json data + JsonElement* augment = ReadJsonFile(filename, LOG_LEVEL_ERR, 5 * 1024 * 1024); + if (augment != NULL) + { + loaded = LoadAugmentsData(ctx, filename, augment); + JsonDestroy(augment); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "could not load JSON augments from '%s'", filename); + } + } + + free(filename); + return loaded; +} + +static bool IsFile(const char *const filename) +{ + struct stat buffer; + if (stat(filename, &buffer) != 0) + { + return false; + } + if (S_ISREG(buffer.st_mode) != 0) + { + return true; + } + return false; +} + +void LoadAugments(EvalContext *ctx, GenericAgentConfig *config) +{ + assert(config != NULL); + + char* def_json = NULL; + // --ignore-preferred-augments command line option: + if (config->ignore_preferred_augments) + { + EvalContextClassPutHard(ctx, "ignore_preferred_augments", "source=command_line_option"); + // def_json is NULL so it will be assigned below + } + else + { + def_json = StringFormat("%s%c%s", config->input_dir, FILE_SEPARATOR, "def_preferred.json"); + if (!IsFile(def_json)) + { + // def_preferred.json does not exist or we cannot read it + FREE_AND_NULL(def_json); + } + } + + if (def_json == NULL) + { + // No def_preferred.json, either because the feature is disabled + // or we could not read the file. + // Fall back to old / default behavior, using def.json: + def_json = StringFormat("%s%c%s", config->input_dir, FILE_SEPARATOR, "def.json"); + } + Log(LOG_LEVEL_VERBOSE, "Loading JSON augments from '%s' (input dir '%s', input file '%s'", def_json, config->input_dir, config->input_file); + LoadAugmentsFiles(ctx, def_json); + free(def_json); +} + +static void AddPolicyEntryVariables (EvalContext *ctx, const GenericAgentConfig *config) +{ + char *abs_input_path = GetAbsolutePath(config->input_file); + /* both dirname() and basename() may actually modify the string they are given (see man:basename(3)) */ + char *dirname_path = xstrdup(abs_input_path); + char *basename_path = xstrdup(abs_input_path); + EvalContextSetEntryPoint(ctx, abs_input_path); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, + "policy_entry_filename", + abs_input_path, + CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, + "policy_entry_dirname", + dirname(dirname_path), + CF_DATA_TYPE_STRING, "source=agent"); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, + "policy_entry_basename", + basename(basename_path), + CF_DATA_TYPE_STRING, "source=agent"); + free(abs_input_path); + free(dirname_path); + free(basename_path); +} + +void GenericAgentDiscoverContext(EvalContext *ctx, GenericAgentConfig *config, + const char *program_name) +{ + assert(config != NULL); + + strcpy(VPREFIX, ""); + if (program_name != NULL) + { + strncpy(CF_PROGRAM_NAME, program_name, sizeof(CF_PROGRAM_NAME) - 1); + } + + Log(LOG_LEVEL_VERBOSE, " %s", NameVersion()); + Banner("Initialization preamble"); + + GenericAgentSetDefaultDigest(&CF_DEFAULT_DIGEST, &CF_DEFAULT_DIGEST_LEN); + GenericAgentInitialize(ctx, config); + + time_t t = SetReferenceTime(); + UpdateTimeClasses(ctx, t); + SanitizeEnvironment(); + + THIS_AGENT_TYPE = config->agent_type; + LoggingSetAgentType(CF_AGENTTYPES[config->agent_type]); + EvalContextClassPutHard(ctx, CF_AGENTTYPES[config->agent_type], + "cfe_internal,source=agent"); + + DetectEnvironment(ctx); + AddPolicyEntryVariables(ctx, config); + + EvalContextHeapPersistentLoadAll(ctx); + LoadSystemConstants(ctx); + + const char *bootstrap_arg = + config->agent_specific.agent.bootstrap_argument; + const char *bootstrap_ip = + config->agent_specific.agent.bootstrap_ip; + + /* Are we bootstrapping the agent? */ + if (config->agent_type == AGENT_TYPE_AGENT && bootstrap_arg != NULL) + { + EvalContextClassPutHard(ctx, "bootstrap_mode", "report,source=environment"); + + if (!config->agent_specific.agent.bootstrap_trigger_policy) + { + EvalContextClassPutHard(ctx, "skip_policy_on_bootstrap", "report,source=environment"); + } + + if (!RemoveAllExistingPolicyInInputs(GetInputDir())) + { + Log(LOG_LEVEL_ERR, + "Error removing existing input files prior to bootstrap"); + DoCleanupAndExit(EXIT_FAILURE); + } + + if (!WriteBuiltinFailsafePolicy(GetInputDir())) + { + Log(LOG_LEVEL_ERR, + "Error writing builtin failsafe to inputs prior to bootstrap"); + DoCleanupAndExit(EXIT_FAILURE); + } + GenericAgentConfigSetInputFile(config, GetInputDir(), "failsafe.cf"); + + char canonified_ipaddr[strlen(bootstrap_ip) + 1]; + StringCanonify(canonified_ipaddr, bootstrap_ip); + + bool am_policy_server = + EvalContextClassGet(ctx, NULL, canonified_ipaddr) != NULL; + + if (am_policy_server) + { + Log(LOG_LEVEL_INFO, "Assuming role as policy server," + " with policy distribution point at: %s", GetMasterDir()); + MarkAsPolicyServer(ctx); + + if (!MasterfileExists(GetMasterDir())) + { + Log(LOG_LEVEL_ERR, "In order to bootstrap as a policy server," + " the file '%s/promises.cf' must exist.", GetMasterDir()); + DoCleanupAndExit(EXIT_FAILURE); + } + + CheckAndSetHAState(GetWorkDir(), ctx); + } + else + { + Log(LOG_LEVEL_INFO, "Assuming role as regular client," + " bootstrapping to policy server: %s", bootstrap_arg); + + if (config->agent_specific.agent.bootstrap_trust_server) + { + EvalContextClassPutHard(ctx, "trust_server", "source=agent"); + Log(LOG_LEVEL_NOTICE, + "Bootstrap mode: implicitly trusting server, " + "use --trust-server=no if server trust is already established"); + } + } + + WriteAmPolicyHubFile(am_policy_server); + + PolicyServerWriteFile(GetWorkDir(), bootstrap_arg); + EvalContextSetPolicyServer(ctx, bootstrap_arg); + char *const bootstrap_id = CreateBootstrapIDFile(GetWorkDir()); + if (bootstrap_id != NULL) + { + EvalContextSetBootstrapID(ctx, bootstrap_id); + free(bootstrap_id); + } + + /* FIXME: Why it is called here? Can't we move both invocations to before if? */ + UpdateLastPolicyUpdateTime(ctx); + } + else + { + char *existing_policy_server = PolicyServerReadFile(GetWorkDir()); + if (existing_policy_server) + { + Log(LOG_LEVEL_VERBOSE, "This agent is bootstrapped to: %s", + existing_policy_server); + EvalContextSetPolicyServer(ctx, existing_policy_server); + char *const bootstrap_id = ReadBootstrapIDFile(GetWorkDir()); + if (bootstrap_id != NULL) + { + EvalContextSetBootstrapID(ctx, bootstrap_id); + free(bootstrap_id); + } + free(existing_policy_server); + UpdateLastPolicyUpdateTime(ctx); + if (GetAmPolicyHub()) + { + MarkAsPolicyServer(ctx); + + /* Should this go in MarkAsPolicyServer() ? */ + CheckAndSetHAState(GetWorkDir(), ctx); + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "This agent is not bootstrapped -" + " can't find policy_server.dat in: %s", GetWorkDir()); + } + } + + /* Load CMDB data *before* augments. */ + if (!config->agent_specific.common.no_host_specific && !LoadCMDBData(ctx)) + { + Log(LOG_LEVEL_ERR, "Failed to load CMDB data"); + } + + if (!config->agent_specific.common.no_augments) + { + /* load augments here so that they can make use of the classes added above + * (especially 'am_policy_hub' and 'policy_server') */ + LoadAugments(ctx, config); + } +} + +static bool IsPolicyPrecheckNeeded(GenericAgentConfig *config, bool force_validation) +{ + bool check_policy = false; + + if (IsFileOutsideDefaultRepository(config->input_file)) + { + check_policy = true; + Log(LOG_LEVEL_VERBOSE, "Input file is outside default repository, validating it"); + } + if (GenericAgentIsPolicyReloadNeeded(config)) + { + check_policy = true; + Log(LOG_LEVEL_VERBOSE, "Input file is changed since last validation, validating it"); + } + if (force_validation) + { + check_policy = true; + Log(LOG_LEVEL_VERBOSE, "always_validate is set, forcing policy validation"); + } + + return check_policy; +} + +bool GenericAgentCheckPolicy(GenericAgentConfig *config, bool force_validation, bool write_validated_file) +{ + if (!MissingInputFile(config->input_file)) + { + { + if (config->agent_type == AGENT_TYPE_SERVER || + config->agent_type == AGENT_TYPE_MONITOR || + config->agent_type == AGENT_TYPE_EXECUTOR) + { + time_t validated_at = ReadTimestampFromPolicyValidatedFile(config, NULL); + config->agent_specific.daemon.last_validated_at = validated_at; + } + } + + if (IsPolicyPrecheckNeeded(config, force_validation)) + { + bool policy_check_ok = GenericAgentArePromisesValid(config); + if (policy_check_ok && write_validated_file) + { + GenericAgentTagReleaseDirectory(config, + NULL, // use GetAutotagDir + write_validated_file, // true + GetAmPolicyHub()); // write release ID? + } + + if (config->agent_specific.agent.bootstrap_argument && !policy_check_ok) + { + Log(LOG_LEVEL_VERBOSE, "Policy is not valid, but proceeding with bootstrap"); + return true; + } + + return policy_check_ok; + } + else + { + Log(LOG_LEVEL_VERBOSE, "Policy is already validated"); + return true; + } + } + return false; +} + +static JsonElement *ReadPolicyValidatedFile(const char *filename) +{ + bool missing = true; + struct stat sb; + if (stat(filename, &sb) != -1) + { + missing = false; + } + + JsonElement *validated_doc = ReadJsonFile(filename, LOG_LEVEL_DEBUG, 5 * 1024 * 1024); + if (validated_doc == NULL) + { + Log(missing ? LOG_LEVEL_DEBUG : LOG_LEVEL_VERBOSE, "Could not parse policy_validated JSON file '%s', using dummy data", filename); + validated_doc = JsonObjectCreate(2); + if (missing) + { + JsonObjectAppendInteger(validated_doc, "timestamp", 0); + } + else + { + JsonObjectAppendInteger(validated_doc, "timestamp", sb.st_mtime); + } + } + + return validated_doc; +} + +static JsonElement *ReadPolicyValidatedFileFromMasterfiles(const GenericAgentConfig *config, const char *maybe_dirname) +{ + char filename[CF_MAXVARSIZE]; + + GetPromisesValidatedFile(filename, sizeof(filename), config, maybe_dirname); + + return ReadPolicyValidatedFile(filename); +} + +/** + * @brief Writes a file with a contained timestamp to mark a policy file as validated + * @param filename the filename + * @return True if successful. + */ +static bool WritePolicyValidatedFile(ARG_UNUSED const GenericAgentConfig *config, const char *filename) +{ + if (!MakeParentDirectory(filename, true, NULL)) + { + Log(LOG_LEVEL_ERR, + "Could not write policy validated marker file: %s", filename); + return false; + } + + int fd = creat(filename, CF_PERMS_DEFAULT); + if (fd == -1) + { + Log(LOG_LEVEL_ERR, "While writing policy validated marker file '%s', could not create file (create: %s)", filename, GetErrorStr()); + return false; + } + + JsonElement *info = JsonObjectCreate(3); + JsonObjectAppendInteger(info, "timestamp", time(NULL)); + + Writer *w = FileWriter(fdopen(fd, "w")); + JsonWrite(w, info, 0); + + WriterClose(w); + JsonDestroy(info); + + Log(LOG_LEVEL_VERBOSE, "Saved policy validated marker file '%s'", filename); + return true; +} + +/** + * @brief Writes the policy validation file and release ID to a directory + * @return True if successful. + */ +bool GenericAgentTagReleaseDirectory(const GenericAgentConfig *config, const char *dirname, bool write_validated, bool write_release) +{ + char local_dirname[PATH_MAX + 1]; + if (dirname == NULL) + { + GetAutotagDir(local_dirname, PATH_MAX, NULL); + dirname = local_dirname; + } + + char filename[CF_MAXVARSIZE]; + char git_checksum[GENERIC_AGENT_CHECKSUM_SIZE]; + bool have_git_checksum = GeneratePolicyReleaseIDFromGit(git_checksum, sizeof(git_checksum), dirname); + + Log(LOG_LEVEL_DEBUG, "Tagging directory %s for release (write_validated: %s, write_release: %s)", + dirname, + write_validated ? "yes" : "no", + write_release ? "yes" : "no"); + + if (write_release) + { + // first, tag the release ID + GetReleaseIdFile(dirname, filename, sizeof(filename)); + char *id = ReadReleaseIdFromReleaseIdFileMasterfiles(dirname); + if (id == NULL + || (have_git_checksum && + strcmp(id, git_checksum) != 0)) + { + if (id == NULL) + { + Log(LOG_LEVEL_DEBUG, "The release_id of %s was missing", dirname); + } + else + { + Log(LOG_LEVEL_DEBUG, "The release_id of %s needs to be updated", dirname); + } + + bool wrote_release = WriteReleaseIdFile(filename, dirname); + if (!wrote_release) + { + Log(LOG_LEVEL_VERBOSE, "The release_id file %s was NOT updated", filename); + free(id); + return false; + } + else + { + Log(LOG_LEVEL_DEBUG, "The release_id file %s was updated", filename); + } + } + + free(id); + } + + // now, tag the promises_validated + if (write_validated) + { + Log(LOG_LEVEL_DEBUG, "Tagging directory %s for validation", dirname); + + GetPromisesValidatedFile(filename, sizeof(filename), config, dirname); + + bool wrote_validated = WritePolicyValidatedFile(config, filename); + + if (!wrote_validated) + { + Log(LOG_LEVEL_VERBOSE, "The promises_validated file %s was NOT updated", filename); + return false; + } + + Log(LOG_LEVEL_DEBUG, "The promises_validated file %s was updated", filename); + return true; + } + + return true; +} + +/** + * @brief Writes a file with a contained release ID based on git SHA, + * or file checksum if git SHA is not available. + * @param filename the release_id file + * @param dirname the directory to checksum or get the Git hash + * @return True if successful + */ +static bool WriteReleaseIdFile(const char *filename, const char *dirname) +{ + char release_id[GENERIC_AGENT_CHECKSUM_SIZE]; + + bool have_release_id = + GeneratePolicyReleaseID(release_id, sizeof(release_id), dirname); + + if (!have_release_id) + { + return false; + } + + int fd = creat(filename, CF_PERMS_DEFAULT); + if (fd == -1) + { + Log(LOG_LEVEL_ERR, "While writing policy release ID file '%s', could not create file (create: %s)", filename, GetErrorStr()); + return false; + } + + JsonElement *info = JsonObjectCreate(3); + JsonObjectAppendString(info, "releaseId", release_id); + + Writer *w = FileWriter(fdopen(fd, "w")); + JsonWrite(w, info, 0); + + WriterClose(w); + JsonDestroy(info); + + Log(LOG_LEVEL_VERBOSE, "Saved policy release ID file '%s'", filename); + return true; +} + +bool GenericAgentArePromisesValid(const GenericAgentConfig *config) +{ + assert(config != NULL); + + char cmd[CF_BUFSIZE]; + const char* const bindir = GetBinDir(); + + Log(LOG_LEVEL_VERBOSE, "Verifying the syntax of the inputs..."); + { + char cfpromises[CF_MAXVARSIZE]; + + snprintf(cfpromises, sizeof(cfpromises), "%s%ccf-promises%s", + bindir, FILE_SEPARATOR, EXEC_SUFFIX); + + struct stat sb; + if (stat(cfpromises, &sb) == -1) + { + Log(LOG_LEVEL_ERR, + "cf-promises%s needs to be installed in %s for pre-validation of full configuration", + EXEC_SUFFIX, bindir); + + return false; + } + + if (config->bundlesequence) + { + snprintf(cmd, sizeof(cmd), "\"%s\" \"", cfpromises); + } + else + { + snprintf(cmd, sizeof(cmd), "\"%s\" -c \"", cfpromises); + } + } + + strlcat(cmd, config->input_file, CF_BUFSIZE); + + strlcat(cmd, "\"", CF_BUFSIZE); + + if (config->bundlesequence) + { + strlcat(cmd, " -b \"", CF_BUFSIZE); + for (const Rlist *rp = config->bundlesequence; rp; rp = rp->next) + { + const char *bundle_ref = RlistScalarValue(rp); + strlcat(cmd, bundle_ref, CF_BUFSIZE); + + if (rp->next) + { + strlcat(cmd, ",", CF_BUFSIZE); + } + } + strlcat(cmd, "\"", CF_BUFSIZE); + } + + if (config->ignore_preferred_augments) + { + strlcat(cmd, " --ignore-preferred-augments", CF_BUFSIZE); + } + + Log(LOG_LEVEL_VERBOSE, "Checking policy with command '%s'", cmd); + + if (!ShellCommandReturnsZero(cmd, true)) + { + Log(LOG_LEVEL_ERR, "Policy failed validation with command '%s'", cmd); + return false; + } + + return true; +} + + + + +/*****************************************************************************/ + +#if !defined(__MINGW32__) +static void OpenLog(int facility) +{ + openlog(CF_PROGRAM_NAME, LOG_PID | LOG_NOWAIT | LOG_ODELAY, facility); +} +#endif + +/*****************************************************************************/ + +#if !defined(__MINGW32__) +void CloseLog(void) +{ + closelog(); +} +#endif + +ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, GenericAgentAddEditionClasses, EvalContext *, ctx) +{ + EvalContextClassPutHard(ctx, "community_edition", "inventory,attribute_name=none,source=agent"); +} + +static int GetDefaultLogFacility() +{ + char log_facility_file[PATH_MAX]; + NDEBUG_UNUSED int written = snprintf(log_facility_file, sizeof(log_facility_file) - 1, + "%s%c%s_log_facility.dat", GetStateDir(), + FILE_SEPARATOR, CF_PROGRAM_NAME); + assert(written < PATH_MAX); + if (access(log_facility_file, R_OK) != 0) + { + return LOG_USER; + } + + FILE *f = fopen(log_facility_file, "r"); + if (f == NULL) + { + return LOG_USER; + } + char facility_str[16] = {0}; /* at most "LOG_DAEMON\n" */ + size_t n_read = fread(facility_str, 1, sizeof(facility_str) - 1, f); + fclose(f); + if (n_read == 0) + { + return LOG_USER; + } + if (facility_str[n_read - 1] == '\n') + { + facility_str[n_read - 1] = '\0'; + } + return ParseFacility(facility_str); +} + +static bool StoreDefaultLogFacility() +{ + char log_facility_file[PATH_MAX]; + NDEBUG_UNUSED int written = snprintf(log_facility_file, sizeof(log_facility_file) - 1, + "%s%c%s_log_facility.dat", GetStateDir(), + FILE_SEPARATOR, CF_PROGRAM_NAME); + assert(written < PATH_MAX); + + FILE *f = fopen(log_facility_file, "w"); + if (f == NULL) + { + return false; + } + const char *facility_str = LogFacilityToString(GetSyslogFacility()); + NDEBUG_UNUSED int printed = fprintf(f, "%s\n", facility_str); + assert(printed > 0); + + fclose(f); + return true; +} + +void GenericAgentInitialize(EvalContext *ctx, GenericAgentConfig *config) +{ + int force = false; + struct stat statbuf, sb; + char vbuff[CF_BUFSIZE]; + char ebuff[CF_EXPANDSIZE]; + +#ifdef __MINGW32__ + InitializeWindows(); +#endif + + /* Set output to line-buffered to avoid truncated debug logs. */ + + /* Bug on HP-UX: Buffered output is discarded if you switch buffering mode + without flushing the buffered output first. This will happen anyway when + switching modes, so no performance is lost. */ + fflush(stdout); + +#ifndef SUNOS_5 + setlinebuf(stdout); +#else + /* CFE-2527: On Solaris we avoid calling setlinebuf, since fprintf() on + Solaris 10 and 11 truncates output under certain conditions. We fully + disable buffering to avoid truncated debug logs; performance impact + should be minimal because we mostly write full lines anyway. */ + setvbuf(stdout, NULL, _IONBF, 0); +#endif + + DetermineCfenginePort(); + + int default_facility = GetDefaultLogFacility(); + OpenLog(default_facility); + SetSyslogFacility(default_facility); + + EvalContextClassPutHard(ctx, "any", "source=agent"); + + GenericAgentAddEditionClasses(ctx); + + /* Make sure the chroot for recording changes this process would normally + * make on the system is setup if that was requested. */ + if (ChrootChanges()) + { + char changes_chroot[PATH_MAX] = {0}; + GetChangesChrootDir(changes_chroot, sizeof(changes_chroot)); + SetChangesChroot(changes_chroot); + RegisterCleanupFunction(DeleteChangesChroot); + Log(LOG_LEVEL_WARNING, "All changes in files will be made in the '%s' chroot", + changes_chroot); + } + +/* Define trusted directories */ + + const char *workdir = GetWorkDir(); + const char *bindir = GetBinDir(); + + if (!workdir) + { + FatalError(ctx, "Error determining working directory"); + } + + Log(LOG_LEVEL_VERBOSE, "Work directory is %s", workdir); + + snprintf(vbuff, CF_BUFSIZE, "%s%cupdate.conf", GetInputDir(), FILE_SEPARATOR); + MakeParentInternalDirectory(vbuff, force, NULL); + snprintf(vbuff, CF_BUFSIZE, "%s%ccf-agent", bindir, FILE_SEPARATOR); + MakeParentInternalDirectory(vbuff, force, NULL); + snprintf(vbuff, CF_BUFSIZE, "%s%coutputs%cspooled_reports", workdir, FILE_SEPARATOR, FILE_SEPARATOR); + MakeParentInternalDirectory(vbuff, force, NULL); + snprintf(vbuff, CF_BUFSIZE, "%s%clastseen%cintermittencies", workdir, FILE_SEPARATOR, FILE_SEPARATOR); + MakeParentInternalDirectory(vbuff, force, NULL); + snprintf(vbuff, CF_BUFSIZE, "%s%creports%cvarious", workdir, FILE_SEPARATOR, FILE_SEPARATOR); + MakeParentInternalDirectory(vbuff, force, NULL); + + snprintf(vbuff, CF_BUFSIZE, "%s%c.", GetLogDir(), FILE_SEPARATOR); + MakeParentInternalDirectory(vbuff, force, NULL); + snprintf(vbuff, CF_BUFSIZE, "%s%c.", GetPidDir(), FILE_SEPARATOR); + MakeParentInternalDirectory(vbuff, force, NULL); + snprintf(vbuff, CF_BUFSIZE, "%s%c.", GetStateDir(), FILE_SEPARATOR); + MakeParentInternalDirectory(vbuff, force, NULL); + + MakeParentInternalDirectory(GetLogDir(), force, NULL); + + snprintf(vbuff, CF_BUFSIZE, "%s", GetInputDir()); + + if (stat(vbuff, &sb) == -1) + { + FatalError(ctx, " No access to WORKSPACE/inputs dir"); + } + + /* ensure WORKSPACE/inputs directory has all user bits set (u+rwx) */ + if ((sb.st_mode & 0700) != 0700) + { + chmod(vbuff, sb.st_mode | 0700); + } + + snprintf(vbuff, CF_BUFSIZE, "%s%coutputs", workdir, FILE_SEPARATOR); + + if (stat(vbuff, &sb) == -1) + { + FatalError(ctx, " No access to WORKSPACE/outputs dir"); + } + + /* ensure WORKSPACE/outputs directory has all user bits set (u+rwx) */ + if ((sb.st_mode & 0700) != 0700) + { + chmod(vbuff, sb.st_mode | 0700); + } + + const char* const statedir = GetStateDir(); + + snprintf(ebuff, sizeof(ebuff), "%s%ccf_procs", + statedir, FILE_SEPARATOR); + MakeParentDirectory(ebuff, force, NULL); + + if (stat(ebuff, &statbuf) == -1) + { + CreateEmptyFile(ebuff); + } + + snprintf(ebuff, sizeof(ebuff), "%s%ccf_rootprocs", + statedir, FILE_SEPARATOR); + + if (stat(ebuff, &statbuf) == -1) + { + CreateEmptyFile(ebuff); + } + + snprintf(ebuff, sizeof(ebuff), "%s%ccf_otherprocs", + statedir, FILE_SEPARATOR); + + if (stat(ebuff, &statbuf) == -1) + { + CreateEmptyFile(ebuff); + } + + snprintf(ebuff, sizeof(ebuff), "%s%cprevious_state%c", + statedir, FILE_SEPARATOR, FILE_SEPARATOR); + MakeParentDirectory(ebuff, force, NULL); + + snprintf(ebuff, sizeof(ebuff), "%s%cdiff%c", + statedir, FILE_SEPARATOR, FILE_SEPARATOR); + MakeParentDirectory(ebuff, force, NULL); + + snprintf(ebuff, sizeof(ebuff), "%s%cuntracked%c", + statedir, FILE_SEPARATOR, FILE_SEPARATOR); + MakeParentDirectory(ebuff, force, NULL); + + OpenNetwork(); + CryptoInitialize(); + + CheckWorkingDirectories(ctx); + + /* Initialize keys and networking. cf-key, doesn't need keys. In fact it + must function properly even without them, so that it generates them! */ + if (config->agent_type != AGENT_TYPE_KEYGEN) + { + LoadSecretKeys(NULL, NULL, NULL, NULL); + char *ipaddr = NULL, *port = NULL; + PolicyServerLookUpFile(workdir, &ipaddr, &port); + PolicyHubUpdateKeys(ipaddr); + free(ipaddr); + free(port); + } + + size_t cwd_size = PATH_MAX; + while (true) + { + char cwd[cwd_size]; + if (!getcwd(cwd, cwd_size)) + { + if (errno == ERANGE) + { + cwd_size *= 2; + continue; + } + else + { + Log(LOG_LEVEL_WARNING, + "Could not determine current directory (getcwd: %s)", + GetErrorStr()); + break; + } + } + + EvalContextSetLaunchDirectory(ctx, cwd); + break; + } + + if (!MINUSF) + { + GenericAgentConfigSetInputFile(config, GetInputDir(), "promises.cf"); + } +} + +static void GetChangesChrootDir(char *buf, size_t buf_size) +{ + snprintf(buf, buf_size, "%s/%ju.changes", GetStateDir(), (uintmax_t) getpid()); +} + +static void DeleteChangesChroot() +{ + char changes_chroot[PATH_MAX] = {0}; + GetChangesChrootDir(changes_chroot, sizeof(changes_chroot)); + Log(LOG_LEVEL_VERBOSE, "Deleting changes chroot '%s'", changes_chroot); + DeleteDirectoryTree(changes_chroot); + + /* DeleteDirectoryTree() doesn't delete the root of the tree. */ + if (rmdir(changes_chroot) != 0) + { + Log(LOG_LEVEL_ERR, "Failed to delete changes chroot '%s'", changes_chroot); + } +} + +void GenericAgentFinalize(EvalContext *ctx, GenericAgentConfig *config) +{ + /* TODO, FIXME: what else from the above do we need to undo here ? */ + if (config->agent_type != AGENT_TYPE_KEYGEN) + { + cfnet_shut(); + } + CryptoDeInitialize(); + GenericAgentConfigDestroy(config); + EvalContextDestroy(ctx); +} + +static bool MissingInputFile(const char *input_file) +{ + struct stat sb; + + if (stat(input_file, &sb) == -1) + { + Log(LOG_LEVEL_ERR, "There is no readable input file at '%s'. (stat: %s)", input_file, GetErrorStr()); + return true; + } + + return false; +} + +// Git only. +static bool GeneratePolicyReleaseIDFromGit(char *release_id_out, +#ifdef NDEBUG /* out_size is only used in an assertion */ + ARG_UNUSED +#endif + size_t out_size, + const char *policy_dir) +{ + char git_filename[PATH_MAX + 1]; + snprintf(git_filename, PATH_MAX, "%s/.git/HEAD", policy_dir); + MapName(git_filename); + + // Note: Probably we should not be reading all of these filenames directly, + // and should instead use git plumbing commands to retrieve the data. + FILE *git_file = safe_fopen(git_filename, "r"); + if (git_file) + { + char git_head[128]; + int scanned = fscanf(git_file, "ref: %127s", git_head); + + if (scanned == 1) + // Found HEAD Reference which means we are on a checked out branch + { + fclose(git_file); + snprintf(git_filename, PATH_MAX, "%s/.git/%s", + policy_dir, git_head); + git_file = safe_fopen(git_filename, "r"); + Log(LOG_LEVEL_DEBUG, "Found a git HEAD ref"); + } + else + { + Log(LOG_LEVEL_DEBUG, + "Unable to find HEAD ref in '%s', looking for commit instead", + git_filename); + assert(out_size > 40); + fseek(git_file, 0, SEEK_SET); + scanned = fscanf(git_file, "%40s", release_id_out); + fclose(git_file); + + if (scanned == 1) + { + Log(LOG_LEVEL_DEBUG, + "Found current git checkout pointing to: %s", + release_id_out); + return true; + } + else + { + /* We didn't find a commit sha in .git/HEAD, so we assume the + * git information is invalid. */ + git_file = NULL; + } + } + if (git_file) + { + assert(out_size > 40); + scanned = fscanf(git_file, "%40s", release_id_out); + fclose(git_file); + return scanned == 1; + } + else + { + Log(LOG_LEVEL_DEBUG, "While generating policy release ID, found git head ref '%s', but unable to open (errno: %s)", + policy_dir, GetErrorStr()); + } + } + else + { + Log(LOG_LEVEL_DEBUG, "While generating policy release ID, directory is '%s' not a git repository", + policy_dir); + } + + return false; +} + +static bool GeneratePolicyReleaseIDFromTree(char *release_id_out, size_t out_size, + const char *policy_dir) +{ + if (access(policy_dir, R_OK) != 0) + { + Log(LOG_LEVEL_ERR, "Cannot access policy directory '%s' to generate release ID", policy_dir); + return false; + } + + // fallback, produce some pseudo sha1 hash + const EVP_MD *const md = HashDigestFromId(GENERIC_AGENT_CHECKSUM_METHOD); + if (md == NULL) + { + Log(LOG_LEVEL_ERR, + "Could not determine function for file hashing"); + return false; + } + + EVP_MD_CTX *crypto_ctx = EVP_MD_CTX_new(); + if (crypto_ctx == NULL) + { + Log(LOG_LEVEL_ERR, "Could not allocate openssl hash context"); + return false; + } + + EVP_DigestInit(crypto_ctx, md); + + bool success = HashDirectoryTree(policy_dir, + (const char *[]) { ".cf", ".dat", ".txt", ".conf", ".mustache", ".json", ".yaml", NULL}, + crypto_ctx); + + int md_len; + unsigned char digest[EVP_MAX_MD_SIZE + 1] = { 0 }; + EVP_DigestFinal(crypto_ctx, digest, &md_len); + EVP_MD_CTX_free(crypto_ctx); + + HashPrintSafe(release_id_out, out_size, digest, + GENERIC_AGENT_CHECKSUM_METHOD, false); + return success; +} + +static bool GeneratePolicyReleaseID(char *release_id_out, size_t out_size, + const char *policy_dir) +{ + if (GeneratePolicyReleaseIDFromGit(release_id_out, out_size, policy_dir)) + { + return true; + } + + return GeneratePolicyReleaseIDFromTree(release_id_out, out_size, + policy_dir); +} + +/** + * @brief Gets the promises_validated file name depending on context and options + */ +static void GetPromisesValidatedFile(char *filename, size_t max_size, const GenericAgentConfig *config, const char *maybe_dirname) +{ + char dirname[max_size]; + + /* TODO overflow error checking! */ + GetAutotagDir(dirname, max_size, maybe_dirname); + + if (maybe_dirname == NULL && MINUSF) + { + snprintf(filename, max_size, "%s/validated_%s", dirname, CanonifyName(config->original_input_file)); + } + else + { + snprintf(filename, max_size, "%s/cf_promises_validated", dirname); + } + + MapName(filename); +} + + /** + * @brief Gets the promises_validated file name depending on context and options + */ +static void GetAutotagDir(char *dirname, size_t max_size, const char *maybe_dirname) +{ + if (maybe_dirname != NULL) + { + strlcpy(dirname, maybe_dirname, max_size); + } + else if (MINUSF) + { + strlcpy(dirname, GetStateDir(), max_size); + } + else + { + strlcpy(dirname, GetMasterDir(), max_size); + } + + MapName(dirname); +} + +/** + * @brief Gets the release_id file name in the given base_path. + */ +void GetReleaseIdFile(const char *base_path, char *filename, size_t max_size) +{ + snprintf(filename, max_size, "%s/cf_promises_release_id", base_path); + MapName(filename); +} + +static JsonElement *ReadReleaseIdFileFromMasterfiles(const char *maybe_dirname) +{ + char filename[CF_MAXVARSIZE]; + + GetReleaseIdFile((maybe_dirname == NULL) ? GetMasterDir() : maybe_dirname, + filename, sizeof(filename)); + + JsonElement *doc = ReadJsonFile(filename, LOG_LEVEL_DEBUG, 5 * 1024 * 1024); + if (doc == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Could not parse release_id JSON file %s", filename); + } + + return doc; +} + +static char* ReadReleaseIdFromReleaseIdFileMasterfiles(const char *maybe_dirname) +{ + JsonElement *doc = ReadReleaseIdFileFromMasterfiles(maybe_dirname); + char *id = NULL; + if (doc) + { + JsonElement *jid = JsonObjectGet(doc, "releaseId"); + if (jid) + { + id = xstrdup(JsonPrimitiveGetAsString(jid)); + } + JsonDestroy(doc); + } + + return id; +} + +// TODO: refactor Read*FromPolicyValidatedMasterfiles +time_t ReadTimestampFromPolicyValidatedFile(const GenericAgentConfig *config, const char *maybe_dirname) +{ + time_t validated_at = 0; + { + JsonElement *validated_doc = ReadPolicyValidatedFileFromMasterfiles(config, maybe_dirname); + if (validated_doc) + { + JsonElement *timestamp = JsonObjectGet(validated_doc, "timestamp"); + if (timestamp) + { + validated_at = JsonPrimitiveGetAsInteger(timestamp); + } + JsonDestroy(validated_doc); + } + } + + return validated_at; +} + +// TODO: refactor Read*FromPolicyValidatedMasterfiles +char* ReadChecksumFromPolicyValidatedMasterfiles(const GenericAgentConfig *config, const char *maybe_dirname) +{ + char *checksum_str = NULL; + + { + JsonElement *validated_doc = ReadPolicyValidatedFileFromMasterfiles(config, maybe_dirname); + if (validated_doc) + { + JsonElement *checksum = JsonObjectGet(validated_doc, "checksum"); + if (checksum ) + { + checksum_str = xstrdup(JsonPrimitiveGetAsString(checksum)); + } + JsonDestroy(validated_doc); + } + } + + return checksum_str; +} + +/** + * @NOTE Updates the config->agent_specific.daemon.last_validated_at timestamp + * used by serverd, execd etc daemons when checking for new policies. + */ +bool GenericAgentIsPolicyReloadNeeded(const GenericAgentConfig *config) +{ + time_t validated_at = ReadTimestampFromPolicyValidatedFile(config, NULL); + time_t now = time(NULL); + + if (validated_at > now) + { + Log(LOG_LEVEL_INFO, + "Clock seems to have jumped back in time, mtime of %jd is newer than current time %jd, touching it", + (intmax_t) validated_at, (intmax_t) now); + + GenericAgentTagReleaseDirectory(config, + NULL, // use GetAutotagDir + true, // write validated + false); // write release ID + return true; + } + + { + struct stat sb; + if (stat(config->input_file, &sb) == -1) + { + Log(LOG_LEVEL_VERBOSE, "There is no readable input file at '%s'. (stat: %s)", config->input_file, GetErrorStr()); + return true; + } + else if (sb.st_mtime > validated_at) + { + Log(LOG_LEVEL_VERBOSE, "Input file '%s' has changed since the last policy read attempt (file is newer than previous)", config->input_file); + return true; + } + } + + // Check the directories first for speed and because non-input/data files should trigger an update + { + if (IsNewerFileTree( (char *)GetInputDir(), validated_at)) + { + Log(LOG_LEVEL_VERBOSE, "Quick search detected file changes"); + return true; + } + } + + { + char filename[MAX_FILENAME]; + snprintf(filename, MAX_FILENAME, "%s/policy_server.dat", GetWorkDir()); + MapName(filename); + + struct stat sb; + if ((stat(filename, &sb) != -1) && (sb.st_mtime > validated_at)) + { + return true; + } + } + + return false; +} + +/*******************************************************************/ + +Seq *ControlBodyConstraints(const Policy *policy, AgentType agent) +{ + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + const Body *body = SeqAt(policy->bodies, i); + + if (strcmp(body->type, CF_AGENTTYPES[agent]) == 0) + { + if (strcmp(body->name, "control") == 0) + { + return body->conlist; + } + } + } + + return NULL; +} + +/*******************************************************************/ + +static int ParseFacility(const char *name) +{ + if (strcmp(name, "LOG_USER") == 0) + { + return LOG_USER; + } + if (strcmp(name, "LOG_DAEMON") == 0) + { + return LOG_DAEMON; + } + if (strcmp(name, "LOG_LOCAL0") == 0) + { + return LOG_LOCAL0; + } + if (strcmp(name, "LOG_LOCAL1") == 0) + { + return LOG_LOCAL1; + } + if (strcmp(name, "LOG_LOCAL2") == 0) + { + return LOG_LOCAL2; + } + if (strcmp(name, "LOG_LOCAL3") == 0) + { + return LOG_LOCAL3; + } + if (strcmp(name, "LOG_LOCAL4") == 0) + { + return LOG_LOCAL4; + } + if (strcmp(name, "LOG_LOCAL5") == 0) + { + return LOG_LOCAL5; + } + if (strcmp(name, "LOG_LOCAL6") == 0) + { + return LOG_LOCAL6; + } + if (strcmp(name, "LOG_LOCAL7") == 0) + { + return LOG_LOCAL7; + } + return -1; +} + +static inline const char *LogFacilityToString(int facility) +{ + switch(facility) + { + case LOG_LOCAL0: return "LOG_LOCAL0"; + case LOG_LOCAL1: return "LOG_LOCAL1"; + case LOG_LOCAL2: return "LOG_LOCAL2"; + case LOG_LOCAL3: return "LOG_LOCAL3"; + case LOG_LOCAL4: return "LOG_LOCAL4"; + case LOG_LOCAL5: return "LOG_LOCAL5"; + case LOG_LOCAL6: return "LOG_LOCAL6"; + case LOG_LOCAL7: return "LOG_LOCAL7"; + case LOG_USER: return "LOG_USER"; + case LOG_DAEMON: return "LOG_DAEMON"; + default: return "UNKNOWN"; + } +} + +void SetFacility(const char *retval) +{ + Log(LOG_LEVEL_VERBOSE, "SET Syslog FACILITY = %s", retval); + + CloseLog(); + OpenLog(ParseFacility(retval)); + SetSyslogFacility(ParseFacility(retval)); + if (!StoreDefaultLogFacility()) + { + Log(LOG_LEVEL_ERR, "Failed to store default log facility"); + } +} + +static void CheckWorkingDirectories(EvalContext *ctx) +/* NOTE: We do not care about permissions (ACLs) in windows */ +{ + struct stat statbuf; + char vbuff[CF_BUFSIZE]; + + const char* const workdir = GetWorkDir(); + const char* const statedir = GetStateDir(); + + if (uname(&VSYSNAME) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't get kernel name info. (uname: %s)", GetErrorStr()); + memset(&VSYSNAME, 0, sizeof(VSYSNAME)); + } + + snprintf(vbuff, CF_BUFSIZE, "%s%c.", workdir, FILE_SEPARATOR); + MakeParentDirectory(vbuff, false, NULL); + + /* check that GetWorkDir() exists */ + if (stat(GetWorkDir(), &statbuf) == -1) + { + FatalError(ctx,"Unable to stat working directory '%s'! (stat: %s)\n", + GetWorkDir(), GetErrorStr()); + } + + Log(LOG_LEVEL_VERBOSE, "Making sure that internal directories are private..."); + + Log(LOG_LEVEL_VERBOSE, "Checking integrity of the trusted workdir"); + + /* fix any improper uid/gid ownership on workdir */ + if (statbuf.st_uid != getuid() || statbuf.st_gid != getgid()) + { + if (chown(workdir, getuid(), getgid()) == -1) + { + const char* error_reason = GetErrorStr(); + + Log(LOG_LEVEL_ERR, "Unable to set ownership on '%s' to '%ju.%ju'. (chown: %s)", + workdir, (uintmax_t)getuid(), (uintmax_t)getgid(), error_reason); + } + } + + /* ensure workdir permissions are go-w */ + if ((statbuf.st_mode & 022) != 0) + { + if (chmod(workdir, (mode_t) (statbuf.st_mode & ~022)) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to set permissions on '%s' to go-w. (chmod: %s)", + workdir, GetErrorStr()); + } + } + + MakeParentDirectory(GetStateDir(), false, NULL); + Log(LOG_LEVEL_VERBOSE, "Checking integrity of the state database"); + + snprintf(vbuff, CF_BUFSIZE, "%s", statedir); + + if (stat(vbuff, &statbuf) == -1) + { + snprintf(vbuff, CF_BUFSIZE, "%s%c", statedir, FILE_SEPARATOR); + MakeParentDirectory(vbuff, false, NULL); + + if (chown(vbuff, getuid(), getgid()) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to set owner on '%s' to '%ju.%ju'. (chown: %s)", vbuff, + (uintmax_t)getuid(), (uintmax_t)getgid(), GetErrorStr()); + } + + chmod(vbuff, (mode_t) 0755); + } + else + { +#ifndef __MINGW32__ + if (statbuf.st_mode & 022) + { + Log(LOG_LEVEL_ERR, "UNTRUSTED: State directory %s (mode %jo) was not private, world and/or group writeable!", statedir, + (uintmax_t)(statbuf.st_mode & 0777)); + } +#endif /* !__MINGW32__ */ + } + + Log(LOG_LEVEL_VERBOSE, "Checking integrity of the module directory"); + + snprintf(vbuff, CF_BUFSIZE, "%s%cmodules", workdir, FILE_SEPARATOR); + + if (stat(vbuff, &statbuf) == -1) + { + snprintf(vbuff, CF_BUFSIZE, "%s%cmodules%c.", workdir, FILE_SEPARATOR, FILE_SEPARATOR); + MakeParentDirectory(vbuff, false, NULL); + + if (chown(vbuff, getuid(), getgid()) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to set owner on '%s' to '%ju.%ju'. (chown: %s)", vbuff, + (uintmax_t)getuid(), (uintmax_t)getgid(), GetErrorStr()); + } + + chmod(vbuff, (mode_t) 0700); + } + else + { +#ifndef __MINGW32__ + if (statbuf.st_mode & 022) + { + Log(LOG_LEVEL_ERR, "UNTRUSTED: Module directory %s (mode %jo) was not private!", vbuff, + (uintmax_t)(statbuf.st_mode & 0777)); + } +#endif /* !__MINGW32__ */ + } + + Log(LOG_LEVEL_VERBOSE, "Checking integrity of the PKI directory"); + + snprintf(vbuff, CF_BUFSIZE, "%s%cppkeys", workdir, FILE_SEPARATOR); + + if (stat(vbuff, &statbuf) == -1) + { + snprintf(vbuff, CF_BUFSIZE, "%s%cppkeys%c", workdir, FILE_SEPARATOR, FILE_SEPARATOR); + MakeParentDirectory(vbuff, false, NULL); + + chmod(vbuff, (mode_t) 0700); /* Keys must be immutable to others */ + } + else + { +#ifndef __MINGW32__ + if (statbuf.st_mode & 077) + { + FatalError(ctx, "UNTRUSTED: Private key directory %s%cppkeys (mode %jo) was not private!\n", workdir, + FILE_SEPARATOR, (uintmax_t)(statbuf.st_mode & 0777)); + } +#endif /* !__MINGW32__ */ + } +} + + +const char *GenericAgentResolveInputPath(const GenericAgentConfig *config, const char *input_file) +{ + static char input_path[CF_BUFSIZE]; /* GLOBAL_R, no initialization needed */ + + switch (FilePathGetType(input_file)) + { + case FILE_PATH_TYPE_ABSOLUTE: + strlcpy(input_path, input_file, CF_BUFSIZE); + break; + + case FILE_PATH_TYPE_NON_ANCHORED: + case FILE_PATH_TYPE_RELATIVE: + snprintf(input_path, CF_BUFSIZE, "%s%c%s", config->input_dir, FILE_SEPARATOR, input_file); + break; + } + + return MapName(input_path); +} + +ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, GenericAgentWriteVersion, Writer *, w) +{ + WriterWriteF(w, "%s\n", NameVersion()); +} + +/*******************************************************************/ + +const char *Version(void) +{ + return VERSION; +} + +/*******************************************************************/ + +const char *NameVersion(void) +{ + return "CFEngine Core " VERSION; +} + +/********************************************************************/ + +static void CleanPidFile(void) +{ + if (unlink(PIDFILE) != 0) + { + if (errno != ENOENT) + { + Log(LOG_LEVEL_ERR, "Unable to remove pid file '%s'. (unlink: %s)", PIDFILE, GetErrorStr()); + } + } +} + +/********************************************************************/ + +static void RegisterPidCleanup(void) +{ + RegisterCleanupFunction(&CleanPidFile); +} + +/********************************************************************/ + +void WritePID(char *filename) +{ + pthread_once(&pid_cleanup_once, RegisterPidCleanup); + + snprintf(PIDFILE, CF_BUFSIZE - 1, "%s%c%s", GetPidDir(), FILE_SEPARATOR, filename); + + FILE *fp = safe_fopen_create_perms(PIDFILE, "w", CF_PERMS_DEFAULT); + if (fp == NULL) + { + Log(LOG_LEVEL_INFO, "Could not write to PID file '%s'. (fopen: %s)", filename, GetErrorStr()); + return; + } + + fprintf(fp, "%ju\n", (uintmax_t)getpid()); + + fclose(fp); +} + +pid_t ReadPID(char *filename) +{ + char pidfile[PATH_MAX]; + snprintf(pidfile, PATH_MAX - 1, "%s%c%s", GetPidDir(), FILE_SEPARATOR, filename); + + if (access(pidfile, F_OK) != 0) + { + Log(LOG_LEVEL_VERBOSE, "PID file '%s' doesn't exist", pidfile); + return -1; + } + + FILE *fp = safe_fopen(pidfile, "r"); + if (fp == NULL) + { + Log(LOG_LEVEL_ERR, "Could not read PID file '%s' (fopen: %s)", filename, GetErrorStr()); + return -1; + } + + intmax_t pid; + if (fscanf(fp, "%jd", &pid) != 1) + { + Log(LOG_LEVEL_ERR, "Could not read PID from '%s'", pidfile); + fclose(fp); + return -1; + } + fclose(fp); + return ((pid_t) pid); +} + +bool GenericAgentConfigParseArguments(GenericAgentConfig *config, int argc, char **argv) +{ + if (argc == 0) + { + return true; + } + + if (argc > 1) + { + return false; + } + + GenericAgentConfigSetInputFile(config, NULL, argv[0]); + MINUSF = true; + return true; +} + +bool GenericAgentConfigParseWarningOptions(GenericAgentConfig *config, const char *warning_options) +{ + if (strlen(warning_options) == 0) + { + return false; + } + + if (strcmp("error", warning_options) == 0) + { + config->agent_specific.common.parser_warnings_error |= PARSER_WARNING_ALL; + return true; + } + + const char *options_start = warning_options; + bool warnings_as_errors = false; + + if (StringStartsWith(warning_options, "error=")) + { + options_start = warning_options + strlen("error="); + warnings_as_errors = true; + } + + StringSet *warnings_set = StringSetFromString(options_start, ','); + StringSetIterator it = StringSetIteratorInit(warnings_set); + const char *warning_str = NULL; + while ((warning_str = StringSetIteratorNext(&it))) + { + int warning = ParserWarningFromString(warning_str); + if (warning == -1) + { + Log(LOG_LEVEL_ERR, "Unrecognized warning '%s'", warning_str); + StringSetDestroy(warnings_set); + return false; + } + + if (warnings_as_errors) + { + config->agent_specific.common.parser_warnings_error |= warning; + } + else + { + config->agent_specific.common.parser_warnings |= warning; + } + } + + StringSetDestroy(warnings_set); + return true; +} + +bool GenericAgentConfigParseColor(GenericAgentConfig *config, const char *mode) +{ + if (!mode || strcmp("auto", mode) == 0) + { + config->color = config->tty_interactive; + return true; + } + else if (strcmp("always", mode) == 0) + { + config->color = true; + return true; + } + else if (strcmp("never", mode) == 0) + { + config->color = false; + return true; + } + else + { + Log(LOG_LEVEL_ERR, "Unrecognized color mode '%s'", mode); + return false; + } +} + +bool GetTTYInteractive(void) +{ + return isatty(0) || isatty(1) || isatty(2); +} + +GenericAgentConfig *GenericAgentConfigNewDefault(AgentType agent_type, bool tty_interactive) +{ + GenericAgentConfig *config = xmalloc(sizeof(GenericAgentConfig)); + + LoggingSetAgentType(CF_AGENTTYPES[agent_type]); + config->agent_type = agent_type; + config->tty_interactive = tty_interactive; + + const char *color_env = getenv("CFENGINE_COLOR"); + config->color = (color_env && strcmp(color_env, "1") == 0); + + config->bundlesequence = NULL; + + config->original_input_file = NULL; + config->input_file = NULL; + config->input_dir = NULL; + config->tag_release_dir = NULL; + + config->check_not_writable_by_others = agent_type != AGENT_TYPE_COMMON; + config->check_runnable = agent_type != AGENT_TYPE_COMMON; + config->ignore_missing_bundles = false; + config->ignore_missing_inputs = false; + config->ignore_preferred_augments = false; + + config->heap_soft = NULL; + config->heap_negated = NULL; + config->ignore_locks = false; + + config->protocol_version = CF_PROTOCOL_UNDEFINED; + + config->agent_specific.agent.bootstrap_argument = NULL; + config->agent_specific.agent.bootstrap_ip = NULL; + config->agent_specific.agent.bootstrap_port = NULL; + config->agent_specific.agent.bootstrap_host = NULL; + + /* By default we trust the network when bootstrapping. */ + config->agent_specific.agent.bootstrap_trust_server = true; + + /* By default we run promises.cf as the last step of boostrapping */ + config->agent_specific.agent.bootstrap_trigger_policy = true; + + /* Log classes */ + config->agent_specific.agent.report_class_log = false; + + config->agent_specific.common.no_augments = false; + config->agent_specific.common.no_host_specific = false; + + switch (agent_type) + { + case AGENT_TYPE_COMMON: + config->agent_specific.common.eval_functions = true; + config->agent_specific.common.show_classes = NULL; + config->agent_specific.common.show_variables = NULL; + config->agent_specific.common.policy_output_format = GENERIC_AGENT_CONFIG_COMMON_POLICY_OUTPUT_FORMAT_NONE; + /* Bitfields of warnings to be recorded, or treated as errors. */ + config->agent_specific.common.parser_warnings = PARSER_WARNING_ALL; + config->agent_specific.common.parser_warnings_error = 0; + break; + + case AGENT_TYPE_AGENT: + config->agent_specific.agent.show_evaluated_classes = NULL; + config->agent_specific.agent.show_evaluated_variables = NULL; + break; + + default: + break; + } + + return config; +} + +void GenericAgentConfigDestroy(GenericAgentConfig *config) +{ + if (config != NULL) + { + RlistDestroy(config->bundlesequence); + StringSetDestroy(config->heap_soft); + StringSetDestroy(config->heap_negated); + free(config->original_input_file); + free(config->input_file); + free(config->input_dir); + free(config->tag_release_dir); + free(config->agent_specific.agent.bootstrap_argument); + free(config->agent_specific.agent.bootstrap_host); + free(config->agent_specific.agent.bootstrap_ip); + free(config->agent_specific.agent.bootstrap_port); + free(config); + } +} + +void GenericAgentConfigApply(EvalContext *ctx, const GenericAgentConfig *config) +{ + assert(config != NULL); + + EvalContextSetConfig(ctx, config); + + if (config->heap_soft) + { + StringSetIterator it = StringSetIteratorInit(config->heap_soft); + const char *context = NULL; + while ((context = StringSetIteratorNext(&it))) + { + Class *cls = EvalContextClassGet(ctx, NULL, context); + if (cls && !cls->is_soft) + { + FatalError(ctx, "You cannot use -D to define a reserved class"); + } + + EvalContextClassPutSoft(ctx, context, CONTEXT_SCOPE_NAMESPACE, "source=environment"); + } + } + + if (config->heap_negated != NULL) + { + /* Takes ownership of heap_negated. */ + EvalContextSetNegatedClasses(ctx, config->heap_negated); + ((GenericAgentConfig *)config)->heap_negated = NULL; + } + + switch (LogGetGlobalLevel()) + { + case LOG_LEVEL_DEBUG: + EvalContextClassPutHard(ctx, "debug_mode", "cfe_internal,source=agent"); + EvalContextClassPutHard(ctx, "opt_debug", "cfe_internal,source=agent"); + // fall through + case LOG_LEVEL_VERBOSE: + EvalContextClassPutHard(ctx, "verbose_mode", "cfe_internal,source=agent"); + // fall through + case LOG_LEVEL_INFO: + EvalContextClassPutHard(ctx, "inform_mode", "cfe_internal,source=agent"); + break; + default: + break; + } + + if (config->color) + { + LoggingSetColor(config->color); + } + + if (config->agent_type == AGENT_TYPE_COMMON) + { + EvalContextSetEvalOption(ctx, EVAL_OPTION_FULL, false); + if (config->agent_specific.common.eval_functions) + { + EvalContextSetEvalOption(ctx, EVAL_OPTION_EVAL_FUNCTIONS, true); + } + } + + EvalContextSetIgnoreLocks(ctx, config->ignore_locks); + + if (DONTDO) + { + EvalContextClassPutHard(ctx, "opt_dry_run", "cfe_internal,source=environment"); + } +} + +bool CheckAndGenerateFailsafe(const char *inputdir, const char *input_file) +{ + char failsafe_path[CF_BUFSIZE]; + + if (strlen(inputdir) + strlen(input_file) > sizeof(failsafe_path) - 2) + { + Log(LOG_LEVEL_ERR, + "Unable to generate path for %s/%s file. Path too long.", + inputdir, input_file); + /* We could create dynamically allocated buffer able to hold the + whole content of the path but this should be unlikely that we + will end up here. */ + return false; + } + snprintf(failsafe_path, CF_BUFSIZE - 1, "%s/%s", inputdir, input_file); + MapName(failsafe_path); + + if (access(failsafe_path, R_OK) != 0) + { + return WriteBuiltinFailsafePolicyToPath(failsafe_path); + } + return true; +} + +void GenericAgentConfigSetInputFile(GenericAgentConfig *config, const char *inputdir, const char *input_file) +{ + free(config->original_input_file); + free(config->input_file); + free(config->input_dir); + + config->original_input_file = xstrdup(input_file); + + if (inputdir && FilePathGetType(input_file) == FILE_PATH_TYPE_NON_ANCHORED) + { + config->input_file = StringFormat("%s%c%s", inputdir, FILE_SEPARATOR, input_file); + } + else + { + config->input_file = xstrdup(input_file); + } + + config->input_dir = xstrdup(config->input_file); + if (!ChopLastNode(config->input_dir)) + { + free(config->input_dir); + config->input_dir = xstrdup("."); + } +} + +void GenericAgentConfigSetBundleSequence(GenericAgentConfig *config, const Rlist *bundlesequence) +{ + RlistDestroy(config->bundlesequence); + config->bundlesequence = RlistCopy(bundlesequence); +} + +bool GenericAgentPostLoadInit(const EvalContext *ctx) +{ + const char *tls_ciphers = + EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_TLS_CIPHERS); + const char *tls_min_version = + EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_TLS_MIN_VERSION); + + const char *system_log_level_str = + EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_SYSTEM_LOG_LEVEL); + + LogLevel system_log_level = LogLevelFromString(system_log_level_str); + if (system_log_level != LOG_LEVEL_NOTHING) + { + LogSetGlobalSystemLogLevel(system_log_level); + } + + return cfnet_init(tls_min_version, tls_ciphers); +} + +void SetupSignalsForAgent(void) +{ + signal(SIGINT, HandleSignalsForAgent); + signal(SIGTERM, HandleSignalsForAgent); + signal(SIGBUS, HandleSignalsForAgent); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGUSR1, HandleSignalsForAgent); + signal(SIGUSR2, HandleSignalsForAgent); +} + +void GenericAgentShowContextsFormatted(EvalContext *ctx, const char *regexp) +{ + assert(regexp != NULL); + + ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true); + + Seq *seq = SeqNew(1000, free); + + Regex *rx = CompileRegex(regexp); + + if (rx == NULL) + { + Log(LOG_LEVEL_ERR, "Sorry, we could not compile regular expression %s", regexp); + return; + } + + Class *cls = NULL; + while ((cls = ClassTableIteratorNext(iter)) != NULL) + { + char *class_name = ClassRefToString(cls->ns, cls->name); + + if (!RegexPartialMatch(rx, class_name)) + { + free(class_name); + continue; + } + + StringSet *tagset = cls->tags; + Buffer *tagbuf = StringSetToBuffer(tagset, ','); + + char *line; + xasprintf(&line, "%-60s %-40s %-40s", class_name, BufferData(tagbuf), NULL_TO_EMPTY_STRING(cls->comment)); + SeqAppend(seq, line); + + BufferDestroy(tagbuf); + free(class_name); + } + + RegexDestroy(rx); + + SeqSort(seq, StrCmpWrapper, NULL); + + printf("%-60s %-40s %-40s\n", "Class name", "Meta tags", "Comment"); + + for (size_t i = 0; i < SeqLength(seq); i++) + { + const char *context = SeqAt(seq, i); + printf("%s\n", context); + } + + SeqDestroy(seq); + + ClassTableIteratorDestroy(iter); +} + +void GenericAgentShowVariablesFormatted(EvalContext *ctx, const char *regexp) +{ + assert(regexp != NULL); + + VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, NULL, NULL); + Variable *v = NULL; + + Seq *seq = SeqNew(2000, free); + + Regex *rx = CompileRegex(regexp); + + if (rx == NULL) + { + Log(LOG_LEVEL_ERR, "Sorry, we could not compile regular expression %s", regexp); + return; + } + + while ((v = VariableTableIteratorNext(iter))) + { + char *varname = VarRefToString(VariableGetRef(v), true); + + if (!RegexPartialMatch(rx, varname)) + { + free(varname); + continue; + } + + Writer *w = StringWriter(); + + Rval var_rval = VariableGetRval(v, false); + if (var_rval.type == RVAL_TYPE_CONTAINER) + { + JsonWriteCompact(w, RvalContainerValue(var_rval)); + } + else + { + RvalWrite(w, var_rval); + } + + const char *var_value; + if (StringIsPrintable(StringWriterData(w))) + { + var_value = StringWriterData(w); + } + else + { + var_value = ""; + } + + + Buffer *tagbuf = NULL; + StringSet *tagset = VariableGetTags(v); + if (tagset != NULL) + { + tagbuf = StringSetToBuffer(tagset, ','); + } + const char *comment = VariableGetComment(v); + + char *line; + xasprintf(&line, "%-40s %-60s %-40s %-40s", + varname, var_value, + tagbuf != NULL ? BufferData(tagbuf) : "", + NULL_TO_EMPTY_STRING(comment)); + + SeqAppend(seq, line); + + BufferDestroy(tagbuf); + WriterClose(w); + free(varname); + } + + RegexDestroy(rx); + + SeqSort(seq, StrCmpWrapper, NULL); + + printf("%-40s %-60s %-40s %-40s\n", "Variable name", "Variable value", "Meta tags", "Comment"); + + for (size_t i = 0; i < SeqLength(seq); i++) + { + const char *variable = SeqAt(seq, i); + printf("%s\n", variable); + } + + SeqDestroy(seq); + VariableTableIteratorDestroy(iter); +} diff --git a/libpromises/generic_agent.h b/libpromises/generic_agent.h new file mode 100644 index 0000000000..24ac5da7b4 --- /dev/null +++ b/libpromises/generic_agent.h @@ -0,0 +1,158 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_GENERIC_AGENT_H +#define CFENGINE_GENERIC_AGENT_H + +#include + +#include +#include + +#define GENERIC_AGENT_CHECKSUM_SIZE ((2*CF_SHA1_LEN) + 1) +#define GENERIC_AGENT_CHECKSUM_METHOD HASH_METHOD_SHA1 + +enum generic_agent_config_common_policy_output_format +{ + GENERIC_AGENT_CONFIG_COMMON_POLICY_OUTPUT_FORMAT_NONE, + GENERIC_AGENT_CONFIG_COMMON_POLICY_OUTPUT_FORMAT_CF, + GENERIC_AGENT_CONFIG_COMMON_POLICY_OUTPUT_FORMAT_JSON, + GENERIC_AGENT_CONFIG_COMMON_POLICY_OUTPUT_FORMAT_CF_FULL, + GENERIC_AGENT_CONFIG_COMMON_POLICY_OUTPUT_FORMAT_JSON_FULL +}; + +typedef struct +{ + AgentType agent_type; + + Rlist *bundlesequence; + + char *original_input_file; + char *input_file; + char *input_dir; + char *tag_release_dir; + + bool check_not_writable_by_others; + bool check_runnable; + + StringSet *heap_soft; + StringSet *heap_negated; + bool ignore_locks; + + bool tty_interactive; // agent is running interactively, via tty/terminal interface + bool color; + + ProtocolVersion protocol_version; + + // agent state + bool ignore_missing_bundles; + bool ignore_missing_inputs; + bool ignore_preferred_augments; // --ignore-preferred-augments + + struct + { + struct + { + enum generic_agent_config_common_policy_output_format policy_output_format; + unsigned int parser_warnings; + unsigned int parser_warnings_error; + bool eval_functions; + char *show_classes; + char *show_variables; + bool no_augments; + bool no_host_specific; + } common; + struct + { + char *bootstrap_argument; + char *bootstrap_host; + char *bootstrap_port; + char *bootstrap_ip; + bool bootstrap_trust_server; + bool bootstrap_trigger_policy; + char *show_evaluated_classes; + char *show_evaluated_variables; + + // BODY AGENT CONTROL + bool report_class_log; + } agent; + struct + { + /* Time of the last validated_at timestamp seen. */ + time_t last_validated_at; + } daemon; /* execd, serverd etc */ + } agent_specific; + +} GenericAgentConfig; + +ENTERPRISE_VOID_FUNC_2ARG_DECLARE(void, GenericAgentSetDefaultDigest, HashMethod *, digest, int *, digest_len); +const char *GenericAgentResolveInputPath(const GenericAgentConfig *config, const char *input_file); +void MarkAsPolicyServer(EvalContext *ctx); +void GenericAgentDiscoverContext(EvalContext *ctx, GenericAgentConfig *config, const char *program_name); +bool GenericAgentCheckPolicy(GenericAgentConfig *config, bool force_validation, bool write_validated_file); + +ENTERPRISE_VOID_FUNC_1ARG_DECLARE(void, GenericAgentAddEditionClasses, EvalContext *, ctx); +void GenericAgentInitialize(EvalContext *ctx, GenericAgentConfig *config); +void GenericAgentFinalize(EvalContext *ctx, GenericAgentConfig *config); +ENTERPRISE_VOID_FUNC_1ARG_DECLARE(void, GenericAgentWriteVersion, Writer *, w); +bool GenericAgentArePromisesValid(const GenericAgentConfig *config); +time_t ReadTimestampFromPolicyValidatedFile(const GenericAgentConfig *config, const char *maybe_dirname); + +bool GenericAgentIsPolicyReloadNeeded(const GenericAgentConfig *config); + +void CloseLog(void); +Seq *ControlBodyConstraints(const Policy *policy, AgentType agent); + +void SetFacility(const char *retval); +void CheckBundleParameters(char *scope, Rlist *args); +void WritePID(char *filename); +pid_t ReadPID(char *filename); + +bool GenericAgentConfigParseArguments(GenericAgentConfig *config, int argc, char **argv); +bool GenericAgentConfigParseWarningOptions(GenericAgentConfig *config, const char *warning_options); +bool GenericAgentConfigParseColor(GenericAgentConfig *config, const char *mode); + +Policy *SelectAndLoadPolicy(GenericAgentConfig *config, EvalContext *ctx, bool validate_policy, bool write_validated_file); +GenericAgentConfig *GenericAgentConfigNewDefault(AgentType agent_type, bool tty_interactive); +bool GetTTYInteractive(void); +void GenericAgentConfigDestroy(GenericAgentConfig *config); +void GenericAgentConfigApply(EvalContext *ctx, const GenericAgentConfig *config); + +bool CheckAndGenerateFailsafe(const char *inputdir, const char *input_file); +void GenericAgentConfigSetInputFile(GenericAgentConfig *config, const char *inputdir, const char *input_file); +void GenericAgentConfigSetBundleSequence(GenericAgentConfig *config, const Rlist *bundlesequence); +bool GenericAgentTagReleaseDirectory(const GenericAgentConfig *config, const char *dirname, bool write_validated, bool write_release); + +void GetReleaseIdFile(const char *base_path, char *filename, size_t max_size); + +bool GenericAgentPostLoadInit(const EvalContext *ctx); + +void SetupSignalsForAgent(void); + +void LoadAugments(EvalContext *ctx, GenericAgentConfig *config); + +void GenericAgentShowContextsFormatted(EvalContext *ctx, const char *regexp); +void GenericAgentShowVariablesFormatted(EvalContext *ctx, const char *regexp); + +#endif diff --git a/libpromises/global_mutex.c b/libpromises/global_mutex.c new file mode 100644 index 0000000000..05f700142c --- /dev/null +++ b/libpromises/global_mutex.c @@ -0,0 +1,42 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +static pthread_mutex_t MUTEXES[] = /* GLOBAL_T */ +{ + PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP, + PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP, + PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP, + PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP, + PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP, + PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP, +}; + +pthread_mutex_t *cft_lock = &MUTEXES[0]; /* GLOBAL_T */ +pthread_mutex_t *cft_count = &MUTEXES[1]; /* GLOBAL_T */ +pthread_mutex_t *cft_getaddr = &MUTEXES[2]; /* GLOBAL_T */ +pthread_mutex_t *cft_server_children = &MUTEXES[3]; /* GLOBAL_T */ +pthread_mutex_t *cft_server_filter = &MUTEXES[4]; /* GLOBAL_T */ +pthread_mutex_t *cft_db_corruption_lock = &MUTEXES[5]; /* GLOBAL_T */ diff --git a/libpromises/global_mutex.h b/libpromises/global_mutex.h new file mode 100644 index 0000000000..83f1f08bad --- /dev/null +++ b/libpromises/global_mutex.h @@ -0,0 +1,37 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_GLOBAL_MUTEX_H +#define CFENGINE_GLOBAL_MUTEX_H + +#include + +extern pthread_mutex_t *cft_lock; +extern pthread_mutex_t *cft_count; +extern pthread_mutex_t *cft_getaddr; +extern pthread_mutex_t *cft_server_children; +extern pthread_mutex_t *cft_server_filter; +extern pthread_mutex_t *cft_db_corruption_lock; + +#endif diff --git a/libpromises/granules.c b/libpromises/granules.c new file mode 100644 index 0000000000..06d25a5b62 --- /dev/null +++ b/libpromises/granules.c @@ -0,0 +1,80 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + + +char *GenTimeKey(time_t now) +{ + struct tm tm; + static char buf[18]; /* GLOBAL_R, no initialization needed */ + + gmtime_r(&now, &tm); + + xsnprintf(buf, sizeof(buf), "%3.3s:Hr%02d:Min%02d_%02d", + DAY_TEXT[tm.tm_wday ? (tm.tm_wday - 1) : 6], + tm.tm_hour, tm.tm_min / 5 * 5, ((tm.tm_min + 5) / 5 * 5) % 60); + + return buf; +} + +int GetTimeSlot(time_t here_and_now) +{ + return ((here_and_now - (4 * 24 * 60 * 60)) % SECONDS_PER_WEEK) / (long)CF_MEASURE_INTERVAL; +} + +int GetShiftSlot(time_t t) +{ + return UnsignedModulus((t - CF_MONDAY_MORNING), SECONDS_PER_WEEK) / CF_SHIFT_INTERVAL; +} + +time_t GetShiftSlotStart(time_t t) +{ + return (t - (t % SECONDS_PER_SHIFT)); +} + +time_t MeasurementSlotStart(time_t t) +{ + return (t - t % (time_t)CF_MEASURE_INTERVAL); +} + +time_t MeasurementSlotTime(size_t slot, size_t num_slots, time_t now) +{ + assert(slot <= num_slots); + + size_t start_slot = GetTimeSlot(now); + size_t distance = 0; + if (slot <= start_slot) + { + distance = start_slot - slot; + } + else + { + distance = start_slot + (num_slots - slot - 1); + } + + time_t start_time = MeasurementSlotStart(now); + return start_time - (distance * CF_MEASURE_INTERVAL); +} diff --git a/libpromises/granules.h b/libpromises/granules.h new file mode 100644 index 0000000000..8f8bd8f1ff --- /dev/null +++ b/libpromises/granules.h @@ -0,0 +1,39 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_GRANULES_H +#define CFENGINE_GRANULES_H + +#include + +char *GenTimeKey(time_t now); +const char *ShiftSlotToString(int shift_slot); +int GetTimeSlot(time_t here_and_now); +int GetShiftSlot(time_t here_and_now); + +time_t GetShiftSlotStart(time_t t); +time_t MeasurementSlotStart(time_t t); +time_t MeasurementSlotTime(size_t slot, size_t num_slots, time_t now); + +#endif diff --git a/libpromises/instrumentation.c b/libpromises/instrumentation.c new file mode 100644 index 0000000000..5fb8acc989 --- /dev/null +++ b/libpromises/instrumentation.c @@ -0,0 +1,198 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include + +#include + +static void NotePerformance(char *eventname, time_t t, double value); + +/* Alter this code at your peril. Berkeley DB is very sensitive to errors. */ + +bool TIMING = false; + +/***************************************************************/ + +struct timespec BeginMeasure() +{ + struct timespec start = { 0 }; + + if (clock_gettime(CLOCK_REALTIME, &start) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Clock gettime failure. (clock_gettime: %s)", GetErrorStr()); + } + else if (TIMING) + { + Log(LOG_LEVEL_VERBOSE, "T: Starting measuring time"); + } + + return start; +} + +/***************************************************************/ + +void EndMeasurePromise(struct timespec start, const Promise *pp) +{ + char id[CF_BUFSIZE], *mid = NULL; + + if (TIMING) + { + Log(LOG_LEVEL_VERBOSE, "\n"); + Log(LOG_LEVEL_VERBOSE, "T: ........................................................."); + Log(LOG_LEVEL_VERBOSE, "T: Promise timing summary for %s", pp->promiser); + } + + mid = PromiseGetConstraintAsRval(pp, "measurement_class", RVAL_TYPE_SCALAR); + + if (mid) + { + snprintf(id, CF_BUFSIZE, "%s:%s:%.100s", mid, PromiseGetPromiseType(pp), pp->promiser); + Chop(id, CF_EXPANDSIZE); + EndMeasure(id, start); + } + else + { + if (TIMING) + { + Log(LOG_LEVEL_VERBOSE, "T: No measurement_class attribute set in action body"); + } + EndMeasure(NULL, start); + } + + if (TIMING) + { + Log(LOG_LEVEL_VERBOSE, "T: ........................................................."); + } +} + +/***************************************************************/ + +void EndMeasure(char *eventname, struct timespec start) +{ + struct timespec stop; + + if (clock_gettime(CLOCK_REALTIME, &stop) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Clock gettime failure. (clock_gettime: %s)", GetErrorStr()); + } + else + { + double dt = (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec) / (double) CF_BILLION; + + if (eventname) + { + NotePerformance(eventname, start.tv_sec, dt); + } + else if (TIMING) + { + Log(LOG_LEVEL_VERBOSE, "T: This execution measured %lf seconds (use measurement_class to track)", dt); + } + } +} + +/***************************************************************/ + +int EndMeasureValueMs(struct timespec start) +{ + struct timespec stop; + + if (clock_gettime(CLOCK_REALTIME, &stop) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Clock gettime failure. (clock_gettime: %s)", GetErrorStr()); + } + else + { + double dt = ((stop.tv_sec - start.tv_sec) * 1e3 + /* 1 s = 1e3 ms */ + (stop.tv_nsec - start.tv_nsec) / 1e6); /* 1e6 ns = 1 ms */ + return (int)dt; + } + + return -1; +} + +/***************************************************************/ + +static void NotePerformance(char *eventname, time_t t, double value) +{ + CF_DB *dbp; + Event e, newe; + double lastseen; + int lsea = SECONDS_PER_WEEK; + time_t now = time(NULL); + + if (!OpenDB(&dbp, dbid_performance)) + { + return; + } + + if (ReadDB(dbp, eventname, &e, sizeof(e))) + { + lastseen = now - e.t; + newe.t = t; + + newe.Q = QAverage(e.Q, value, 0.3); + + /* Have to kickstart variance computation, assume 1% to start */ + + if (newe.Q.var <= 0.0009) + { + newe.Q.var = newe.Q.expect / 100.0; + } + } + else + { + lastseen = 0.0; + newe.t = t; + newe.Q.q = value; + newe.Q.dq = 0; + newe.Q.expect = value; + newe.Q.var = 0.001; + } + + if (lastseen > (double) lsea) + { + Log(LOG_LEVEL_DEBUG, "Performance record '%s' expired", eventname); + DeleteDB(dbp, eventname); + } + else + { + WriteDB(dbp, eventname, &newe, sizeof(newe)); + + if (TIMING) + { + Log(LOG_LEVEL_VERBOSE, "T: This measurement event, alias '%s', measured at time %s\n", eventname, ctime(&newe.t)); + Log(LOG_LEVEL_VERBOSE, "T: Last measured %lf seconds ago\n", lastseen); + Log(LOG_LEVEL_VERBOSE, "T: This execution measured %lf seconds\n", newe.Q.q); + Log(LOG_LEVEL_VERBOSE, "T: Average execution time %lf +/- %lf seconds\n", newe.Q.expect, sqrt(newe.Q.var)); + } + } + + CloseDB(dbp); +} diff --git a/libpromises/instrumentation.h b/libpromises/instrumentation.h new file mode 100644 index 0000000000..b0114d7067 --- /dev/null +++ b/libpromises/instrumentation.h @@ -0,0 +1,38 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_INSTRUMENTATION_H +#define CFENGINE_INSTRUMENTATION_H + +#include + +#include +#include + +struct timespec BeginMeasure(void); +void EndMeasure(char *eventname, struct timespec start); +int EndMeasureValueMs(struct timespec start); +void EndMeasurePromise(struct timespec start, const Promise *pp); +extern bool TIMING; +#endif diff --git a/libpromises/item_lib.c b/libpromises/item_lib.c new file mode 100644 index 0000000000..fd697f328f --- /dev/null +++ b/libpromises/item_lib.c @@ -0,0 +1,1102 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include /* StringMatchFull,CompileRegex,StringMatchWithPrecompiledRegex */ +#include +#include + +#ifdef CYCLE_DETECTION +/* While looping over entry lp in an Item list, advance slow half as + * fast as lp; let n be the number of steps it has fallen behind; this + * increases by one every second time round the loop. If there's a + * cycle of length M, lp shall run round and round it; once slow gets + * into the loop, they shall be n % M steps apart; at most 2*M more + * times round the loop and n % M shall be 0 so lp == slow. If the + * lead-in to the loop is of length L, this takes at most 2*(L+M) + * turns round the loop to discover the cycle. The added cost is O(1) + * per time round the loop, so the typical O(list length) user doesn't + * change order when this is enabled, albeit the constant of + * proportionality is up. + * + * Note, however, that none of this works if you're messing with the + * structure (e.g. reversing or deleting) of the list as you go. + * + * To use the macros: before the loop, declare and initialize your + * loop variable; pass it as lp to CYCLE_DECLARE(), followed by two + * names not in use in your code. Then, in the body of the loop, + * after advancing the loop variable, CYCLE_CHECK() the same three + * parameters. This is apt to require a while loop where you might + * otherwise have used a for loop; you also need to make sure your + * loop doesn't continue past the checking. When you compile with + * CYCLE_DETECTION defined, your function shall catch cycles, raising + * a ProgrammingError() if it sees one. + */ +#define CYCLE_DECLARE(lp, slow, toggle) \ + const Item *slow = lp; bool toggle = false +#define CYCLE_VERIFY(lp, slow) if (!lp) { /* skip */ } \ + else if (!slow) ProgrammingError("Loop-detector bug :-("); \ + else if (lp == slow) ProgrammingError("Found loop in Item list") +#define CYCLE_CHECK(lp, slow, toggle) \ + CYCLE_VERIFY(lp, slow); \ + if (toggle) { slow = slow->next; CYCLE_VERIFY(lp, slow); } \ + toggle = !toggle +#else +#define CYCLE_DECLARE(lp, slow, toggle) /* skip */ +#define CYCLE_CHECK(lp, slow, toggle) /* skip */ +#endif + +#ifndef NDEBUG +/* Only intended for use in assertions. Note that its cost is O(list + * length), so you don't want to call it inside a loop over the + * list. */ +static bool ItemIsInList(const Item *list, const Item *item) +{ + CYCLE_DECLARE(list, slow, toggle); + while (list) + { + if (list == item) + { + return true; + } + list = list->next; + CYCLE_CHECK(list, slow, toggle); + } + return false; +} +#endif /* NDEBUG */ + +/*******************************************************************/ + +Item *ReverseItemList(Item *list) +{ + /* TODO: cycle-detection, which is somewhat harder here, without + * turning this into a quadratic-cost function, albeit only when + * assert() is enabled. + */ + Item *tail = NULL; + while (list) + { + Item *here = list; + list = here->next; + /* assert(!ItemIsInList(here, list)); // quadratic cost */ + here->next = tail; + tail = here; + } + return tail; +} + +/*******************************************************************/ + +void PrintItemList(const Item *list, Writer *w) +{ + WriterWriteChar(w, '{'); + const Item *ip = list; + CYCLE_DECLARE(ip, slow, toggle); + + while (ip != NULL) + { + if (ip != list) + { + WriterWriteChar(w, ','); + } + + WriterWriteChar(w, '\''); + WriterWrite(w, ip->name); + WriterWriteChar(w, '\''); + + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + + WriterWriteChar(w, '}'); +} + +/*********************************************************************/ + +size_t ItemListSize(const Item *list) +{ + size_t size = 0; + const Item *ip = list; + CYCLE_DECLARE(ip, slow, toggle); + + while (ip != NULL) + { + if (ip->name) + { + size += strlen(ip->name); + } + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + + return size; +} + +/*********************************************************************/ + +Item *ReturnItemIn(Item *list, const char *item) +{ + if (item == NULL || item[0] == '\0') + { + return NULL; + } + + Item *ptr = list; + CYCLE_DECLARE(ptr, slow, toggle); + while (ptr != NULL) + { + if (strcmp(ptr->name, item) == 0) + { + return ptr; + } + ptr = ptr->next; + CYCLE_CHECK(ptr, slow, toggle); + } + + return NULL; +} + +/*********************************************************************/ + +Item *ReturnItemInClass(Item *list, const char *item, const char *classes) +{ + if (item == NULL || item[0] == '\0') + { + return NULL; + } + + Item *ptr = list; + CYCLE_DECLARE(ptr, slow, toggle); + while (ptr != NULL) + { + if (strcmp(ptr->name, item) == 0 && + strcmp(ptr->classes, classes) == 0) + { + return ptr; + } + ptr = ptr->next; + CYCLE_CHECK(ptr, slow, toggle); + } + + return NULL; +} + +/*********************************************************************/ + +Item *ReturnItemAtIndex(Item *list, int index) +{ + Item *ptr = list; + for (int i = 0; ptr != NULL && i < index; i++) + { + ptr = ptr->next; + } + + return ptr; +} + +/*********************************************************************/ + +bool IsItemIn(const Item *list, const char *item) +{ + if (item == NULL || item[0] == '\0') + { + return true; + } + + const Item *ptr = list; + CYCLE_DECLARE(ptr, slow, toggle); + while (ptr != NULL) + { + if (strcmp(ptr->name, item) == 0) + { + return true; + } + ptr = ptr->next; + CYCLE_CHECK(ptr, slow, toggle); + } + + return false; +} + +/*********************************************************************/ +/* True precisely if the lists are of equal length and every entry of + * the first appears in the second. As long as each list is known to + * have no duplication of its entries, this is equivalent to testing + * they have the same set of entries (ignoring order). + * + * This is not, in general, the same as the lists being equal ! They + * may have the same entries in different orders. If the first list + * has some duplicate entries, the second list can have some entries + * not in the first, yet compare equal. Two lists with the same set + * of entries but with different multiplicities are equal or different + * precisely if of equal length. + */ + +bool ListsCompare(const Item *list1, const Item *list2) +{ + if (ListLen(list1) != ListLen(list2)) + { + return false; + } + + const Item *ptr = list1; + CYCLE_DECLARE(ptr, slow, toggle); + while (ptr != NULL) + { + if (IsItemIn(list2, ptr->name) == false) + { + return false; + } + ptr = ptr->next; + CYCLE_CHECK(ptr, slow, toggle); + } + + return true; +} + +/** + * Checks whether list1 is a subset of list2, i.e. every entry in list1 must + * be found in list2. + */ +bool ListSubsetOfList(const Item *list1, const Item *list2) +{ + const Item *list1_ptr = list1; + CYCLE_DECLARE(list1_ptr, slow, toggle); + + while (list1_ptr != NULL) + { + if (!IsItemIn(list2, list1_ptr->name)) + { + return false; + } + + list1_ptr = list1_ptr->next; + CYCLE_CHECK(list1_ptr, slow, toggle); + } + + return true; /* all elements of list1 were found in list2 */ +} + +/*********************************************************************/ + +Item *EndOfList(Item *ip) +{ + Item *prev = CF_UNDEFINED_ITEM; + + CYCLE_DECLARE(ip, slow, toggle); + while (ip != NULL) + { + prev = ip; + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + + return prev; +} + +/*********************************************************************/ + +Item *IdempPrependItem(Item **liststart, const char *itemstring, const char *classes) +{ + Item *ip = ReturnItemIn(*liststart, itemstring); + if (ip) + { + return ip; + } + + PrependItem(liststart, itemstring, classes); + return *liststart; +} + +/*********************************************************************/ + +Item *IdempPrependItemClass(Item **liststart, const char *itemstring, const char *classes) +{ + Item *ip = ReturnItemInClass(*liststart, itemstring, classes); + if (ip) // already exists + { + return ip; + } + + PrependItem(liststart, itemstring, classes); + return *liststart; +} + +/*********************************************************************/ + +void IdempItemCount(Item **liststart, const char *itemstring, const char *classes) +{ + Item *ip = ReturnItemIn(*liststart, itemstring); + + if (ip) + { + ip->counter++; + } + else + { + PrependItem(liststart, itemstring, classes); + } + +// counter+1 is the histogram of occurrences +} + +/*********************************************************************/ + +Item *PrependItem(Item **liststart, const char *itemstring, const char *classes) +{ + Item *ip = xcalloc(1, sizeof(Item)); + + ip->name = xstrdup(itemstring); + if (classes != NULL) + { + ip->classes = xstrdup(classes); + } + + ip->next = *liststart; + *liststart = ip; + + return ip; +} + +/*********************************************************************/ + +void PrependFullItem(Item **liststart, const char *itemstring, const char *classes, int counter, time_t t) +{ + Item *ip = xcalloc(1, sizeof(Item)); + + ip->name = xstrdup(itemstring); + ip->next = *liststart; + ip->counter = counter; + ip->time = t; + if (classes != NULL) + { + ip->classes = xstrdup(classes); + } + + *liststart = ip; +} + +/*********************************************************************/ +/* Warning: doing this a lot incurs quadratic costs, as we have to run + * to the end of the list each time. If you're building long lists, + * it is usually better to build the list with PrependItemList() and + * then use ReverseItemList() to get the entries in the order you + * wanted; for modest-sized n, 2*n < n*n, even after you've applied + * different fixed scalings to the two sides. + */ + +void AppendItem(Item **liststart, const char *itemstring, const char *classes) +{ + Item *ip = xcalloc(1, sizeof(Item)); + + ip->name = xstrdup(itemstring); + if (classes) + { + ip->classes = xstrdup(classes); /* unused now */ + } + + if (*liststart == NULL) + { + *liststart = ip; + } + else + { + Item *lp = EndOfList(*liststart); + assert(lp != CF_UNDEFINED_ITEM); + lp->next = ip; + } +} + +/*********************************************************************/ + +void PrependItemList(Item **liststart, const char *itemstring) +{ + Item *ip = xcalloc(1, sizeof(Item)); + ip->name = xstrdup(itemstring); + + ip->next = *liststart; + *liststart = ip; +} + +/*********************************************************************/ + +size_t ListLen(const Item *list) +{ + int count = 0; + const Item *ip = list; + CYCLE_DECLARE(ip, slow, toggle); + + while (ip != NULL) + { + count++; + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + + return count; +} + +/***************************************************************************/ + +void CopyList(Item **dest, const Item *source) +/* Copy a list. */ +{ + if (*dest != NULL) + { + ProgrammingError("CopyList - list not initialized"); + } + + if (source == NULL) + { + return; + } + + const Item *ip = source; + CYCLE_DECLARE(ip, slow, toggle); + Item *backwards = NULL; + while (ip != NULL) + { + PrependFullItem(&backwards, ip->name, + ip->classes, ip->counter, ip->time); + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + *dest = ReverseItemList(backwards); +} + +/*********************************************************************/ + +Item *ConcatLists(Item *list1, Item *list2) +/* Notes: * Refrain from freeing list2 after using ConcatLists + * list1 must have at least one element in it */ +{ + if (list1 == NULL) + { + ProgrammingError("ConcatLists: first argument must have at least one element"); + } + Item *tail = EndOfList(list1); + assert(tail != CF_UNDEFINED_ITEM); + assert(tail->next == NULL); + /* If any entry in list1 is in list2, so is tail; so this is a + * sufficient check that we're not creating a loop: */ + assert(!ItemIsInList(list2, tail)); + tail->next = list2; + return list1; +} + +void InsertAfter(Item **filestart, Item *ptr, const char *string) +{ + if (*filestart == NULL || ptr == CF_UNDEFINED_ITEM) + { + AppendItem(filestart, string, NULL); + return; + } + + if (ptr == NULL) + { + AppendItem(filestart, string, NULL); + return; + } + + Item *ip = xcalloc(1, sizeof(Item)); + + ip->next = ptr->next; + ptr->next = ip; + ip->name = xstrdup(string); + ip->classes = NULL; +} + +/** + * Splits a string containing a separator like ':' into a linked list of + * separate items, + * + * @NOTE backslashes can be used to escape either the separator, or the + * backslash itself, e.g. "\:" or "\\" (ofcourse proper C strings need + * double backslash). + * + * @NOTE separator can't be the backslash. + */ +Item *SplitString(const char *string, char sep) +{ + Item *liststart = NULL; + + size_t string_len = strlen(string); + + /* Temporary buffer for each chunk we append. This has the maximum + * possible size we might possibly need. */ + char *buf = xmalloc(string_len + 1); + size_t buf_len = 0; + + /* We scan the string for separator or backslash. */ + char sep2[3] = { sep, '\\', '\0' }; + size_t z = 0; + + while ((z = strcspn(string, sep2)) < string_len) + { + memcpy(&buf[buf_len], string, z); + buf_len += z; + + if (string[z] == '\\') + { + /* Check next character after backslash, if it's backslash or + * separator then skip backslash, append character to buffer. */ + if (string[z+1] == '\\' || string[z+1] == sep) + { + z++; + } + + /* Append the single character to our buffer. */ + buf[buf_len] = string[z]; + buf_len++; + + /* SKIP, find next backslash or separator. */ + string += z + 1; + string_len -= z + 1; + continue; + } + else /* separator was found, and it's not escaped */ + { + assert(string[z] == sep); + assert((z == 0) || + (z > 0 && string[z-1] != '\\')); + } + + /* Terminate buffer and add to Item list. */ + buf[buf_len] = '\0'; + PrependItem(&liststart, buf, NULL); + + /* Empty the buffer. */ + buf_len = 0; + /* Start new search from after separator. */ + string += z + 1; + string_len -= z + 1; + } + + memcpy(&buf[buf_len], string, z); + buf_len += z; + buf[buf_len] = '\0'; + PrependItem(&liststart, buf, NULL); + + free(buf); + return ReverseItemList(liststart); +} + +/*********************************************************************/ + +Item *SplitStringAsItemList(const char *string, char sep) + /* Splits a string containing a separator like : + into a linked list of separate items, */ +{ + Item *liststart = NULL; + char node[256]; + char format[] = "%255[^\0]"; + + /* Overwrite format's internal \0 with sep: */ + format[strlen(format)] = sep; + assert(strlen(format) + 1 == sizeof(format) || sep == '\0'); + + for (const char *sp = string; *sp != '\0'; sp++) + { + if (sscanf(sp, format, node) == 1 && + node[0] != '\0') + { + sp += strlen(node) - 1; + PrependItem(&liststart, node, NULL); + } + } + + return ReverseItemList(liststart); +} + +/*********************************************************************/ + +/* NB: does not escape entries in list ! */ +char *ItemList2CSV(const Item *list) +{ + /* After each entry, we need space for either a ',' (before the + * next entry) or a final '\0'. */ + size_t s_size = ItemListSize(list) + ListLen(list); + if (s_size == 0) + { + s_size = 1; + } + + char *s = xmalloc(s_size); + *s = '\0'; + + /* No point cycle-checking; done while computing s_size. */ + for (const Item *ip = list; ip != NULL; ip = ip->next) + { + if (ip->name) + { + strcat(s, ip->name); + } + + if (ip->next) + { + strcat(s, ","); + } + } + + assert(strlen(s) + 1 == s_size); + + return s; +} + +/** + * Write all strings in list to buffer #buf, separating them with + * #separator. Watch out, no escaping happens. + * + * @return Final strlen(#buf), or #buf_size if string was truncated. + * Always '\0'-terminates #buf (within #buf_size). + */ +size_t ItemList2CSV_bound(const Item *list, char *buf, size_t buf_size, + char separator) +{ + size_t space = buf_size - 1; /* Reserve one for a '\0' */ + char *tail = buf; /* Point to end of what we've written. */ + const Item *ip = list; + CYCLE_DECLARE(ip, slow, toggle); + + while (ip != NULL) + { + size_t len = strlen(ip->name); + assert(buf + buf_size == tail + space + 1); + + if (space < len) /* We must truncate */ + { + memcpy(tail, ip->name, space); + tail[space] = '\0'; /* a.k.a. buf[buf_size - 1] */ + return buf_size; /* This signifies truncation */ + } + else + { + memcpy(tail, ip->name, len); + tail += len; + space -= len; + } + + /* Output separator if list has more entries. */ + if (ip->next != NULL) + { + if (space > 0) + { + *tail = separator; + tail++; + space--; + } + else /* truncation */ + { + *tail = '\0'; + return buf_size; + } + } + + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + + *tail = '\0'; + return tail - buf; +} + +/*********************************************************************/ +/* Basic operations */ +/*********************************************************************/ + +void IncrementItemListCounter(Item *list, const char *item) +{ + if (item == NULL || item[0] == '\0') + { + return; + } + + Item *ptr = list; + CYCLE_DECLARE(ptr, slow, toggle); + while (ptr != NULL) + { + if (strcmp(ptr->name, item) == 0) + { + ptr->counter++; + return; + } + ptr = ptr->next; + CYCLE_CHECK(ptr, slow, toggle); + } +} + +/*********************************************************************/ + +void SetItemListCounter(Item *list, const char *item, int value) +{ + if (item == NULL || item[0] == '\0') + { + return; + } + + Item *ptr = list; + CYCLE_DECLARE(ptr, slow, toggle); + while (ptr != NULL) + { + if (strcmp(ptr->name, item) == 0) + { + ptr->counter = value; + return; + } + ptr = ptr->next; + CYCLE_CHECK(ptr, slow, toggle); + } +} + +/*********************************************************************/ + +bool IsMatchItemIn(const Item *list, const char *item) +/* Solve for possible regex/fuzzy models unified */ +{ + if (item == NULL || item[0] == '\0') + { + return true; + } + + const Item *ptr = list; + CYCLE_DECLARE(ptr, slow, toggle); + while (ptr != NULL) + { + if (FuzzySetMatch(ptr->name, item) == 0 || + (IsRegex(ptr->name) && + StringMatchFull(ptr->name, item))) + { + return true; + } + ptr = ptr->next; + CYCLE_CHECK(ptr, slow, toggle); + } + + return false; +} + +/*********************************************************************/ +/* Cycle-detection: you'll get a double free if there's a cycle. */ + +void DeleteItemList(Item *item) /* delete starting from item */ +{ + while (item != NULL) + { + Item *here = item; + item = here->next; /* before free()ing here */ + + free(here->name); + free(here->classes); + free(here); + } +} + +/*********************************************************************/ + +void DeleteItem(Item **liststart, Item *item) +{ + if (item != NULL) + { + if (item == *liststart) + { + *liststart = item->next; + } + else + { + Item *ip = *liststart; + CYCLE_DECLARE(ip, slow, toggle); + while (ip && ip->next != item) + { + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + + if (ip != NULL) + { + assert(ip->next == item); + ip->next = item->next; + } + } + + free(item->name); + free(item->classes); + free(item); + } +} + +/*********************************************************************/ + +/* DeleteItem* function notes: + * -They all take an item list and an item specification ("string" argument.) + * -Some of them treat the item spec as a literal string, while others + * treat it as a regular expression. + * -They all delete the first item meeting their criteria, as below. + * function deletes item + * ------------------------------------------------------------------------ + * DeleteItemStarting start is literally equal to string item spec + * DeleteItemLiteral literally equal to string item spec + * DeleteItemMatching fully matched by regex item spec + * DeleteItemContaining containing string item spec + */ + +/*********************************************************************/ + +bool DeleteItemGeneral(Item **list, const char *string, ItemMatchType type) +{ + if (list == NULL) + { + return false; + } + + Regex *rx = NULL; + if (type == ITEM_MATCH_TYPE_REGEX_COMPLETE_NOT || + type == ITEM_MATCH_TYPE_REGEX_COMPLETE) + { + rx = CompileRegex(string); + if (!rx) + { + return false; + } + } + + Item *ip = *list, *last = NULL; + CYCLE_DECLARE(ip, slow, toggle); + while (ip != NULL) + { + if (ip->name != NULL) + { + bool match = false, flip = false; + switch (type) + { + case ITEM_MATCH_TYPE_LITERAL_START_NOT: + flip = true; + /* fall through */ + case ITEM_MATCH_TYPE_LITERAL_START: + match = (strncmp(ip->name, string, strlen(string)) == 0); + break; + + case ITEM_MATCH_TYPE_LITERAL_COMPLETE_NOT: + flip = true; + /* fall through */ + case ITEM_MATCH_TYPE_LITERAL_COMPLETE: + match = (strcmp(ip->name, string) == 0); + break; + + case ITEM_MATCH_TYPE_LITERAL_SOMEWHERE_NOT: + flip = true; + /* fall through */ + case ITEM_MATCH_TYPE_LITERAL_SOMEWHERE: + match = (strstr(ip->name, string) != NULL); + break; + + case ITEM_MATCH_TYPE_REGEX_COMPLETE_NOT: + flip = true; + /* fall through */ + case ITEM_MATCH_TYPE_REGEX_COMPLETE: + match = StringMatchFullWithPrecompiledRegex(rx, ip->name); + break; + } + if (flip) + { + match = !match; + } + + if (match) + { + if (ip == *list) + { + *list = ip->next; + } + else + { + assert(ip != NULL); + assert(last != NULL); + assert(last->next == ip); + last->next = ip->next; + } + + free(ip->name); + free(ip->classes); + free(ip); + if (rx) + { + RegexDestroy(rx); + } + + return true; + } + } + last = ip; + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + + if (rx) + { + RegexDestroy(rx); + } + + return false; +} + +/*********************************************************************/ + +bool DeleteItemStarting(Item **list, const char *string) /* delete 1st item starting with string */ +{ + return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_START); +} + +/*********************************************************************/ + +bool DeleteItemNotStarting(Item **list, const char *string) /* delete 1st item starting with string */ +{ + return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_START_NOT); +} + +/*********************************************************************/ + +bool DeleteItemLiteral(Item **list, const char *string) /* delete 1st item which is string */ +{ + return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_COMPLETE); +} + +/*********************************************************************/ + +bool DeleteItemMatching(Item **list, const char *string) /* delete 1st item fully matching regex */ +{ + return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_REGEX_COMPLETE); +} + +/*********************************************************************/ + +bool DeleteItemNotMatching(Item **list, const char *string) /* delete 1st item fully matching regex */ +{ + return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_REGEX_COMPLETE_NOT); +} + +/*********************************************************************/ + +bool DeleteItemContaining(Item **list, const char *string) /* delete first item containing string */ +{ + return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_SOMEWHERE); +} + +/*********************************************************************/ + +bool DeleteItemNotContaining(Item **list, const char *string) /* delete first item containing string */ +{ + return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_SOMEWHERE_NOT); +} + +/*********************************************************************/ + +bool RawSaveItemList(const Item *liststart, const char *filename, NewLineMode new_line_mode) +{ + char new[CF_BUFSIZE]; + snprintf(new, sizeof(new), "%s%s", filename, CF_EDITED); + unlink(new); /* Just in case of races */ + + FILE *fp = safe_fopen( + new, (new_line_mode == NewLineMode_Native) ? "wt" : "w"); + if (fp == NULL) + { + Log(LOG_LEVEL_ERR, "Couldn't write file '%s'. (fopen: %s)", new, GetErrorStr()); + return false; + } + + const Item *ip = liststart; + CYCLE_DECLARE(ip, slow, toggle); + while (ip != NULL) + { + fprintf(fp, "%s\n", ip->name); + ip = ip->next; + CYCLE_CHECK(ip, slow, toggle); + } + + if (fclose(fp) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to close file '%s' while writing. (fclose: %s)", new, GetErrorStr()); + return false; + } + + if (rename(new, filename) == -1) + { + Log(LOG_LEVEL_INFO, "Error while renaming file '%s' to '%s'. (rename: %s)", new, filename, GetErrorStr()); + return false; + } + + return true; +} + +Item *RawLoadItemList(const char *filename) +{ + FILE *fp = safe_fopen(filename, "r"); + if (!fp) + { + return NULL; + } + + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + + Item *list = NULL; + while (CfReadLine(&line, &line_size, fp) != -1) + { + PrependItem(&list, line, NULL); + } + + free(line); + + if (!feof(fp)) + { + Log(LOG_LEVEL_ERR, "Error while reading item list from file: %s", filename); + DeleteItemList(list); + list = NULL; + } + fclose(fp); + + return ReverseItemList(list); +} + +bool IsInterfaceAddress(const Item *ip_addresses, const char *adr) + /* Does this address belong to a local interface */ +{ + for (const Item *ip = ip_addresses; ip != NULL; ip = ip->next) + { + if (strncasecmp(adr, ip->name, strlen(adr)) == 0) + { + Log(LOG_LEVEL_DEBUG, "Identifying '%s' as one of my interfaces", adr); + return true; + } + } + + Log(LOG_LEVEL_DEBUG, "'%s' is not one of my interfaces", adr); + return false; +} diff --git a/libpromises/item_lib.h b/libpromises/item_lib.h new file mode 100644 index 0000000000..2d8d289978 --- /dev/null +++ b/libpromises/item_lib.h @@ -0,0 +1,98 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ITEM_LIB_H +#define CFENGINE_ITEM_LIB_H + +#include +#include +#include + +struct Item_ +{ + char *name; + char *classes; + int counter; + time_t time; + Item *next; +}; + +typedef enum +{ + ITEM_MATCH_TYPE_LITERAL_START, + ITEM_MATCH_TYPE_LITERAL_COMPLETE, + ITEM_MATCH_TYPE_LITERAL_SOMEWHERE, + ITEM_MATCH_TYPE_REGEX_COMPLETE, + ITEM_MATCH_TYPE_LITERAL_START_NOT, + ITEM_MATCH_TYPE_LITERAL_COMPLETE_NOT, + ITEM_MATCH_TYPE_LITERAL_SOMEWHERE_NOT, + ITEM_MATCH_TYPE_REGEX_COMPLETE_NOT +} ItemMatchType; + +void PrintItemList(const Item *list, Writer *w); +void PrependFullItem(Item **liststart, const char *itemstring, const char *classes, int counter, time_t t); +Item *ReturnItemIn(Item *list, const char *item); +Item *ReturnItemInClass(Item *list, const char *item, const char *classes); +Item *ReturnItemAtIndex(Item *list, int index); +Item *EndOfList(Item *start); +void PrependItemList(Item **liststart, const char *itemstring); +void InsertAfter(Item **filestart, Item *ptr, const char *string); +bool RawSaveItemList(const Item *liststart, const char *filename, NewLineMode new_line_mode); +Item *RawLoadItemList(const char *filename); +Item *SplitStringAsItemList(const char *string, char sep); +Item *SplitString(const char *string, char sep); +bool DeleteItemGeneral(Item **filestart, const char *string, ItemMatchType type); +bool DeleteItemLiteral(Item **filestart, const char *string); +bool DeleteItemStarting(Item **list, const char *string); +bool DeleteItemNotStarting(Item **list, const char *string); +bool DeleteItemMatching(Item **list, const char *string); +bool DeleteItemNotMatching(Item **list, const char *string); +bool DeleteItemContaining(Item **list, const char *string); +bool DeleteItemNotContaining(Item **list, const char *string); +size_t ListLen(const Item *list); +bool IsItemIn(const Item *list, const char *item); +bool ListsCompare(const Item *list1, const Item *list2); +bool ListSubsetOfList(const Item *list1, const Item *list2); +bool IsMatchItemIn(const Item *list, const char *item); +Item *ConcatLists(Item *list1, Item *list2); +void CopyList(Item **dest, const Item *source); +void IdempItemCount(Item **liststart, const char *itemstring, const char *classes); +Item *IdempPrependItem(Item **liststart, const char *itemstring, const char *classes); +Item *IdempPrependItemClass(Item **liststart, const char *itemstring, const char *classes); +Item *ReverseItemList(Item *list); /* Eats list, spits it out reversed. */ +Item *PrependItem(Item **liststart, const char *itemstring, const char *classes); +/* Warning: AppendItem()'s cost is proportional to list length; it is + * usually cheaper to build a list using PrependItem, then reverse it; + * building it with AppendItem() is quadratic in length. */ +void AppendItem(Item **liststart, const char *itemstring, const char *classes); +void DeleteItemList(Item *item); +void DeleteItem(Item **liststart, Item *item); +void IncrementItemListCounter(Item *ptr, const char *string); +void SetItemListCounter(Item *ptr, const char *string, int value); +char *ItemList2CSV(const Item *list); +size_t ItemList2CSV_bound(const Item *list, char *buf, size_t buf_size, char separator); +size_t ItemListSize(const Item *list); +bool IsInterfaceAddress(const Item *ip_addresses, const char *adr); + +#endif diff --git a/libpromises/iteration.c b/libpromises/iteration.c new file mode 100644 index 0000000000..4a69c5c4f6 --- /dev/null +++ b/libpromises/iteration.c @@ -0,0 +1,1068 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include /* ExpandScalar */ +#include /* DataTypeIsIterable */ + + + +/** + * WHEELS + * + * The iteration engine for CFEngine is set up with a number of "wheels" that + * roll in all combinations in order to iterate over everything - like the + * combination lock found on suitcases. One wheel is added for each iterable + * variable in the promiser-promisee-constraints strings. Iterable variables + * are slists and containers. But wheels are created in other cases as well, + * like variables that don't resolve yet but might change later on and + * possibly become iterables. + * + * The wheels are in struct PromiseIterator_ and are added right after + * initialisation of it, using PromiseIteratorPrepare() that calls ProcessVar(). + * + * Wheels are added in the Seq in an order that matters: variables that depend + * on others to expand are *on the right* of their dependencies. That means + * that *independent variables are on the left*. + * + * EXAMPLE reports promise: + * "Value of A is $(A[$(i)][$(j)]) for indexes $(i) and $(j)" + * + * One appropriate wheels Seq for that would be: i j A[$(i)][$(j)] + * + * So for that promise 3 wheels get generated, and always the dependent + * variables are on the right of their dependencies. The wheels sequence would + * be exactly the same if the reports promise was simply "$(A[$(i)][$(j)])", + * because there are again the same 3 variables. + */ + +/** + * ITERATING + * + * We push a new iteration context for each iteration, and VariablePut() into + * THIS context all selected single values of the iterable variables (slists + * or containers) represented by the wheels. + * + * *Thus EvalContext "THIS" never contains iterables (lists or containers).* + * + * This presents a problem for absolute references like $(abs.var), since + * these cannot be mapped into "this" without some magic (see MANGLING). + * + * The iteration context is popped and re-pushed for each iteration, until no + * further combinations of the wheel variables are left to be selected. + */ + +/** + * SCOPE/NAMESPACE MANGLING + * + * One important thing to notice is that the variables that are + * namespaced/scope need to be *mangled* in order to be added as wheels. This + * means that the scope separator '.' and namespace separator ':' are replaced + * with '#' and '*' respectively. This happens in ProcessVar(), see comments + * for reasoning and further info. + */ + + + +typedef struct { + + /* The unexpanded variable name, dependent on inner expansions. This + * field never changes after Wheel initialisation. */ + char *varname_unexp; + + /* On each iteration of the wheels, the unexpanded string is + * re-expanded, so the following is refilled, again and again. */ + char *varname_exp; + + /* + * Values of varname_exp, to iterate on. WE DO NOT OWN THE RVALS, they + * belong to EvalContext, so don't free(). Only if vartype is CONTAINER do + * we own the strings and we must free() them. + * + * After the iteration engine has started (via PromiseIteratorNext()) + * "values" can be NULL when a variable does not resolve, or when it's + * not an iterable but it's already there in EvalContext, so no need to + * Put() separately; this means that it has exactly one value. + * + * When the variable resolves to an empty iterable (like empty slist or + * container) then it's not NULL, but SeqLength(values)==0. + * + * TODO values==NULL should only be unresolved variable - + * non-iterable variable should be SeqLength()==1. + */ + Seq *values; + + /* This is the list-type of the iterable variable, and this sets the type + * of the elements stored in Seq values. Only possibilities are INTLIST, + * REALLIST, SLIST, CONTAINER, NONE (if the variable did not resolve). */ + DataType vartype; + + size_t iter_index; /* current iteration index */ + +} Wheel; + + +struct PromiseIterator_ { + Seq *wheels; + const Promise *pp; /* not owned by us */ + size_t count; /* total iterations count */ +}; + + +/** + * @NOTE #varname doesn't need to be '\0'-terminated, since the length is + * provided. + */ +static Wheel *WheelNew(const char *varname, size_t varname_len) +{ + Wheel new_wheel = { + .varname_unexp = xstrndup(varname, varname_len), + .varname_exp = NULL, + .values = NULL, + .vartype = -1, + .iter_index = 0 + }; + + return xmemdup(&new_wheel, sizeof(new_wheel)); +} + +static void WheelValuesSeqDestroy(Wheel *w) +{ + if (w->values != NULL) + { + /* Only if the variable resolved to type CONTAINER do we need to free + * the values, since we trasformed it to a Seq of strings. */ + if (w->vartype == CF_DATA_TYPE_CONTAINER) + { + size_t values_len = SeqLength(w->values); + for (size_t i = 0; i < values_len; i++) + { + char *value = SeqAt(w->values, i); + free(value); + } + } + SeqDestroy(w->values); + w->values = NULL; + } + w->vartype = -1; +} + +static void WheelDestroy(void *wheel) +{ + Wheel *w = wheel; + free(w->varname_unexp); + free(w->varname_exp); + WheelValuesSeqDestroy(w); + free(w); +} + +/* Type of this function is SeqItemComparator for use in SeqLookup(). */ +static int WheelCompareUnexpanded(const void *wheel1, const void *wheel2, + void *user_data ARG_UNUSED) +{ + const Wheel *w1 = wheel1; + const Wheel *w2 = wheel2; + return strcmp(w1->varname_unexp, w2->varname_unexp); +} + +PromiseIterator *PromiseIteratorNew(const Promise *pp) +{ + PromiseIterator iterctx = { + .wheels = SeqNew(4, WheelDestroy), + .pp = pp, + .count = 0 + }; + return xmemdup(&iterctx, sizeof(iterctx)); +} + +void PromiseIteratorDestroy(PromiseIterator *iterctx) +{ + SeqDestroy(iterctx->wheels); + free(iterctx); +} + +size_t PromiseIteratorIndex(const PromiseIterator *iter_ctx) +{ + return iter_ctx->count; +} + + +/** + * Returns offset to "$(" or "${" in the string. + * Reads bytes up to s[max-1], s[max] is NOT read. + * If a '\0' is encountered before the pattern, return offset to `\0` byte + * If no '\0' byte or pattern is found within max bytes, max is returned + */ +static size_t FindDollarParen(const char *s, size_t max) +{ + size_t i = 0; + + while (i < max && s[i] != '\0') + { + if (i+1 < max && (s[i] == '$' && (s[i+1] == '(' || s[i+1] == '{'))) + { + return i; + } + i++; + } + assert(i == max || s[i] == '\0'); + return i; +} + +static char opposite(char c) +{ + switch (c) + { + case '(': return ')'; + case '{': return '}'; + default : ProgrammingError("Was expecting '(' or '{' but got: '%c'", c); + } + return 0; +} + +/** + * Find the closing parenthesis for #c in #s. #c is considered to *not* be part + * of #s (IOW, #s is considered to be a string after #c). + * + * @return A closing parenthesis for #c in #s or %NULL if not found + */ +static char *FindClosingParen(char *s, char c) +{ + char closing = opposite(c); + int counter = 0; + for (char *cur=s; *cur != '\0'; cur++) + { + if (*cur == closing) + { + if (counter == 0) + { + return cur; + } + counter--; + } + if (*cur == c) + { + counter++; + } + } + return NULL; +} + +/** + * Check if variable reference is mangled, while avoiding going into the inner + * variables that are being expanded, or into array indexes. + * + * @NOTE variable name is naked, i.e. shouldn't start with dollar-paren. + */ +static bool IsMangled(const char *s) +{ + assert(s != NULL); + size_t s_length = strlen(s); + size_t dollar_paren = FindDollarParen(s, s_length); + size_t bracket = strchrnul(s, '[') - s; + size_t upto = MIN(dollar_paren, bracket); + size_t mangled_ns = strchrnul(s, CF_MANGLED_NS) - s; + size_t mangled_scope = strchrnul(s, CF_MANGLED_SCOPE) - s; + + if (mangled_ns < upto || + mangled_scope < upto) + { + return true; + } + else + { + return false; + } +} + +/** + * Mangle namespace and scope separators, up to '$(', '${', '[', '\0', + * whichever comes first. + * + * "this" scope is never mangled, no need to VariablePut() a mangled reference + * in THIS scope, since the non-manled one already exists. + */ +static void MangleVarRefString(char *ref_str, size_t len) +{ + // printf("MangleVarRefString: %.*s\n", (int) len, ref_str); + + size_t dollar_paren = FindDollarParen(ref_str, len); + size_t upto = MIN(len, dollar_paren); + char *bracket = memchr(ref_str, '[', upto); + if (bracket != NULL) + { + upto = bracket - ref_str; + } + + char *ns = memchr(ref_str, ':', upto); + char *ref_str2 = ref_str; + if (ns != NULL) + { + *ns = CF_MANGLED_NS; + ref_str2 = ns + 1; + assert(upto >= (ns + 1 - ref_str)); + upto -= (ns + 1 - ref_str); + } + + bool mangled_scope = false; + char *scope = memchr(ref_str2, '.', upto); + if (scope != NULL && + strncmp(ref_str2, "this", 4) != 0) + { + *scope = CF_MANGLED_SCOPE; + mangled_scope = true; + } + + if (mangled_scope || ns != NULL) + { + LogDebug(LOG_MOD_ITERATIONS, + "Mangled namespaced/scoped variable for iterating over it: %.*s", + (int) len, ref_str); + } +} + +/** + * Lookup a variable within iteration context. Since the scoped or namespaced + * variable names may be mangled, we have to look them up using special + * separators CF_MANGLED_NS and CF_MANGLED_SCOPE. + */ +static const void *IterVariableGet(const PromiseIterator *iterctx, + const EvalContext *evalctx, + const char *varname, DataType *type) +{ + const void *value; + const Bundle *bundle = PromiseGetBundle(iterctx->pp); + + /* Equivalent to: + VarRefParseFromBundle(varname, PromiseGetBundle(iterctx->pp)) + + but with custom namespace,scope separators. Even !IsMangled(varname) it + should be resolved properly since the secondary separators shouldn't + alter the result for an unqualified varname. */ + VarRef *ref = + VarRefParseFromNamespaceAndScope(varname, bundle->ns, bundle->name, + CF_MANGLED_NS, CF_MANGLED_SCOPE); + value = EvalContextVariableGet(evalctx, ref, type); + VarRefDestroy(ref); + + if (*type == CF_DATA_TYPE_NONE) /* did not resolve */ + { + assert(value == NULL); + + if (!IsMangled(varname)) + { + /* Lookup with no mangling, it might be a scoped/namespaced + * variable that is not an iterable, so it was not mangled in + * ProcessVar(). */ + VarRef *ref2 = VarRefParse(varname); + value = EvalContextVariableGet(evalctx, ref2, type); + VarRefDestroy(ref2); + } + } + + return value; +} + +/* TODO this is ugly!!! mapdata() needs to be refactored to put a whole slist + as "this.k". But how? It is executed *after* PromiseIteratorNext()! */ +static bool VarIsSpecial(const char *s) +{ + if (strcmp(s, "this") == 0 || + strcmp(s, "this.k") == 0 || + strcmp(s, "this.v") == 0 || + strcmp(s, "this.k[1]") == 0 || + strcmp(s, "this.this") == 0) + { + return true; + } + else + { + return false; + } +} + +/** + * Decide whether to mangle varname and add wheel to the iteration engine. + * + * If variable contains inner expansions -> mangle and add wheel + * (because you don't know if it will be an iterable or not - you will + * know after inner variable is iterated and the variable is looked up) + * + * else if it resolves to iterable -> mangle and add wheel + * + * else if it resolves to empty iterable -> mangle and add wheel + * (see comments in code) + * + * else if the variable name is special for some functions (this.k etc) + * -> mangle and add wheel + * + * else if it resolves to non-iterable -> no mangle, no wheel + * + * else if it doesn't resolve -> no mangle, no wheel + * + * @NOTE Important special scopes (e.g. "connection.ip" for cf-serverd) must + * not be mangled to work correctly. This is auto-OK because such + * variables do not resolve usually. + */ +static bool ShouldAddVariableAsIterationWheel( + const PromiseIterator *iterctx, + const EvalContext *evalctx, + char *varname, size_t varname_len) +{ + bool result; + /* Shorten string temporarily to the appropriate length. */ + char tmp_c = varname[varname_len]; + varname[varname_len] = '\0'; + + VarRef *ref = VarRefParseFromBundle(varname, + PromiseGetBundle(iterctx->pp)); + DataType t; + ARG_UNUSED const void *value = EvalContextVariableGet(evalctx, ref, &t); + VarRefDestroy(ref); + + size_t dollar_paren = FindDollarParen(varname, varname_len); + if (dollar_paren < varname_len) + { + /* Varname contains inner expansions, so maybe the variable will + * resolve to an iterable during the iteration - must add wheel. */ + result = true; + } + else if (DataTypeIsIterable(t)) + { + result = true; + + /* NOTE: If it is an EMPTY ITERABLE i.e. value==NULL, we are still + * adding an iteration wheel, but with "wheel->values" set to an empty + * Seq. The reason is that the iteration engine will completely *skip* + * all promise evaluations when one of the wheels is empty. + * + * Otherwise, if we didn't add the empty wheel, even if the promise + * contained no other wheels, the promise would get evaluated exactly + * once with "$(varname)" literally in there. */ + } + else if (VarIsSpecial(varname)) + { + result = true; + } + else + { + /* + * Either varname resolves to a non-iterable, e.g. string. + * Or it does not resolve. + * + * Since this variable does not contain inner expansions, this can't + * change during iteration of other variables. So don't add wheel - + * i.e. don't iterate over this variable's values, because we know + * there will always be only one value. + */ + result = false; + } + + varname[varname_len] = tmp_c; /* Restore original string */ + return result; +} + +/** + * Recursive function that adds wheels to the iteration engine, according to + * the variable (and possibly its inner variables) in #s. + * + * Another important thing it does, is *modify* the string #s, mangling all + * scoped or namespaced variable names. Mangling is done in order to iterate + * over foreign variables, without modifying the foreign value. For example if + * "test.var" is an slist, then we mangle it as "test#var" and on each + * iteration we just VariablePut(test#var) in the local scope. + * Mangling is skipped for variables that do not resolve, since they are not + * to be iterated over. + * + * @param s is the start of a variable name, right after "$(" or "${". + * @param c is the character after '$', i.e. must be either '(' or '{'. + * @return pointer to the closing parenthesis or brace of the variable, or + * if not found, returns a pointer to terminating '\0' of #s. + */ +static char *ProcessVar(PromiseIterator *iterctx, const EvalContext *evalctx, + char *s, char c) +{ + assert(s != NULL); + assert(c == '(' || c == '{'); + + char *s_end = FindClosingParen(s, c); + const size_t s_max = strlen(s); + if (s_end == NULL) + { + /* Set s_end to the point to the NUL byte if no closing parenthesis was + * found. It's used for comparisons and other things below. */ + s_end = s + s_max; + } + char *next_var = s + FindDollarParen(s, s_max); + + while (next_var < s_end) /* does it have nested variables? */ + { + /* It's a dependent variable, the wheels of the dependencies must be + * added first. Example: "$(blah_$(dependency))" */ + + assert(next_var[0] != '\0'); + assert(next_var[1] != '\0'); + + char *subvar_end = ProcessVar(iterctx, evalctx, + &next_var[2], next_var[1]); + + /* Was there unbalanced paren for the inner expansion? */ + if (*subvar_end == '\0') + { + /* Despite unclosed parenthesis for the inner expansion, + * the outer variable might close with a brace, or not. */ + const size_t s_end_len = strlen(s_end); + next_var = s_end + FindDollarParen(s_end, s_end_len); + /* s_end is already correct */ + } + else /* inner variable processed correctly */ + { + /* This variable depends on inner expansions. */ + /* We are sure (subvar_end+1) is not out of bounds. */ + char *s_next = subvar_end + 1; + const size_t s_next_len = strlen(s_next); + s_end = FindClosingParen(s_next, c); + if (s_end == NULL) + { + /* Set s_end to the point to the NUL byte if no closing parenthesis was + * found. It's used for comparisons and other things below. */ + s_end = s_next + s_next_len; + } + next_var = s_next + FindDollarParen(s_next, s_next_len); + } + } + + assert(s_end != NULL); + if (*s_end == '\0') + { + Log(LOG_LEVEL_ERR, "No closing '%c' found for variable: %s", + opposite(c), s); + return s_end; + } + + const size_t s_len = s_end - s; + + if (ShouldAddVariableAsIterationWheel(iterctx, evalctx, s, s_len)) + { + /* Change the variable name in order to mangle namespaces and scopes. */ + MangleVarRefString(s, s_len); + + Wheel *new_wheel = WheelNew(s, s_len); + + /* If identical variable is already inserted, it means that it has + * been seen before and has been inserted together with all + * dependencies; skip. */ + /* It can happen if variables exist twice in a string, for example: + "$(i) blah $(A[$(i)])" has i variable twice. */ + + bool same_var_found = (SeqLookup(iterctx->wheels, new_wheel, + WheelCompareUnexpanded) != NULL); + if (same_var_found) + { + LogDebug(LOG_MOD_ITERATIONS, + "Skipped adding iteration wheel for already existing variable: %s", + new_wheel->varname_unexp); + WheelDestroy(new_wheel); + } + else + { + /* If this variable is dependent on other variables, we've already + * appended the wheels of the dependencies during the recursive + * calls. Or it happens and this is an independent variable. So + * now APPEND the wheel for this variable. */ + SeqAppend(iterctx->wheels, new_wheel); + + LogDebug(LOG_MOD_ITERATIONS, + "Added iteration wheel %zu for variable: %s", + SeqLength(iterctx->wheels) - 1, + new_wheel->varname_unexp); + } + } + + assert(s_end != NULL); + assert(*s_end == opposite(c)); + return s_end; +} + +/** + * @brief Fills up the wheels of the iterator according to the variables + * found in #s. Also mangles all namespaced/scoped variables in #s. + * + * @EXAMPLE Have a look in iteration_test.c:test_PromiseIteratorPrepare() + * + * @NOTE the wheel numbers can't change once iteration started, so make sure + * you call WheelIteratorPrepare() in advance, as many times it's + * needed. + */ +void PromiseIteratorPrepare(PromiseIterator *iterctx, + const EvalContext *evalctx, + char *s) +{ + assert(s != NULL); + LogDebug(LOG_MOD_ITERATIONS, "PromiseIteratorPrepare(\"%s\")", s); + const size_t s_len = strlen(s); + const size_t offset = FindDollarParen(s, s_len); + + assert(offset <= s_len); // FindDollarParen guarantees this + if (offset == s_len) + { + return; // Don't search past NULL terminator + } + + char *var_start = s + offset; + while (*var_start != '\0') + { + char paren_or_brace = var_start[1]; + var_start += 2; /* skip dollar-paren */ + + assert(paren_or_brace == '(' || paren_or_brace == '{'); + + char *var_end = ProcessVar(iterctx, evalctx, var_start, paren_or_brace); + assert(var_end != NULL); + if (*var_end == '\0') + { + return; // Don't search past NULL terminator + } + char *var_next = var_end + 1; + const size_t var_next_len = s_len - (var_next - s); + const size_t var_offset = FindDollarParen(var_next, var_next_len); + assert(var_offset <= var_next_len); + if (var_offset == var_next_len) + { + return; // Don't search past NULL terminator + } + var_start = var_next + var_offset; + } +} + +static void IterListElementVariablePut(EvalContext *evalctx, + const char *varname, + DataType listtype, void *value) +{ + DataType t; + + switch (listtype) + { + case CF_DATA_TYPE_CONTAINER: t = CF_DATA_TYPE_STRING; break; + case CF_DATA_TYPE_STRING_LIST: t = CF_DATA_TYPE_STRING; break; + case CF_DATA_TYPE_INT_LIST: t = CF_DATA_TYPE_INT; break; + case CF_DATA_TYPE_REAL_LIST: t = CF_DATA_TYPE_REAL; break; + default: + t = CF_DATA_TYPE_NONE; /* silence warning */ + ProgrammingError("IterVariablePut() invalid type: %d", + listtype); + } + + EvalContextVariablePutSpecial(evalctx, SPECIAL_SCOPE_THIS, + varname, value, + t, "source=promise_iteration"); +} + +static void SeqAppendContainerPrimitive(Seq *seq, const JsonElement *primitive) +{ + assert(JsonGetElementType(primitive) == JSON_ELEMENT_TYPE_PRIMITIVE); + + switch (JsonGetPrimitiveType(primitive)) + { + case JSON_PRIMITIVE_TYPE_BOOL: + SeqAppend(seq, (JsonPrimitiveGetAsBool(primitive) ? + xstrdup("true") : xstrdup("false"))); + break; + case JSON_PRIMITIVE_TYPE_INTEGER: + { + char *str = StringFromLong(JsonPrimitiveGetAsInteger(primitive)); + SeqAppend(seq, str); + break; + } + case JSON_PRIMITIVE_TYPE_REAL: + { + char *str = StringFromDouble(JsonPrimitiveGetAsReal(primitive)); + SeqAppend(seq, str); + break; + } + case JSON_PRIMITIVE_TYPE_STRING: + SeqAppend(seq, xstrdup(JsonPrimitiveGetAsString(primitive))); + break; + + case JSON_PRIMITIVE_TYPE_NULL: + break; + } +} + +static Seq *ContainerToSeq(const JsonElement *container) +{ + Seq *seq = SeqNew(5, NULL); + + switch (JsonGetElementType(container)) + { + case JSON_ELEMENT_TYPE_PRIMITIVE: + SeqAppendContainerPrimitive(seq, container); + break; + + case JSON_ELEMENT_TYPE_CONTAINER: + { + JsonIterator iter = JsonIteratorInit(container); + const JsonElement *child; + + while ((child = JsonIteratorNextValue(&iter)) != NULL) + { + if (JsonGetElementType(child) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + SeqAppendContainerPrimitive(seq, child); + } + } + break; + } + } + + /* TODO SeqFinalise() to save space? */ + return seq; +} + +static Seq *RlistToSeq(const Rlist *p) +{ + Seq *seq = SeqNew(5, NULL); + + const Rlist *rlist = p; + while(rlist != NULL) + { + Rval val = rlist->val; + SeqAppend(seq, val.item); + rlist = rlist->next; + } + + /* TODO SeqFinalise() to save space? */ + return seq; +} + +static Seq *IterableToSeq(const void *v, DataType t) +{ + switch (t) + { + case CF_DATA_TYPE_CONTAINER: + return ContainerToSeq(v); + break; + case CF_DATA_TYPE_STRING_LIST: + case CF_DATA_TYPE_INT_LIST: + case CF_DATA_TYPE_REAL_LIST: + /* All lists are stored as Rlist internally. */ + assert(DataTypeToRvalType(t) == RVAL_TYPE_LIST); + return RlistToSeq(v); + + default: + ProgrammingError("IterableToSeq() got non-iterable type: %d", t); + } +} + +/** + * For each of the wheels to the right of wheel_idx (including this one) + * + * 1. varname_exp = expand the variable name + * - if it's same with previous varname_exp, skip steps 2-4 + * 2. values = VariableGet(varname_exp); + * 3. if the value is an iterable (slist/container), set the wheel size. + * 4. reset the wheel in order to re-iterate over all combinations. + * 5. Put(varname_exp:first_value) in the EvalContext + */ +static void ExpandAndPutWheelVariablesAfter( + const PromiseIterator *iterctx, + EvalContext *evalctx, + size_t wheel_idx) +{ + /* Buffer to store the expanded wheel variable name, for each wheel. */ + Buffer *tmpbuf = BufferNew(); + + size_t wheels_num = SeqLength(iterctx->wheels); + for (size_t i = wheel_idx; i < wheels_num; i++) + { + Wheel *wheel = SeqAt(iterctx->wheels, i); + BufferClear(tmpbuf); + + /* Reset wheel in order to re-iterate over all combinations. */ + wheel->iter_index = 0; + + /* The wheel variable may depend on previous wheels, for example + * "B_$(k)_$(v)" is dependent on variables "k" and "v", which are + * wheels already set (to the left, or at lower i index). */ + const char *varname = ExpandScalar(evalctx, + PromiseGetNamespace(iterctx->pp), + /* Use NULL as scope so that we try both "this" and "bundle" scopes. */ + NULL, + wheel->varname_unexp, tmpbuf); + + /* If it expanded to something different than before. */ + if (wheel->varname_exp == NULL + || strcmp(varname, wheel->varname_exp) != 0) + { + free(wheel->varname_exp); /* could be NULL */ + wheel->varname_exp = xstrdup(varname); + + WheelValuesSeqDestroy(wheel); /* free previous values */ + + /* After expanding the variable name, we have to lookup its value, + and set the size of the wheel if it's an slist or container. */ + DataType value_type; + const void *value = IterVariableGet(iterctx, evalctx, + varname, &value_type); + wheel->vartype = value_type; + + /* Set wheel values and size according to variable type. */ + if (DataTypeIsIterable(value_type)) + { + wheel->values = IterableToSeq(value, value_type); + + if (SeqLength(wheel->values) == 0) + { + /* + * If this variable now expands to a 0-length list, then + * we should skip this iteration, no matter the + * other variables: "zero times whatever" multiplication + * always equals zero. + */ + Log(LOG_LEVEL_VERBOSE, + "Skipping iteration since variable '%s'" + " resolves to an empty list", varname); + } + else + { + assert( wheel->values != NULL); + assert(SeqLength(wheel->values) > 0); + assert( SeqAt(wheel->values, 0) != NULL); + + /* Put the first value of the iterable. */ + IterListElementVariablePut(evalctx, varname, value_type, + SeqAt(wheel->values, 0)); + } + } + /* It it's NOT AN ITERABLE BUT IT RESOLVED AND IT IS MANGLED: this + * is possibly a variable that was unresolvable during the + * Prepare() stage, but now resolves to a string etc. We still + * need to Put() it despite not being an iterable, since the + * mangled version is not in the EvalContext. + * The "values" Seq is left as NULL. */ + else if (value_type != CF_DATA_TYPE_NONE && IsMangled(varname)) + { + EvalContextVariablePutSpecial(evalctx, SPECIAL_SCOPE_THIS, + varname, value, value_type, + "source=promise_iteration"); + } + /* It's NOT AN ITERABLE AND IT'S NOT MANGLED, which means that + * the variable with the correct value (the only value) is already + * in the EvalContext, no need to Put() it again. */ + /* OR it doesn't resolve at all! */ + else + { + /* DO NOTHING, everything is already set. */ + + assert(!DataTypeIsIterable(value_type)); + assert(value_type == CF_DATA_TYPE_NONE || /* var does not resolve */ + !IsMangled(varname)); /* or is not mangled */ + /* We don't allocate Seq for non-iterables. */ + assert(wheel->values == NULL); + } + } + else /* The variable name expanded to the same name */ + { + /* speedup: the variable name expanded to the same name, so the + * value is the same and wheel->values is already correct. So if + * it's an iterable, we VariablePut() the first element. */ + if (wheel->values != NULL && SeqLength(wheel->values) > 0) + { + /* Put the first value of the iterable. */ + IterListElementVariablePut(evalctx, + wheel->varname_exp, wheel->vartype, + SeqAt(wheel->values, 0)); + } + } + } + + BufferDestroy(tmpbuf); +} + +static bool IteratorHasEmptyWheel(const PromiseIterator *iterctx) +{ + size_t wheels_num = SeqLength(iterctx->wheels); + for (size_t i = 0; i < wheels_num; i++) + { + Wheel *wheel = SeqAt(iterctx->wheels, i); + assert(wheel != NULL); + + if (VarIsSpecial(wheel->varname_unexp)) /* TODO this is ugly! */ + { + return false; + } + + /* If variable resolves to an empty iterable or it doesn't resolve. */ + if ((wheel->values != NULL && + SeqLength(wheel->values) == 0) + || + wheel->vartype == CF_DATA_TYPE_NONE) + { + return true; + } + } + + return false; +} + +/* Try incrementing the rightmost wheel first that has values left to iterate on. + (rightmost i.e. the most dependent variable). */ +static size_t WheelRightmostIncrement(PromiseIterator *iterctx) +{ + size_t wheels_num = SeqLength(iterctx->wheels); + size_t i = wheels_num; + Wheel *wheel; + + assert(wheels_num > 0); + + do + { + if (i == 0) + { + return (size_t) -1; /* all wheels have been iterated over */ + } + + i--; /* move one wheel to the left */ + wheel = SeqAt(iterctx->wheels, i); + wheel->iter_index++; + + /* Stop when we have found a wheel with value available at iter_index. */ + } while (wheel->values == NULL || + wheel->vartype == CF_DATA_TYPE_NONE || + SeqLength(wheel->values) == 0 || + wheel->iter_index >= SeqLength(wheel->values)); + + return i; /* return which wheel was incremented */ +} + +/* Nothing to iterate on, so get out after running the promise once. + * Because all promises, even if there are zero variables to be + * expanded in them, must be evaluated. */ +static bool RunOnlyOnce(PromiseIterator *iterctx) +{ + assert(SeqLength(iterctx->wheels) == 0); + + if (iterctx->count == 0) + { + iterctx->count++; + return true; + } + else + { + return false; + } +} + +bool PromiseIteratorNext(PromiseIterator *iterctx, EvalContext *evalctx) +{ + size_t wheels_num = SeqLength(iterctx->wheels); + + if (wheels_num == 0) + { + return RunOnlyOnce(iterctx); + } + + bool done = false; + + /* First iteration: we initialise all wheels. */ + if (iterctx->count == 0) + { + Log(LOG_LEVEL_DEBUG, "Starting iteration engine with %zu wheels" + " --- ENTERING WARP SPEED", + wheels_num); + + ExpandAndPutWheelVariablesAfter(iterctx, evalctx, 0); + + done = ! IteratorHasEmptyWheel(iterctx); + } + + while (!done) + { + size_t i = WheelRightmostIncrement(iterctx); + if (i == (size_t) -1) /* all combinations have been tried */ + { + Log(LOG_LEVEL_DEBUG, "Iteration engine finished" + " --- WARPING OUT"); + return false; + } + + /* + * Alright, incrementing the wheel at index "i" was successful. Now + * Put() the new value of the variable in the EvalContext. This is the + * *basic iteration step*, just going to the next value of the + * iterable. + */ + Wheel *wheel = SeqAt(iterctx->wheels, i); + void *new_value = SeqAt(wheel->values, wheel->iter_index); + + IterListElementVariablePut( + evalctx, wheel->varname_exp, wheel->vartype, new_value); + + /* All the wheels to the right of the one we changed have to be reset + * and recomputed, in order to do all possible combinations. */ + ExpandAndPutWheelVariablesAfter(iterctx, evalctx, i + 1); + + /* If any of the wheels has no values to offer, then this iteration + * should be skipped completely; so the function doesn't yield any + * result yet, it just loops over until it finds a meaningful one. */ + done = ! IteratorHasEmptyWheel(iterctx); + + LogDebug(LOG_MOD_ITERATIONS, "PromiseIteratorNext():" + " count=%zu wheels_num=%zu current_wheel=%zd", + iterctx->count, wheels_num, (ssize_t) i); + + /* TODO if not done, then we are re-Put()ing variables in the EvalContect, + * hopefully overwriting the previous values, but possibly not! */ + } + + // Recompute `with` + for (size_t i = 0; i < SeqLength(iterctx->pp->conlist); i++) + { + Constraint *cp = SeqAt(iterctx->pp->conlist, i); + if (StringEqual(cp->lval, "with")) + { + Rval final = EvaluateFinalRval(evalctx, PromiseGetPolicy(iterctx->pp), NULL, + "this", cp->rval, false, iterctx->pp); + if (final.type == RVAL_TYPE_SCALAR && !IsCf3VarString(RvalScalarValue(final))) + { + EvalContextVariablePutSpecial(evalctx, SPECIAL_SCOPE_THIS, + "with", RvalScalarValue(final), + CF_DATA_TYPE_STRING, + "source=promise_iteration/with"); + } + RvalDestroy(final); + } + } + iterctx->count++; + return true; +} diff --git a/libpromises/iteration.h b/libpromises/iteration.h new file mode 100644 index 0000000000..1bcf8c7bcb --- /dev/null +++ b/libpromises/iteration.h @@ -0,0 +1,45 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ITERATION_H +#define CFENGINE_ITERATION_H + + +#include + + +typedef struct PromiseIterator_ PromiseIterator; + + +PromiseIterator *PromiseIteratorNew(const Promise *pp); +void PromiseIteratorDestroy(PromiseIterator *iterctx); +void PromiseIteratorPrepare(PromiseIterator *iterctx, + const EvalContext *evalctx, + char *s); +bool PromiseIteratorNext(PromiseIterator *iterctx, + EvalContext *evalctx); +size_t PromiseIteratorIndex(const PromiseIterator *iter_ctx); + + +#endif diff --git a/libpromises/keyring.c b/libpromises/keyring.c new file mode 100644 index 0000000000..9c5b2cb191 --- /dev/null +++ b/libpromises/keyring.c @@ -0,0 +1,118 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include + +/***************************************************************/ + +bool HostKeyAddressUnknown(const char *value) +{ + if (strcmp(value, "location unknown") == 0) + { + return true; + } + +// Is there some other non-ip string left over? + + if (!((strchr(value, '.')) || (strchr(value, ':')))) + { + return false; + } + + return false; +} + +/***************************************************************/ + +int RemovePublicKey(const char *id) +{ + Dir *dirh = NULL; + int removed = 0; + char keysdir[CF_BUFSIZE]; + const struct dirent *dirp; + char suffix[CF_BUFSIZE]; + + snprintf(keysdir, CF_BUFSIZE, "%s/ppkeys", GetWorkDir()); + + MapName(keysdir); + + if ((dirh = DirOpen(keysdir)) == NULL) + { + if (errno == ENOENT) + { + return 0; + } + else + { + Log(LOG_LEVEL_ERR, "Unable to open keys directory at '%s'. (opendir: %s)", keysdir, GetErrorStr()); + return -1; + } + } + + snprintf(suffix, CF_BUFSIZE, "-%s.pub", id); + + while ((dirp = DirRead(dirh)) != NULL) + { + char *c = strstr(dirp->d_name, suffix); + + if (c && c[strlen(suffix)] == '\0') /* dirp->d_name ends with suffix */ + { + char keyfilename[sizeof(keysdir) + sizeof(dirp->d_name)]; + // both sizeof's include NUL, so one of those is for the '/' + + snprintf(keyfilename, sizeof(keyfilename), "%s/%s", keysdir, dirp->d_name); + MapName(keyfilename); + + if (unlink(keyfilename) < 0) + { + if (errno != ENOENT) + { + Log(LOG_LEVEL_ERR, "Unable to remove key file '%s'. (unlink: %s)", dirp->d_name, GetErrorStr()); + DirClose(dirh); + return -1; + } + } + else + { + removed++; + } + } + } + + if (errno) + { + Log(LOG_LEVEL_ERR, "Unable to enumerate files in keys directory. (ReadDir: %s)", GetErrorStr()); + DirClose(dirh); + return -1; + } + + DirClose(dirh); + return removed; +} +/***************************************************************/ diff --git a/libpromises/keyring.h b/libpromises/keyring.h new file mode 100644 index 0000000000..705fe0315e --- /dev/null +++ b/libpromises/keyring.h @@ -0,0 +1,33 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_KEYRING_H +#define CFENGINE_KEYRING_H + +#include + +bool HostKeyAddressUnknown(const char *value); +int RemovePublicKey(const char *id); + +#endif diff --git a/libpromises/lastseen.c b/libpromises/lastseen.c new file mode 100644 index 0000000000..0dc7bdc295 --- /dev/null +++ b/libpromises/lastseen.c @@ -0,0 +1,765 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#ifdef LMDB +#include +#endif + +void UpdateLastSawHost(const char *hostkey, const char *address, + bool incoming, time_t timestamp); + +/* + * Lastseen database schema (version 1): + * + * Version entry + * + * key: "version\0" + * value: "1\0" + * + * "Quality of connection" entries + * + * key: q (direction: 'i' for incoming, 'o' for outgoing) + * value: struct KeyHostSeen + * + * "Hostkey" entries + * + * key: k ("MD5-ffffefefeefef..." or "SHA-abacabaca...") + * value:
    (IPv4 or IPv6) + * + * "Address", or reverse, entries (auxiliary) + * + * key: a
    (IPv6 or IPv6) + * value: + * + * + * + * Schema version 0 mapped direction + hostkey to address + quality of + * connection. This approach had a number of drawbacks: + * - There were two potentially conflicting addresses for given hostkey. + * - There was no way to quickly lookup hostkey by address. + * - Address update required traversal of the whole database. + * + * In order to overcome these limitations, new schema normalized (in relational + * algebra sense) the data relations. + */ + +/* TODO #ifndef NDEBUG check, report loudly, and fix consistency issues in every operation. */ + +/*****************************************************************************/ + +/** + * @brief Same as LastSaw() but the digest parameter is the hash as a + * "SHA=..." string, to avoid converting twice. + */ +void LastSaw1(const char *ipaddress, const char *hashstr, + LastSeenRole role) +{ + const char *mapip = MapAddress(ipaddress); + UpdateLastSawHost(hashstr, mapip, role == LAST_SEEN_ROLE_ACCEPT, time(NULL)); +} + +void LastSaw(const char *ipaddress, const unsigned char *digest, LastSeenRole role) +{ + char databuf[CF_HOSTKEY_STRING_SIZE]; + + if (strlen(ipaddress) == 0) + { + Log(LOG_LEVEL_INFO, "LastSeen registry for empty IP with role %d", role); + return; + } + + HashPrintSafe(databuf, sizeof(databuf), digest, CF_DEFAULT_DIGEST, true); + + const char *mapip = MapAddress(ipaddress); + + UpdateLastSawHost(databuf, mapip, role == LAST_SEEN_ROLE_ACCEPT, time(NULL)); +} + +/*****************************************************************************/ + +void UpdateLastSawHost(const char *hostkey, const char *address, + bool incoming, time_t timestamp) +{ + DBHandle *db = NULL; + if (!OpenDB(&db, dbid_lastseen)) + { + Log(LOG_LEVEL_ERR, "Unable to open last seen db"); + return; + } + + /* Update quality-of-connection entry */ + + char quality_key[CF_BUFSIZE]; + snprintf(quality_key, CF_BUFSIZE, "q%c%s", incoming ? 'i' : 'o', hostkey); + + KeyHostSeen newq = { .lastseen = timestamp }; + + KeyHostSeen q; + if (ReadDB(db, quality_key, &q, sizeof(q))) + { + newq.Q = QAverage(q.Q, newq.lastseen - q.lastseen, 0.4); + } + else + { + /* FIXME: more meaningful default value? */ + newq.Q = QDefinite(0); + } + WriteDB(db, quality_key, &newq, sizeof(newq)); + + /* Update forward mapping */ + + char hostkey_key[CF_BUFSIZE]; + snprintf(hostkey_key, CF_BUFSIZE, "k%s", hostkey); + + WriteDB(db, hostkey_key, address, strlen(address) + 1); + + /* Update reverse mapping */ + + char address_key[CF_BUFSIZE]; + snprintf(address_key, CF_BUFSIZE, "a%s", address); + + WriteDB(db, address_key, hostkey, strlen(hostkey) + 1); + + CloseDB(db); +} +/*****************************************************************************/ + +/* Lookup a reverse entry (IP->KeyHash) in lastseen database. */ +static bool Address2HostkeyInDB(DBHandle *db, const char *address, char *result, size_t result_size) +{ + char address_key[CF_BUFSIZE]; + char hostkey[CF_HOSTKEY_STRING_SIZE]; + + /* Address key: "a" + address */ + snprintf(address_key, CF_BUFSIZE, "a%s", address); + + if (!ReadDB(db, address_key, &hostkey, sizeof(hostkey))) + { + return false; + } + +#ifndef NDEBUG + /* Check for inconsistencies. Return success even if db is found + * inconsistent, since the reverse entry is already found. */ + + char hostkey_key[1 + CF_HOSTKEY_STRING_SIZE]; + char back_address[CF_BUFSIZE]; + + /* Hostkey key: "k" + hostkey */ + snprintf(hostkey_key, sizeof(hostkey_key), "k%s", hostkey); + + if (!ReadDB(db, hostkey_key, &back_address, sizeof(back_address))) + { + Log(LOG_LEVEL_WARNING, "Lastseen db inconsistency: " + "no key entry '%s' for existing host entry '%s'", + hostkey_key, address_key); + } +#endif + + strlcpy(result, hostkey, result_size); + return true; +} + +/*****************************************************************************/ + +/* Given an address it returns a key - its own key if address is 127.0.0.1, + * else it looks the "aADDRESS" entry in lastseen. */ +bool Address2Hostkey(char *dst, size_t dst_size, const char *address) +{ + bool retval = false; + dst[0] = '\0'; + + if ((strcmp(address, "127.0.0.1") == 0) || + (strcmp(address, "::1") == 0) || + (strcmp(address, VIPADDRESS) == 0)) + { + Log(LOG_LEVEL_DEBUG, + "Address2Hostkey: Returning local key for address %s", + address); + + if (PUBKEY) + { + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + HashPubKey(PUBKEY, digest, CF_DEFAULT_DIGEST); + HashPrintSafe(dst, dst_size, digest, + CF_DEFAULT_DIGEST, true); + retval = true; + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Local key not found, generate one with cf-key?"); + retval = false; + } + } + else /* lastseen lookup */ + { + DBHandle *db; + if (OpenDB(&db, dbid_lastseen)) + { + retval = Address2HostkeyInDB(db, address, dst, dst_size); + CloseDB(db); + + if (!retval) + { + Log(LOG_LEVEL_VERBOSE, + "Key digest for address '%s' was not found in lastseen db!", + address); + } + } + } + + return retval; +} + +char *HostkeyToAddress(const char *hostkey) +{ + DBHandle *db; + if (OpenDB(&db, dbid_lastseen)) + { + char hostkey_key[CF_HOSTKEY_STRING_SIZE + 1]; + char address[CF_BUFSIZE]; + + /* Hostkey key: "k" + hostkey */ + snprintf(hostkey_key, sizeof(hostkey_key), "k%s", hostkey); + + if (ReadDB(db, hostkey_key, &address, sizeof(address))) + { + CloseDB(db); + Log(LOG_LEVEL_DEBUG, "Found hostkey '%s' in lastseen LMDB", hostkey); + return xstrdup(address); + } + else + { + CloseDB(db); + Log(LOG_LEVEL_VERBOSE, "Could not find hostkey '%s' in lastseen LMDB", hostkey); + return NULL; + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed to open lastseen DB"); + return NULL; + } +} + +/** + * @brief detects whether input is a host/ip name or a key digest + * + * @param[in] key digest (SHA/MD5 format) or free host name string + * (character '=' is optional but recommended) + * @retval true if a key digest, false otherwise + */ +static bool IsDigestOrHost(const char *input) +{ + if (strncmp(input, "SHA=", 3) == 0) + { + return true; + } + else if (strncmp(input, "MD5=", 3) == 0) + { + return true; + } + else + { + return false; + } +} + +/** + * @brief check whether the lastseen DB is coherent or not. + * + * It is allowed for a aIP1 -> KEY1 to not have a reverse kKEY1 -> IP. + * kKEY1 *must* exist, but may point to another IP. + * Same for IP values, they *must* appear as aIP entries, but we don't + * care where they point to. + * So for every aIP->KEY1 entry there should be a kKEY1->whatever entry. + * And for every kKEY->IP1 entry there should be a aIP1->whatever entry. + * + * If a host changes IP, then we have a new entry aIP2 -> KEY1 together + * with the aIP1 -> KEY1 entry. ALLOWED. + * + * If a host changes key, then its entry will become aIP1 -> KEY2. + * Then still it will exist kKEY1 -> IP1 but also kKEY2 -> IP1. ALLOWED + * + * Can I have a IP value of some kKEY that does not have any aIP entry? + * NO because at some time aIP it was written in the database. + * SO EVERY kIP must be found in aIPS. + * kIPS SUBSET OF aIPS + * + * Can I have a KEY value of some aIP that does not have any kKEY entry? + * NO for the same reason. + * SO EVERY akey must be found in kkeys. + * aKEYS SUBSET OF kKEYS + * + * FIN + * + * @TODO P.S. redesign lastseen. Really, these whole requirements are + * implemented on top of a simple key-value store, no wonder it's such a + * mess. I believe that reverse mapping is not even needed since only + * aIP entries are ever looked up. kKEY entries can be deprecated and + * forget all the false notion of "schema consistency" in this key-value + * store... + * + * @retval true if the lastseen DB is coherent, false otherwise. + */ +bool IsLastSeenCoherent(void) +{ + DBHandle *db; + DBCursor *cursor; + + if (!OpenDB(&db, dbid_lastseen)) + { + char *db_path = DBIdToPath(dbid_lastseen); + Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path); + free(db_path); + return false; + } + + if (!NewDBCursor(db, &cursor)) + { + Log(LOG_LEVEL_ERR, "Unable to create lastseen database cursor"); + CloseDB(db); + return false; + } + + char *key; + void *value; + int ksize, vsize; + + Item *qKEYS = NULL; + Item *aKEYS = NULL; + Item *kKEYS = NULL; + Item *aIPS = NULL; + Item *kIPS = NULL; + + bool result = true; + while (NextDB(cursor, &key, &ksize, &value, &vsize)) + { + if (strcmp(key, "version") != 0 && + strncmp(key, "qi", 2) != 0 && + strncmp(key, "qo", 2) != 0 && + key[0] != 'k' && + key[0] != 'a') + { + Log(LOG_LEVEL_WARNING, + "lastseen db inconsistency, unexpected key: %s", + key); + result = false; + } + + if (key[0] == 'q' ) + { + if (strncmp(key,"qiSHA=",5)==0 || strncmp(key,"qoSHA=",5)==0 || + strncmp(key,"qiMD5=",5)==0 || strncmp(key,"qoMD5=",5)==0) + { + if (!IsItemIn(qKEYS, key+2)) + { + PrependItem(&qKEYS, key+2, NULL); + } + } + } + + if (key[0] == 'k' ) + { + if (strncmp(key, "kSHA=", 4)==0 || strncmp(key, "kMD5=", 4)==0) + { + if (!IsItemIn(kKEYS, key+1)) + { + PrependItem(&kKEYS, key+1, NULL); + } + if (!IsItemIn(kIPS, value)) + { + PrependItem(&kIPS, value, NULL); + } + } + } + + if (key[0] == 'a' ) + { + if (!IsItemIn(aIPS, key+1)) + { + PrependItem(&aIPS, key+1, NULL); + } + if (!IsItemIn(aKEYS, value)) + { + PrependItem(&aKEYS, value, NULL); + } + } + } + + DeleteDBCursor(cursor); + CloseDB(db); + + + /* For every kKEY->IP1 entry there should be a aIP1->whatever entry. + * So basically: kIPS SUBSET OF aIPS. */ + Item *kip = kIPS; + while (kip != NULL) + { + if (!IsItemIn(aIPS, kip->name)) + { + Log(LOG_LEVEL_WARNING, + "lastseen db inconsistency, found kKEY -> '%s' entry, " + "but no 'a%s' -> any key entry exists!", + kip->name, kip->name); + + result = false; + } + + kip = kip->next; + } + + /* For every aIP->KEY1 entry there should be a kKEY1->whatever entry. + * So basically: aKEYS SUBSET OF kKEYS. */ + Item *akey = aKEYS; + while (akey != NULL) + { + if (!IsItemIn(kKEYS, akey->name)) + { + Log(LOG_LEVEL_WARNING, + "lastseen db inconsistency, found aIP -> '%s' entry, " + "but no 'k%s' -> any ip entry exists!", + akey->name, akey->name); + + result = false; + } + + akey = akey->next; + } + + DeleteItemList(qKEYS); + DeleteItemList(aKEYS); + DeleteItemList(kKEYS); + DeleteItemList(aIPS); + DeleteItemList(kIPS); + + return result; +} + +/** + * @brief removes all traces of host 'ip' from lastseen DB + * + * @param[in] ip : either in (SHA/MD5 format) + * @param[in,out] digest: return corresponding digest of input host. + * If NULL, return nothing + * @param[in] digest_size: size of digest parameter + * @retval true if entry was deleted, false otherwise + */ +bool DeleteIpFromLastSeen(const char *ip, char *digest, size_t digest_size) +{ + DBHandle *db; + bool res = false; + + if (!OpenDB(&db, dbid_lastseen)) + { + char *db_path = DBIdToPath(dbid_lastseen); + Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path); + free(db_path); + return false; + } + + char bufkey[CF_BUFSIZE + 1]; + char bufhost[CF_BUFSIZE + 1]; + + strcpy(bufhost, "a"); + strlcat(bufhost, ip, CF_BUFSIZE); + + char key[CF_BUFSIZE]; + if (ReadDB(db, bufhost, &key, sizeof(key)) == true) + { + strcpy(bufkey, "k"); + strlcat(bufkey, key, CF_BUFSIZE); + if (HasKeyDB(db, bufkey, strlen(bufkey) + 1) == false) + { + res = false; + goto clean; + } + else + { + if (digest != NULL) + { + strlcpy(digest, bufkey + 1, digest_size); + } + DeleteDB(db, bufkey); + DeleteDB(db, bufhost); + res = true; + } + } + else + { + res = false; + goto clean; + } + + strcpy(bufkey, "qi"); + strlcat(bufkey, key, CF_BUFSIZE); + DeleteDB(db, bufkey); + + strcpy(bufkey, "qo"); + strlcat(bufkey, key, CF_BUFSIZE); + DeleteDB(db, bufkey); + +clean: + CloseDB(db); + return res; +} + +/** + * @brief removes all traces of key digest 'key' from lastseen DB + * + * @param[in] key : either in (SHA/MD5 format) + * @param[in,out] ip : return the key corresponding host. + * If NULL, return nothing + * @param[in] ip_size : length of ip parameter + * @param[in] a_entry_required : whether 'aIP_ADDR' entry is required for + * the 'kHOSTKEY' entry deletion + * @retval true if entry was deleted, false otherwise + */ +bool DeleteDigestFromLastSeen(const char *key, char *ip, size_t ip_size, bool a_entry_required) +{ + DBHandle *db; + bool res = false; + + if (!OpenDB(&db, dbid_lastseen)) + { + char *db_path = DBIdToPath(dbid_lastseen); + Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path); + free(db_path); + return false; + } + char bufkey[CF_BUFSIZE + 1]; + char bufhost[CF_BUFSIZE + 1]; + + strcpy(bufkey, "k"); + strlcat(bufkey, key, CF_BUFSIZE); + + char host[CF_BUFSIZE]; + if (ReadDB(db, bufkey, &host, sizeof(host)) == true) + { + strcpy(bufhost, "a"); + strlcat(bufhost, host, CF_BUFSIZE); + if (a_entry_required && !HasKeyDB(db, bufhost, strlen(bufhost) + 1)) + { + res = false; + goto clean; + } + else + { + if (ip != NULL) + { + strlcpy(ip, host, ip_size); + } + DeleteDB(db, bufhost); + DeleteDB(db, bufkey); + res = true; + } + } + else + { + res = false; + goto clean; + } + + strcpy(bufkey, "qi"); + strlcat(bufkey, key, CF_BUFSIZE); + DeleteDB(db, bufkey); + + strcpy(bufkey, "qo"); + strlcat(bufkey, key, CF_BUFSIZE); + DeleteDB(db, bufkey); + +clean: + CloseDB(db); + return res; +} + +/*****************************************************************************/ +bool ScanLastSeenQuality(LastSeenQualityCallback callback, void *ctx) +{ + StringMap *lastseen_db = LoadDatabaseToStringMap(dbid_lastseen); + if (!lastseen_db) + { + return false; + } + MapIterator it = MapIteratorInit(lastseen_db->impl); + MapKeyValue *item; + + Seq *hostkeys = SeqNew(100, free); + while ((item = MapIteratorNext(&it)) != NULL) + { + char *key = item->key; + /* Only look for "keyhost" entries */ + if (key[0] != 'k') + { + continue; + } + + SeqAppend(hostkeys, xstrdup(key + 1)); + } + for (size_t i = 0; i < SeqLength(hostkeys); ++i) + { + const char *hostkey = SeqAt(hostkeys, i); + + char keyhost_key[CF_BUFSIZE]; + snprintf(keyhost_key, CF_BUFSIZE, "k%s", hostkey); + char *address = NULL; + address = (char*)StringMapGet(lastseen_db, keyhost_key); + if (!address) + { + Log(LOG_LEVEL_ERR, "Failed to read address for key '%s'.", hostkey); + continue; + } + + char incoming_key[CF_BUFSIZE]; + snprintf(incoming_key, CF_BUFSIZE, "qi%s", hostkey); + KeyHostSeen *incoming = NULL; + incoming = (KeyHostSeen*)StringMapGet(lastseen_db, incoming_key); + if (incoming) + { + if (!(*callback)(hostkey, address, true, incoming, ctx)) + { + break; + } + } + + char outgoing_key[CF_BUFSIZE]; + snprintf(outgoing_key, CF_BUFSIZE, "qo%s", hostkey); + KeyHostSeen *outgoing = NULL; + outgoing = (KeyHostSeen*)StringMapGet(lastseen_db, outgoing_key); + if (outgoing) + { + if (!(*callback)(hostkey, address, false, outgoing, ctx)) + { + break; + } + } + } + + StringMapDestroy(lastseen_db); + SeqDestroy(hostkeys); + + return true; +} + +/*****************************************************************************/ + +int LastSeenHostKeyCount(void) +{ + CF_DB *dbp; + CF_DBC *dbcp; + QPoint entry; + char *key; + void *value; + int ksize, vsize; + + int count = 0; + + if (OpenDB(&dbp, dbid_lastseen)) + { + memset(&entry, 0, sizeof(entry)); + + if (NewDBCursor(dbp, &dbcp)) + { + while (NextDB(dbcp, &key, &ksize, &value, &vsize)) + { + /* Only look for valid "hostkey" entries */ + + if ((key[0] != 'k') || (value == NULL)) + { + continue; + } + + count++; + } + + DeleteDBCursor(dbcp); + } + + CloseDB(dbp); + } + + return count; +} +/** + * @brief removes all traces of entry 'input' from lastseen DB + * + * @param[in] key digest (SHA/MD5 format) or free host name string + * @param[in] must_be_coherent. false : delete if lastseen is incoherent, + * true : don't if lastseen is incoherent + * @param[out] equivalent. If input is a host, return its corresponding + * digest. If input is a digest, return its + * corresponding host. CAN BE NULL! If equivalent + * is null, it stays as NULL + * @retval 0 if entry was deleted, <>0 otherwise + */ +int RemoveKeysFromLastSeen(const char *input, bool must_be_coherent, + char *equivalent, size_t equivalent_size) +{ + bool is_coherent = false; + + if (must_be_coherent == true) + { + is_coherent = IsLastSeenCoherent(); + if (is_coherent == false) + { + Log(LOG_LEVEL_ERR, "Lastseen database is incoherent (there is not a 1-to-1 relationship between hosts and keys) and coherence check is enforced. Will not proceed to remove entries from it."); + return 254; + } + } + + bool is_digest; + is_digest = IsDigestOrHost(input); + + if (is_digest == true) + { + Log(LOG_LEVEL_VERBOSE, "Removing digest '%s' from lastseen database\n", input); + if (!DeleteDigestFromLastSeen(input, equivalent, equivalent_size, must_be_coherent)) + { + Log(LOG_LEVEL_ERR, "Unable to remove digest from lastseen database."); + return 252; + } + } + else + { + Log(LOG_LEVEL_VERBOSE, "Removing host '%s' from lastseen database\n", input); + if (DeleteIpFromLastSeen(input, equivalent, equivalent_size) == false) + { + Log(LOG_LEVEL_ERR, "Unable to remove host from lastseen database."); + return 253; + } + } + + Log(LOG_LEVEL_INFO, "Removed corresponding entries from lastseen database."); + + return 0; +} diff --git a/libpromises/lastseen.h b/libpromises/lastseen.h new file mode 100644 index 0000000000..7fb4878ba9 --- /dev/null +++ b/libpromises/lastseen.h @@ -0,0 +1,65 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_LASTSEEN_H +#define CFENGINE_LASTSEEN_H + +#include + +typedef struct +{ + time_t lastseen; + QPoint Q; // Average time between connections (rolling weighted average) +} KeyHostSeen; + +typedef enum +{ + LAST_SEEN_ROLE_CONNECT, + LAST_SEEN_ROLE_ACCEPT +} LastSeenRole; + + +bool Address2Hostkey(char *dst, size_t dst_size, const char *address); +char *HostkeyToAddress(const char *hostkey); + +void LastSaw1(const char *ipaddress, const char *hashstr, LastSeenRole role); +void LastSaw(const char *ipaddress, const unsigned char *digest, LastSeenRole role); + +bool DeleteIpFromLastSeen(const char *ip, char *digest, size_t digest_size); +bool DeleteDigestFromLastSeen(const char *key, char *ip, size_t ip_size, bool a_entry_required); + +/* + * Return false in order to stop iteration + */ +typedef bool (*LastSeenQualityCallback)(const char *hostkey, const char *address, + bool incoming, const KeyHostSeen *quality, + void *ctx); + +bool ScanLastSeenQuality(LastSeenQualityCallback callback, void *ctx); +int LastSeenHostKeyCount(void); +bool IsLastSeenCoherent(void); +int RemoveKeysFromLastSeen(const char *input, bool must_be_coherent, + char *equivalent, size_t equivalent_size); + +#endif diff --git a/libpromises/loading.c b/libpromises/loading.c new file mode 100644 index 0000000000..244a30d7f6 --- /dev/null +++ b/libpromises/loading.c @@ -0,0 +1,658 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: remove +#include /* IsCf3VarString */ +#include /* FatalError */ + + +static Policy *LoadPolicyFile(EvalContext *ctx, GenericAgentConfig *config, const char *policy_file, + StringMap *policy_files_hashes, StringSet *parsed_files_checksums, + StringSet *failed_files); + + + + +/* + * The difference between filename and input_input file is that the latter is the file specified by -f or + * equivalently the file containing body common control. This will hopefully be squashed in later refactoring. + */ +Policy *Cf3ParseFile(const GenericAgentConfig *config, const char *input_path) +{ + struct stat statbuf; + + if (stat(input_path, &statbuf) == -1) + { + if (config->ignore_missing_inputs) + { + return PolicyNew(); + } + + Log(LOG_LEVEL_ERR, "Can't stat file '%s' for parsing. (stat: %s)", input_path, GetErrorStr()); + DoCleanupAndExit(EXIT_FAILURE); + } + else if (S_ISDIR(statbuf.st_mode)) + { + if (config->ignore_missing_inputs) + { + return PolicyNew(); + } + + Log(LOG_LEVEL_ERR, "Can't parse directory '%s'.", input_path); + DoCleanupAndExit(EXIT_FAILURE); + } + +#ifndef _WIN32 + if (config->check_not_writable_by_others && (statbuf.st_mode & (S_IWGRP | S_IWOTH))) + { + Log(LOG_LEVEL_ERR, "File %s (owner %ju) is writable by others (security exception)", input_path, (uintmax_t)statbuf.st_uid); + DoCleanupAndExit(EXIT_FAILURE); + } +#endif + + Log(LOG_LEVEL_VERBOSE, "BEGIN parsing file: %s", input_path); + + if (!FileCanOpen(input_path, "r")) + { + Log(LOG_LEVEL_ERR, "Can't open file '%s' for parsing", input_path); + DoCleanupAndExit(EXIT_FAILURE); + } + + Policy *policy = NULL; + if (StringEndsWith(input_path, ".json")) + { + Writer *contents = FileRead(input_path, SIZE_MAX, NULL); + if (!contents) + { + Log(LOG_LEVEL_ERR, "Error reading JSON input file '%s'", input_path); + return NULL; + } + JsonElement *json_policy = NULL; + const char *data = StringWriterData(contents); + if (JsonParse(&data, &json_policy) != JSON_PARSE_OK) + { + Log(LOG_LEVEL_ERR, "Error parsing JSON input file '%s'", input_path); + WriterClose(contents); + return NULL; + } + + policy = PolicyFromJson(json_policy); + if (policy == NULL) + { + Log(LOG_LEVEL_ERR, + "Failed to deserialize a policy from the JSON input file '%s'", + input_path); + JsonDestroy(json_policy); + WriterClose(contents); + return NULL; + } + + JsonDestroy(json_policy); + WriterClose(contents); + } + else + { + if (config->agent_type == AGENT_TYPE_COMMON) + { + policy = ParserParseFile(config->agent_type, input_path, config->agent_specific.common.parser_warnings, config->agent_specific.common.parser_warnings_error); + } + else + { + policy = ParserParseFile(config->agent_type, input_path, 0, 0); + } + } + + Log(LOG_LEVEL_VERBOSE, "END parsing file: %s", input_path); + return policy; +} + +static Policy *LoadPolicyInputFiles(EvalContext *ctx, GenericAgentConfig *config, const Rlist *inputs, + StringMap *policy_files_hashes, StringSet *parsed_files_checksums, + StringSet *failed_files) +{ + Policy *policy = PolicyNew(); + + for (const Rlist *rp = inputs; rp; rp = rp->next) + { + if (rp->val.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, "Non-file object in inputs list"); + continue; + } + + const char *unresolved_input = RlistScalarValue(rp); + if (IsExpandable(unresolved_input)) + { + PolicyResolve(ctx, policy, config); + } + + Rval resolved_input = EvaluateFinalRval(ctx, policy, NULL, "sys", rp->val, true, NULL); + + Policy *aux_policy = NULL; + switch (resolved_input.type) + { + case RVAL_TYPE_SCALAR: + if (IsCf3VarString(RvalScalarValue(resolved_input))) + { + Log(LOG_LEVEL_ERR, "Unresolved variable '%s' in input list, cannot parse", RvalScalarValue(resolved_input)); + break; + } + + aux_policy = LoadPolicyFile(ctx, config, + GenericAgentResolveInputPath(config, RvalScalarValue(resolved_input)), + policy_files_hashes, parsed_files_checksums, failed_files); + break; + + case RVAL_TYPE_LIST: + aux_policy = LoadPolicyInputFiles(ctx, config, + RvalRlistValue(resolved_input), + policy_files_hashes, parsed_files_checksums, failed_files); + break; + + default: + ProgrammingError("Unknown type in input list for parsing: %d", resolved_input.type); + break; + } + + if (aux_policy) + { + policy = PolicyMerge(policy, aux_policy); + } + + RvalDestroy(resolved_input); + } + + return policy; +} + +// TODO: should be replaced by something not complected with loading +static void ShowContext(EvalContext *ctx) +{ + Seq *hard_contexts = SeqNew(1000, NULL); + Seq *soft_contexts = SeqNew(1000, NULL); + + { + ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true); + Class *cls = NULL; + while ((cls = ClassTableIteratorNext(iter))) + { + if (cls->is_soft) + { + SeqAppend(soft_contexts, cls->name); + } + else + { + SeqAppend(hard_contexts, cls->name); + } + } + + ClassTableIteratorDestroy(iter); + } + + SeqSort(soft_contexts, StrCmpWrapper, NULL); + SeqSort(hard_contexts, StrCmpWrapper, NULL); + + Log(LOG_LEVEL_VERBOSE, "----------------------------------------------------------------"); + + { + Log(LOG_LEVEL_VERBOSE, "BEGIN Discovered hard classes:"); + + for (size_t i = 0; i < SeqLength(hard_contexts); i++) + { + const char *context = SeqAt(hard_contexts, i); + Log(LOG_LEVEL_VERBOSE, "C: discovered hard class %s", context); + } + + Log(LOG_LEVEL_VERBOSE, "END Discovered hard classes"); + } + + Log(LOG_LEVEL_VERBOSE, "----------------------------------------------------------------"); + + if (SeqLength(soft_contexts)) + { + Log(LOG_LEVEL_VERBOSE, "BEGIN initial soft classes:"); + + for (size_t i = 0; i < SeqLength(soft_contexts); i++) + { + const char *context = SeqAt(soft_contexts, i); + Log(LOG_LEVEL_VERBOSE, "C: added soft class %s", context); + } + + Log(LOG_LEVEL_VERBOSE, "END initial soft classes"); + } + + SeqDestroy(hard_contexts); + SeqDestroy(soft_contexts); +} + +static void RenameMainBundle(EvalContext *ctx, Policy *policy) +{ + assert(policy != NULL); + assert(ctx != NULL); + assert(policy->bundles != NULL); + char *const entry_point = GetRealPath(EvalContextGetEntryPoint(ctx)); + if (NULL_OR_EMPTY(entry_point)) + { + free(entry_point); + return; + } + Seq *bundles = policy->bundles; + int length = SeqLength(bundles); + bool removed = false; + for (int i = 0; i < length; ++i) + { + Bundle *const bundle = SeqAt(bundles, i); + if (StringEqual(bundle->name, "__main__")) + { + char *abspath = GetRealPath(bundle->source_path); + if (StringEqual(abspath, entry_point)) + { + Log(LOG_LEVEL_VERBOSE, + "Redefining __main__ bundle from file %s to be main", + abspath); + strncpy(bundle->name, "main", 4+1); + // "__main__" is always big enough for "main" + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Dropping __main__ bundle from file %s (entry point: %s)", + abspath, + entry_point); + removed = true; + SeqSet(bundles, i, NULL); // SeqSet calls destroy function + } + free(abspath); + } + } + if (removed) + { + SeqRemoveNulls(bundles); + } + free(entry_point); +} + +static Policy *LoadPolicyFile(EvalContext *ctx, GenericAgentConfig *config, const char *policy_file, + StringMap *policy_files_hashes, StringSet *parsed_files_checksums, + StringSet *failed_files) +{ + unsigned char digest[EVP_MAX_MD_SIZE + 1] = { 0 }; + char hashbuffer[CF_HOSTKEY_STRING_SIZE] = { 0 }; + + HashFile(policy_file, digest, CF_DEFAULT_DIGEST, false); + HashPrintSafe(hashbuffer, sizeof(hashbuffer), digest, + CF_DEFAULT_DIGEST, true); + + Log(LOG_LEVEL_DEBUG, "Hashed policy file %s to %s", policy_file, hashbuffer); + + if (StringMapHasKey(policy_files_hashes, policy_file)) + { + Log(LOG_LEVEL_DEBUG, "Skipping loading of duplicate policy file %s", policy_file); + return NULL; + } + else if (StringSetContains(parsed_files_checksums, hashbuffer)) + { + Log(LOG_LEVEL_DEBUG, "Skipping loading of duplicate (detected by hash) policy file %s", + policy_file); + return NULL; + } + else + { + Log(LOG_LEVEL_DEBUG, "Loading policy file %s", policy_file); + } + + Policy *policy = Cf3ParseFile(config, policy_file); + + StringMapInsert(policy_files_hashes, xstrdup(policy_file), xstrdup(hashbuffer)); + StringSetAdd(parsed_files_checksums, xstrdup(hashbuffer)); + + if (policy) + { + RenameMainBundle(ctx, policy); + Seq *errors = SeqNew(10, free); + if (!PolicyCheckPartial(policy, errors)) + { + Writer *writer = FileWriter(stderr); + for (size_t i = 0; i < errors->length; i++) + { + PolicyErrorWrite(writer, errors->data[i]); + } + WriterClose(writer); + SeqDestroy(errors); + + StringSetAdd(failed_files, xstrdup(policy_file)); + PolicyDestroy(policy); + return NULL; + } + + SeqDestroy(errors); + } + else + { + StringSetAdd(failed_files, xstrdup(policy_file)); + return NULL; + } + + PolicyResolve(ctx, policy, config); + + Body *body_common_control = PolicyGetBody(policy, NULL, "common", "control"); + Body *body_file_control = PolicyGetBody(policy, NULL, "file", "control"); + + if (body_common_control) + { + Seq *potential_inputs = BodyGetConstraint(body_common_control, "inputs"); + Constraint *cp = EffectiveConstraint(ctx, potential_inputs); + SeqDestroy(potential_inputs); + + if (cp) + { + Policy *aux_policy = LoadPolicyInputFiles(ctx, config, RvalRlistValue(cp->rval), + policy_files_hashes, parsed_files_checksums, + failed_files); + if (aux_policy) + { + policy = PolicyMerge(policy, aux_policy); + } + } + } + + if (body_file_control) + { + Seq *potential_inputs = BodyGetConstraint(body_file_control, "inputs"); + Constraint *cp = EffectiveConstraint(ctx, potential_inputs); + SeqDestroy(potential_inputs); + + if (cp) + { + Policy *aux_policy = LoadPolicyInputFiles(ctx, config, RvalRlistValue(cp->rval), + policy_files_hashes, parsed_files_checksums, + failed_files); + if (aux_policy) + { + policy = PolicyMerge(policy, aux_policy); + } + } + } + + return policy; +} + +static bool VerifyBundleSequence(EvalContext *ctx, const Policy *policy, const GenericAgentConfig *config) +{ + Rlist *fallback = NULL; + const Rlist *bundlesequence = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_BUNDLESEQUENCE); + if (!bundlesequence) + { + RlistAppendScalar(&fallback, "main"); + bundlesequence = fallback; + } + + const char *name; + bool ok = true; + for (const Rlist *rp = bundlesequence; rp != NULL; rp = rp->next) + { + switch (rp->val.type) + { + case RVAL_TYPE_SCALAR: + name = RlistScalarValue(rp); + break; + + case RVAL_TYPE_FNCALL: + name = RlistFnCallValue(rp)->name; + break; + + default: + name = NULL; + ok = false; + { + Writer *w = StringWriter(); + WriterWrite(w, "Illegal item found in bundlesequence '"); + RvalWrite(w, rp->val); + WriterWrite(w, "'"); + Log(LOG_LEVEL_ERR, "%s", StringWriterData(w)); + WriterClose(w); + } + continue; + } + + if (!config->ignore_missing_bundles && !PolicyGetBundle(policy, NULL, NULL, name)) + { + Log(LOG_LEVEL_ERR, "Bundle '%s' listed in the bundlesequence is not a defined bundle", name); + ok = false; + } + } + + RlistDestroy(fallback); + return ok; +} + +/** + * @brief Reads the release_id file from inputs and return a JsonElement. + */ +static JsonElement *ReadReleaseIdFileFromInputs() +{ + char filename[CF_MAXVARSIZE]; + + GetReleaseIdFile(GetInputDir(), filename, sizeof(filename)); + + struct stat sb; + if (stat(filename, &sb) == -1) + { + return NULL; + } + + JsonElement *validated_doc = NULL; + JsonParseError err = JsonParseFile(filename, 4096, &validated_doc); + if (err != JSON_PARSE_OK) + { + Log(LOG_LEVEL_WARNING, + "Could not read release ID: '%s' did not contain valid JSON data. " + "(JsonParseFile: '%s')", filename, JsonParseErrorToString(err)); + } + + return validated_doc; +} + +Policy *LoadPolicy(EvalContext *ctx, GenericAgentConfig *config) +{ + StringMap *policy_files_hashes = StringMapNew(); + StringSet *parsed_files_checksums = StringSetNew(); + StringSet *failed_files = StringSetNew(); + + Banner("Loading policy"); + + Policy *policy = LoadPolicyFile(ctx, config, config->input_file, + policy_files_hashes, parsed_files_checksums, + failed_files); + + if (StringSetSize(failed_files) > 0) + { + Log(LOG_LEVEL_ERR, "There are syntax errors in policy files"); + DoCleanupAndExit(EXIT_FAILURE); + } + + StringSetDestroy(parsed_files_checksums); + StringSetDestroy(failed_files); + if (policy != NULL) + { + policy->policy_files_hashes = policy_files_hashes; + } + else + { + StringMapDestroy(policy_files_hashes); + } + + { + Seq *errors = SeqNew(100, PolicyErrorDestroy); + + if (PolicyCheckPartial(policy, errors)) + { + if (!config->bundlesequence && + (PolicyIsRunnable(policy) || config->check_runnable)) + { + Log(LOG_LEVEL_VERBOSE, + "Running full policy integrity checks"); + PolicyCheckRunnable(ctx, policy, errors); + } + } + + if (SeqLength(errors) > 0) + { + Writer *writer = FileWriter(stderr); + for (size_t i = 0; i < errors->length; i++) + { + PolicyErrorWrite(writer, errors->data[i]); + } + WriterClose(writer); + SeqDestroy(errors); + DoCleanupAndExit(EXIT_FAILURE); // TODO: do not exit + } + + SeqDestroy(errors); + } + + if (LogGetGlobalLevel() >= LOG_LEVEL_VERBOSE) + { + Legend(); + ShowContext(ctx); + } + + if (config->agent_type == AGENT_TYPE_AGENT) + { + Banner("Preliminary variable/class-context convergence"); + } + + if (policy) + { + /* store names of all bundles in the EvalContext */ + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + Bundle *bp = SeqAt(policy->bundles, i); + EvalContextPushBundleName(ctx, bp->name); + } + + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + Bundle *bp = SeqAt(policy->bundles, i); + EvalContextStackPushBundleFrame(ctx, bp, NULL, false); + + for (size_t j = 0; j < SeqLength(bp->sections); j++) + { + BundleSection *sp = SeqAt(bp->sections, j); + EvalContextStackPushBundleSectionFrame(ctx, sp); + + for (size_t ppi = 0; ppi < SeqLength(sp->promises); ppi++) + { + Promise *pp = SeqAt(sp->promises, ppi); + + /* Skip constraint syntax verification through all + * slist/container iterations for cf-promises! cf-agent + * still checks though. */ + if (config->agent_type != AGENT_TYPE_COMMON) + { + ExpandPromise(ctx, pp, CommonEvalPromise, NULL); + } + } + + EvalContextStackPopFrame(ctx); + } + + EvalContextStackPopFrame(ctx); + } + + PolicyResolve(ctx, policy, config); + + // TODO: need to move this inside PolicyCheckRunnable eventually. + if (!config->bundlesequence && config->check_runnable) + { + // only verify policy-defined bundlesequence for cf-agent, cf-promises + if ((config->agent_type == AGENT_TYPE_AGENT) || + (config->agent_type == AGENT_TYPE_COMMON)) + { + if (!VerifyBundleSequence(ctx, policy, config)) + { + FatalError(ctx, "Errors in promise bundles: could not verify bundlesequence"); + } + } + } + } + + if (config->agent_type == AGENT_TYPE_AGENT && + config->agent_specific.agent.bootstrap_argument != NULL) + { + /* Doing bootstrap, set the release_id to "bootstrap" and also write it + * into a file so that sub-agent executed as part of bootstrap can just + * pick it up and then rewrite it with the actual value from + * masterfiles. */ + policy->release_id = xstrdup("bootstrap"); + + char filename[PATH_MAX]; + GetReleaseIdFile(GetInputDir(), filename, sizeof(filename)); + FILE *release_id_stream = safe_fopen_create_perms(filename, "w", + CF_PERMS_DEFAULT); + if (release_id_stream == NULL) + { + Log(LOG_LEVEL_ERR, "Failed to open the release_id file for writing during bootstrap"); + } + else + { + Writer *release_id_writer = FileWriter(release_id_stream); + WriterWrite(release_id_writer, "{ releaseId: \"bootstrap\" }\n"); + WriterClose(release_id_writer); + } + } + else + { + JsonElement *validated_doc = ReadReleaseIdFileFromInputs(); + if (validated_doc) + { + const char *release_id = JsonObjectGetAsString(validated_doc, "releaseId"); + if (release_id) + { + policy->release_id = xstrdup(release_id); + } + JsonDestroy(validated_doc); + } + } + + return policy; +} diff --git a/libpromises/loading.h b/libpromises/loading.h new file mode 100644 index 0000000000..36013a1915 --- /dev/null +++ b/libpromises/loading.h @@ -0,0 +1,34 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_LOADING_H +#define CFENGINE_LOADING_H + +#include +#include + +Policy *LoadPolicy(EvalContext *ctx, GenericAgentConfig *config); +Policy *Cf3ParseFile(const GenericAgentConfig *config, const char *input_path); + +#endif diff --git a/libpromises/locks.c b/libpromises/locks.c new file mode 100644 index 0000000000..ae408fab21 --- /dev/null +++ b/libpromises/locks.c @@ -0,0 +1,1293 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LMDB +// Be careful if you want to change this, +// it must match mdb_env_get_maxkeysize(env) +#define LMDB_MAX_KEY_SIZE 511 +#endif + +#define CFLOGSIZE 1048576 /* Size of lock-log before rotation */ +#define CF_MAXLOCKNUM 8192 + +#define CF_CRITIAL_SECTION "CF_CRITICAL_SECTION" + +#define LOG_LOCK_ENTRY(__lock, __lock_sum, __lock_data) \ + log_lock("Entering", __FUNCTION__, __lock, __lock_sum, __lock_data) +#define LOG_LOCK_EXIT(__lock, __lock_sum, __lock_data) \ + log_lock("Exiting", __FUNCTION__, __lock, __lock_sum, __lock_data) +#define LOG_LOCK_OP(__lock, __lock_sum, __lock_data) \ + log_lock("Performing", __FUNCTION__, __lock, __lock_sum, __lock_data) + +/** + * Map the locks DB usage percentage to the lock horizon interval (how old locks + * we want to keep). + */ +#define N_LOCK_HORIZON_USAGE_INTERVALS 4 /* 0-25, 26-50,... */ +static const time_t LOCK_HORIZON_USAGE_INTERVALS[N_LOCK_HORIZON_USAGE_INTERVALS] = { + 0, /* plenty of space, no cleanup needed (0 is a special + * value) */ + 4 * SECONDS_PER_WEEK, /* used to be the fixed value */ + 2 * SECONDS_PER_WEEK, /* a bit more aggressive, but still reasonable */ + SECONDS_PER_WEEK, /* as far as we want to go to avoid making long locks + * unreliable and practically non-functional */ +}; + +typedef struct CfLockStack_ { + char lock[CF_BUFSIZE]; + char last[CF_BUFSIZE]; + struct CfLockStack_ *previous; +} CfLockStack; + +static CfLockStack *LOCK_STACK = NULL; + +static void PushLock(char *lock, char *last) +{ + CfLockStack *new_lock = malloc(sizeof(CfLockStack)); + strlcpy(new_lock->lock, lock, CF_BUFSIZE); + strlcpy(new_lock->last, last, CF_BUFSIZE); + + new_lock->previous = LOCK_STACK; + LOCK_STACK = new_lock; +} + +static CfLockStack *PopLock() +{ + if (!LOCK_STACK) + { + return NULL; + } + CfLockStack *lock = LOCK_STACK; + LOCK_STACK = lock->previous; + return lock; +} + +static void CopyLockDatabaseAtomically(const char *from, const char *to, + const char *from_pretty_name, + const char *to_pretty_name); + +CF_DB *OpenLock() +{ + CF_DB *dbp; + + if (!OpenDB(&dbp, dbid_locks)) + { + return NULL; + } + + return dbp; +} + +void CloseLock(CF_DB *dbp) +{ + if (dbp) + { + CloseDB(dbp); + } +} + +static pthread_once_t lock_cleanup_once = PTHREAD_ONCE_INIT; /* GLOBAL_X */ + +#ifdef LMDB +static inline void log_lock(const char *op, + const char *function, + const char *lock, + const char *lock_sum, + const LockData *lock_data) +{ + /* Check log level first to save cycles when not in debug mode. */ + if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) + { + if (lock_data) + { + LogDebug(LOG_MOD_LOCKS, "%s lock operation in '%s()': " + "lock_id = '%s', lock_checksum = '%s', " + "lock.pid = '%d', lock.time = '%d', " + "lock.process_start_time = '%d'", + op, function, lock, lock_sum, + (int)lock_data->pid, (int)lock_data->time, + (int)lock_data->process_start_time); + } + else + { + LogDebug(LOG_MOD_LOCKS, "%s lock operation in '%s()'. " + "lock_id = '%s', lock_checksum = '%s'", + op, function, lock, lock_sum); + } + } +} + +static void HashLockKeyIfNecessary(const char *const istring, char *const ohash) +{ + assert(strlen("CF_CRITICAL_SECTION") < LMDB_MAX_KEY_SIZE); + assert(strlen("lock.track_license_bundle.track_license") < LMDB_MAX_KEY_SIZE); + StringCopyTruncateAndHashIfNecessary(istring, ohash, LMDB_MAX_KEY_SIZE); +} +#endif + +static bool WriteLockData(CF_DB *dbp, const char *lock_id, LockData *lock_data) +{ + bool ret; + +#ifdef LMDB + unsigned char digest2[LMDB_MAX_KEY_SIZE]; + + HashLockKeyIfNecessary(lock_id, digest2); + + LOG_LOCK_ENTRY(lock_id, digest2, lock_data); + ret = WriteDB(dbp, digest2, lock_data, sizeof(LockData)); + LOG_LOCK_EXIT(lock_id, digest2, lock_data); +#else + ret = WriteDB(dbp, lock_id, lock_data, sizeof(LockData)); +#endif + + return ret; +} + +static bool WriteLockDataCurrent(CF_DB *dbp, const char *lock_id) +{ + LockData lock_data = { 0 }; + lock_data.pid = getpid(); + lock_data.time = time(NULL); + lock_data.process_start_time = GetProcessStartTime(getpid()); + + return WriteLockData(dbp, lock_id, &lock_data); +} + +static int WriteLock(const char *name) +{ + CF_DB *dbp = OpenLock(); + + if (dbp == NULL) + { + return -1; + } + + ThreadLock(cft_lock); + WriteLockDataCurrent(dbp, name); + + CloseLock(dbp); + ThreadUnlock(cft_lock); + + return 0; +} + +static time_t FindLockTime(const char *name) +{ + bool ret; + CF_DB *dbp = OpenLock(); + if (dbp == NULL) + { + return -1; + } + + LockData entry = { 0 }; + entry.process_start_time = PROCESS_START_TIME_UNKNOWN; + +#ifdef LMDB + unsigned char ohash[LMDB_MAX_KEY_SIZE]; + HashLockKeyIfNecessary(name, ohash); + + LOG_LOCK_ENTRY(name, ohash, &entry); + ret = ReadDB(dbp, ohash, &entry, sizeof(entry)); + LOG_LOCK_EXIT(name, ohash, &entry); +#else + ret = ReadDB(dbp, name, &entry, sizeof(entry)); +#endif + + if (ret) + { + CloseLock(dbp); + return entry.time; + } + else + { + CloseLock(dbp); + return -1; + } +} + +static void RemoveDates(char *s) +{ + int i, a = 0, b = 0, c = 0, d = 0; + char *dayp = NULL, *monthp = NULL, *sp; + char *days[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; + char *months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +// Canonifies or blanks our times/dates for locks where there would be an explosion of state + + /* Has s always been generated by something that uses two-digit hh:mm:ss? + * Are there any time-zones whose abbreviations are shorter than three + * letters? + */ + if (strlen(s) < sizeof("Fri Oct 1 15:15:23 EST 2010") - 1) + { + // Probably not a full date + return; + } + + for (i = 0; i < 7; i++) + { + if ((dayp = strstr(s, days[i]))) + { + *dayp = 'D'; + *(dayp + 1) = 'A'; + *(dayp + 2) = 'Y'; + break; + } + } + + for (i = 0; i < 12; i++) + { + if ((monthp = strstr(s, months[i]))) + { + *monthp = 'M'; + *(monthp + 1) = 'O'; + *(monthp + 2) = 'N'; + break; + } + } + + if (dayp && monthp) // looks like a full date + { + sscanf(monthp + 4, "%d %d:%d:%d", &a, &b, &c, &d); + + if (a * b * c * d == 0) + { + // Probably not a date + return; + } + + for (sp = monthp + 4; *sp != '\0'; sp++) + { + if (sp > monthp + 15) + { + break; + } + + if (isdigit((int)*sp)) + { + *sp = 't'; + } + } + } +} + +static int RemoveLock(const char *name) +{ + CF_DB *dbp = OpenLock(); + if (dbp == NULL) + { + return -1; + } + + ThreadLock(cft_lock); +#ifdef LMDB + unsigned char digest2[LMDB_MAX_KEY_SIZE]; + + HashLockKeyIfNecessary(name, digest2); + + LOG_LOCK_ENTRY(name, digest2, NULL); + DeleteDB(dbp, digest2); + LOG_LOCK_EXIT(name, digest2, NULL); +#else + DeleteDB(dbp, name); +#endif + ThreadUnlock(cft_lock); + + CloseLock(dbp); + return 0; +} + +static bool NoOrObsoleteLock(LockData *entry, ARG_UNUSED size_t entry_size, size_t *max_old) +{ + assert((entry == NULL) || (entry_size == sizeof(LockData))); + + if (entry == NULL) + { + return true; + } + + time_t now = time(NULL); + if ((now - entry->time) <= (time_t) *max_old) + { + Log(LOG_LEVEL_DEBUG, "Giving time to process '%d' (holding lock for %ld s)", entry->pid, (now - entry->time)); + } + return ((now - entry->time) > (time_t) *max_old); +} + +void WaitForCriticalSection(const char *section_id) +{ + ThreadLock(cft_lock); + + CF_DB *dbp = OpenLock(); + if (dbp == NULL) + { + Log(LOG_LEVEL_CRIT, "Failed to open lock database when waiting for critical section"); + ThreadUnlock(cft_lock); + return; + } + + time_t started = time(NULL); + LockData entry = { 0 }; + entry.pid = getpid(); + entry.process_start_time = PROCESS_START_TIME_UNKNOWN; + +#ifdef LMDB + unsigned char ohash[LMDB_MAX_KEY_SIZE]; + HashLockKeyIfNecessary(section_id, ohash); + Log(LOG_LEVEL_DEBUG, "Hashed critical section lock '%s' to '%s'", section_id, ohash); + section_id = ohash; +#endif + + /* If another agent has been waiting more than a minute, it means there + is likely crash detritus to clear up... After a minute we take our + chances ... */ + size_t max_old = 60; + + Log(LOG_LEVEL_DEBUG, "Acquiring critical section lock '%s'", section_id); + bool got_lock = false; + while (!got_lock && ((time(NULL) - started) <= (time_t) max_old)) + { + entry.time = time(NULL); + got_lock = OverwriteDB(dbp, section_id, &entry, sizeof(entry), + (OverwriteCondition) NoOrObsoleteLock, &max_old); + if (!got_lock) + { + Log(LOG_LEVEL_DEBUG, "Waiting for critical section lock '%s'", section_id); + sleep(1); + } + } + + /* If we still haven't gotten the lock, let's try the biggest hammer we + * have. */ + if (!got_lock) + { + Log(LOG_LEVEL_NOTICE, "Failed to wait for critical section lock '%s', force-writing new lock", section_id); + if (!WriteDB(dbp, section_id, &entry, sizeof(entry))) + { + Log(LOG_LEVEL_CRIT, "Failed to force-write critical section lock '%s'", section_id); + } + } + else + { + Log(LOG_LEVEL_DEBUG, "Acquired critical section lock '%s'", section_id); + } + + CloseLock(dbp); + ThreadUnlock(cft_lock); +} + +void ReleaseCriticalSection(const char *section_id) +{ + Log(LOG_LEVEL_DEBUG, "Releasing critical section lock '%s'", section_id); + if (RemoveLock(section_id) == 0) + { + Log(LOG_LEVEL_DEBUG, "Released critical section lock '%s'", section_id); + } + else + { + Log(LOG_LEVEL_DEBUG, "Failed to release critical section lock '%s'", section_id); + } +} + +static time_t FindLock(char *last) +{ + time_t mtime; + + if ((mtime = FindLockTime(last)) == -1) + { + /* Do this to prevent deadlock loops from surviving if IfElapsed > T_sched */ + + if (WriteLock(last) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to lock %s", last); + return 0; + } + + return 0; + } + else + { + return mtime; + } +} + +static void LocksCleanup(void) +{ + CfLockStack *lock; + while ((lock = PopLock()) != NULL) + { + CfLock best_guess = { + .lock = xstrdup(lock->lock), + .last = xstrdup(lock->last), + }; + YieldCurrentLock(best_guess); + free(lock); + } +} + +static void RegisterLockCleanup(void) +{ + RegisterCleanupFunction(&LocksCleanup); +} + +/** + * Return a type template for the promise for lock-type + * identification. WARNING: instead of truncation, it does not include any + * parts (i.e. constraints or promise type) that don't fit. + * + * @WARNING currently it only prints up to the 5 first constraints (WHY?) + */ +static void PromiseTypeString(char *dst, size_t dst_size, const Promise *pp) +{ + const char *sp = PromiseGetPromiseType(pp); + size_t sp_len = strlen(sp); + + dst[0] = '\0'; + size_t dst_len = 0; + + if (sp_len + 1 < dst_size) + { + strcpy(dst, sp); + strcat(dst, "."); + dst_len += sp_len + 1; + } + + if (pp->conlist != NULL) + { + /* Number of constraints (attributes) of that promise. */ + size_t cons_num = SeqLength(pp->conlist); + for (size_t i = 0; (i < 5) && (i < cons_num); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + const char *con = cp->lval; /* the constraint */ + + /* Exception for args (promise type commands), + by symmetry, for locking. */ + if (strcmp(con, "args") == 0) + { + continue; + } + + /* Exception for arglist (promise type commands), + by symmetry, for locking. */ + if (strcmp(con, "arglist") == 0) + { + continue; + } + + size_t con_len = strlen(con); + if (dst_len + con_len + 1 < dst_size) + { + strcat(dst, con); + strcat(dst, "."); + dst_len += con_len + 1; + } + } + } +} + +/** + * A helper best-effort function to prevent us from killing non CFEngine + * processes with matching PID-start_time combinations **when/where it's easy to + * check**. + */ +static bool IsCfengineLockHolder(pid_t pid) +{ + char procfile[PATH_MAX]; + snprintf(procfile, PATH_MAX, "/proc/%ju/comm", (uintmax_t) pid); + int f = open(procfile, O_RDONLY); + /* On platforms where /proc doesn't exist, we would have to do a more + complicated check probably not worth it in this helper best-effort + function. */ + if (f == -1) + { + /* assume true where we cannot check */ + return true; + } + + /* more than any possible CFEngine lock holder's name */ + char command[32] = {0}; + ssize_t n_read = FullRead(f, command, sizeof(command)); + close(f); + if ((n_read <= 1) || (n_read == sizeof(command))) + { + Log(LOG_LEVEL_VERBOSE, "Failed to get command for process %ju", (uintmax_t) pid); + /* assume true where we cannot check */ + return true; + } + if (command[n_read - 1] == '\n') + { + command[n_read - 1] = '\0'; + } + + /* potential CFEngine lock holders (others like cf-net, cf-key,... are not + * supposed/expected to be lock holders) */ + const char *const cfengine_procs[] = { + "cf-promises", + "lt-cf-agent", /* when running from a build with 'libtool --mode=execute' */ + "cf-agent", + "cf-execd", + "cf-serverd", + "cf-monitord", + "cf-hub", + NULL + }; + for (size_t i = 0; cfengine_procs[i] != NULL; i++) + { + if (StringEqual(cfengine_procs[i], command)) + { + return true; + } + } + Log(LOG_LEVEL_DEBUG, "'%s' not considered a CFEngine process", command); + return false; +} + +static bool KillLockHolder(const char *lock) +{ + bool ret; + CF_DB *dbp = OpenLock(); + if (dbp == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to open locks database"); + return false; + } + + LockData lock_data = { 0 }; + lock_data.process_start_time = PROCESS_START_TIME_UNKNOWN; + +#ifdef LMDB + unsigned char ohash[LMDB_MAX_KEY_SIZE]; + HashLockKeyIfNecessary(lock, ohash); + + LOG_LOCK_ENTRY(lock, ohash, &lock_data); + ret = ReadDB(dbp, ohash, &lock_data, sizeof(lock_data)); + LOG_LOCK_EXIT(lock, ohash, &lock_data); +#else + ret = ReadDB(dbp, lock, &lock_data, sizeof(lock_data)); +#endif + + if (!ret) + { + /* No lock found */ + CloseLock(dbp); + return true; + } + + CloseLock(dbp); + + if (!IsCfengineLockHolder(lock_data.pid)) { + Log(LOG_LEVEL_VERBOSE, + "Lock holder with PID %ju was replaced by a non CFEngine process, ignoring request to kill it", + (uintmax_t) lock_data.pid); + return true; + } + + if (GracefulTerminate(lock_data.pid, lock_data.process_start_time)) + { + Log(LOG_LEVEL_INFO, + "Process with PID %jd successfully killed", + (intmax_t) lock_data.pid); + return true; + } + else + { + if (errno == ESRCH) + { + Log(LOG_LEVEL_VERBOSE, + "Process with PID %jd has already been killed", + (intmax_t) lock_data.pid); + return true; + } + else + { + Log(LOG_LEVEL_ERR, + "Failed to kill process with PID: %jd (kill: %s)", + (intmax_t) lock_data.pid, GetErrorStr()); + return false; + } + } +} + +static void RvalDigestUpdate(EVP_MD_CTX *context, Rlist *rp) +{ + assert(context != NULL); + assert(rp != NULL); + + switch (rp->val.type) + { + case RVAL_TYPE_SCALAR: + EVP_DigestUpdate(context, RlistScalarValue(rp), + strlen(RlistScalarValue(rp))); + break; + + case RVAL_TYPE_FNCALL: + // TODO: This should be recursive and not just hash the function name + EVP_DigestUpdate(context, RlistFnCallValue(rp)->name, + strlen(RlistFnCallValue(rp)->name)); + break; + + default: + ProgrammingError("Unhandled case in switch"); + break; + } +} + +void PromiseRuntimeHash(const Promise *pp, const char *salt, + unsigned char digest[EVP_MAX_MD_SIZE + 1], + HashMethod type) +{ + static const char PACK_UPIFELAPSED_SALT[] = "packageuplist"; + + int md_len; + const EVP_MD *md = NULL; + Rlist *rp; + FnCall *fp; + + char *noRvalHash[] = { "mtime", "atime", "ctime", "stime_range", "ttime_range", "log_string", "template_data", NULL }; + int doHash; + + md = HashDigestFromId(type); + if (md == NULL) + { + Log(LOG_LEVEL_ERR, + "Could not determine function for file hashing (type=%d)", + (int) type); + return; + } + + EVP_MD_CTX *context = EVP_MD_CTX_new(); + if (context == NULL) + { + Log(LOG_LEVEL_ERR, "Could not allocate openssl hash context"); + return; + } + + EVP_DigestInit(context, md); + +// multiple packages (promisers) may share same package_list_update_ifelapsed lock + if ( (!salt) || strcmp(salt, PACK_UPIFELAPSED_SALT) ) + { + EVP_DigestUpdate(context, pp->promiser, strlen(pp->promiser)); + } + + if (pp->comment) + { + EVP_DigestUpdate(context, pp->comment, strlen(pp->comment)); + } + + if (pp->parent_section && pp->parent_section->parent_bundle) + { + if (pp->parent_section->parent_bundle->ns) + { + EVP_DigestUpdate(context, + pp->parent_section->parent_bundle->ns, + strlen(pp->parent_section->parent_bundle->ns)); + } + + if (pp->parent_section->parent_bundle->name) + { + EVP_DigestUpdate(context, + pp->parent_section->parent_bundle->name, + strlen(pp->parent_section->parent_bundle->name)); + } + } + + // Unused: pp start, end, and line attributes (describing source position). + + if (salt) + { + EVP_DigestUpdate(context, salt, strlen(salt)); + } + + if (pp->conlist) + { + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + + EVP_DigestUpdate(context, cp->lval, strlen(cp->lval)); + + // don't hash rvals that change (e.g. times) + doHash = true; + + for (int j = 0; noRvalHash[j] != NULL; j++) + { + if (strcmp(cp->lval, noRvalHash[j]) == 0) + { + doHash = false; + break; + } + } + + if (!doHash) + { + continue; + } + + switch (cp->rval.type) + { + case RVAL_TYPE_SCALAR: + EVP_DigestUpdate(context, cp->rval.item, strlen(cp->rval.item)); + break; + + case RVAL_TYPE_LIST: + for (rp = cp->rval.item; rp != NULL; rp = rp->next) + { + RvalDigestUpdate(context, rp); + } + break; + + case RVAL_TYPE_CONTAINER: + { + const JsonElement *rval_json = RvalContainerValue(cp->rval); + Writer *writer = StringWriter(); + JsonWriteCompact(writer, rval_json); /* sorts elements and produces canonical form */ + EVP_DigestUpdate(context, StringWriterData(writer), StringWriterLength(writer)); + WriterClose(writer); + break; + } + + case RVAL_TYPE_FNCALL: + + /* Body or bundle */ + + fp = (FnCall *) cp->rval.item; + + EVP_DigestUpdate(context, fp->name, strlen(fp->name)); + + for (rp = fp->args; rp != NULL; rp = rp->next) + { + RvalDigestUpdate(context, rp); + } + break; + + default: + break; + } + } + } + + EVP_DigestFinal(context, digest, &md_len); + EVP_MD_CTX_free(context); + +/* Digest length stored in md_len */ +} + +static CfLock CfLockNew(const char *last, const char *lock, bool is_dummy) +{ + return (CfLock) { + .last = last ? xstrdup(last) : NULL, + .lock = lock ? xstrdup(lock) : NULL, + .is_dummy = is_dummy + }; +} + +static CfLock CfLockNull(void) +{ + return (CfLock) { + .last = NULL, + .lock = NULL, + .is_dummy = false + }; +} + +CfLock AcquireLock(EvalContext *ctx, const char *operand, const char *host, + time_t now, int ifelapsed, int expireafter, const Promise *pp, + bool ignoreProcesses) +{ + if (now == 0) + { + return CfLockNull(); + } + + char str_digest[CF_HOSTKEY_STRING_SIZE]; + { + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + PromiseRuntimeHash(pp, operand, digest, CF_DEFAULT_DIGEST); + HashPrintSafe(str_digest, sizeof(str_digest), digest, + CF_DEFAULT_DIGEST, true); + } + + if (EvalContextPromiseLockCacheContains(ctx, str_digest)) + { +// Log(LOG_LEVEL_DEBUG, "This promise has already been verified"); + return CfLockNull(); + } + + EvalContextPromiseLockCachePut(ctx, str_digest); + + // Finally if we're supposed to ignore locks ... do the remaining stuff + if (EvalContextIsIgnoringLocks(ctx)) + { + return CfLockNew(NULL, "dummy", true); + } + + char cc_operator[CF_MAXVARSIZE]; + { + char promise[CF_MAXVARSIZE - CF_BUFFERMARGIN]; + PromiseTypeString(promise, sizeof(promise), pp); + snprintf(cc_operator, sizeof(cc_operator), "%s-%s", promise, host); + } + + char cc_operand[CF_BUFSIZE]; + strlcpy(cc_operand, operand, CF_BUFSIZE); + CanonifyNameInPlace(cc_operand); + RemoveDates(cc_operand); + + + Log(LOG_LEVEL_DEBUG, + "AcquireLock(%s,%s), ExpireAfter = %d, IfElapsed = %d", + cc_operator, cc_operand, expireafter, ifelapsed); + + int sum = 0; + for (int i = 0; cc_operator[i] != '\0'; i++) + { + sum = (CF_MACROALPHABET * sum + cc_operator[i]) % CF_MAXLOCKNUM; + } + + for (int i = 0; cc_operand[i] != '\0'; i++) + { + sum = (CF_MACROALPHABET * sum + cc_operand[i]) % CF_MAXLOCKNUM; + } + + const char *bundle_name = PromiseGetBundle(pp)->name; + + char cflock[CF_BUFSIZE] = ""; + snprintf(cflock, CF_BUFSIZE, "lock.%.100s.%s.%.100s_%d_%s", + bundle_name, cc_operator, cc_operand, sum, str_digest); + + char cflast[CF_BUFSIZE] = ""; + snprintf(cflast, CF_BUFSIZE, "last.%.100s.%s.%.100s_%d_%s", + bundle_name, cc_operator, cc_operand, sum, str_digest); + + Log(LOG_LEVEL_DEBUG, "Locking bundle '%s' with lock '%s'", + bundle_name, cflock); + + // Now see if we can get exclusivity to edit the locks + WaitForCriticalSection(CF_CRITIAL_SECTION); + + // Look for non-existent (old) processes + time_t lastcompleted = FindLock(cflast); + time_t elapsedtime = (time_t) (now - lastcompleted) / 60; + + // For promises/locks with ifelapsed == 0, skip all detection logic of + // previously acquired locks, whether in this agent or a parallel one. + if (ifelapsed != 0) + { + if (elapsedtime < 0) + { + Log(LOG_LEVEL_VERBOSE, + "Another cf-agent seems to have done this since I started " + "(elapsed=%jd)", + (intmax_t) elapsedtime); + ReleaseCriticalSection(CF_CRITIAL_SECTION); + return CfLockNull(); + } + + if (elapsedtime < ifelapsed) + { + Log(LOG_LEVEL_VERBOSE, + "Nothing promised here [%.40s] (%jd/%u minutes elapsed)", + cflast, (intmax_t) elapsedtime, ifelapsed); + ReleaseCriticalSection(CF_CRITIAL_SECTION); + return CfLockNull(); + } + } + + // Look for existing (current) processes + lastcompleted = FindLock(cflock); + if (!ignoreProcesses) + { + elapsedtime = (time_t) (now - lastcompleted) / 60; + + if (lastcompleted != 0) + { + if (elapsedtime >= expireafter) + { + Log(LOG_LEVEL_INFO, + "Lock expired after %jd/%u minutes: %s", + (intmax_t) elapsedtime, expireafter, cflock); + + if (KillLockHolder(cflock)) + { + Log(LOG_LEVEL_VERBOSE, + "Lock successfully expired: %s", cflock); + unlink(cflock); + } + else + { + Log(LOG_LEVEL_ERR, "Failed to expire lock: %s", cflock); + } + } + else + { + ReleaseCriticalSection(CF_CRITIAL_SECTION); + Log(LOG_LEVEL_VERBOSE, + "Couldn't obtain lock for %s (already running!)", + cflock); + return CfLockNull(); + } + } + + int ret = WriteLock(cflock); + if (ret != -1) + { + /* Register a cleanup handler *after* having opened the DB, so that + * CloseAllDB() atexit() handler is registered in advance, and it + * is called after removing this lock. + + * There is a small race condition here that we'll leave a stale + * lock if we exit before the following line. */ + pthread_once(&lock_cleanup_once, &RegisterLockCleanup); + } + } + + ReleaseCriticalSection(CF_CRITIAL_SECTION); + + // Keep this as a global for signal handling + PushLock(cflock, cflast); + + return CfLockNew(cflast, cflock, false); +} + +void YieldCurrentLock(CfLock lock) +{ + if (lock.is_dummy) + { + free(lock.lock); /* allocated in AquireLock as a special case */ + return; + } + + if (lock.lock == (char *) CF_UNDEFINED) + { + return; + } + + Log(LOG_LEVEL_DEBUG, "Yielding lock '%s'", lock.lock); + + if (RemoveLock(lock.lock) == -1) + { + Log(LOG_LEVEL_VERBOSE, "Unable to remove lock %s", lock.lock); + free(lock.last); + free(lock.lock); + return; + } + + if (WriteLock(lock.last) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to create '%s'. (create: %s)", + lock.last, GetErrorStr()); + free(lock.last); + free(lock.lock); + return; + } + + /* This lock has ben yield'ed, don't try to yield it again in case process + * is terminated abnormally. + */ + CfLockStack *stack = LOCK_STACK; + CfLockStack *last = NULL; + while (stack) + { + if ((strcmp(stack->lock, lock.lock) == 0) + && (strcmp(stack->last, lock.last) == 0)) + { + CfLockStack *delete_me = stack; + stack = stack->previous; + if (!last) + { + assert(delete_me == LOCK_STACK); + LOCK_STACK = stack; + } else { + last->previous = stack; + } + free(delete_me); + continue; + } + last = stack; + stack = stack->previous; + } + + free(lock.last); + free(lock.lock); +} + +void YieldCurrentLockAndRemoveFromCache(EvalContext *ctx, CfLock lock, + const char *operand, const Promise *pp) +{ + unsigned char digest[EVP_MAX_MD_SIZE + 1]; + PromiseRuntimeHash(pp, operand, digest, CF_DEFAULT_DIGEST); + char str_digest[CF_HOSTKEY_STRING_SIZE]; + HashPrintSafe(str_digest, sizeof(str_digest), digest, + CF_DEFAULT_DIGEST, true); + + YieldCurrentLock(lock); + EvalContextPromiseLockCacheRemove(ctx, str_digest); +} + + +void GetLockName(char *lockname, const char *locktype, + const char *base, const Rlist *params) +{ + int max_sample, count = 0; + + for (const Rlist *rp = params; rp != NULL; rp = rp->next) + { + count++; + } + + if (count) + { + max_sample = CF_BUFSIZE / (2 * count); + } + else + { + max_sample = 0; + } + + strlcpy(lockname, locktype, CF_BUFSIZE / 10); + strlcat(lockname, "_", CF_BUFSIZE / 10); + strlcat(lockname, base, CF_BUFSIZE / 10); + strlcat(lockname, "_", CF_BUFSIZE / 10); + + for (const Rlist *rp = params; rp != NULL; rp = rp->next) + { + switch (rp->val.type) + { + case RVAL_TYPE_SCALAR: + strncat(lockname, RlistScalarValue(rp), max_sample); + break; + + case RVAL_TYPE_FNCALL: + strncat(lockname, RlistFnCallValue(rp)->name, max_sample); + break; + + default: + ProgrammingError("Unhandled case in switch %d", rp->val.type); + break; + } + } +} + +void RestoreLockDatabase(void) +{ + // We don't do any locking here (since we can't trust the database), but + // worst case someone else will just copy the same file to the same + // location. + char *db_path = DBIdToPath(dbid_locks); + char *db_path_backup; + xasprintf(&db_path_backup, "%s.backup", db_path); + + CopyLockDatabaseAtomically(db_path_backup, db_path, "lock database backup", + "lock database"); + + free(db_path); + free(db_path_backup); +} + +static void CopyLockDatabaseAtomically(const char *from, const char *to, + const char *from_pretty_name, + const char *to_pretty_name) +{ + char *tmp_file_name; + xasprintf(&tmp_file_name, "%s.tmp", to); + + int from_fd = open(from, O_RDONLY | O_BINARY); + if (from_fd < 0) + { + Log(LOG_LEVEL_WARNING, + "Could not open '%s' (open: %s)", + from_pretty_name, GetErrorStr()); + goto cleanup; + } + + int to_fd = open(tmp_file_name, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600); + if (to_fd < 0) + { + Log(LOG_LEVEL_WARNING, + "Could not open '%s' temporary file (open: %s)", + to_pretty_name, GetErrorStr()); + goto cleanup; + } + + size_t total_bytes_written; + bool last_write_was_hole; + bool ok1 = FileSparseCopy(from_fd, from_pretty_name, + to_fd, to_pretty_name, DEV_BSIZE, + &total_bytes_written, &last_write_was_hole); + + /* Make sure changes are persistent on disk, so database cannot get + * corrupted at system crash. */ + bool do_sync = true; + bool ok2 = FileSparseClose(to_fd, to_pretty_name, do_sync, + total_bytes_written, last_write_was_hole); + + if (!ok1 || !ok2) + { + Log(LOG_LEVEL_WARNING, + "Error while moving database from '%s' to '%s'", + from_pretty_name, to_pretty_name); + } + + if (rename(tmp_file_name, to) != 0) + { + Log(LOG_LEVEL_WARNING, + "Could not move '%s' into place (rename: %s)", + to_pretty_name, GetErrorStr()); + } + + cleanup: + if (from_fd != -1) + { + close(from_fd); + } + unlink(tmp_file_name); + free(tmp_file_name); +} + +void BackupLockDatabase(void) +{ + WaitForCriticalSection(CF_CRITIAL_SECTION); + + char *db_path = DBIdToPath(dbid_locks); + char *db_path_backup; + xasprintf(&db_path_backup, "%s.backup", db_path); + + CopyLockDatabaseAtomically(db_path, db_path_backup, "lock database", + "lock database backup"); + + free(db_path); + free(db_path_backup); + + ReleaseCriticalSection(CF_CRITIAL_SECTION); +} + +void PurgeLocks(void) +{ + DBHandle *db = OpenLock(); + if (db == NULL) + { + return; + } + + time_t now = time(NULL); + + int usage_pct = GetDBUsagePercentage(db); + if (usage_pct == -1) + { + /* error already logged */ + /* no usage info, assume 50% */ + usage_pct = 50; + } + + unsigned short interval_idx = MIN(usage_pct / (100 / N_LOCK_HORIZON_USAGE_INTERVALS), + N_LOCK_HORIZON_USAGE_INTERVALS - 1); + const time_t lock_horizon_interval = LOCK_HORIZON_USAGE_INTERVALS[interval_idx]; + if (lock_horizon_interval == 0) + { + Log(LOG_LEVEL_VERBOSE, "No lock purging needed (lock DB usage: %d %%)", usage_pct); + CloseLock(db); + return; + } + const time_t purge_horizon = now - lock_horizon_interval; + + LockData lock_horizon; + memset(&lock_horizon, 0, sizeof(lock_horizon)); + if (ReadDB(db, "lock_horizon", &lock_horizon, sizeof(lock_horizon))) + { + if (lock_horizon.time > purge_horizon) + { + Log(LOG_LEVEL_VERBOSE, "No lock purging scheduled"); + CloseLock(db); + return; + } + } + + Log(LOG_LEVEL_VERBOSE, + "Looking for stale locks (older than %jd seconds) to purge", + (intmax_t) lock_horizon_interval); + + DBCursor *cursor; + if (!NewDBCursor(db, &cursor)) + { + char *db_path = DBIdToPath(dbid_locks); + Log(LOG_LEVEL_ERR, "Unable to get cursor for locks database '%s'", + db_path); + free(db_path); + CloseLock(db); + return; + } + + char *key; + int ksize, vsize; + LockData *entry = NULL; + while (NextDB(cursor, &key, &ksize, (void **)&entry, &vsize)) + { +#ifdef LMDB + LOG_LOCK_OP("", key, entry); +#endif + if (StringStartsWith(key, "last.internal_bundle.track_license.handle")) + { + continue; + } + + if (entry->time < purge_horizon) + { + Log(LOG_LEVEL_VERBOSE, "Purging lock (%jd s elapsed): %s", + (intmax_t) (now - entry->time), key); + DBCursorDeleteEntry(cursor); + } + } + DeleteDBCursor(cursor); + + Log(LOG_LEVEL_DEBUG, "Finished purging locks"); + + lock_horizon.time = now; + WriteDB(db, "lock_horizon", &lock_horizon, sizeof(lock_horizon)); + CloseLock(db); +} diff --git a/libpromises/locks.h b/libpromises/locks.h new file mode 100644 index 0000000000..9689dd3580 --- /dev/null +++ b/libpromises/locks.h @@ -0,0 +1,55 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_LOCKS_H +#define CFENGINE_LOCKS_H + +#include + + +CfLock AcquireLock(EvalContext *ctx, const char *operand, const char *host, + time_t now, int ifelapsed, int expireafter, const Promise *pp, + bool ignoreProcesses); +void YieldCurrentLock(CfLock lock); +void YieldCurrentLockAndRemoveFromCache(EvalContext *ctx, CfLock lock, + const char *operand, const Promise *pp); +void GetLockName(char *lockname, const char *locktype, + const char *base, const Rlist *params); +void PurgeLocks(void); +void BackupLockDatabase(void); +void RestoreLockDatabase(void); + +// Used in enterprise/nova code: +CF_DB *OpenLock(); +void CloseLock(CF_DB *dbp); + +/* These are only used in ENT! TODO fix and remove from header file. */ +void WaitForCriticalSection(const char *section_id); +void ReleaseCriticalSection(const char *section_id); +void PromiseRuntimeHash(const Promise *pp, const char *salt, + unsigned char digest[EVP_MAX_MD_SIZE + 1], + HashMethod type); + + +#endif diff --git a/libpromises/logic_expressions.c b/libpromises/logic_expressions.c new file mode 100644 index 0000000000..c98533ff51 --- /dev/null +++ b/libpromises/logic_expressions.c @@ -0,0 +1,301 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include + +#include + +/* */ + +static ParseResult ParsePrimary(const char *expr, int start, int end) +{ + if (start < end && expr[start] == '(') + { + ParseResult res = ParseExpression(expr, start + 1, end); + + if (res.result) + { + /* Check if there is a matching ')' at the end */ + if (res.position < end && expr[res.position] == ')') + { + return (ParseResult) {res.result, res.position + 1}; + } + else + { + /* Haven't found a matching bracket. Give up */ + FreeExpression(res.result); + return (ParseResult) {NULL, res.position}; + } + } + else + { + return res; + } + } + else + { + StringParseResult strres = ParseStringExpression(expr, start, end); + + if (strres.result) + { + Expression *res = xcalloc(1, sizeof(Expression)); + + res->op = LOGICAL_OP_EVAL; + res->val.eval.name = strres.result; + + return (ParseResult) {res, strres.position}; + } + else + { + return (ParseResult) {NULL, strres.position}; + } + } +} + +/* */ + +static ParseResult ParseNotExpression(const char *expr, int start, int end) +{ + if (start < end && expr[start] == '!') + { + ParseResult primres = ParsePrimary(expr, start + 1, end); + + if (primres.result) + { + Expression *res = xcalloc(1, sizeof(Expression)); + + res->op = LOGICAL_OP_NOT; + res->val.not.arg = primres.result; + + return (ParseResult) {res, primres.position}; + } + else + { + return primres; + } + } + else + { + return ParsePrimary(expr, start, end); + } +} + +/* */ + +static ParseResult ParseAndExpression(const char *expr, int start, int end) +{ + ParseResult lhs, rhs; + Expression *res; + + lhs = ParseNotExpression(expr, start, end); + + if (!lhs.result) + { + return lhs; + } + + if (lhs.position == end || (expr[lhs.position] != '.' && expr[lhs.position] != '&')) + { + return lhs; + } + + rhs = ParseAndExpression(expr, lhs.position + 1, end); + + if (!rhs.result) + { + FreeExpression(lhs.result); + return rhs; + } + + res = xcalloc(1, sizeof(Expression)); + res->op = LOGICAL_OP_AND; + res->val.andor.lhs = lhs.result; + res->val.andor.rhs = rhs.result; + + return (ParseResult) {res, rhs.position}; +} + +/* */ + +ParseResult ParseExpression(const char *expr, int start, int end) +{ + ParseResult lhs, rhs; + Expression *res; + int position; + + lhs = ParseAndExpression(expr, start, end); + + if (!lhs.result) + { + return lhs; + } + +/* End of left-hand side expression */ + position = lhs.position; + + if (position == end || expr[position] != '|') + { + return lhs; + } + +/* Skip second '|' in 'lhs||rhs' */ + + if (position + 1 < end && expr[position + 1] == '|') + { + position++; + } + + rhs = ParseExpression(expr, position + 1, end); + + if (!rhs.result) + { + FreeExpression(lhs.result); + return rhs; + } + + res = xcalloc(1, sizeof(Expression)); + res->op = LOGICAL_OP_OR; + res->val.andor.lhs = lhs.result; + res->val.andor.rhs = rhs.result; + + return (ParseResult) {res, rhs.position}; +} + +/* Evaluation */ + +ExpressionValue EvalExpression(const Expression *expr, + NameEvaluator nameevalfn, VarRefEvaluator varrefevalfn, void *param) +{ + switch (expr->op) + { + case LOGICAL_OP_OR: + case LOGICAL_OP_AND: + { + ExpressionValue lhs = EXPRESSION_VALUE_ERROR, rhs = EXPRESSION_VALUE_ERROR; + + lhs = EvalExpression(expr->val.andor.lhs, nameevalfn, varrefevalfn, param); + if (lhs == EXPRESSION_VALUE_ERROR) + { + return EXPRESSION_VALUE_ERROR; + } + + rhs = EvalExpression(expr->val.andor.rhs, nameevalfn, varrefevalfn, param); + + if (rhs == EXPRESSION_VALUE_ERROR) + { + return EXPRESSION_VALUE_ERROR; + } + + if (expr->op == LOGICAL_OP_OR) + { + return ((ExpressionValue) (lhs || rhs)); + } + else + { + return ((ExpressionValue) (lhs && rhs)); + } + } + + case LOGICAL_OP_NOT: + { + ExpressionValue arg = EvalExpression(expr->val.not.arg, + nameevalfn, + varrefevalfn, + param); + + if (arg == EXPRESSION_VALUE_ERROR) + { + return EXPRESSION_VALUE_ERROR; + } + else + { + return !arg; + } + } + + case LOGICAL_OP_EVAL: + { + ExpressionValue ret = EXPRESSION_VALUE_ERROR; + char *name = EvalStringExpression(expr->val.eval.name, + varrefevalfn, + param); + + if (name == NULL) + { + return EXPRESSION_VALUE_ERROR; + } + else if (strcmp("true", name) == 0) + { + ret = EXPRESSION_VALUE_TRUE; + } + else if (strcmp("false", name) == 0) + { + ret = EXPRESSION_VALUE_FALSE; + } + else + { + ret = (*nameevalfn) (name, param); + } + + free(name); + return ret; + } + + default: + ProgrammingError("Unexpected class expression type is found: %d", expr->op); + } +} + +/* Freeing results */ + +void FreeExpression(Expression *e) +{ + if (!e) + { + return; + } + + switch (e->op) + { + case LOGICAL_OP_OR: + case LOGICAL_OP_AND: + FreeExpression(e->val.andor.lhs); + FreeExpression(e->val.andor.rhs); + break; + case LOGICAL_OP_NOT: + FreeExpression(e->val.not.arg); + break; + case LOGICAL_OP_EVAL: + FreeStringExpression(e->val.eval.name); + break; + default: + ProgrammingError("Unknown logic expression type encountered in" "FreeExpression: %d", e->op); + } + free(e); +} diff --git a/libpromises/logic_expressions.h b/libpromises/logic_expressions.h new file mode 100644 index 0000000000..bafc4412c6 --- /dev/null +++ b/libpromises/logic_expressions.h @@ -0,0 +1,132 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_LOGIC_EXPRESSIONS_H +#define CFENGINE_LOGIC_EXPRESSIONS_H + +#include +#include + +/* + Logic expressions grammar: + + ::= + + ::= + | + || + + ::= + . + & + + ::= ! + + + ::= ( ) + + + Basis of logic evaluation is values which are provided by + StringExpression and suitable string->bool evaluator. +*/ + +typedef enum +{ + LOGICAL_OP_OR, + LOGICAL_OP_AND, + LOGICAL_OP_NOT, + LOGICAL_OP_EVAL +} LogicalOp; + +typedef struct Expression_ Expression; + +struct Expression_ +{ + LogicalOp op; + union + { + struct + { + Expression *lhs; + Expression *rhs; + } andor; + + struct + { + Expression *arg; + } not; + + struct + { + StringExpression *name; + } eval; + } val; +}; + +/* Parsing and evaluation */ + +/* + * Result of parsing. + * + * if succeeded, then result is the result of parsing and position is last + * character consumed. + * + * if not succeeded, then result is NULL and position is last character consumed + * before the error. + */ +typedef struct +{ + Expression *result; + int position; +} ParseResult; + +ParseResult ParseExpression(const char *expr, int start, int end); + +typedef enum ExpressionValue +{ + EXPRESSION_VALUE_ERROR = -1, + EXPRESSION_VALUE_FALSE = false, + EXPRESSION_VALUE_TRUE = true, +} ExpressionValue; + +/* + * Evaluator should return FALSE, TRUE or ERROR if unable to parse result. In + * later case evaluation will be aborted and ERROR will be returned from + * EvalExpression. + */ +typedef ExpressionValue(*NameEvaluator) (const char *name, void *param); + +/* + * Result is heap-allocated. In case evalfn() returns ERROR whole + * EvalExpression returns ERROR as well. + */ +ExpressionValue EvalExpression(const Expression *expr, + NameEvaluator nameevalfn, VarRefEvaluator varrefevalfn, void *param); + +/* + * Frees Expression produced by ParseExpression. NULL-safe. + */ +void FreeExpression(Expression *expr); + +#endif diff --git a/libpromises/match_scope.c b/libpromises/match_scope.c new file mode 100644 index 0000000000..b7ff3d0816 --- /dev/null +++ b/libpromises/match_scope.c @@ -0,0 +1,141 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include /* StringFromLong */ +#include /* CompileRegex */ + + +/* Sets variables */ +static bool RegExMatchSubString(EvalContext *ctx, Regex *regex, const char *teststring, int *start, int *end) +{ + pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL); + int result = pcre2_match(regex, (PCRE2_SPTR) teststring, PCRE2_ZERO_TERMINATED, + 0, 0, match_data, NULL); + /* pcre2_match() returns the highest capture group number + 1, i.e. 1 means + * a match with 0 capture groups. 0 means the vector of offsets is small, + * negative numbers are errors (incl. no match). */ + if (result > 0) + { + size_t *ovector = pcre2_get_ovector_pointer(match_data); + *start = ovector[0]; + *end = ovector[1]; + + EvalContextVariableClearMatch(ctx); + + for (int i = 0; i < result; i++) /* make backref vars $(1),$(2) etc */ + { + const char *backref_start = teststring + ovector[i * 2]; + int backref_len = ovector[i * 2 + 1] - ovector[i * 2]; + + if (backref_len < CF_MAXVARSIZE) + { + char substring[CF_MAXVARSIZE]; + char *index = StringFromLong(i); + strlcpy(substring, backref_start, MIN(CF_MAXVARSIZE, backref_len + 1)); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_MATCH, index, substring, CF_DATA_TYPE_STRING, "source=regex"); + free(index); + } + } + } + else + { + *start = 0; + *end = 0; + } + + pcre2_match_data_free(match_data); + RegexDestroy(regex); + return result > 0; +} + +/* Sets variables */ +static bool RegExMatchFullString(EvalContext *ctx, Regex *rx, const char *teststring) +{ + int match_start; + int match_len; + + if (RegExMatchSubString(ctx, rx, teststring, &match_start, &match_len)) + { + return ((size_t) match_start == 0) && ((size_t) match_len == strlen(teststring)); + } + else + { + return false; + } +} + +bool FullTextMatch(EvalContext *ctx, const char *regexp, const char *teststring) +{ + if (strcmp(regexp, teststring) == 0) + { + return true; + } + + Regex *rx = CompileRegex(regexp); + if (rx == NULL) + { + return false; + } + + if (RegExMatchFullString(ctx, rx, teststring)) + { + return true; + } + else + { + return false; + } +} + +bool ValidateRegEx(const char *regex) +{ + Regex *rx = CompileRegex(regex); + bool regex_valid = rx != NULL; + + RegexDestroy(rx); + return regex_valid; +} + +bool BlockTextMatch(EvalContext *ctx, const char *regexp, const char *teststring, int *start, int *end) +{ + Regex *rx = CompileRegex(regexp); + + if (rx == NULL) + { + return false; + } + + if (RegExMatchSubString(ctx, rx, teststring, start, end)) + { + return true; + } + else + { + return false; + } +} diff --git a/libpromises/match_scope.h b/libpromises/match_scope.h new file mode 100644 index 0000000000..112539bf2c --- /dev/null +++ b/libpromises/match_scope.h @@ -0,0 +1,34 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MATCH_SCOPE_H +#define CFENGINE_MATCH_SCOPE_H + +#include + +bool FullTextMatch(EvalContext *ctx, const char *regptr, const char *cmpptr); /* Sets variables */ +bool BlockTextMatch(EvalContext *ctx, const char *regexp, const char *teststring, int *s, int *e); /* Sets variables */ +bool ValidateRegEx(const char *regex); /* Pure */ + +#endif diff --git a/libpromises/matching.c b/libpromises/matching.c new file mode 100644 index 0000000000..6351cc2608 --- /dev/null +++ b/libpromises/matching.c @@ -0,0 +1,419 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* CompileRegex,StringMatchFull */ +#include + + +/* Pure, non-thread-safe */ +static char *FirstBackReference(Regex *regex, const char *teststring) +{ + static char backreference[CF_BUFSIZE]; /* GLOBAL_R, no initialization needed */ + memset(backreference, 0, CF_BUFSIZE); + + pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL); + int result = pcre2_match(regex, (PCRE2_SPTR) teststring, PCRE2_ZERO_TERMINATED, + 0, 0, match_data, NULL); + /* pcre2_match() returns the highest capture group number + 1, i.e. 1 means + * a match with 0 capture groups. 0 means the vector of offsets is small, + * negative numbers are errors (incl. no match). */ + if (result > 0) + { + size_t *ovector = pcre2_get_ovector_pointer(match_data); + /* ovector[0] and ovector[1] are for the start and end of the whole + * match, the capture groups follow in [2] and [3], etc. */ + const char *backref_start = teststring + ovector[2]; + size_t backref_len = ovector[3] - ovector[2]; + if (backref_len < CF_MAXVARSIZE) + { + strncpy(backreference, backref_start, backref_len); + } + } + + pcre2_match_data_free(match_data); + RegexDestroy(regex); + return backreference; +} + +char *ExtractFirstReference(const char *regexp, const char *teststring) +{ + char *backreference; + + if ((regexp == NULL) || (teststring == NULL)) + { + return ""; + } + + Regex *rx = CompileRegex(regexp); + if (rx == NULL) + { + return ""; + } + + backreference = FirstBackReference(rx, teststring); + + if (strlen(backreference) == 0) + { + strlcpy(backreference, "CF_NOMATCH", CF_MAXVARSIZE); + } + + return backreference; +} + +bool IsRegex(const char *str) +{ + const char *sp; + bool ret = false; + enum { r_norm, r_norepeat, r_literal } special = r_norepeat; + int bracket = 0; + int paren = 0; + +/* Try to see when something is intended as a regular expression */ + + for (sp = str; *sp != '\0'; sp++) + { + if (special == r_literal) + { + special = r_norm; + continue; + } + else if (*sp == '\\') + { + special = r_literal; + continue; + } + else if (bracket && (*sp != ']')) + { + if (*sp == '[') + { + return false; + } + continue; + } + + switch (*sp) + { + case '^': + special = (sp == str) ? r_norepeat : r_norm; + break; + case '*': + case '+': + if (special == r_norepeat) + { + return false; + } + special = r_norepeat; + ret = true; + break; + case '[': + special = r_norm; + bracket++; + ret = true; + break; + case ']': + if (bracket == 0) + { + return false; + } + bracket = 0; + special = r_norm; + break; + case '(': + special = r_norepeat; + paren++; + break; + + case ')': + special = r_norm; + paren--; + if (paren < 0) + { + return false; + } + break; + + case '|': + special = r_norepeat; + if (paren > 0) + { + ret = true; + } + break; + + default: + special = r_norm; + } + + } + + if ((bracket != 0) || (paren != 0) || (special == r_literal)) + { + return false; + } + else + { + return ret; + } +} + +bool IsPathRegex(const char *str) +{ + bool result = IsRegex(str); + + if (result) + { + int s = 0, r = 0; /* Count square and round brackets. */ + for (const char *sp = str; *sp != '\0'; sp++) + { + switch (*sp) + { + case '[': + s++; + break; + case ']': + s--; + break; + case '(': + r++; + break; + case ')': + r--; + break; + default: + + if (*sp == FILE_SEPARATOR && (r || s)) + { + Log(LOG_LEVEL_ERR, + "Path regular expression %s seems to use expressions containing the directory symbol %c", str, + FILE_SEPARATOR); + Log(LOG_LEVEL_ERR, "Use a work-around to avoid pathological behaviour"); + return false; + } + break; + } + } + } + + return result; +} + +/* Checks whether item matches a list of wildcards */ + +bool IsRegexItemIn(const EvalContext *ctx, const Item *list, const char *regex) +{ + for (const Item *ptr = list; ptr != NULL; ptr = ptr->next) + { + if (ctx != NULL && ptr->classes != NULL && + !IsDefinedClass(ctx, ptr->classes)) + { + continue; + } + + /* Cheap pre-test: */ + if (strcmp(regex, ptr->name) == 0) + { + return true; + } + + /* Make it commutative */ + + if (StringMatchFull(regex, ptr->name) || StringMatchFull(ptr->name, regex)) + { + return true; + } + } + + return false; +} + +/* Escapes non-alphanumeric chars, except sequence given in noEscSeq */ + +void EscapeSpecialChars(const char *str, char *strEsc, size_t strEscSz, char *noEscSeq, char *noEscList) +{ + size_t strEscPos = 0; + + if (noEscSeq == NULL) + { + noEscSeq = ""; + } + + if (noEscList == NULL) + { + noEscList = ""; + } + + memset(strEsc, 0, strEscSz); + + for (const char *sp = str; (*sp != '\0') && (strEscPos < strEscSz - 2); sp++) + { + if (strncmp(sp, noEscSeq, strlen(noEscSeq)) == 0) + { + if (strEscSz <= strEscPos + strlen(noEscSeq)) + { + Log(LOG_LEVEL_ERR, + "EscapeSpecialChars: Output string truncated. in='%s' out='%s'", + str, strEsc); + break; + } + + strlcat(strEsc, noEscSeq, strEscSz); + strEscPos += strlen(noEscSeq); + sp += strlen(noEscSeq); + } + + if (strchr(noEscList,*sp) != NULL) + { + // Found current char (*sp) in noEscList, do nothing + } + else if ((*sp != '\0') && (!isalnum((int)*sp))) + { + strEsc[strEscPos++] = '\\'; + } + + strEsc[strEscPos++] = *sp; + } +} + +size_t EscapeRegexCharsLen(const char *str) +{ + size_t ret = 2; + for (const char *sp = str; *sp != '\0'; sp++) + { + switch (*sp) + { + case '.': + case '*': + ret++; + break; + default: + break; + } + + ret++; + } + + return ret; +} + +void EscapeRegexChars(char *str, char *strEsc, int strEscSz) +{ + char *sp; + int strEscPos = 0; + + memset(strEsc, 0, strEscSz); + + for (sp = str; (*sp != '\0') && (strEscPos < strEscSz - 2); sp++) + { + switch (*sp) + { + case '.': + case '*': + strEsc[strEscPos++] = '\\'; + break; + default: + break; + } + + strEsc[strEscPos++] = *sp; + } +} + +/* Escapes characters esc in the string str of size strSz */ + +char *EscapeChar(char *str, size_t strSz, char esc) +{ + char strDup[CF_BUFSIZE]; + size_t strPos, strDupPos; + + if (sizeof(strDup) < strSz) + { + ProgrammingError("Too large string passed to EscapeCharInplace()"); + } + + snprintf(strDup, sizeof(strDup), "%s", str); + memset(str, 0, strSz); + + for (strPos = 0, strDupPos = 0; strPos < strSz - 2; strPos++, strDupPos++) + { + if (strDup[strDupPos] == esc) + { + str[strPos] = '\\'; + strPos++; + } + + str[strPos] = strDup[strDupPos]; + } + + return str; +} + +void AnchorRegex(const char *regex, char *out, int outSz) +{ + if (NULL_OR_EMPTY(regex)) + { + memset(out, 0, outSz); + } + else + { + snprintf(out, outSz, "^(%s)$", regex); + } +} + +char *AnchorRegexNew(const char *regex) +{ + if (NULL_OR_EMPTY(regex)) + { + return xstrdup("^$"); + } + + char *ret = NULL; + xasprintf(&ret, "^(%s)$", regex); + + return ret; +} + +bool HasRegexMetaChars(const char *string) +{ + if (!string) + { + return false; + } + + if (string[strcspn(string, "\\^${}[]().*+?|<>-&")] == '\0') /* i.e. no metachars appear in string */ + { + return false; + } + + return true; +} diff --git a/libpromises/matching.h b/libpromises/matching.h new file mode 100644 index 0000000000..7e121614f4 --- /dev/null +++ b/libpromises/matching.h @@ -0,0 +1,48 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MATCHING_H +#define CFENGINE_MATCHING_H + +#include + +bool IsRegex(const char *str); /* Pure */ +bool IsRegexItemIn(const EvalContext *ctx, const Item *list, const char *regex); /* Uses context */ + +char *ExtractFirstReference(const char *regexp, const char *teststring); /* Pure, not thread-safe */ + +bool IsPathRegex(const char *str); /* Pure */ +bool HasRegexMetaChars(const char *string); +void EscapeRegexChars(char *str, char *strEsc, int strEscSz); /* Pure */ +void EscapeSpecialChars(const char *str, char *strEsc, size_t strEscSz, char *noEscseq, char *noEsclist); /* Pure */ +size_t EscapeRegexCharsLen(const char *str); /* Pure */ +char *EscapeChar(char *str, size_t strSz, char esc); /* Pure */ +void AnchorRegex(const char *regex, char *out, int outSz); /* Pure */ + +/** + result is malloced + */ +char *AnchorRegexNew(const char *regex); + +#endif // MATCHING_H diff --git a/libpromises/math.pc b/libpromises/math.pc new file mode 100644 index 0000000000..813576fc8d --- /dev/null +++ b/libpromises/math.pc @@ -0,0 +1,1138 @@ +/* A recursive-descent parser generated by peg 0.1.15 */ + +#include +#include +#include +#define YYRULECOUNT 23 +#ifndef YY_MALLOC +#define YY_MALLOC(C, N) malloc(N) +#endif +#ifndef YY_REALLOC +#define YY_REALLOC(C, P, N) realloc(P, N) +#endif +#ifndef YY_FREE +#define YY_FREE(C, P) free(P) +#endif +#ifndef YY_LOCAL +#define YY_LOCAL(T) static T +#endif +#ifndef YY_ACTION +#define YY_ACTION(T) static T +#endif +#ifndef YY_RULE +#define YY_RULE(T) static T +#endif +#ifndef YY_PARSE +#define YY_PARSE(T) T +#endif +#ifndef YYPARSE +#define YYPARSE yyparse +#endif +#ifndef YYPARSEFROM +#define YYPARSEFROM yyparsefrom +#endif +#ifndef YYRELEASE +#define YYRELEASE yyrelease +#endif +#ifndef YY_BEGIN +#define YY_BEGIN ( yy->__begin= yy->__pos, 1) +#endif +#ifndef YY_END +#define YY_END ( yy->__end= yy->__pos, 1) +#endif +#ifdef YY_DEBUG +# define yyprintf(args) fprintf args +#else +# define yyprintf(args) +#endif +#ifndef YYSTYPE +#define YYSTYPE int +#endif +#ifndef YY_STACK_SIZE +#define YY_STACK_SIZE 128 +#endif + +#ifndef YY_BUFFER_SIZE +#define YY_BUFFER_SIZE 1024 +#endif + +#ifndef YY_PART + +typedef struct _yycontext yycontext; +typedef void (*yyaction)(yycontext *yy, char *yytext, int yyleng); +typedef struct _yythunk { int begin, end; yyaction action; struct _yythunk *next; } yythunk; + +struct _yycontext { + char *__buf; + int __buflen; + int __pos; + int __limit; + char *__text; + int __textlen; + int __begin; + int __end; + int __textmax; + yythunk *__thunks; + int __thunkslen; + int __thunkpos; + YYSTYPE __; + YYSTYPE *__val; + YYSTYPE *__vals; + int __valslen; +#ifdef YY_CTX_MEMBERS + YY_CTX_MEMBERS +#endif +}; + +#ifdef YY_CTX_LOCAL +#define YY_CTX_PARAM_ yycontext *yyctx, +#define YY_CTX_PARAM yycontext *yyctx +#define YY_CTX_ARG_ yyctx, +#define YY_CTX_ARG yyctx +#ifndef YY_INPUT +#define YY_INPUT(yy, buf, result, max_size) \ + { \ + int yyc= getchar(); \ + result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \ + yyprintf((stderr, "<%c>", yyc)); \ + } +#endif +#else +#define YY_CTX_PARAM_ +#define YY_CTX_PARAM +#define YY_CTX_ARG_ +#define YY_CTX_ARG +yycontext _yyctx= { 0, 0 }; +yycontext *yyctx= &_yyctx; +#ifndef YY_INPUT +#define YY_INPUT(buf, result, max_size) \ + { \ + int yyc= getchar(); \ + result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \ + yyprintf((stderr, "<%c>", yyc)); \ + } +#endif +#endif + +YY_LOCAL(int) yyrefill(yycontext *yy) +{ + int yyn; + while (yy->__buflen - yy->__pos < 512) + { + yy->__buflen *= 2; + yy->__buf= (char *)YY_REALLOC(yy, yy->__buf, yy->__buflen); + } +#ifdef YY_CTX_LOCAL + YY_INPUT(yy, (yy->__buf + yy->__pos), yyn, (yy->__buflen - yy->__pos)); +#else + YY_INPUT((yy->__buf + yy->__pos), yyn, (yy->__buflen - yy->__pos)); +#endif + if (!yyn) return 0; + yy->__limit += yyn; + return 1; +} + +YY_LOCAL(int) yymatchDot(yycontext *yy) +{ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + ++yy->__pos; + return 1; +} + +YY_LOCAL(int) yymatchChar(yycontext *yy, int c) +{ + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + if ((unsigned char)yy->__buf[yy->__pos] == c) + { + ++yy->__pos; + yyprintf((stderr, " ok yymatchChar(yy, %c) @ %s\n", c, yy->__buf+yy->__pos)); + return 1; + } + yyprintf((stderr, " fail yymatchChar(yy, %c) @ %s\n", c, yy->__buf+yy->__pos)); + return 0; +} + +YY_LOCAL(int) yymatchString(yycontext *yy, const char *s) +{ + int yysav= yy->__pos; + while (*s) + { + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + if (yy->__buf[yy->__pos] != *s) + { + yy->__pos= yysav; + return 0; + } + ++s; + ++yy->__pos; + } + return 1; +} + +YY_LOCAL(int) yymatchClass(yycontext *yy, unsigned char *bits) +{ + int c; + if (yy->__pos >= yy->__limit && !yyrefill(yy)) return 0; + c= (unsigned char)yy->__buf[yy->__pos]; + if (bits[c >> 3] & (1 << (c & 7))) + { + ++yy->__pos; + yyprintf((stderr, " ok yymatchClass @ %s\n", yy->__buf+yy->__pos)); + return 1; + } + yyprintf((stderr, " fail yymatchClass @ %s\n", yy->__buf+yy->__pos)); + return 0; +} + +YY_LOCAL(void) yyDo(yycontext *yy, yyaction action, int begin, int end) +{ + while (yy->__thunkpos >= yy->__thunkslen) + { + yy->__thunkslen *= 2; + yy->__thunks= (yythunk *)YY_REALLOC(yy, yy->__thunks, sizeof(yythunk) * yy->__thunkslen); + } + yy->__thunks[yy->__thunkpos].begin= begin; + yy->__thunks[yy->__thunkpos].end= end; + yy->__thunks[yy->__thunkpos].action= action; + ++yy->__thunkpos; +} + +YY_LOCAL(int) yyText(yycontext *yy, int begin, int end) +{ + int yyleng= end - begin; + if (yyleng <= 0) + yyleng= 0; + else + { + while (yy->__textlen < (yyleng + 1)) + { + yy->__textlen *= 2; + yy->__text= (char *)YY_REALLOC(yy, yy->__text, yy->__textlen); + } + memcpy(yy->__text, yy->__buf + begin, yyleng); + } + yy->__text[yyleng]= '\0'; + return yyleng; +} + +YY_LOCAL(void) yyDone(yycontext *yy) +{ + int pos; + for (pos= 0; pos < yy->__thunkpos; ++pos) + { + yythunk *thunk= &yy->__thunks[pos]; + int yyleng= thunk->end ? yyText(yy, thunk->begin, thunk->end) : thunk->begin; + yyprintf((stderr, "DO [%d] %p %s\n", pos, thunk->action, yy->__text)); + thunk->action(yy, yy->__text, yyleng); + } + yy->__thunkpos= 0; +} + +YY_LOCAL(void) yyCommit(yycontext *yy) +{ + if ((yy->__limit -= yy->__pos)) + { + memmove(yy->__buf, yy->__buf + yy->__pos, yy->__limit); + } + yy->__begin -= yy->__pos; + yy->__end -= yy->__pos; + yy->__pos= yy->__thunkpos= 0; +} + +YY_LOCAL(int) yyAccept(yycontext *yy, int tp0) +{ + if (tp0) + { + fprintf(stderr, "accept denied at %d\n", tp0); + return 0; + } + else + { + yyDone(yy); + yyCommit(yy); + } + return 1; +} + +YY_LOCAL(void) yyPush(yycontext *yy, char *text, int count) +{ + yy->__val += count; + while (yy->__valslen <= yy->__val - yy->__vals) + { + long offset= yy->__val - yy->__vals; + yy->__valslen *= 2; + yy->__vals= (YYSTYPE *)YY_REALLOC(yy, yy->__vals, sizeof(YYSTYPE) * yy->__valslen); + yy->__val= yy->__vals + offset; + } +} +YY_LOCAL(void) yyPop(yycontext *yy, char *text, int count) { yy->__val -= count; } +YY_LOCAL(void) yySet(yycontext *yy, char *text, int count) { yy->__val[count]= yy->__; } + +#endif /* YY_PART */ + +#define YYACCEPT yyAccept(yy, yythunkpos0) + +YY_RULE(int) yy_Fname(yycontext *yy); /* 23 */ +YY_RULE(int) yy_CLOSE(yycontext *yy); /* 22 */ +YY_RULE(int) yy_OPEN(yycontext *yy); /* 21 */ +YY_RULE(int) yy_Funcall(yycontext *yy); /* 20 */ +YY_RULE(int) yy_Constant(yycontext *yy); /* 19 */ +YY_RULE(int) yy_SI_Unit(yycontext *yy); /* 18 */ +YY_RULE(int) yy_F_NUMBER(yycontext *yy); /* 17 */ +YY_RULE(int) yy_MOD(yycontext *yy); /* 16 */ +YY_RULE(int) yy_DIVIDE(yycontext *yy); /* 15 */ +YY_RULE(int) yy_TIMES(yycontext *yy); /* 14 */ +YY_RULE(int) yy_POW(yycontext *yy); /* 13 */ +YY_RULE(int) yy_Value(yycontext *yy); /* 12 */ +YY_RULE(int) yy_GREATER_THAN(yycontext *yy); /* 11 */ +YY_RULE(int) yy_GREATEREQ_THAN(yycontext *yy); /* 10 */ +YY_RULE(int) yy_LESS_THAN(yycontext *yy); /* 9 */ +YY_RULE(int) yy_LESSEQ_THAN(yycontext *yy); /* 8 */ +YY_RULE(int) yy_CLOSE_ENOUGH(yycontext *yy); /* 7 */ +YY_RULE(int) yy_MINUS(yycontext *yy); /* 6 */ +YY_RULE(int) yy_PLUS(yycontext *yy); /* 5 */ +YY_RULE(int) yy_Product(yycontext *yy); /* 4 */ +YY_RULE(int) yy_Sum(yycontext *yy); /* 3 */ +YY_RULE(int) yy_SPACE(yycontext *yy); /* 2 */ +YY_RULE(int) yy_Expr(yycontext *yy); /* 1 */ + +YY_ACTION(void) yy_5_SI_Unit(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_5_SI_Unit\n")); + { + math_eval_push(1000000000000000, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_SI_Unit(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_SI_Unit\n")); + { + math_eval_push(1000000000000, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_SI_Unit(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_SI_Unit\n")); + { + math_eval_push(1000000000, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_SI_Unit(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_SI_Unit\n")); + { + math_eval_push(1000000, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_SI_Unit(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_SI_Unit\n")); + { + math_eval_push(1000, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_13_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_13_Constant\n")); + { + math_eval_push(0.70710678118654752440, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_12_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_12_Constant\n")); + { + math_eval_push(1.41421356237309504880, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_11_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_11_Constant\n")); + { + math_eval_push(1.12837916709551257390, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_10_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_10_Constant\n")); + { + math_eval_push(0.63661977236758134308, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_9_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_9_Constant\n")); + { + math_eval_push(0.31830988618379067154, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_8_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_8_Constant\n")); + { + math_eval_push(0.78539816339744830962, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_7_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_7_Constant\n")); + { + math_eval_push(1.57079632679489661923, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_6_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_6_Constant\n")); + { + math_eval_push(3.14159265358979323846, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_5_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_5_Constant\n")); + { + math_eval_push(2.30258509299404568402, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_Constant\n")); + { + math_eval_push(0.69314718055994530942, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_Constant\n")); + { + math_eval_push(0.43429448190325182765, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_Constant\n")); + { + math_eval_push(1.4426950408889634074, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_Constant(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_Constant\n")); + { + math_eval_push(2.7182818284590452354, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_Fname(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_Fname\n")); + { + strcpy(yy->fname, yytext); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_Funcall(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_Funcall\n")); + { + math_eval_push(EvaluateMathFunction(yy->fname, math_eval_pop(yy->stack, &(yy->stackp))), yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_Value(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_Value\n")); + { + double scanned = 0; sscanf(yytext, "%lf", &scanned); math_eval_push(scanned, yy->stack, &(yy->stackp)); /*Log(LOG_LEVEL_ERR, "YY: read FP %lf", scanned);*/ ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_Value(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_Value\n")); + { + double scanned = 0; sscanf(yytext, "%lf", &scanned); math_eval_push(math_eval_pop(yy->stack, &(yy->stackp)) * scanned, yy->stack, &(yy->stackp)); /* Log(LOG_LEVEL_ERR, "YY: read FP %lf", scanned); */ ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_Product(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_Product\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push((long)l % (long)r, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_Product(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_Product\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l / r, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_Product(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_Product\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l * r, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_Product(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_Product\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(pow(l, r), yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_7_Sum(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_7_Sum\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l > r, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_6_Sum(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_6_Sum\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push((l > r || fabs(l - r) < 0.00000000000000001), yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_5_Sum(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_5_Sum\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l < r, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_4_Sum(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_4_Sum\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push((l < r || fabs(l - r) < 0.00000000000000001), yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_3_Sum(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_3_Sum\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(fabs(l - r) < 0.00000000000000001, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_Sum(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_Sum\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l - r, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_Sum(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_Sum\n")); + { + double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l + r, yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_2_Expr(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_2_Expr\n")); + { + strcpy(yy->failure, "expression could not be parsed"); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} +YY_ACTION(void) yy_1_Expr(yycontext *yy, char *yytext, int yyleng) +{ +#define __ yy->__ +#define yypos yy->__pos +#define yythunkpos yy->__thunkpos + yyprintf((stderr, "do yy_1_Expr\n")); + { + yy->result = math_eval_pop(yy->stack, &(yy->stackp)); ; + } +#undef yythunkpos +#undef yypos +#undef yy +} + +YY_RULE(int) yy_Fname(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "Fname")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l1; +#undef yytext +#undef yyleng + } + { int yypos2= yy->__pos, yythunkpos2= yy->__thunkpos; if (!yymatchString(yy, "ceil")) goto l3; goto l2; + l3:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "floor")) goto l4; goto l2; + l4:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "log10")) goto l5; goto l2; + l5:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "log2")) goto l6; goto l2; + l6:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "log")) goto l7; goto l2; + l7:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "sqrt")) goto l8; goto l2; + l8:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "sin")) goto l9; goto l2; + l9:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "cos")) goto l10; goto l2; + l10:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "tan")) goto l11; goto l2; + l11:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "asin")) goto l12; goto l2; + l12:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "acos")) goto l13; goto l2; + l13:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "atan")) goto l14; goto l2; + l14:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "abs")) goto l15; goto l2; + l15:; yy->__pos= yypos2; yy->__thunkpos= yythunkpos2; if (!yymatchString(yy, "step")) goto l1; + } + l2:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l1; +#undef yytext +#undef yyleng + } yyDo(yy, yy_1_Fname, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "Fname", yy->__buf+yy->__pos)); + return 1; + l1:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "Fname", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_CLOSE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "CLOSE")); if (!yymatchChar(yy, ')')) goto l16; if (!yy_SPACE(yy)) goto l16; + yyprintf((stderr, " ok %s @ %s\n", "CLOSE", yy->__buf+yy->__pos)); + return 1; + l16:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "CLOSE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_OPEN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "OPEN")); if (!yymatchChar(yy, '(')) goto l17; if (!yy_SPACE(yy)) goto l17; + yyprintf((stderr, " ok %s @ %s\n", "OPEN", yy->__buf+yy->__pos)); + return 1; + l17:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "OPEN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_Funcall(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "Funcall")); if (!yy_Fname(yy)) goto l18; if (!yy_OPEN(yy)) goto l18; if (!yy_Value(yy)) goto l18; if (!yy_CLOSE(yy)) goto l18; yyDo(yy, yy_1_Funcall, yy->__begin, yy->__end); + yyprintf((stderr, " ok %s @ %s\n", "Funcall", yy->__buf+yy->__pos)); + return 1; + l18:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "Funcall", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_Constant(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "Constant")); + { int yypos20= yy->__pos, yythunkpos20= yy->__thunkpos; if (!yymatchChar(yy, 'e')) goto l21; yyDo(yy, yy_1_Constant, yy->__begin, yy->__end); goto l20; + l21:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "log2e")) goto l22; yyDo(yy, yy_2_Constant, yy->__begin, yy->__end); goto l20; + l22:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "log10e")) goto l23; yyDo(yy, yy_3_Constant, yy->__begin, yy->__end); goto l20; + l23:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "ln2")) goto l24; yyDo(yy, yy_4_Constant, yy->__begin, yy->__end); goto l20; + l24:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "ln10")) goto l25; yyDo(yy, yy_5_Constant, yy->__begin, yy->__end); goto l20; + l25:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "pi")) goto l26; yyDo(yy, yy_6_Constant, yy->__begin, yy->__end); goto l20; + l26:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "pi_2")) goto l27; yyDo(yy, yy_7_Constant, yy->__begin, yy->__end); goto l20; + l27:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "pi_4")) goto l28; yyDo(yy, yy_8_Constant, yy->__begin, yy->__end); goto l20; + l28:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "1_pi")) goto l29; yyDo(yy, yy_9_Constant, yy->__begin, yy->__end); goto l20; + l29:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "2_pi")) goto l30; yyDo(yy, yy_10_Constant, yy->__begin, yy->__end); goto l20; + l30:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "2_sqrtpi")) goto l31; yyDo(yy, yy_11_Constant, yy->__begin, yy->__end); goto l20; + l31:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "sqrt2")) goto l32; yyDo(yy, yy_12_Constant, yy->__begin, yy->__end); goto l20; + l32:; yy->__pos= yypos20; yy->__thunkpos= yythunkpos20; if (!yymatchString(yy, "sqrt1_2")) goto l19; yyDo(yy, yy_13_Constant, yy->__begin, yy->__end); + } + l20:; + yyprintf((stderr, " ok %s @ %s\n", "Constant", yy->__buf+yy->__pos)); + return 1; + l19:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "Constant", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_SI_Unit(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "SI_Unit")); + { int yypos34= yy->__pos, yythunkpos34= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\000\010\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l35; if (!yy_SPACE(yy)) goto l35; yyDo(yy, yy_1_SI_Unit, yy->__begin, yy->__end); goto l34; + l35:; yy->__pos= yypos34; yy->__thunkpos= yythunkpos34; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\000\040\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l36; if (!yy_SPACE(yy)) goto l36; yyDo(yy, yy_2_SI_Unit, yy->__begin, yy->__end); goto l34; + l36:; yy->__pos= yypos34; yy->__thunkpos= yythunkpos34; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\200\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l37; if (!yy_SPACE(yy)) goto l37; yyDo(yy, yy_3_SI_Unit, yy->__begin, yy->__end); goto l34; + l37:; yy->__pos= yypos34; yy->__thunkpos= yythunkpos34; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\000\000\020\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l38; if (!yy_SPACE(yy)) goto l38; yyDo(yy, yy_4_SI_Unit, yy->__begin, yy->__end); goto l34; + l38:; yy->__pos= yypos34; yy->__thunkpos= yythunkpos34; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\000\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l33; if (!yy_SPACE(yy)) goto l33; yyDo(yy, yy_5_SI_Unit, yy->__begin, yy->__end); + } + l34:; + yyprintf((stderr, " ok %s @ %s\n", "SI_Unit", yy->__buf+yy->__pos)); + return 1; + l33:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "SI_Unit", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_F_NUMBER(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "F_NUMBER")); yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_BEGIN)) goto l39; +#undef yytext +#undef yyleng + } + { int yypos40= yy->__pos, yythunkpos40= yy->__thunkpos; + { int yypos42= yy->__pos, yythunkpos42= yy->__thunkpos; if (!yymatchChar(yy, '-')) goto l42; goto l43; + l42:; yy->__pos= yypos42; yy->__thunkpos= yythunkpos42; + } + l43:; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l41; + l44:; + { int yypos45= yy->__pos, yythunkpos45= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l45; goto l44; + l45:; yy->__pos= yypos45; yy->__thunkpos= yythunkpos45; + } + { int yypos46= yy->__pos, yythunkpos46= yy->__thunkpos; if (!yymatchChar(yy, '.')) goto l46; goto l47; + l46:; yy->__pos= yypos46; yy->__thunkpos= yythunkpos46; + } + l47:; + l48:; + { int yypos49= yy->__pos, yythunkpos49= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l49; goto l48; + l49:; yy->__pos= yypos49; yy->__thunkpos= yythunkpos49; + } goto l40; + l41:; yy->__pos= yypos40; yy->__thunkpos= yythunkpos40; + { int yypos50= yy->__pos, yythunkpos50= yy->__thunkpos; if (!yymatchChar(yy, '-')) goto l50; goto l51; + l50:; yy->__pos= yypos50; yy->__thunkpos= yythunkpos50; + } + l51:; if (!yymatchChar(yy, '.')) goto l39; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l39; + l52:; + { int yypos53= yy->__pos, yythunkpos53= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l53; goto l52; + l53:; yy->__pos= yypos53; yy->__thunkpos= yythunkpos53; + } + } + l40:; yyText(yy, yy->__begin, yy->__end); { +#define yytext yy->__text +#define yyleng yy->__textlen +if (!(YY_END)) goto l39; +#undef yytext +#undef yyleng + } if (!yy_SPACE(yy)) goto l39; + yyprintf((stderr, " ok %s @ %s\n", "F_NUMBER", yy->__buf+yy->__pos)); + return 1; + l39:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "F_NUMBER", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_MOD(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "MOD")); if (!yymatchChar(yy, '%')) goto l54; if (!yy_SPACE(yy)) goto l54; + yyprintf((stderr, " ok %s @ %s\n", "MOD", yy->__buf+yy->__pos)); + return 1; + l54:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "MOD", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_DIVIDE(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "DIVIDE")); if (!yymatchChar(yy, '/')) goto l55; if (!yy_SPACE(yy)) goto l55; + yyprintf((stderr, " ok %s @ %s\n", "DIVIDE", yy->__buf+yy->__pos)); + return 1; + l55:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "DIVIDE", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_TIMES(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "TIMES")); if (!yymatchChar(yy, '*')) goto l56; if (!yy_SPACE(yy)) goto l56; + yyprintf((stderr, " ok %s @ %s\n", "TIMES", yy->__buf+yy->__pos)); + return 1; + l56:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "TIMES", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_POW(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "POW")); + { int yypos58= yy->__pos, yythunkpos58= yy->__thunkpos; if (!yymatchChar(yy, '^')) goto l59; if (!yy_SPACE(yy)) goto l59; goto l58; + l59:; yy->__pos= yypos58; yy->__thunkpos= yythunkpos58; if (!yymatchString(yy, "**")) goto l57; if (!yy_SPACE(yy)) goto l57; + } + l58:; + yyprintf((stderr, " ok %s @ %s\n", "POW", yy->__buf+yy->__pos)); + return 1; + l57:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "POW", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_Value(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "Value")); + { int yypos61= yy->__pos, yythunkpos61= yy->__thunkpos; if (!yy_F_NUMBER(yy)) goto l62; if (!yy_SI_Unit(yy)) goto l62; yyDo(yy, yy_1_Value, yy->__begin, yy->__end); goto l61; + l62:; yy->__pos= yypos61; yy->__thunkpos= yythunkpos61; if (!yy_F_NUMBER(yy)) goto l63; yyDo(yy, yy_2_Value, yy->__begin, yy->__end); goto l61; + l63:; yy->__pos= yypos61; yy->__thunkpos= yythunkpos61; if (!yy_Constant(yy)) goto l64; goto l61; + l64:; yy->__pos= yypos61; yy->__thunkpos= yythunkpos61; if (!yy_Funcall(yy)) goto l65; goto l61; + l65:; yy->__pos= yypos61; yy->__thunkpos= yythunkpos61; if (!yy_OPEN(yy)) goto l60; if (!yy_Sum(yy)) goto l60; if (!yy_CLOSE(yy)) goto l60; + } + l61:; + yyprintf((stderr, " ok %s @ %s\n", "Value", yy->__buf+yy->__pos)); + return 1; + l60:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "Value", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_GREATER_THAN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "GREATER_THAN")); if (!yymatchChar(yy, '>')) goto l66; if (!yy_SPACE(yy)) goto l66; + yyprintf((stderr, " ok %s @ %s\n", "GREATER_THAN", yy->__buf+yy->__pos)); + return 1; + l66:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "GREATER_THAN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_GREATEREQ_THAN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "GREATEREQ_THAN")); if (!yymatchString(yy, ">=")) goto l67; if (!yy_SPACE(yy)) goto l67; + yyprintf((stderr, " ok %s @ %s\n", "GREATEREQ_THAN", yy->__buf+yy->__pos)); + return 1; + l67:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "GREATEREQ_THAN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_LESS_THAN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "LESS_THAN")); if (!yymatchChar(yy, '<')) goto l68; if (!yy_SPACE(yy)) goto l68; + yyprintf((stderr, " ok %s @ %s\n", "LESS_THAN", yy->__buf+yy->__pos)); + return 1; + l68:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "LESS_THAN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_LESSEQ_THAN(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "LESSEQ_THAN")); if (!yymatchString(yy, "<=")) goto l69; if (!yy_SPACE(yy)) goto l69; + yyprintf((stderr, " ok %s @ %s\n", "LESSEQ_THAN", yy->__buf+yy->__pos)); + return 1; + l69:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "LESSEQ_THAN", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_CLOSE_ENOUGH(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "CLOSE_ENOUGH")); if (!yymatchString(yy, "==")) goto l70; if (!yy_SPACE(yy)) goto l70; + yyprintf((stderr, " ok %s @ %s\n", "CLOSE_ENOUGH", yy->__buf+yy->__pos)); + return 1; + l70:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "CLOSE_ENOUGH", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_MINUS(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "MINUS")); if (!yymatchChar(yy, '-')) goto l71; if (!yy_SPACE(yy)) goto l71; + yyprintf((stderr, " ok %s @ %s\n", "MINUS", yy->__buf+yy->__pos)); + return 1; + l71:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "MINUS", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_PLUS(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "PLUS")); if (!yymatchChar(yy, '+')) goto l72; if (!yy_SPACE(yy)) goto l72; + yyprintf((stderr, " ok %s @ %s\n", "PLUS", yy->__buf+yy->__pos)); + return 1; + l72:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "PLUS", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_Product(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "Product")); if (!yy_Value(yy)) goto l73; + l74:; + { int yypos75= yy->__pos, yythunkpos75= yy->__thunkpos; + { int yypos76= yy->__pos, yythunkpos76= yy->__thunkpos; if (!yy_POW(yy)) goto l77; if (!yy_Value(yy)) goto l77; yyDo(yy, yy_1_Product, yy->__begin, yy->__end); goto l76; + l77:; yy->__pos= yypos76; yy->__thunkpos= yythunkpos76; if (!yy_TIMES(yy)) goto l78; if (!yy_Value(yy)) goto l78; yyDo(yy, yy_2_Product, yy->__begin, yy->__end); goto l76; + l78:; yy->__pos= yypos76; yy->__thunkpos= yythunkpos76; if (!yy_DIVIDE(yy)) goto l79; if (!yy_Value(yy)) goto l79; yyDo(yy, yy_3_Product, yy->__begin, yy->__end); goto l76; + l79:; yy->__pos= yypos76; yy->__thunkpos= yythunkpos76; if (!yy_MOD(yy)) goto l75; if (!yy_Value(yy)) goto l75; yyDo(yy, yy_4_Product, yy->__begin, yy->__end); + } + l76:; goto l74; + l75:; yy->__pos= yypos75; yy->__thunkpos= yythunkpos75; + } + yyprintf((stderr, " ok %s @ %s\n", "Product", yy->__buf+yy->__pos)); + return 1; + l73:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "Product", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_Sum(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "Sum")); if (!yy_Product(yy)) goto l80; + l81:; + { int yypos82= yy->__pos, yythunkpos82= yy->__thunkpos; + { int yypos83= yy->__pos, yythunkpos83= yy->__thunkpos; if (!yy_PLUS(yy)) goto l84; if (!yy_Product(yy)) goto l84; yyDo(yy, yy_1_Sum, yy->__begin, yy->__end); goto l83; + l84:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_MINUS(yy)) goto l85; if (!yy_Product(yy)) goto l85; yyDo(yy, yy_2_Sum, yy->__begin, yy->__end); goto l83; + l85:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_CLOSE_ENOUGH(yy)) goto l86; if (!yy_Product(yy)) goto l86; yyDo(yy, yy_3_Sum, yy->__begin, yy->__end); goto l83; + l86:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_LESSEQ_THAN(yy)) goto l87; if (!yy_Product(yy)) goto l87; yyDo(yy, yy_4_Sum, yy->__begin, yy->__end); goto l83; + l87:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_LESS_THAN(yy)) goto l88; if (!yy_Product(yy)) goto l88; yyDo(yy, yy_5_Sum, yy->__begin, yy->__end); goto l83; + l88:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_GREATEREQ_THAN(yy)) goto l89; if (!yy_Product(yy)) goto l89; yyDo(yy, yy_6_Sum, yy->__begin, yy->__end); goto l83; + l89:; yy->__pos= yypos83; yy->__thunkpos= yythunkpos83; if (!yy_GREATER_THAN(yy)) goto l82; if (!yy_Product(yy)) goto l82; yyDo(yy, yy_7_Sum, yy->__begin, yy->__end); + } + l83:; goto l81; + l82:; yy->__pos= yypos82; yy->__thunkpos= yythunkpos82; + } + yyprintf((stderr, " ok %s @ %s\n", "Sum", yy->__buf+yy->__pos)); + return 1; + l80:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "Sum", yy->__buf+yy->__pos)); + return 0; +} +YY_RULE(int) yy_SPACE(yycontext *yy) +{ + yyprintf((stderr, "%s\n", "SPACE")); + l91:; + { int yypos92= yy->__pos, yythunkpos92= yy->__thunkpos; if (!yymatchClass(yy, (unsigned char *)"\000\002\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l92; goto l91; + l92:; yy->__pos= yypos92; yy->__thunkpos= yythunkpos92; + } + yyprintf((stderr, " ok %s @ %s\n", "SPACE", yy->__buf+yy->__pos)); + return 1; +} +YY_RULE(int) yy_Expr(yycontext *yy) +{ int yypos0= yy->__pos, yythunkpos0= yy->__thunkpos; + yyprintf((stderr, "%s\n", "Expr")); + { int yypos94= yy->__pos, yythunkpos94= yy->__thunkpos; if (!yy_SPACE(yy)) goto l95; if (!yy_Sum(yy)) goto l95; yyDo(yy, yy_1_Expr, yy->__begin, yy->__end); goto l94; + l95:; yy->__pos= yypos94; yy->__thunkpos= yythunkpos94; if (!yymatchDot(yy)) goto l93; yyDo(yy, yy_2_Expr, yy->__begin, yy->__end); + } + l94:; + yyprintf((stderr, " ok %s @ %s\n", "Expr", yy->__buf+yy->__pos)); + return 1; + l93:; yy->__pos= yypos0; yy->__thunkpos= yythunkpos0; + yyprintf((stderr, " fail %s @ %s\n", "Expr", yy->__buf+yy->__pos)); + return 0; +} + +#ifndef YY_PART + +typedef int (*yyrule)(yycontext *yy); + +YY_PARSE(int) YYPARSEFROM(YY_CTX_PARAM_ yyrule yystart) +{ + int yyok; + if (!yyctx->__buflen) + { + yyctx->__buflen= YY_BUFFER_SIZE; + yyctx->__buf= (char *)YY_MALLOC(yyctx, yyctx->__buflen); + yyctx->__textlen= YY_BUFFER_SIZE; + yyctx->__text= (char *)YY_MALLOC(yyctx, yyctx->__textlen); + yyctx->__thunkslen= YY_STACK_SIZE; + yyctx->__thunks= (yythunk *)YY_MALLOC(yyctx, sizeof(yythunk) * yyctx->__thunkslen); + yyctx->__valslen= YY_STACK_SIZE; + yyctx->__vals= (YYSTYPE *)YY_MALLOC(yyctx, sizeof(YYSTYPE) * yyctx->__valslen); + yyctx->__begin= yyctx->__end= yyctx->__pos= yyctx->__limit= yyctx->__thunkpos= 0; + } + yyctx->__begin= yyctx->__end= yyctx->__pos; + yyctx->__thunkpos= 0; + yyctx->__val= yyctx->__vals; + yyok= yystart(yyctx); + if (yyok) yyDone(yyctx); + yyCommit(yyctx); + return yyok; +} + +YY_PARSE(int) YYPARSE(YY_CTX_PARAM) +{ + return YYPARSEFROM(YY_CTX_ARG_ yy_Expr); +} + +YY_PARSE(yycontext *) YYRELEASE(yycontext *yyctx) +{ + if (yyctx->__buflen) + { + yyctx->__buflen= 0; + YY_FREE(yyctx, yyctx->__buf); + YY_FREE(yyctx, yyctx->__text); + YY_FREE(yyctx, yyctx->__thunks); + YY_FREE(yyctx, yyctx->__vals); + } + return yyctx; +} + +#endif diff --git a/libpromises/math.peg b/libpromises/math.peg new file mode 100644 index 0000000000..416ca473b0 --- /dev/null +++ b/libpromises/math.peg @@ -0,0 +1,94 @@ + # Copyright 2021 Northern.tech AS + + # This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + # This program is free software; you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by the + # Free Software Foundation; version 3. + + # This program is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + + # You should have received a copy of the GNU General Public License + # along with this program; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + # To the extent this program is licensed as part of the Enterprise + # versions of CFEngine, the applicable Commercial Open Source License + # (COSL) may apply to this file if you as a licensee so wish it. See + # included file COSL.txt. + +# Math Grammar + +# The result of this grammar is commited to the repo as math.pc +# It's not generated as part of a build, but as a manual step +# if you change this grammar, use 3rdparty/peg-0.1.15 to regenerate math.pc + +Expr <- SPACE Sum { yy->result = math_eval_pop(yy->stack, &(yy->stackp)); } + / . { strcpy(yy->failure, "expression could not be parsed"); } + +Sum <- Product ( PLUS Product { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l + r, yy->stack, &(yy->stackp)); } + / MINUS Product { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l - r, yy->stack, &(yy->stackp)); } + / CLOSE_ENOUGH Product { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(fabs(l - r) < 0.00000000000000001, yy->stack, &(yy->stackp)); } + / LESSEQ_THAN Product { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push((l < r || fabs(l - r) < 0.00000000000000001), yy->stack, &(yy->stackp)); } + / LESS_THAN Product { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l < r, yy->stack, &(yy->stackp)); } + / GREATEREQ_THAN Product { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push((l > r || fabs(l - r) < 0.00000000000000001), yy->stack, &(yy->stackp)); } + / GREATER_THAN Product { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l > r, yy->stack, &(yy->stackp)); } + )* + +Product <- Value ( POW Value { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(pow(l, r), yy->stack, &(yy->stackp)); } + / TIMES Value { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l * r, yy->stack, &(yy->stackp)); } + / DIVIDE Value { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push(l / r, yy->stack, &(yy->stackp)); } + / MOD Value { double r= math_eval_pop(yy->stack, &(yy->stackp)), l= math_eval_pop(yy->stack, &(yy->stackp)); math_eval_push((long)l % (long)r, yy->stack, &(yy->stackp)); } + )* + +Value <- F_NUMBER SI_Unit { double scanned = 0; sscanf(yytext, "%lf", &scanned); math_eval_push(math_eval_pop(yy->stack, &(yy->stackp)) * scanned, yy->stack, &(yy->stackp)); /* Log(LOG_LEVEL_ERR, "YY: read FP %lf", scanned); */ } + / F_NUMBER { double scanned = 0; sscanf(yytext, "%lf", &scanned); math_eval_push(scanned, yy->stack, &(yy->stackp)); /*Log(LOG_LEVEL_ERR, "YY: read FP %lf", scanned);*/ } + / Constant + / Funcall + / OPEN Sum CLOSE + +Funcall <- Fname OPEN Value CLOSE { math_eval_push(EvaluateMathFunction(yy->fname, math_eval_pop(yy->stack, &(yy->stackp))), yy->stack, &(yy->stackp)); } + +Fname <- < ( "ceil" / "floor" / "log10" / "log2" / "log" / "sqrt" / "sin" / "cos" / "tan" / "asin" / "acos" / "atan" / "abs" / "step" ) > { strcpy(yy->fname, yytext); } + +Constant <- "e" { math_eval_push(2.7182818284590452354, yy->stack, &(yy->stackp)); } + / "log2e" { math_eval_push(1.4426950408889634074, yy->stack, &(yy->stackp)); } + / "log10e" { math_eval_push(0.43429448190325182765, yy->stack, &(yy->stackp)); } + / "ln2" { math_eval_push(0.69314718055994530942, yy->stack, &(yy->stackp)); } + / "ln10" { math_eval_push(2.30258509299404568402, yy->stack, &(yy->stackp)); } + / "pi" { math_eval_push(3.14159265358979323846, yy->stack, &(yy->stackp)); } + / "pi_2" { math_eval_push(1.57079632679489661923, yy->stack, &(yy->stackp)); } + / "pi_4" { math_eval_push(0.78539816339744830962, yy->stack, &(yy->stackp)); } + / "1_pi" { math_eval_push(0.31830988618379067154, yy->stack, &(yy->stackp)); } + / "2_pi" { math_eval_push(0.63661977236758134308, yy->stack, &(yy->stackp)); } + / "2_sqrtpi" { math_eval_push(1.12837916709551257390, yy->stack, &(yy->stackp)); } + / "sqrt2" { math_eval_push(1.41421356237309504880, yy->stack, &(yy->stackp)); } + / "sqrt1_2" { math_eval_push(0.70710678118654752440, yy->stack, &(yy->stackp)); } + +SI_Unit <- [kK] SPACE { math_eval_push(1000, yy->stack, &(yy->stackp)); } + / [mM] SPACE { math_eval_push(1000000, yy->stack, &(yy->stackp)); } + / [gG] SPACE { math_eval_push(1000000000, yy->stack, &(yy->stackp)); } + / [tT] SPACE { math_eval_push(1000000000000, yy->stack, &(yy->stackp)); } + / [pP] SPACE { math_eval_push(1000000000000000, yy->stack, &(yy->stackp)); } + +# Lexemes + +F_NUMBER <- < ( '-'? [0-9]+ '.'? [0-9]* / '-'? '.' [0-9]+ ) > SPACE +CLOSE_ENOUGH <- '==' SPACE +LESSEQ_THAN <- '<=' SPACE +LESS_THAN <- '<' SPACE +GREATEREQ_THAN <- '>=' SPACE +GREATER_THAN <- '>' SPACE + +PLUS <- '+' SPACE +MINUS <- '-' SPACE +TIMES <- '*' SPACE +DIVIDE <- '/' SPACE +MOD <- '%' SPACE +POW <- '^' SPACE / '**' SPACE +OPEN <- '(' SPACE +CLOSE <- ')' SPACE +SPACE <- [ \t]* diff --git a/libpromises/math_eval.c b/libpromises/math_eval.c new file mode 100644 index 0000000000..2aeff91a15 --- /dev/null +++ b/libpromises/math_eval.c @@ -0,0 +1,145 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#define MATH_EVAL_STACK_SIZE 1024 + +const char *const math_eval_function_names[] = +{ + "ceil", "floor", "log10", "log2", "log", "sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "abs", "step" +}; + +static double _math_eval_step(double p) +{ + return ((p < 0) ? 0 : 1); +} + +typedef double (*MathEvalFunctionType)(double); + +static const MathEvalFunctionType math_eval_functions[] = +{ + ceil, floor, log10, log2, log, sqrt, sin, cos, tan, asin, acos, atan, fabs, _math_eval_step +}; + + +double math_eval_push(double n, double *stack, int *stackp) +{ + if (*stackp > MATH_EVAL_STACK_SIZE) + { + Log(LOG_LEVEL_ERR, "Math evaluation stack size exceeded"); + return 0; + } + + return stack[++(*stackp)]= n; +} + +double math_eval_pop(double *stack, int *stackp) +{ + if (*stackp < 0) + { + Log(LOG_LEVEL_ERR, "Math evaluation stack could not be popped, internal error!"); + return 0; + } + + return stack[(*stackp)--]; +} + +#define YYSTYPE double +#define YYPARSE yymath_parse +#define YYPARSEFROM yymath_parsefrom +#define YY_CTX_LOCAL +#define YY_PARSE(T) T +#define YY_INPUT(ctx, buf, result, max_size) { \ + result = 0; \ + if (ctx->input != NULL) \ + { \ + /*Log(LOG_LEVEL_ERR, "YYINPUT: %s", ctx->input);*/ \ + strncpy(buf, ctx->input, max_size); \ + int n = strlen(ctx->input)+1; \ + if (n > max_size) n = max_size; \ + if (n > 0) buf[n - 1]= '\0'; \ + result = strlen(buf); \ + ctx->input = NULL; \ + } \ + } + +#undef malloc +#undef realloc +#define malloc xmalloc +#define realloc xrealloc + +#define YY_CTX_MEMBERS char *failure; \ + const char *input; \ + const char *original_input; \ + EvalContext *eval_context; \ + double result; \ + char fname[50]; \ + double stack[MATH_EVAL_STACK_SIZE]; \ + int stackp; + +/* Mark unused functions as such */ +struct _yycontext; +static int yyAccept(struct _yycontext *yy, int tp0) FUNC_UNUSED; +static void yyPush(struct _yycontext *yy, char *text, int count) FUNC_UNUSED; +static void yyPop(struct _yycontext *yy, char *text, int count) FUNC_UNUSED; +static void yySet(struct _yycontext *yy, char *text, int count) FUNC_UNUSED; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#include // Generated from math.peg using 3rdparty/peg-0.1.15 + +#pragma GCC diagnostic pop + +double EvaluateMathInfix(EvalContext *ctx, const char *input, char *failure) +{ + yycontext yyctx; + memset(&yyctx, 0, sizeof(yycontext)); + yyctx.failure = failure; + yyctx.original_input = input; + yyctx.input = input; + yyctx.eval_context = ctx; + yyctx.result = 0; + yyctx.stackp = -1; + yymath_parse(&yyctx); + yyrelease(&yyctx); + return yyctx.result; +} + +double EvaluateMathFunction(const char *f, double p) +{ + int count = sizeof(math_eval_functions)/sizeof(math_eval_functions[0]); + + for (int i=0; i < count; i++) + { + if (strcmp(math_eval_function_names[i], f) == 0) + { + return (*math_eval_functions[i])(p); + } + } + + return p; +} diff --git a/libpromises/math_eval.h b/libpromises/math_eval.h new file mode 100644 index 0000000000..fcfcef5437 --- /dev/null +++ b/libpromises/math_eval.h @@ -0,0 +1,33 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MATH_EVAL_H +#define CFENGINE_MATH_EVAL_H + +#include + +double EvaluateMathInfix(EvalContext *ctx, const char *input, char *failure); +double EvaluateMathFunction(const char *f, double p); + +#endif diff --git a/libpromises/mod_access.c b/libpromises/mod_access.c new file mode 100644 index 0000000000..4605b4aa52 --- /dev/null +++ b/libpromises/mod_access.c @@ -0,0 +1,149 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* + This file can act as a template for adding functionality to cfengine 3. All + functionality can be added by extending the main array + + CF_MOD_PROMISE_TYPES[CF3_MODULES] + + and its array dimension, in mod_common, in the manner shown here. +*/ + +#include + +#include +#include +#include + +/* + Read this module file backwards, as dependencies have to be defined first - + these arrays declare pairs of constraints + + lval => rval + + in the form (lval,type,range) + + If the type is cf_body then the range is a pointer to another array of pairs, + like in a body "sub-routine" +*/ + +static const char *const POLICY_ERROR_WRONG_RESOURCE_FOR_DATA_SELECT = + "Constraint report_data_select is allowed only for 'query' resource_type"; + +static bool AccessParseTreeCheck(const Promise *pp, Seq *errors); + +static const ConstraintSyntax report_data_select_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("classes_include", CF_ANYSTRING, "List of regex filters for class names to be included into class report", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("classes_exclude", CF_ANYSTRING, "List of regex filters for class names to be excluded from class report", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("variables_include", CF_ANYSTRING, "List of regex filters for variable full qualified path to be included into variables report", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("variables_exclude", CF_ANYSTRING, "List of regex filters for variable full qualified path to be excluded from variables report", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("promise_notkept_log_include", CF_ANYSTRING, "List of regex filters for handle name to be included into promise not kept log report", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("promise_notkept_log_exclude", CF_ANYSTRING, "List of regex filters for handle name to be excluded from promise not kept log report", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("promise_repaired_log_include", CF_ANYSTRING, "List of regex filters for handle name to be included into promise repaired log report", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("promise_repaired_log_exclude", CF_ANYSTRING, "List of regex filters for handle name to be excluded from promise repaired log report", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("monitoring_include", CF_ANYSTRING, "List of regex filters for slot name to be included from monitoring report", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("monitoring_exclude", CF_ANYSTRING, "List of regex filters for slot name to be excluded from monitoring report", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("metatags_include", CF_ANYSTRING, "List of regex filters for metatags to be included into reports", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("metatags_exclude", CF_ANYSTRING, "List of regex filters for metatags to be excluded from reports", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("promise_handle_include", CF_ANYSTRING, "List of regex filters for promise handle to be included into reports", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("promise_handle_exclude", CF_ANYSTRING, "List of regex filters for promise handle to be excluded from reports", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax report_data_select_body = BodySyntaxNew("report_data_select", report_data_select_constraints, NULL, SYNTAX_STATUS_NORMAL); + +const ConstraintSyntax CF_REMACCESS_BODIES[REMOTE_ACCESS_NONE + 1] = +{ + ConstraintSyntaxNewStringList("admit", "", "List of host names or IP addresses to grant access to file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("deny", "", "List of host names or IP addresses to deny access to file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("admit_ips", "", "List of IP addresses or subnet masks to grant access to file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("deny_ips", "", "List of IP addresses or subnet masks to deny access to file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("admit_hostnames", "", "List of hostnames to grant access to file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("deny_hostnames", "", "List of hostnames to deny access to file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("admit_keys", "", "List of host keys that will be granted access to file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("deny_keys", "", "List of host keys that will be denied access to file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("maproot", "", "List of host names or IP addresses to grant full read-privilege on the server", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("ifencrypted", "true/false whether the current file access promise is conditional on the connection from the client being encrypted. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("resource_type", "path,literal,context,query,variable,bundle", "The type of object being granted access (the default is path and grants access to files)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("report_data_select", &report_data_select_body, "Report content filter", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("shortcut", "", "For path resource_type, the server will expand a relative path beginning with this text", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CF_REMROLE_BODIES[REMOTE_ROLE_NONE + 1] = +{ + ConstraintSyntaxNewStringList("authorize", "", "List of public-key user names that are allowed to activate the promised class during remote agent activation", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_REMACCESS_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("server", "access", CF_REMACCESS_BODIES, &AccessParseTreeCheck, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("server", "roles", CF_REMROLE_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; + +static bool AccessParseTreeCheck(const Promise *pp, Seq *errors) +{ + bool success = true; + + bool isResourceType = false; + bool isReportDataSelect = false; + Constraint *data_select_const = NULL; + + for (size_t i = 0; i conlist); i++) + { + Constraint *con = SeqAt(pp->conlist, i); + + if (StringSafeCompare("resource_type", con->lval) == 0) + { + if (con->rval.type == RVAL_TYPE_SCALAR) + { + if (StringSafeCompare("query", (char*)con->rval.item) == 0) + { + isResourceType = true; + } + } + } + else if (StringSafeCompare("report_data_select", con->lval) == 0) + { + data_select_const = con; + isReportDataSelect = true; + } + + } + + if (isReportDataSelect && !isResourceType) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, data_select_const, + POLICY_ERROR_WRONG_RESOURCE_FOR_DATA_SELECT)); + success = false; + } + + return success; +} + diff --git a/libpromises/mod_access.h b/libpromises/mod_access.h new file mode 100644 index 0000000000..9f3b1169dc --- /dev/null +++ b/libpromises/mod_access.h @@ -0,0 +1,59 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_ACCESS_H +#define CFENGINE_MOD_ACCESS_H + +#include + +typedef enum +{ + REMOTE_ACCESS_ADMIT, + REMOTE_ACCESS_DENY, + REMOTE_ACCESS_ADMITIPS, + REMOTE_ACCESS_DENYIPS, + REMOTE_ACCESS_ADMITHOSTNAMES, + REMOTE_ACCESS_DENYHOSTNAMES, + REMOTE_ACCESS_ADMITKEYS, + REMOTE_ACCESS_DENYKEYS, + REMOTE_ACCESS_MAPROOT, + REMOTE_ACCESS_IFENCRYPTED, + REMOTE_ACCESS_RESOURCE_TYPE, + REMOTE_ACCESS_REPORT_DATA_SELECT, + REMOTE_ACCESS_SHORTCUT, + REMOTE_ACCESS_NONE +} RemoteAccess; + +typedef enum +{ + REMOTE_ROLE_AUTHORIZE, + REMOTE_ROLE_NONE +} RemoteRole; + + +extern const PromiseTypeSyntax CF_REMACCESS_PROMISE_TYPES[]; +extern const ConstraintSyntax CF_REMACCESS_BODIES[REMOTE_ACCESS_NONE + 1]; +extern const ConstraintSyntax CF_REMROLE_BODIES[REMOTE_ROLE_NONE + 1]; + +#endif diff --git a/libpromises/mod_common.c b/libpromises/mod_common.c new file mode 100644 index 0000000000..a923fb6f2b --- /dev/null +++ b/libpromises/mod_common.c @@ -0,0 +1,562 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* This is a root node in the syntax tree */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CF_LOGRANGE "stdout|udp_syslog|(\042?[a-zA-Z]:\\\\.*)|(/.*)" +#define CF_FACILITY "LOG_USER,LOG_DAEMON,LOG_LOCAL0,LOG_LOCAL1,LOG_LOCAL2,LOG_LOCAL3,LOG_LOCAL4,LOG_LOCAL5,LOG_LOCAL6,LOG_LOCAL7" + +static const char *const POLICY_ERROR_VARS_CONSTRAINT_DUPLICATE_TYPE = + "Variable contains existing data type contstraint %s, tried to " + "redefine with %s"; +static const char *const POLICY_ERROR_VARS_PROMISER_NUMERICAL = + "Variable promises cannot have a purely numerical name (promiser)"; +static const char *const POLICY_ERROR_VARS_PROMISER_INVALID = + "Variable promise is using an invalid name (promiser)"; +static const char *const POLICY_ERROR_CLASSES_PROMISER_NUMERICAL = + "Classes promises cannot have a purely numerical name (promiser)"; + +static bool ActionCheck(const Body *body, Seq *errors) +{ + bool success = true; + + if (BodyHasConstraint(body, "log_kept") + || BodyHasConstraint(body, "log_repaired") + || BodyHasConstraint(body, "log_failed")) + { + if (!BodyHasConstraint(body, "log_string")) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BODY, body, "An action body with log_kept, log_repaired or log_failed is required to have a log_string attribute")); + success = false; + } + } + + return success; +} + +static const ConstraintSyntax action_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("action_policy", "fix,warn,nop", "Whether to repair or report about non-kept promises", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("ifelapsed", CF_VALRANGE, "Number of minutes before next allowed assessment of promise. Default value: control body value", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("expireafter", CF_VALRANGE, "Number of minutes before a repair action is interrupted and retried. Default value: control body value", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("log_string", "", "A message to be written to the log when a promise verification leads to a repair", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("log_level", "inform,verbose,error,log", "The reporting level sent to syslog", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("log_kept", CF_LOGRANGE,"This should be filename of a file to which log_string will be saved, if undefined it goes to the system logger", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("log_priority", "emergency,alert,critical,error,warning,notice,info,debug","The priority level of the log message, as interpreted by a syslog server", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("log_repaired", CF_LOGRANGE,"This should be filename of a file to which log_string will be saved, if undefined it goes to the system logger", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("log_failed", CF_LOGRANGE,"This should be filename of a file to which log_string will be saved, if undefined it goes to the system logger", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewReal("value_kept", CF_REALRANGE, "A real number value attributed to keeping this promise", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewReal("value_repaired", CF_REALRANGE, "A real number value attributed to reparing this promise", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewReal("value_notkept", CF_REALRANGE, "A real number value (possibly negative) attributed to not keeping this promise", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewBool("audit", "true/false switch for detailed audit records of this promise. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("background", "true/false switch for parallelizing the promise repair. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("report_level", "inform,verbose,error,log", "The reporting level for standard output for this promise. Default value: none", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("measurement_class", "", "If set performance will be measured and recorded under this identifier", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax action_body = BodySyntaxNew("action", action_constraints, ActionCheck, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax classes_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("scope", "namespace,bundle", "Scope of the contexts set by this body", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("promise_repaired", CF_IDRANGE, "A list of classes to be defined globally", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("repair_failed", CF_IDRANGE, "A list of classes to be defined globally", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("repair_denied", CF_IDRANGE, "A list of classes to be defined globally", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("repair_timeout", CF_IDRANGE, "A list of classes to be defined globally", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("promise_kept", CF_IDRANGE, "A list of classes to be defined globally", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("cancel_kept", CF_IDRANGE, "A list of classes to be cancelled if the promise is kept", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("cancel_repaired", CF_IDRANGE, "A list of classes to be cancelled if the promise is repaired", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("cancel_notkept", CF_IDRANGE, "A list of classes to be cancelled if the promise is not kept for any reason", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("kept_returncodes", CF_INTLISTRANGE, "A list of return codes indicating a kept command-related promise", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("repaired_returncodes", CF_INTLISTRANGE,"A list of return codes indicating a repaired command-related promise", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("failed_returncodes", CF_INTLISTRANGE, "A list of return codes indicating a failed command-related promise", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("persist_time", CF_VALRANGE, "A number of minutes the specified classes should remain active", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("timer_policy", "absolute,reset", "Whether a persistent class restarts its counter when rediscovered. Default value: reset", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax classes_body = BodySyntaxNew("classes", classes_constraints, NULL, SYNTAX_STATUS_NORMAL); + +const ConstraintSyntax CF_VARBODY[] = +{ + ConstraintSyntaxNewString("string", "", "A scalar string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("int", CF_INTRANGE, "A scalar integer", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewReal("real", CF_REALRANGE, "A scalar real number", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("slist", "", "A list of scalar strings", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntList("ilist", "A list of integers", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewRealList("rlist", "A list of real numbers", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContainer("data", "A data container", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("policy", "free,overridable,constant,ifdefined", "The policy for (dis)allowing (re)definition of variables", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static bool CheckIdentifierNotPurelyNumerical(const char *identifier) +{ + if (*identifier == '\0') + { + return true; + } + + for (const char *check = identifier; *check != '\0' && check - identifier < CF_BUFSIZE; check++) + { + if (!isdigit(*check)) + { + return true; + } + } + + return false; +} + +static bool VarsParseTreeCheck(const Promise *pp, Seq *errors) +{ + bool success = true; + + if (!CheckIdentifierNotPurelyNumerical(pp->promiser)) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_PROMISE, pp, + POLICY_ERROR_VARS_PROMISER_NUMERICAL)); + success = false; + } + + if (!CheckParseVariableName(pp->promiser)) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_PROMISE, pp, + POLICY_ERROR_VARS_PROMISER_INVALID)); + success = false; + } + + // ensure variables are declared with only one type. + { + char *data_type = NULL; + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + + if (DataTypeFromString(cp->lval) != CF_DATA_TYPE_NONE) + { + if (data_type != NULL) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, cp, + POLICY_ERROR_VARS_CONSTRAINT_DUPLICATE_TYPE, + data_type, cp->lval)); + success = false; + } + data_type = cp->lval; + } + } + } + + return success; +} + +const ConstraintSyntax CF_METABODY[] = +{ + ConstraintSyntaxNewString("string", "", "A scalar string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("slist", "", "A list of scalar strings", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContainer("data", "A data container", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CF_DEFAULTSBODY[] = +{ + ConstraintSyntaxNewString("if_match_regex", "", "If this regular expression matches the current value of the variable, replace it with default", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("string", "", "A scalar string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("slist", "", "A list of scalar strings", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + + +const ConstraintSyntax CF_CLASSBODY[] = +{ + ConstraintSyntaxNewOption("scope", "namespace,bundle", "Scope of the class set by this promise", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContextList("and", "Combine class sources with AND", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewRealList("dist", "Generate a probabilistic class distribution (from strategies in cfengine 2)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContext("expression", "Evaluate string expression of classes in normal form", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContextList("or", "Combine class sources with inclusive OR", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("persistence", CF_VALRANGE, "Make the class persistent (cached) to avoid reevaluation, time in minutes", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContext("not", "Evaluate the negation of string expression in normal form", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContextList("select_class", "Select one of the named list of classes to define based on host identity. Default value: random_selection", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContextList("xor", "Combine class sources with XOR", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static bool ClassesParseTreeCheck(const Promise *pp, Seq *errors) +{ + bool success = true; + + if (!CheckIdentifierNotPurelyNumerical(pp->promiser)) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_PROMISE, pp, + POLICY_ERROR_CLASSES_PROMISER_NUMERICAL)); + success = false; + } + + return success; +} + +const ConstraintSyntax CFG_CONTROLBODY[COMMON_CONTROL_MAX + 1] = +{ + ConstraintSyntaxNewStringList("bundlesequence", ".*", "List of promise bundles to verify in order", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("goal_patterns", "", "A list of regular expressions that match promisees/topics considered to be organizational goals", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("ignore_missing_bundles", "If any bundles in the bundlesequence do not exist, ignore and continue. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("ignore_missing_inputs", "If any input files do not exist, ignore and continue. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("inputs", ".*", "List of additional filenames to parse for promises", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("version", "", "Scalar version string for this configuration", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("lastseenexpireafter", CF_VALRANGE, "Number of minutes after which last-seen entries are purged. Default value: one week", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("output_prefix", "", "The string prefix for standard output", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("domain", ".*", "Specify the domain name for this host", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("require_comments", "Warn about promises that do not have comment documentation. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("host_licenses_paid", CF_VALRANGE, "This promise is deprecated since CFEngine version 3.1 and is ignored. Default value: 25", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewContextList("site_classes", "A list of classes that will represent geographical site locations for hosts. These should be defined elsewhere in the configuration in a classes promise.", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("syslog_host", CF_IPRANGE, "The name or address of a host to which syslog messages should be sent directly by UDP. Default value: localhost", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("syslog_port", CF_VALRANGE, "The port number of a UDP syslog service. Default value: 514", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("system_log_level", "(critical|error|warning|notice|info)", "Minimum system log level of messages that should be logged to system log", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("fips_mode", "Activate full FIPS mode restrictions. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewReal("bwlimit", CF_VALRANGE, "Limit outgoing protocol bandwidth in Bytes per second", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("cache_system_functions", "Cache the result of system functions. Default value: true", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("protocol_version", "1,classic,2,tls,3,cookie,latest", "CFEngine protocol version to use when connecting to the server. Default: \"latest\"", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("tls_ciphers", "", "List of acceptable ciphers in outgoing TLS connections, defaults to OpenSSL's default. For syntax help see man page for \"openssl ciphers\"", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("tls_min_version", "", "Minimum acceptable TLS version for outgoing connections, defaults to OpenSSL's default", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("package_inventory", ".*", "Name of the package manager used for software inventory management", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_module", ".*", "Name of the default package manager", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CFA_CONTROLBODY[] = +{ + ConstraintSyntaxNewStringList("abortclasses", ".*", "A list of classes which if defined in an agent bundle lead to termination of cf-agent", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("abortbundleclasses", ".*", "A list of classes which if defined lead to termination of current bundle", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("addclasses", ".*", "A list of classes to be defined always in the current context", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("agentaccess", ".*", "A list of user names allowed to execute cf-agent", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("agentfacility", CF_FACILITY, "The syslog facility for cf-agent. Default value: LOG_USER", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("allclassesreport", "Generate allclasses.txt report", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("alwaysvalidate", "true/false flag to determine whether configurations will always be checked before executing, or only after updates", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("auditing", "This option is deprecated, does nothing and is kept for backward compatibility. Default value: false", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("binarypaddingchar", "", "Character used to pad unequal replacements in binary editing. Default value: space (ASC=32)", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("bindtointerface", ".*", "Use this interface for outgoing connections", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("hashupdates", "true/false whether stored hashes are updated when change is detected in source. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("childlibpath", ".*", "LD_LIBRARY_PATH for child processes", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("checksum_alert_time", "0,60", "The persistence time for the checksum_alert class. Default value: 10 mins", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("defaultcopytype", "mtime,atime,ctime,digest,hash,binary", "ctime or mtime differ", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("dryrun", "All talk and no action mode. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("editbinaryfilesize", CF_VALRANGE, "Integer limit on maximum binary file size to be edited. Default value: 100000", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("editfilesize", CF_VALRANGE, "Integer limit on maximum text file size to be edited. Default value: 100000", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("environment", "[A-Za-z0-9_]+=.*", "List of environment variables to be inherited by children", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("exclamation", "true/false print exclamation marks during security warnings. Default value: true", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewInt("expireafter", CF_VALRANGE, "Global default for time before on-going promise repairs are interrupted. Default value: 1 min", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("files_single_copy", "", "List of filenames to be watched for multiple-source conflicts", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("files_auto_define", "", "List of filenames to define classes if copied", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("hostnamekeys", "true/false label ppkeys by hostname not IP address. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("ifelapsed", CF_VALRANGE, "Global default for time that must elapse before promise will be rechecked. Default value: 1", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("inform", "true/false set inform level default. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("intermittency", "This option is deprecated, does nothing and is kept for backward compatibility. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("max_children", CF_VALRANGE, "Maximum number of background tasks that should be allowed concurrently. Default value: 1 concurrent agent promise", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("maxconnections", CF_VALRANGE, "Maximum number of outgoing connections to cf-serverd. Default value: 30 remote queries", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("mountfilesystems", "true/false mount any filesystems promised. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("nonalphanumfiles", "true/false warn about filenames with no alphanumeric content. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("repchar", ".", "The character used to canonize pathnames in the file repository. Default value: _", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("refresh_processes", CF_IDRANGE, "Reload the process table before verifying the bundles named in this list (lazy evaluation)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("default_repository", CF_ABSPATHRANGE, "Path to the default file repository. Default value: in situ", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("secureinput", "true/false check whether input files are writable by unauthorized users. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("sensiblecount", CF_VALRANGE, "Minimum number of files a mounted filesystem is expected to have. Default value: 2 files", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("sensiblesize", CF_VALRANGE, "Minimum number of bytes a mounted filesystem is expected to have. Default value: 1000 bytes", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("skipidentify", "Do not send IP/name during server connection because address resolution is broken. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("suspiciousnames", "", "List of names to skip and warn about if found during any file search", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("syslog", "true/false switches on output to syslog at the inform level. Default value: false", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewBool("track_value", "true/false switches on tracking of promise valuation. Default value: false", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("timezone", "", "List of allowed timezones this machine must comply with", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("default_timeout", CF_VALRANGE, "Maximum time a network connection should attempt to connect. Default value: 10 seconds", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("verbose", "true/false switches on verbose standard output. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("report_class_log", "true/false enables logging classes at the end of agent execution. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("select_end_match_eof", "Set the default behavior of select_end_match_eof in edit_line promises. Default: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("copyfrom_restrict_keys", ".*", "A list of key hashes to restrict copy_from to", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CFS_CONTROLBODY[SERVER_CONTROL_MAX + 1] = +{ + ConstraintSyntaxNewStringList("allowallconnects", "","List of IPs or hostnames that may have more than one connection to the server port", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("allowconnects", "", "List of IPs or hostnames that may connect to the server port", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("allowusers", "", "List of usernames who may execute requests from this server", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("auditing", "true/false activate auditing of server connections. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("bindtointerface", "", "IP of the interface to which the server should bind on multi-homed hosts", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("cfruncommand", CF_PATHRANGE, "Path to the cf-agent command or cf-execd wrapper for remote execution", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("call_collect_interval", CF_VALRANGE, "The interval in minutes in between collect calls to the policy hub offering a tunnel for report collection (Enterprise)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("collect_window", CF_VALRANGE, "A time in seconds that a collect-call tunnel remains open to a hub to attempt a report transfer before it is closed (Enterprise)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("denybadclocks", "true/false accept connections from hosts with clocks that are out of sync. Default value: true", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("denyconnects", "", "List of IPs or hostnames that may NOT connect to the server port", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("dynamicaddresses", "", "List of IPs or hostnames for which the IP/name binding is expected to change", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("hostnamekeys", "true/false store keys using hostname lookup instead of IP addresses. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("keycacheTTL", CF_VALRANGE, "Maximum number of hours to hold public keys in the cache. Default value: 24", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewBool("logallconnections", "true/false causes the server to log all new connections to syslog. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("logencryptedtransfers", "true/false log all successful transfers required to be encrypted. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("maxconnections", CF_VALRANGE, "Maximum number of connections that will be accepted by cf-serverd. Default value: 30 remote queries", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("port", "1,65535", "Default port for cfengine server. Default value: 5308", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("serverfacility", CF_FACILITY, "Menu option for syslog facility level. Default value: LOG_USER", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("skipverify", "", "This option is deprecated, does nothing and is kept for backward compatibility.", SYNTAX_STATUS_DEPRECATED), + ConstraintSyntaxNewStringList("trustkeysfrom", "", "List of IPs from whom we accept public keys on trust", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("listen", "true/false enable server daemon to listen on defined port. Default value: true", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("allowciphers", "", "List of ciphers the server accepts. For Syntax help see man page for \"openssl ciphers\". Default is \"AES256-GCM-SHA384:AES256-SHA\"", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("allowlegacyconnects", "", "List of IPs from whom we accept legacy protocol connections", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("allowtlsversion", "", "Minimum TLS version allowed for incoming connections", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CFM_CONTROLBODY[] = +{ + ConstraintSyntaxNewReal("forgetrate", "0,1", "Decimal fraction [0,1] weighting of new values over old in 2d-average computation. Default value: 0.6", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("monitorfacility", CF_FACILITY, "Menu option for syslog facility. Default value: LOG_USER", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("histograms", "Ignored, kept for backward compatibility. Default value: true", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("tcpdump", "true/false use tcpdump if found. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("tcpdumpcommand", CF_ABSPATHRANGE, "Path to the tcpdump command on this system", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CFR_CONTROLBODY[] = +{ + ConstraintSyntaxNewStringList("hosts", "", "List of host or IP addresses to attempt connection with", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("port", "1,65535", "Default port for cfengine server. Default value: 5308", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("force_ipv4", "true/false force use of ipv4 in connection. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("trustkey", "true/false automatically accept all keys on trust from servers. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("encrypt", "true/false encrypt connections with servers. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("background_children", "true/false parallelize connections to servers. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("max_children", CF_VALRANGE, "Maximum number of simultaneous connections to attempt. Default value: 50 runagents", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("output_to_file", "true/false whether to send collected output to file(s). Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("output_directory", CF_ABSPATHRANGE, "Directory where the output is stored", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("timeout", "1,9999", "Connection timeout, sec", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CFEX_CONTROLBODY[] = /* enum cfexcontrol */ +{ + ConstraintSyntaxNewInt("splaytime", CF_VALRANGE, "Time in minutes to splay this host based on its name hash. Default value: 0", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("mailfrom", ".*@.*", "Email-address cfengine mail appears to come from", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("mailto", ".*@.*", "Email-address cfengine mail is sent to", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("mailsubject", "", "Define a custom mailsubject for the email message", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("smtpserver", ".*", "Name or IP of a willing smtp server for sending email", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("mailmaxlines", "0,1000", "Maximum number of lines of output to send by email. Default value: 30", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("mailfilter_include", "", "Which lines from the cf-agent output will be included in emails (regular expression)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("mailfilter_exclude", "", "Which lines from the cf-agent output will be excluded in emails (regular expression)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("schedule", "", "The class schedule used by cf-execd for activating cf-agent", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("executorfacility", CF_FACILITY, "Menu option for syslog facility level. Default value: LOG_USER", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("exec_command", CF_ABSPATHRANGE,"The full path and command to the executable run by default (overriding builtin)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("agent_expireafter", "0,10080", "Maximum agent runtime (in minutes). Default value: 120", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("runagent_socket_allow_users", "", "Users allowed to work with the runagent.socket to trigger agent runs", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +// Must be in sync with enum HubControl +const ConstraintSyntax CFH_CONTROLBODY[] = +{ + ConstraintSyntaxNewString("export_zenoss", CF_PATHRANGE, "Generate report for Zenoss integration", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("exclude_hosts", "", "A list of IP addresses of hosts to exclude from report collection", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("hub_schedule", "", "The class schedule used by cf-hub for report collation", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("query_timeout", "0,300", "Timeout (s) for connecting to host when querying. Default value: 30 (s)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("port", "1,65535", "Default port for contacting hub nodes. Default value: 5308", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("client_history_timeout", "1,65535", "Threshold in hours over which if client did not report, hub will start query for full state of the host and discard all accumulated report history on the client. Default value: 6 hours", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax file_control_constraints[] = /* enum cfh_control */ +{ + ConstraintSyntaxNewString("namespace", "[a-zA-Z_][a-zA-Z0-9_]*", "Switch to a private namespace to protect current file from duplicate definitions", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("inputs", ".*", "List of additional filenames to parse for promises", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CFRE_CONTROLBODY[] = /* enum cfrecontrol */ +{ + ConstraintSyntaxNewString("aggregation_point", CF_ABSPATHRANGE, "The root directory of the data cache for CMDB aggregation", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("auto_scaling", CF_BOOL, "true/false whether to auto-scale graph output to optimize use of space. Default value: true", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("build_directory", ".*", "The directory in which to generate output files", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("csv2xml", "", "A list of csv formatted files in the build directory to convert to simple xml", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("error_bars", CF_BOOL, "true/false whether to generate error bars on graph output", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("html_banner", "", "HTML code for a banner to be added to rendered in html after the header", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("html_embed", CF_BOOL, "If true, no header and footer tags will be added to html output", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("html_footer", "", "HTML code for a page footer to be added to rendered in html before the end body tag", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("query_engine", "", "Name of a dynamic web-page used to accept and drive queries in a browser", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOptionList("reports", "all,audit,performance,all_locks,active_locks,hashes,classes,last_seen,monitor_now,monitor_history,monitor_summary,compliance,setuid,file_changes,installed_software,software_patches,value,variables", "A list of reports that may be generated", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("report_output", "csv,html,text,xml", "Menu option for generated output format. Applies only to text reports, graph data remain in xydy format.", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("style_sheet", "", "Name of a style-sheet to be used in rendering html output (added to headers)", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("time_stamps", CF_BOOL, "true/false whether to generate timestamps in the output directory name", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewNull() +}; + + +const ConstraintSyntax CFK_CONTROLBODY[] = +{ + ConstraintSyntaxNewString("build_directory", ".*", "The directory in which to generate output files", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("document_root", ".*", "The directory in which the web root resides", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("generate_manual", CF_BOOL, "true/false generate texinfo manual page skeleton for this version", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("graph_directory", CF_ABSPATHRANGE, "Path to directory where rendered .png files will be created", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("graph_output", CF_BOOL, "true/false generate png visualization of topic map if possible (requires lib)", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("html_banner", "", "HTML code for a banner to be added to rendered in html after the header", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("html_footer", "", "HTML code for a page footer to be added to rendered in html before the end body tag", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("id_prefix", ".*", "The LTM identifier prefix used to label topic maps (used for disambiguation in merging)", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("manual_source_directory", CF_ABSPATHRANGE, "Path to directory where raw text about manual topics is found (defaults to build_directory)", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("query_engine", "", "Name of a dynamic web-page used to accept and drive queries in a browser", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("query_output", "html,text", "Menu option for generated output format", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("sql_type", "mysql,postgres", "Menu option for supported database type", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("sql_database", "", "Name of database used for the topic map", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("sql_owner", "", "User id of sql database user", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("sql_passwd", "", "Embedded password for accessing sql database", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("sql_server", "", "Name or IP of database server (or localhost)", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("sql_connection_db", "", "The name of an existing database to connect to in order to create/manage other databases", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("style_sheet", "", "Name of a style-sheet to be used in rendering html output (added to headers)", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("view_projections", CF_BOOL, "Perform view-projection analytics in graph generation", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewNull() +}; + + +/* This list is for checking free standing body lval => rval bindings */ + +const BodySyntax CONTROL_BODIES[] = +{ + BodySyntaxNew(CF_COMMONC, CFG_CONTROLBODY, NULL, SYNTAX_STATUS_NORMAL), + BodySyntaxNew(CF_AGENTC, CFA_CONTROLBODY, NULL, SYNTAX_STATUS_NORMAL), + BodySyntaxNew(CF_SERVERC, CFS_CONTROLBODY, NULL, SYNTAX_STATUS_NORMAL), + BodySyntaxNew(CF_MONITORC, CFM_CONTROLBODY, NULL, SYNTAX_STATUS_NORMAL), + BodySyntaxNew(CF_RUNC, CFR_CONTROLBODY, NULL, SYNTAX_STATUS_NORMAL), + BodySyntaxNew(CF_EXECC, CFEX_CONTROLBODY, NULL, SYNTAX_STATUS_NORMAL), + BodySyntaxNew(CF_HUBC, CFH_CONTROLBODY, NULL, SYNTAX_STATUS_NORMAL), + BodySyntaxNew("file", file_control_constraints, NULL, SYNTAX_STATUS_NORMAL), + + BodySyntaxNew("reporter", CFRE_CONTROLBODY, NULL, SYNTAX_STATUS_REMOVED), + BodySyntaxNew("knowledge", CFK_CONTROLBODY, NULL, SYNTAX_STATUS_REMOVED), + + // get others from modules e.g. "agent","files",CF_FILES_BODIES, + + BodySyntaxNewNull() +}; + +/*********************************************************/ +/* */ +/* Constraint values/types */ +/* */ +/*********************************************************/ + + /* This is where we place lval => rval bindings that + apply to more than one promise_type, e.g. generic + processing behavioural details */ + +const ConstraintSyntax CF_COMMON_BODIES[] = +{ + ConstraintSyntaxNewBody("action", &action_body, "Output behaviour", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("classes", &classes_body, "Signalling behaviour", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("comment", "", "A comment about this promise's real intention that follows through the program", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("depends_on", "","A list of promise handles that this promise builds on or depends on somehow (for knowledge management)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("handle", "", "A unique id-tag string for referring to this as a promisee elsewhere", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("ifvarclass", "", "Extended classes ANDed with context (alias for 'if')", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("if", "", "Extended classes ANDed with context", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("unless", "", "Negated 'if'", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("meta", "", "User-data associated with policy, e.g. key=value strings", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("with", "", "A string that will replace every instance of $(with)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + + /* This is where we place promise promise_types that apply + to more than one type of bundle, e.g. agent,server.. */ + +const PromiseTypeSyntax CF_COMMON_PROMISE_TYPES[] = +{ + + PromiseTypeSyntaxNew("*", "classes", CF_CLASSBODY, &ClassesParseTreeCheck, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("*", "defaults", CF_DEFAULTSBODY, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("*", "meta", CF_METABODY, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("*", "reports", CF_REPORT_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("*", "vars", CF_VARBODY, &VarsParseTreeCheck, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("*", "*", CF_COMMON_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; + +/*********************************************************/ +/* THIS IS WHERE TO ATTACH SYNTAX MODULES */ +/*********************************************************/ + +/* Read in all parsable Bundle definitions */ +/* REMEMBER TO REGISTER THESE IN cf3.extern.h */ + +const PromiseTypeSyntax *const CF_ALL_PROMISE_TYPES[] = +{ + CF_COMMON_PROMISE_TYPES, /* Add modules after this, mod_report.c is here */ + CF_EXEC_PROMISE_TYPES, /* mod_exec.c */ + CF_DATABASES_PROMISE_TYPES, /* mod_databases.c */ + CF_ENVIRONMENT_PROMISE_TYPES, /* mod_environ.c */ + CF_FILES_PROMISE_TYPES, /* mod_files.c */ + CF_METHOD_PROMISE_TYPES, /* mod_methods.c */ + CF_OUTPUTS_PROMISE_TYPES, /* mod_outputs.c */ + CF_PACKAGES_PROMISE_TYPES, /* mod_packages.c */ + CF_PROCESS_PROMISE_TYPES, /* mod_process.c */ + CF_SERVICES_PROMISE_TYPES, /* mod_services.c */ + CF_STORAGE_PROMISE_TYPES, /* mod_storage.c */ + CF_REMACCESS_PROMISE_TYPES, /* mod_access.c */ + CF_MEASUREMENT_PROMISE_TYPES, /* mod_measurement.c */ + CF_KNOWLEDGE_PROMISE_TYPES, /* mod_knowledge.c */ + CF_USERS_PROMISE_TYPES, /* mod_users.c */ +}; + +const int CF3_MODULES = (sizeof(CF_ALL_PROMISE_TYPES) / sizeof(CF_ALL_PROMISE_TYPES[0])); + + +CommonControl CommonControlFromString(const char *lval) +{ + int i = 0; + for (const ConstraintSyntax *s = CFG_CONTROLBODY; s->lval; s++, i++) + { + if (strcmp(lval, s->lval) == 0) + { + return (CommonControl)i; + } + } + + return COMMON_CONTROL_MAX; +} diff --git a/libpromises/mod_common.h b/libpromises/mod_common.h new file mode 100644 index 0000000000..46d28f705d --- /dev/null +++ b/libpromises/mod_common.h @@ -0,0 +1,67 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_COMMON_H +#define CFENGINE_MOD_COMMON_H + +#include + + +typedef enum +{ + SERVER_CONTROL_ALLOW_ALL_CONNECTS, + SERVER_CONTROL_ALLOW_CONNECTS, + SERVER_CONTROL_ALLOW_USERS, + SERVER_CONTROL_AUDITING, + SERVER_CONTROL_BIND_TO_INTERFACE, + SERVER_CONTROL_CFRUNCOMMAND, + SERVER_CONTROL_CALL_COLLECT_INTERVAL, + SERVER_CONTROL_CALL_COLLECT_WINDOW, + SERVER_CONTROL_DENY_BAD_CLOCKS, + SERVER_CONTROL_DENY_CONNECTS, + SERVER_CONTROL_DYNAMIC_ADDRESSES, + SERVER_CONTROL_HOSTNAME_KEYS, + SERVER_CONTROL_KEY_TTL, + SERVER_CONTROL_LOG_ALL_CONNECTIONS, + SERVER_CONTROL_LOG_ENCRYPTED_TRANSFERS, + SERVER_CONTROL_MAX_CONNECTIONS, + SERVER_CONTROL_PORT_NUMBER, + SERVER_CONTROL_SERVER_FACILITY, + SERVER_CONTROL_SKIP_VERIFY, + SERVER_CONTROL_TRUST_KEYS_FROM, + SERVER_CONTROL_LISTEN, + SERVER_CONTROL_ALLOWCIPHERS, + SERVER_CONTROL_ALLOWLEGACYCONNECTS, + SERVER_CONTROL_ALLOWTLSVERSION, + SERVER_CONTROL_MAX +} ServerControl; + + +extern const ConstraintSyntax CFS_CONTROLBODY[SERVER_CONTROL_MAX + 1]; + + +CommonControl CommonControlFromString(const char *lval); + + +#endif diff --git a/libpromises/mod_custom.c b/libpromises/mod_custom.c new file mode 100644 index 0000000000..36ddc954a7 --- /dev/null +++ b/libpromises/mod_custom.c @@ -0,0 +1,1348 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include // StringStartsWith() +#include // SeqStrginFromString() +#include // Promise +#include // cfPS(), EvalContextVariableGet() +#include // GetClassContextAttributes(), IsClassesBodyConstraint() +#include // ExpandScalar() +#include // StringContainsUnresolved(), StringIsBareNonScalarRef() +#include // Map* +#include // AcquireLock() +#include // GracefulTerminate(), GetProcessStartTime() + +static Map *custom_modules = NULL; + +static const ConstraintSyntax promise_constraints[] = { + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewString( + "path", "", "Path to promise module", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString( + "interpreter", "", "Path to interpreter", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull()}; + +const BodySyntax CUSTOM_PROMISE_BLOCK_SYNTAX = + BodySyntaxNew("promise", promise_constraints, NULL, SYNTAX_STATUS_NORMAL); + +const BodySyntax CUSTOM_BODY_BLOCK_SYNTAX = + BodySyntaxNew("custom", NULL, NULL, SYNTAX_STATUS_CUSTOM); + +Body *FindCustomPromiseType(const Promise *promise) +{ + assert(promise != NULL); + + const char *const promise_type = PromiseGetPromiseType(promise); + const Policy *const policy = + promise->parent_section->parent_bundle->parent_policy; + Seq *custom_promise_types = policy->custom_promise_types; + const size_t length = SeqLength(custom_promise_types); + for (size_t i = 0; i < length; ++i) + { + Body *current = SeqAt(custom_promise_types, i); + if (StringEqual(current->name, promise_type)) + { + return current; + } + } + return NULL; +} + +static bool GetInterpreterAndPath( + EvalContext *ctx, + Body *promise_block, + char **interpreter_out, + char **path_out) +{ + assert(promise_block != NULL); + assert(interpreter_out != NULL); + assert(path_out != NULL); + + char *interpreter = NULL; + char *path = NULL; + + const char *promise_type = promise_block->name; + Seq *promise_block_attributes = promise_block->conlist; + const size_t length = SeqLength(promise_block_attributes); + + for (size_t i = 0; i < length; ++i) + { + Constraint *attribute = SeqAt(promise_block_attributes, i); + const char *name = attribute->lval; + const char *value = RvalScalarValue(attribute->rval); + + if (StringEqual("interpreter", name)) + { + free(interpreter); + interpreter = ExpandScalar(ctx, NULL, NULL, value, NULL); + } + else if (StringEqual("path", name)) + { + free(path); + path = ExpandScalar(ctx, NULL, NULL, value, NULL); + } + else + { + debug_abort_if_reached(); + } + } + + if (path == NULL) + { + Log(LOG_LEVEL_ERR, + "Custom promise type '%s' missing path", + promise_type); + free(interpreter); + free(path); + return false; + } + + *interpreter_out = interpreter; + *path_out = path; + return true; +} + +static inline LogLevel PromiseModule_LogJson(JsonElement *object, const Promise *pp, const char *promise_log_level) +{ + const char *level_string = JsonObjectGetAsString(object, "level"); + const char *message = JsonObjectGetAsString(object, "message"); + + assert(level_string != NULL && message != NULL); + const LogLevel level = LogLevelFromString(level_string); + assert(level != LOG_LEVEL_NOTHING); + + /* Check if there is a log level specified for the particular promise. */ + if ((pp != NULL) && (promise_log_level != NULL)) + { + LogLevel specific = ActionAttributeLogLevelFromString(promise_log_level); + if (specific < level) + { + /* Do not log messages that have a higher log level than the log + * level specified for the promise (e.g. 'info' messages when + * 'error' was requested for the promise). */ + return level; + } + } + + Log(level, "%s", message); + + return level; +} + +static inline JsonElement *PromiseModule_ParseResultClasses(char *value) +{ + JsonElement *result_classes = JsonArrayCreate(1); + char *delim = strchr(value, ','); + while (delim != NULL) + { + *delim = '\0'; + JsonArrayAppendString(result_classes, value); + value = delim + 1; + delim = strchr(value, ','); + } + JsonArrayAppendString(result_classes, value); + return result_classes; +} + +static JsonElement *PromiseModule_Receive(PromiseModule *module, const Promise *pp, + uint16_t n_log_msgs[LOG_LEVEL_DEBUG + 1]) +{ + assert(module != NULL); + + bool line_based = !(module->json); + + char *line = NULL; + size_t size = 0; + bool empty_line = false; + JsonElement *log_array = JsonArrayCreate(10); + JsonElement *response = NULL; + + if (line_based) + { + response = JsonObjectCreate(10); + } + + const char *promise_log_level = NULL; + if (pp != NULL) + { + promise_log_level = PromiseGetConstraintAsRval(pp, "log_level", RVAL_TYPE_SCALAR); + } + + ssize_t bytes; + while (!empty_line + && ((bytes = getline(&line, &size, module->output)) > 0)) + { + assert(bytes > 0); + assert(line != NULL); + + assert(line[bytes] == '\0'); + assert(line[bytes - 1] == '\n'); + line[bytes - 1] = '\0'; + + // Log only non-empty lines: + if (bytes > 1) + { + Log(LOG_LEVEL_DEBUG, "Received line from module: '%s'", line); + } + + if (line[0] == '\0') + { + empty_line = true; + } + else if (StringStartsWith(line, "log_")) + { + const char *const equal_sign = strchr(line, '='); + assert(equal_sign != NULL); + if (equal_sign == NULL) + { + Log(LOG_LEVEL_ERR, + "Promise module sent invalid log line: '%s'", + line); + // Skip this line but keep parsing + FREE_AND_NULL(line); + size = 0; + continue; + } + const char *const message = equal_sign + 1; + const char *const level_start = line + strlen("log_"); + const size_t level_length = equal_sign - level_start; + char *const level = xstrndup(level_start, level_length); + assert(strlen(level) == level_length); + + JsonElement *log_message = JsonObjectCreate(2); + JsonObjectAppendString(log_message, "level", level); + JsonObjectAppendString(log_message, "message", message); + LogLevel log_level = PromiseModule_LogJson(log_message, pp, promise_log_level); + if (log_level > LOG_LEVEL_NOTHING) + { + n_log_msgs[log_level]++; + } + JsonArrayAppendObject(log_array, log_message); + + free(level); + } + else if (line_based) + { + const char *const equal_sign = strchr(line, '='); + assert(equal_sign != NULL); + if (equal_sign == NULL) + { + Log(LOG_LEVEL_ERR, + "Promise module sent invalid line: '%s'", + line); + } + else + { + const char *const value = equal_sign + 1; + const size_t key_length = equal_sign - line; + char *const key = xstrndup(line, key_length); + assert(strlen(key) == key_length); + if (StringEqual(key, "result_classes")) + { + char *result_classes_str = xstrdup(value); + JsonElement *result_classes = PromiseModule_ParseResultClasses(result_classes_str); + JsonObjectAppendArray(response, key, result_classes); + free(result_classes_str); + } + else + { + JsonObjectAppendString(response, key, value); + } + free(key); + } + } + else // JSON protocol: + { + assert(strlen(line) > 0); + assert(response == NULL); // Should be first and only line + const char *data = line; // JsonParse() moves this while parsing + JsonParseError err = JsonParse(&data, &response); + if (err != JSON_PARSE_OK) + { + assert(response == NULL); + Log(LOG_LEVEL_ERR, + "Promise module '%s' sent invalid JSON", + module->path); + free(line); + return NULL; + } + assert(response != NULL); + } + + FREE_AND_NULL(line); + size = 0; + } + + if (response == NULL) + { + // This can happen if using the JSON protocol, and the module sends + // nothing (newlines) or only log= lines. + assert(!line_based); + Log(LOG_LEVEL_ERR, + "The '%s' promise module sent an invalid/incomplete response with JSON based protocol", + module->path); + return NULL; + } + + if (line_based) + { + JsonObjectAppendArray(response, "log", log_array); + log_array = NULL; + } + else + { + JsonElement *json_log_messages = JsonObjectGet(response, "log"); + + // Log messages inside JSON data haven't been printed yet, + // do it now: + if (json_log_messages != NULL) + { + size_t length = JsonLength(json_log_messages); + for (size_t i = 0; i < length; ++i) + { + LogLevel log_level = PromiseModule_LogJson(JsonArrayGet(json_log_messages, i), + pp, promise_log_level); + if (log_level > LOG_LEVEL_NOTHING) + { + n_log_msgs[log_level]++; + } + } + } + + JsonElement *merged = NULL; + bool had_log_lines = (log_array != NULL && JsonLength(log_array) > 0); + if (json_log_messages == NULL && !had_log_lines) + { + // No log messages at all, no need to add anything to JSON + } + else if (!had_log_lines) + { + // No separate log lines before JSON data, leave JSON as is + } + else if (had_log_lines && (json_log_messages == NULL)) + { + // Separate log lines, but no log messages in JSON data + JsonObjectAppendArray(response, "log", log_array); + log_array = NULL; + } + else + { + // both log messages as separate lines and in JSON, merge: + merged = JsonMerge(log_array, json_log_messages); + JsonObjectAppendArray(response, "log", merged); + // json_log_messages will be destroyed since we append over it + } + } + JsonDestroy(log_array); + + assert(response != NULL); + return response; +} + +static void PromiseModule_SendMessage(PromiseModule *module, Seq *message) +{ + assert(module != NULL); + + const size_t length = SeqLength(message); + for (size_t i = 0; i < length; ++i) + { + const char *line = SeqAt(message, i); + NDEBUG_UNUSED const size_t line_length = strlen(line); + assert(line_length > 0 && memchr(line, '\n', line_length) == NULL); + fprintf(module->input, "%s\n", line); + } + fprintf(module->input, "\n"); + fflush(module->input); +} + +static Seq *PromiseModule_ReceiveHeader(PromiseModule *module) +{ + assert(module != NULL); + + // Read header: + char *line = NULL; + size_t size = 0; + ssize_t bytes = getline(&line, &size, module->output); + if (bytes <= 0) + { + Log(LOG_LEVEL_ERR, + "Did not receive header from promise module '%s'", + module->path); + free(line); + return NULL; + } + if (line[bytes - 1] != '\n') + { + Log(LOG_LEVEL_ERR, + "Promise module '%s %s' sent an invalid header with no newline: '%s'", + module->interpreter, + module->path, + line); + free(line); + return NULL; + } + line[bytes - 1] = '\0'; + + Log(LOG_LEVEL_DEBUG, "Received header from promise module: '%s'", line); + + Seq *header = SeqStringFromString(line, ' '); + + FREE_AND_NULL(line); + size = 0; + + // Read empty line: + bytes = getline(&line, &size, module->output); + if (bytes != 1 || line[0] != '\n') + { + Log(LOG_LEVEL_ERR, + "Promise module '%s %s' failed to send empty line after header: '%s'", + module->interpreter, + module->path, + line); + SeqDestroy(header); + free(line); + return NULL; + } + + free(line); + return header; +} + +// Internal function, use PromiseModule_Terminate instead +static void PromiseModule_DestroyInternal(PromiseModule *module) +{ + assert(module != NULL); + + free(module->path); + free(module->interpreter); + + cf_pclose_full_duplex(&(module->fds)); + free(module); +} + +static PromiseModule *PromiseModule_Start(char *interpreter, char *path) +{ + assert(path != NULL); + + if ((interpreter != NULL) && (access(interpreter, X_OK) != 0)) + { + Log(LOG_LEVEL_ERR, + "Promise module interpreter '%s' is not an executable file", + interpreter); + return NULL; + } + + if ((interpreter == NULL) && (access(path, X_OK) != 0)) + { + Log(LOG_LEVEL_ERR, + "Promise module path '%s' is not an executable file", + path); + return NULL; + } + + if (access(path, F_OK) != 0) + { + Log(LOG_LEVEL_ERR, + "Promise module '%s' does not exist", + path); + return NULL; + } + + PromiseModule *module = xcalloc(1, sizeof(PromiseModule)); + + module->interpreter = interpreter; + module->path = path; + + char command[CF_BUFSIZE]; + if (interpreter == NULL) + { + snprintf(command, CF_BUFSIZE, "%s", path); + } + else + { + snprintf(command, CF_BUFSIZE, "%s %s", interpreter, path); + } + + Log(LOG_LEVEL_VERBOSE, "Starting custom promise module '%s' with command '%s'", + path, command); + module->fds = cf_popen_full_duplex_streams(command, false, true); + module->output = module->fds.read_stream; + module->input = module->fds.write_stream; + module->message = NULL; + + if (!PipeToPid(&module->pid, module->fds.write_stream)) + { + Log(LOG_LEVEL_ERR, "Failed to get PID of custom promise module '%s'", path); + PromiseModule_DestroyInternal(module); + return NULL; + } + + module->process_start_time = GetProcessStartTime(module->pid); + if (module->process_start_time == PROCESS_START_TIME_UNKNOWN) + { + Log(LOG_LEVEL_ERR, "Failed to get process start time of custom promise module '%s'", path); + PromiseModule_DestroyInternal(module); + return NULL; + } + + fprintf(module->input, "cf-agent %s v1\n\n", Version()); + fflush(module->input); + + Seq *header = PromiseModule_ReceiveHeader(module); + + if (header == NULL) + { + // error logged in PromiseModule_ReceiveHeader() + + /* Make sure 'path' and 'interpreter' are not free'd twice (the calling + * code frees them if it gets NULL). */ + module->path = NULL; + module->interpreter = NULL; + PromiseModule_DestroyInternal(module); + return NULL; + } + + /* line_based is the default, but the module should specify it + * explicitly. */ + module->json = false; + bool protocol_specified = false; + + const size_t header_length = SeqLength(header); + const size_t flags_offset = 3; /* where flags start */ + assert(header_length > flags_offset); /* at least one flag required -- json_based/line_based */ + for (size_t i = flags_offset; i < header_length; ++i) + { + const char *const flag = SeqAt(header, i); + if (StringEqual(flag, "json_based")) + { + module->json = true; + if (protocol_specified) + { + Log(LOG_LEVEL_WARNING, + "Ambiguous protocol specification from the custom promise module '%s'." + " Please report this as a bug in the module", + module->path); + } + protocol_specified = true; + } + else if (StringEqual(flag, "line_based")) + { + module->json = false; + if (protocol_specified) + { + Log(LOG_LEVEL_WARNING, + "Ambiguous protocol specification from the custom promise module '%s'." + " Please report this as a bug in the module", + module->path); + } + protocol_specified = true; + } + else if (StringEqual(flag, "action_policy")) + { + module->action_policy = true; + } + } + + if (!protocol_specified) + { + Log(LOG_LEVEL_WARNING, + "Custom promise module '%s' didn't fully specify protocol." + " Using 'line_based' as the default. Please report this as a bug in the module", + module->path); + } + + SeqDestroy(header); + + return module; +} + +static void PromiseModule_AppendString( + PromiseModule *module, const char *key, const char *value) +{ + assert(module != NULL); + + if (module->message == NULL) + { + module->message = JsonObjectCreate(10); + } + JsonObjectAppendString(module->message, key, value); +} + +static void PromiseModule_AppendInteger( + PromiseModule *module, const char *key, int64_t value) +{ + assert(module != NULL); + + if (module->message == NULL) + { + module->message = JsonObjectCreate(10); + } + JsonObjectAppendInteger64(module->message, key, value); +} + +static void PromiseModule_AppendAttribute( + PromiseModule *module, const char *key, JsonElement *value) +{ + assert(module != NULL); + + if (module->message == NULL) + { + module->message = JsonObjectCreate(10); + } + + JsonElement *attributes = JsonObjectGet(module->message, "attributes"); + if (attributes == NULL) + { + attributes = JsonObjectCreate(10); + JsonObjectAppendObject(module->message, "attributes", attributes); + } + + JsonObjectAppendElement(attributes, key, value); +} + +static void PromiseModule_Send(PromiseModule *module) +{ + assert(module != NULL); + + if (module->json) + { + Writer *w = FileWriter(module->input); + JsonWriteCompact(w, module->message); + FileWriterDetach(w); + DESTROY_AND_NULL(JsonDestroy, module->message); + fprintf(module->input, "\n\n"); + fflush(module->input); + return; + } + + Seq *message = SeqNew(10, free); + + JsonIterator iter = JsonIteratorInit(module->message); + const char *key; + while ((key = JsonIteratorNextKey(&iter)) != NULL) + { + if (StringEqual("attributes", key)) + { + JsonElement *attributes = JsonIteratorCurrentValue(&iter); + JsonIterator attr_iter = JsonIteratorInit(attributes); + + const char *attr_name; + while ((attr_name = JsonIteratorNextKey(&attr_iter)) != NULL) + { + const char *attr_val = JsonPrimitiveGetAsString( + JsonIteratorCurrentValue(&attr_iter)); + char *attr_line = NULL; + xasprintf(&attr_line, "attribute_%s=%s", attr_name, attr_val); + SeqAppend(message, attr_line); + } + } + else + { + const char *value = + JsonPrimitiveGetAsString(JsonIteratorCurrentValue(&iter)); + char *line = NULL; + xasprintf(&line, "%s=%s", key, value); + SeqAppend(message, line); + } + } + + PromiseModule_SendMessage(module, message); + SeqDestroy(message); + DESTROY_AND_NULL(JsonDestroy, module->message); +} + +static inline bool TryToGetContainerFromScalarRef(const EvalContext *ctx, const char *scalar, JsonElement **out) +{ + if (StringIsBareNonScalarRef(scalar)) + { + /* Resolve a potential 'data' variable reference. */ + const size_t scalar_len = strlen(scalar); + char *var_ref_str = xstrndup(scalar + 2, scalar_len - 3); + VarRef *ref = VarRefParse(var_ref_str); + + DataType type = CF_DATA_TYPE_NONE; + const void *val = EvalContextVariableGet(ctx, ref, &type); + free(var_ref_str); + VarRefDestroy(ref); + + if ((val != NULL) && (type == CF_DATA_TYPE_CONTAINER)) + { + if (out != NULL) + { + *out = JsonCopy(val); + } + return true; + } + } + return false; +} + +static void PromiseModule_AppendAllAttributes( + PromiseModule *module, const EvalContext *ctx, const Promise *pp) +{ + assert(module != NULL); + assert(pp != NULL); + + /* Need to make sure action_policy is "warn" in case of dry-run/simulate + * modes. */ + const bool dontdo = (EVAL_MODE != EVAL_MODE_NORMAL); + bool seen_action_policy = false; + + const size_t attributes = SeqLength(pp->conlist); + for (size_t i = 0; i < attributes; i++) + { + const Constraint *attribute = SeqAt(pp->conlist, i); + const char *const name = attribute->lval; + assert(!StringEqual(name, "ifvarclass")); // Not allowed by validation + if (IsClassesBodyConstraint(name) + || StringEqual(name, "if") + || StringEqual(name, "ifvarclass") + || StringEqual(name, "unless") + || StringEqual(name, "depends_on") + || StringEqual(name, "with") + || StringEqual(name, "meta") + || StringEqual(name, "expireafter")) + { + // Evaluated by agent and not sent to module, skip + continue; + } + + if (StringEqual(name, "action") || StringEqual(name, "action_name")) + { + /* We only pass "action_policy" to the module (see below). */ + continue; + } + + if (StringEqual(attribute->lval, "log_level")) + { + /* Passed to the module as 'log_level' request field, not as an attribute. */ + continue; + } + + JsonElement *value = NULL; + if (dontdo && StringEqual(name, "action_policy")) + { + /* Override the value in case of dry-run/simulate modes. */ + seen_action_policy = true; + value = JsonStringCreate("warn"); + } + else if (attribute->rval.type == RVAL_TYPE_SCALAR) + { + /* Could be a '@(container)' reference. */ + if (!TryToGetContainerFromScalarRef(ctx, RvalScalarValue(attribute->rval), &value)) + { + /* Didn't resolve to a container value, let's just use the + * scalar value as-is. */ + value = RvalToJson(attribute->rval); + } + } + else if ((attribute->rval.type == RVAL_TYPE_LIST) || + (attribute->rval.type == RVAL_TYPE_CONTAINER)) + { + value = RvalToJson(attribute->rval); + } + + if (value != NULL) + { + PromiseModule_AppendAttribute(module, name, value); + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Unsupported type of the '%s' attribute (%c), cannot be sent to custom promise module", + name, attribute->rval.type); + } + + seen_action_policy = (seen_action_policy || StringEqual(name, "action_policy")); + } + + if (dontdo && !seen_action_policy) + { + /* Make sure action_policy is specified in case of dry-run/simulate modes. */ + PromiseModule_AppendAttribute(module, "action_policy", JsonStringCreate("warn")); + } +} + +static bool CheckPrimitiveForUnexpandedVars(JsonElement *primitive, ARG_UNUSED void *data) +{ + assert(JsonGetElementType(primitive) == JSON_ELEMENT_TYPE_PRIMITIVE); + + /* Stop the iteration if a variable expression is found. */ + return (!StringContainsUnresolved(JsonPrimitiveGetAsString(primitive))); +} + +static bool CheckObjectForUnexpandedVars(JsonElement *object, ARG_UNUSED void *data) +{ + assert(JsonGetType(object) == JSON_TYPE_OBJECT); + + /* Stop the iteration if a variable expression is found among children + * keys. (elements inside the object are checked separately) */ + JsonIterator iter = JsonIteratorInit(object); + while (JsonIteratorHasMore(&iter)) + { + const char *key = JsonIteratorNextKey(&iter); + if (StringContainsUnresolved(key)) + { + return false; + } + } + return true; +} + +static inline bool CustomPromise_IsFullyResolved(const EvalContext *ctx, const Promise *pp, bool nonscalars_allowed) +{ + assert(pp != NULL); + + if (StringContainsUnresolved(pp->promiser)) + { + return false; + } + const size_t attributes = SeqLength(pp->conlist); + for (size_t i = 0; i < attributes; i++) + { + const Constraint *attribute = SeqAt(pp->conlist, i); + if (IsClassesBodyConstraint(attribute->lval) || + StringEqual(attribute->lval, "meta")) + { + /* Not passed to the modules, handled on the agent side. */ + continue; + } + if (StringEqual(attribute->lval, "log_level")) + { + /* Passed to the module as 'log_level' request field, not as an attribute. */ + continue; + } + if (StringEqual(attribute->lval, "unless")) + { + /* unless can actually have unresolved variables here, + it defaults to evaluate in case of unresolved variables, + to be the true opposite of if. (if would skip).*/ + continue; + } + if ((attribute->rval.type == RVAL_TYPE_FNCALL) || + (!nonscalars_allowed && (attribute->rval.type != RVAL_TYPE_SCALAR))) + { + return false; + } + if (attribute->rval.type == RVAL_TYPE_SCALAR) + { + const char *const value = RvalScalarValue(attribute->rval); + if (StringContainsUnresolved(value) && !TryToGetContainerFromScalarRef(ctx, value, NULL)) + { + return false; + } + } + else if (attribute->rval.type == RVAL_TYPE_LIST) + { + assert(nonscalars_allowed); + for (Rlist *rl = RvalRlistValue(attribute->rval); rl != NULL; rl = rl->next) + { + assert(rl->val.type == RVAL_TYPE_SCALAR); + const char *const value = RvalScalarValue(rl->val); + if (StringContainsUnresolved(value)) + { + return false; + } + } + } + else + { + assert(nonscalars_allowed); + assert(attribute->rval.type == RVAL_TYPE_CONTAINER); + JsonElement *attr_data = RvalContainerValue(attribute->rval); + return JsonWalk(attr_data, CheckObjectForUnexpandedVars, NULL, + CheckPrimitiveForUnexpandedVars, NULL); + } + } + return true; +} + + +static inline bool HasResultAndResultIsValid(JsonElement *response) +{ + const char *const result = JsonObjectGetAsString(response, "result"); + return ((result != NULL) && StringEqual(result, "valid")); +} + +static inline const char *LogLevelToRequestFromModule(const Promise *pp) +{ + LogLevel log_level = LogGetGlobalLevel(); + + /* Check if there is a log level specified for the particular promise. */ + const char *value = PromiseGetConstraintAsRval(pp, "log_level", RVAL_TYPE_SCALAR); + if (value != NULL) + { + LogLevel specific = ActionAttributeLogLevelFromString(value); + + /* Promise-specific log level cannot go above the global log level + * (e.g. no 'info' messages for a particular promise if the global level + * is 'error'). */ + log_level = MIN(log_level, specific); + } + + // We will never request LOG_LEVEL_NOTHING or LOG_LEVEL_CRIT from the + // module: + if (log_level < LOG_LEVEL_ERR) + { + assert((log_level == LOG_LEVEL_NOTHING) || (log_level == LOG_LEVEL_CRIT)); + return LogLevelToString(LOG_LEVEL_ERR); + } + return LogLevelToString(log_level); +} + +static bool PromiseModule_Validate(PromiseModule *module, const EvalContext *ctx, const Promise *pp) +{ + assert(module != NULL); + assert(pp != NULL); + + const char *const promise_type = PromiseGetPromiseType(pp); + const char *const promiser = pp->promiser; + + const char *action_policy = PromiseGetConstraintAsRval(pp, "action_policy", RVAL_TYPE_SCALAR); + const bool dontdo = ((EVAL_MODE != EVAL_MODE_NORMAL) || + StringEqual(action_policy, "warn") || StringEqual(action_policy, "nop")); + if (dontdo && !module->action_policy) + { + Log(LOG_LEVEL_ERR, + "Not making changes to the system, but the custom promise module '%s' doesn't support action_policy", + module->path); + return false; + } + + PromiseModule_AppendString(module, "operation", "validate_promise"); + PromiseModule_AppendString(module, "log_level", LogLevelToRequestFromModule(pp)); + PromiseModule_AppendString(module, "promise_type", promise_type); + PromiseModule_AppendString(module, "promiser", promiser); + PromiseModule_AppendInteger(module, "line_number", pp->offset.line); + PromiseModule_AppendString(module, "filename", PromiseGetBundle(pp)->source_path); + PromiseModule_AppendAllAttributes(module, ctx, pp); + PromiseModule_Send(module); + + // Prints errors / log messages from module: + uint16_t n_log_msgs[LOG_LEVEL_DEBUG + 1] = {0}; + JsonElement *response = PromiseModule_Receive(module, pp, n_log_msgs); + + if (response == NULL) + { + // Error already printed in PromiseModule_Receive() + return false; + } + + const bool valid = HasResultAndResultIsValid(response); + + JsonDestroy(response); + + if (!valid) + { + // Detailed error messages from module should already have been printed + const char *const filename = + pp->parent_section->parent_bundle->source_path; + const size_t line = pp->offset.line; + Log(LOG_LEVEL_VERBOSE, + "%s promise with promiser '%s' failed validation (%s:%zu)", + promise_type, + promiser, + filename, + line); + + if ((n_log_msgs[LOG_LEVEL_ERR] == 0) && (n_log_msgs[LOG_LEVEL_CRIT] == 0)) + { + Log(LOG_LEVEL_CRIT, + "Bug in promise module - No error(s) logged for invalid %s promise with promiser '%s' (%s:%zu)", + promise_type, + promiser, + filename, + line); + } + } + + return valid; +} + +static PromiseResult PromiseModule_Evaluate( + PromiseModule *module, EvalContext *ctx, const Promise *pp) +{ + assert(module != NULL); + assert(pp != NULL); + + const char *const promise_type = PromiseGetPromiseType(pp); + const char *const promiser = pp->promiser; + + PromiseModule_AppendString(module, "operation", "evaluate_promise"); + PromiseModule_AppendString( + module, "log_level", LogLevelToRequestFromModule(pp)); + PromiseModule_AppendString(module, "promise_type", promise_type); + PromiseModule_AppendString(module, "promiser", promiser); + PromiseModule_AppendInteger(module, "line_number", pp->offset.line); + PromiseModule_AppendString(module, "filename", PromiseGetBundle(pp)->source_path); + + PromiseModule_AppendAllAttributes(module, ctx, pp); + PromiseModule_Send(module); + + const char *action_policy = PromiseGetConstraintAsRval(pp, "action_policy", RVAL_TYPE_SCALAR); + const bool dontdo = ((EVAL_MODE != EVAL_MODE_NORMAL) || + StringEqual(action_policy, "warn") || StringEqual(action_policy, "nop")); + + uint16_t n_log_msgs[LOG_LEVEL_DEBUG + 1] = {0}; + JsonElement *response = PromiseModule_Receive(module, pp, n_log_msgs); + if (response == NULL) + { + // Log from PromiseModule_Receive + return PROMISE_RESULT_FAIL; + } + + JsonElement *result_classes = JsonObjectGetAsArray(response, "result_classes"); + if (result_classes != NULL) + { + const size_t n_classes = JsonLength(result_classes); + for (size_t i = 0; i < n_classes; i++) + { + const char *class_name = JsonArrayGetAsString(result_classes, i); + assert(class_name != NULL); + EvalContextClassPutSoft(ctx, class_name, CONTEXT_SCOPE_BUNDLE, "source=promise-module"); + } + } + + PromiseResult result; + const char *const result_str = JsonObjectGetAsString(response, "result"); + + /* Attributes needed for setting outcome classes etc. */ + Attributes a = GetClassContextAttributes(ctx, pp); + + const char *const filename = pp->parent_section->parent_bundle->source_path; + const size_t line = pp->offset.line; + + if (dontdo && (n_log_msgs[LOG_LEVEL_INFO] > 0)) + { + Log(LOG_LEVEL_CRIT, + "Bug in promise module - 'info:' log messages reported for %s promise with promiser '%s' (%s:%zu)" + " while making changes on the system disabled", + promise_type, + promiser, + filename, + line); + } + + if (result_str == NULL) + { + result = PROMISE_RESULT_FAIL; + cfPS( + ctx, + LOG_LEVEL_ERR, + result, + pp, + &a, + "Promise module did not return a result for promise evaluation (%s promise, promiser: '%s' module: '%s')", + promise_type, + promiser, + module->path); + } + else if (StringEqual(result_str, "kept")) + { + result = PROMISE_RESULT_NOOP; + cfPS( + ctx, + LOG_LEVEL_VERBOSE, + result, + pp, + &a, + "Promise with promiser '%s' was kept by promise module '%s'", + promiser, + module->path); + } + else if (StringEqual(result_str, "not_kept")) + { + result = PROMISE_RESULT_FAIL; + cfPS( + ctx, + LOG_LEVEL_VERBOSE, + result, + pp, + &a, + "Promise with promiser '%s' was not kept by promise module '%s'", + promiser, + module->path); + + if (!dontdo && (n_log_msgs[LOG_LEVEL_ERR] == 0) && (n_log_msgs[LOG_LEVEL_CRIT] == 0)) + { + Log(LOG_LEVEL_CRIT, + "Bug in promise module - Failed to log errors for not kept %s promise with promiser '%s' (%s:%zu)", + promise_type, + promiser, + filename, + line); + } + else if (dontdo && + ((n_log_msgs[LOG_LEVEL_WARNING] + n_log_msgs[LOG_LEVEL_ERR] + n_log_msgs[LOG_LEVEL_CRIT]) == 0)) + { + Log(LOG_LEVEL_CRIT, + "Bug in promise module - Failed to log warnings for not kept %s promise with promiser '%s' (%s:%zu)" + " while making changes on the system disabled", + promise_type, + promiser, + filename, + line); + } + } + else if (StringEqual(result_str, "repaired")) + { + result = PROMISE_RESULT_CHANGE; + cfPS( + ctx, + LOG_LEVEL_VERBOSE, + result, + pp, + &a, + "Promise with promiser '%s' was repaired by promise module '%s'", + promiser, + module->path); + + if (dontdo) + { + Log(LOG_LEVEL_CRIT, + "Bug in promise module - %s promise with promiser '%s' (%s:%zu)" + " repaired while making changes on the system disabled", + promise_type, + promiser, + filename, + line); + } + + if (n_log_msgs[LOG_LEVEL_INFO] == 0) + { + Log(LOG_LEVEL_CRIT, + "Bug in promise module - Failed to log about changes made by a repaired %s promise with promiser '%s' (%s:%zu)", + promise_type, + promiser, + filename, + line); + } + } + else if (StringEqual(result_str, "error")) + { + result = PROMISE_RESULT_FAIL; + cfPS( + ctx, + LOG_LEVEL_ERR, + result, + pp, + &a, + "An unexpected error occured in promise module (%s promise, promiser: '%s' module: '%s')", + promise_type, + promiser, + module->path); + } + else + { + result = PROMISE_RESULT_FAIL; + cfPS( + ctx, + LOG_LEVEL_ERR, + result, + pp, + &a, + "Promise module returned unacceptable result: '%s' (%s promise, promiser: '%s' module: '%s')", + result_str, + promise_type, + promiser, + module->path); + } + + JsonDestroy(response); + return result; +} + +static void PromiseModule_Terminate(PromiseModule *module, const Promise *pp) +{ + if (module != NULL) + { + PromiseModule_AppendString(module, "operation", "terminate"); + PromiseModule_Send(module); + + uint16_t n_log_msgs[LOG_LEVEL_DEBUG + 1] = {0}; + JsonElement *response = PromiseModule_Receive(module, pp, n_log_msgs); + JsonDestroy(response); + + PromiseModule_DestroyInternal(module); + } +} + +static void PromiseModule_Terminate_untyped(void *data) +{ + PromiseModule *module = data; + PromiseModule_Terminate(module, NULL); +} + +void TerminateCustomPromises(void) +{ + MapIterator iter = MapIteratorInit(custom_modules); + + for (const MapKeyValue *item = MapIteratorNext(&iter); item != NULL; item = MapIteratorNext(&iter)) + { + const char *const path = item->key; + const PromiseModule *const module = item->value; + + if (!GracefulTerminate(module->pid, module->process_start_time)) + { + Log(LOG_LEVEL_ERR, "Failed to terminate custom promise module '%s'", path); + } + } +} + +bool InitializeCustomPromises() +{ + /* module_path -> PromiseModule map */ + custom_modules = MapNew(StringHash_untyped, + StringEqual_untyped, + free, + PromiseModule_Terminate_untyped); + assert(custom_modules != NULL); + + return (custom_modules != NULL); +} + +void FinalizeCustomPromises() +{ + MapDestroy(custom_modules); +} + +PromiseResult EvaluateCustomPromise(EvalContext *ctx, const Promise *pp) +{ + assert(ctx != NULL); + assert(pp != NULL); + + Body *promise_block = FindCustomPromiseType(pp); + if (promise_block == NULL) + { + Log(LOG_LEVEL_ERR, + "Undefined promise type '%s'", + PromiseGetPromiseType(pp)); + return PROMISE_RESULT_FAIL; + } + + /* Attributes needed for setting outcome classes etc. */ + Attributes a = GetClassContextAttributes(ctx, pp); + + char *interpreter = NULL; + char *path = NULL; + + bool success = GetInterpreterAndPath(ctx, promise_block, &interpreter, &path); + + if (!success) + { + assert(interpreter == NULL && path == NULL); + /* Details logged in GetInterpreterAndPath() */ + cfPS(ctx, LOG_LEVEL_NOTHING, PROMISE_RESULT_FAIL, pp, &a, NULL); + return PROMISE_RESULT_FAIL; + } + + /* Used below, constructed here while path and interpreter are definitely + * valid pointers. */ + char custom_promise_id[CF_BUFSIZE]; + NDEBUG_UNUSED size_t ret = snprintf(custom_promise_id, + sizeof(custom_promise_id), + "%s-%s-%s", pp->promiser, path, + interpreter ? interpreter : "(null)"); + assert((ret > 0) && (ret < sizeof(custom_promise_id))); + + PromiseModule *module = MapGet(custom_modules, path); + if (module == NULL) + { + /* Takes ownership of interpreter and path. */ + module = PromiseModule_Start(interpreter, path); + if (module != NULL) + { + MapInsert(custom_modules, xstrdup(path), module); + } + else + { + free(interpreter); + free(path); + // Error logged in PromiseModule_Start() + cfPS(ctx, LOG_LEVEL_NOTHING, PROMISE_RESULT_FAIL, pp, &a, NULL); + return PROMISE_RESULT_FAIL; + } + } + else + { + if (!StringEqual(interpreter, module->interpreter)) + { + Log(LOG_LEVEL_ERR, "Conflicting interpreter specifications for custom promise module '%s'" + " (started with '%s' and '%s' requested for promise '%s' of type '%s')", + path, module->interpreter, interpreter, pp->promiser, PromiseGetPromiseType(pp)); + free(interpreter); + free(path); + cfPS(ctx, LOG_LEVEL_NOTHING, PROMISE_RESULT_FAIL, pp, &a, NULL); + return PROMISE_RESULT_FAIL; + } + free(interpreter); + free(path); + } + + // TODO: Do validation earlier (cf-promises --full-check) + bool valid = CustomPromise_IsFullyResolved(ctx, pp, module->json); + if ((!valid) && (EvalContextGetPass(ctx) == CF_DONEPASSES - 1)) + { + Log(LOG_LEVEL_ERR, + "%s promise with promiser '%s' has unresolved/unexpanded variables", + PromiseGetPromiseType(pp), + pp->promiser); + } + + CfLock promise_lock = AcquireLock(ctx, custom_promise_id, VUQNAME, CFSTARTTIME, + a.transaction.ifelapsed, a.transaction.expireafter, + pp, false); + if (promise_lock.lock == NULL) + { + return PROMISE_RESULT_SKIPPED; + } + + if (valid) + { + valid = PromiseModule_Validate(module, ctx, pp); + } + + PromiseResult result; + if (valid) + { + result = PromiseModule_Evaluate(module, ctx, pp); + } + else + { + // PromiseModule_Validate() already printed an error + Log(LOG_LEVEL_VERBOSE, + "%s promise with promiser '%s' will be skipped because it failed validation", + PromiseGetPromiseType(pp), + pp->promiser); + cfPS(ctx, LOG_LEVEL_NOTHING, PROMISE_RESULT_FAIL, pp, &a, NULL); + result = PROMISE_RESULT_FAIL; // TODO: Investigate if DENIED is more + // appropriate + } + + YieldCurrentLock(promise_lock); + return result; +} diff --git a/libpromises/mod_custom.h b/libpromises/mod_custom.h new file mode 100644 index 0000000000..10cde3f9f4 --- /dev/null +++ b/libpromises/mod_custom.h @@ -0,0 +1,67 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_CUSTOM_H +#define CFENGINE_MOD_CUSTOM_H + +#include +#include // IOData + +// mod_custom is not like the other modules which define promise types +// (except for mod_common). It just defines some basics needed, for +// custom promise types (promise modules) to work. + +// Defines the syntax for the top level promise block, i.e. what +// constraints are allowed when adding new promises via promise modules +// Uses BodySyntax, since promise blocks are syntatctically the same +// as body blocks. (bundles are different, with sections and +// promise attributes). +extern const BodySyntax CUSTOM_PROMISE_BLOCK_SYNTAX; + +// Defines custom promise block with no syntax, as syntax will be checked by +// the custom promise module. +extern const BodySyntax CUSTOM_BODY_BLOCK_SYNTAX; + +typedef struct PromiseModule +{ + pid_t pid; + time_t process_start_time; + IOData fds; + FILE *input; + FILE *output; + char *path; + char *interpreter; + bool json; + bool action_policy; + JsonElement *message; +} PromiseModule; + +bool InitializeCustomPromises(); +void FinalizeCustomPromises(); +void TerminateCustomPromises(); + +Body *FindCustomPromiseType(const Promise *promise); +PromiseResult EvaluateCustomPromise(ARG_UNUSED EvalContext *ctx, const Promise *pp); + +#endif diff --git a/libpromises/mod_databases.c b/libpromises/mod_databases.c new file mode 100644 index 0000000000..04381ad6f5 --- /dev/null +++ b/libpromises/mod_databases.c @@ -0,0 +1,57 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax database_server_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewString("db_server_owner", "", "User name for database connection", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("db_server_password", "", "Clear text password for database connection", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("db_server_host", "", "Hostname or address for connection to database, blank means localhost", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("db_server_type", "postgres,mysql", "The dialect of the database server. Default value: none", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("db_server_connection_db", "", "The name of an existing database to connect to in order to create/manage other databases", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax database_server_body = BodySyntaxNew("database_server", database_server_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax databases_constraints[] = +{ + ConstraintSyntaxNewBody("database_server", &database_server_body, "Credentials for connecting to a local/remote database server", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("database_type", "sql,ms_registry", "The type of database that is to be manipulated. Default value: none", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("database_operation", "create,delete,drop,cache,verify,restore", "The nature of the promise - to be or not to be", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("database_columns", ".*", "A list of column definitions to be promised by SQL databases", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("database_rows", ".*,.*", "An ordered list of row values to be promised by SQL databases", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("registry_exclude", "", "A list of regular expressions to ignore in key/value verification", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_DATABASES_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "databases", databases_constraints, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_databases.h b/libpromises/mod_databases.h new file mode 100644 index 0000000000..5fd375cbfe --- /dev/null +++ b/libpromises/mod_databases.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_DATABASES_H +#define CFENGINE_MOD_DATABASES_H + +#include + +extern const PromiseTypeSyntax CF_DATABASES_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_environ.c b/libpromises/mod_environ.c new file mode 100644 index 0000000000..0a2d5a0366 --- /dev/null +++ b/libpromises/mod_environ.c @@ -0,0 +1,67 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax environment_resources_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewInt("env_cpus", CF_VALRANGE, "Number of virtual CPUs in the environment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("env_memory", CF_VALRANGE, "Amount of primary storage (RAM) in the virtual environment (KB)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("env_disk", CF_VALRANGE, "Amount of secondary storage (DISK) in the virtual environment (MB)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("env_baseline", CF_ABSPATHRANGE, "The path to an image with which to baseline the virtual environment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("env_spec", CF_ANYSTRING, "A string containing a technology specific set of promises for the virtual instance", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax environment_resources_body = BodySyntaxNew("environment_resources", environment_resources_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax environment_interface_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("env_addresses", "", "The IP addresses of the environment's network interfaces", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("env_name", "", "The hostname of the virtual environment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("env_network", "", "The hostname of the virtual network", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax environment_interface_body = BodySyntaxNew("environment_interface", environment_interface_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax CF_ENVIRON_BODIES[] = +{ + ConstraintSyntaxNewString("environment_host", "[a-zA-Z0-9_]+", "A class indicating which physical node will execute this guest machine", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("environment_interface", &environment_interface_body, "Virtual environment outward identity and location", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("environment_resources", &environment_resources_body, "Virtual environment resource description", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("environment_state", "create,delete,running,suspended,down", "The desired dynamical state of the specified environment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("environment_type", "xen,kvm,esx,vbox,test,xen_net,kvm_net,esx_net,test_net,zone,ec2,eucalyptus", "Virtual environment type", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_ENVIRONMENT_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "guest_environments", CF_ENVIRON_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_environ.h b/libpromises/mod_environ.h new file mode 100644 index 0000000000..3b114f4229 --- /dev/null +++ b/libpromises/mod_environ.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_ENVIRON_H +#define CFENGINE_MOD_ENVIRON_H + +#include + +extern const PromiseTypeSyntax CF_ENVIRONMENT_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_exec.c b/libpromises/mod_exec.c new file mode 100644 index 0000000000..2e48be8898 --- /dev/null +++ b/libpromises/mod_exec.c @@ -0,0 +1,60 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax contain_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("useshell", "noshell,useshell,powershell," CF_BOOL, "noshell/useshell/powershell embed the command in the given shell environment. Default value: noshell", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("umask", "", "The umask value for the child process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("exec_owner", "", "The user name or id under which to run the process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("exec_group", "", "The group name or id under which to run the process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("exec_timeout", "1,3600", "Timeout in seconds for command completion", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("chdir", CF_ABSPATHRANGE, "Directory for setting current/base directory for the process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("chroot", CF_ABSPATHRANGE, "Directory of root sandbox for process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("preview", "true/false preview command when running in dry-run mode (with -n). Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("no_output", "true/false discard all output from the command. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax contain_body = BodySyntaxNew("contain", contain_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax commands_constraints[] = +{ + ConstraintSyntaxNewString("args", "", "Alternative string of arguments for the command (concatenated with promiser string and 'arglist' attribute)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("arglist", CF_ANYSTRING, "Alternative string list of arguments for the command (concatenated with promiser string and 'args' attribute)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("contain", &contain_body, "Containment options for the execution process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("module", "true/false whether to expect the cfengine module protocol. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("inform", "true/false whether to print info messages for command execution. Default value: true", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_EXEC_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "commands", commands_constraints, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_exec.h b/libpromises/mod_exec.h new file mode 100644 index 0000000000..c68fdb65fa --- /dev/null +++ b/libpromises/mod_exec.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_EXEC_H +#define CFENGINE_MOD_EXEC_H + +#include + +extern const PromiseTypeSyntax CF_EXEC_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_files.c b/libpromises/mod_files.c new file mode 100644 index 0000000000..d8e94fd835 --- /dev/null +++ b/libpromises/mod_files.c @@ -0,0 +1,422 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include + +static const ConstraintSyntax location_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("before_after", "before,after", "Menu option, point cursor before of after matched line. Default value: after", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("first_last", "first,last", "Menu option, choose first or last occurrence of match in file. Default value: last", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("select_line_matching", CF_ANYSTRING, "Regular expression for matching file line location", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax location_body = BodySyntaxNew("location", location_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax edit_field_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewBool("allow_blank_fields", "true/false allow blank fields in a line (do not purge). Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("extend_fields", "true/false add new fields at end of line if necessary to complete edit. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("field_operation", "prepend,append,alphanum,delete,set", "Menu option policy for editing subfields. Default value: none", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("field_separator", CF_ANYSTRING, "The regular expression used to separate fields in a line. Default value: none", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("field_value", CF_ANYSTRING, "Set field value to a fixed value", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("select_field", "0,99999999", "Integer index of the field required 0..n (default starts from 1)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("start_fields_from_zero", "If set, the default field numbering starts from 0", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("value_separator", CF_CHARRANGE, "Character separator for subfields inside the selected field. Default value: none", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax edit_field_body = BodySyntaxNew("edit_field", edit_field_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax replace_with_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("occurrences", "all,first", "Menu option to replace all occurrences or just first (NB the latter is non-convergent). Default value: all", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("replace_value", CF_ANYSTRING, "Value used to replace regular expression matches in search", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax replace_with_body = BodySyntaxNew("replace_with", replace_with_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax select_region_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewBool("include_start_delimiter", "Whether to include the section delimiter. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("include_end_delimiter", "Whether to include the section delimiter. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("select_start", CF_ANYSTRING, "Regular expression matching start of edit region", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("select_end", CF_ANYSTRING, "Regular expression matches end of edit region from start", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("select_end_match_eof", "Whether to include EOF as end of the region. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax select_region_body = BodySyntaxNew("select_region", select_region_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax delete_select_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("delete_if_startwith_from_list", CF_ANYSTRING, "Delete line if it starts with a string in the list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("delete_if_not_startwith_from_list", CF_ANYSTRING, "Delete line if it DOES NOT start with a string in the list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("delete_if_match_from_list", CF_ANYSTRING, "Delete line if it fully matches a regex in the list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("delete_if_not_match_from_list", CF_ANYSTRING,"Delete line if it DOES NOT fully match a regex in the list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("delete_if_contains_from_list", CF_ANYSTRING, "Delete line if a regex in the list match a line fragment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("delete_if_not_contains_from_list", CF_ANYSTRING,"Delete line if a regex in the list DOES NOT match a line fragment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax delete_select_body = BodySyntaxNew("delete_select", delete_select_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax insert_select_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("insert_if_startwith_from_list", CF_ANYSTRING, "Insert line if it starts with a string in the list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("insert_if_not_startwith_from_list", CF_ANYSTRING,"Insert line if it DOES NOT start with a string in the list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("insert_if_match_from_list", CF_ANYSTRING, "Insert line if it fully matches a regex in the list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("insert_if_not_match_from_list", CF_ANYSTRING,"Insert line if it DOES NOT fully match a regex in the list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("insert_if_contains_from_list", CF_ANYSTRING,"Insert line if a regex in the list match a line fragment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("insert_if_not_contains_from_list", CF_ANYSTRING, "Insert line if a regex in the list DOES NOT match a line fragment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax insert_select_body = BodySyntaxNew("insert_select", insert_select_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax CF_INSERTLINES_BODIES[] = +{ + ConstraintSyntaxNewBool("expand_scalars", "Expand any unexpanded variables. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("insert_type", "literal,string,file,file_preserve_block,preserve_block,preserve_all_lines", "Type of object the promiser string refers to. Default value: literal", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("insert_select", &insert_select_body, "Insert only if lines pass filter criteria", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("location", &location_body, "Specify where in a file an insertion will be made", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOptionList("whitespace_policy", "ignore_leading,ignore_trailing,ignore_embedded,exact_match", "Criteria for matching and recognizing existing lines", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax CF_DELETELINES_BODIES[] = +{ + ConstraintSyntaxNewBody("delete_select", &delete_select_body, "Delete only if lines pass filter criteria", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("not_matching", "true/false negate match criterion. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax CF_COLUMN_BODIES[] = +{ + ConstraintSyntaxNewBody("edit_field", &edit_field_body, "Edit line-based file as matrix of fields", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax CF_REPLACE_BODIES[] = +{ + ConstraintSyntaxNewBody("replace_with", &replace_with_body, "Search-replace pattern", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const ConstraintSyntax CF_COMMON_EDITBODIES[] = +{ + ConstraintSyntaxNewBody("select_region", &select_region_body, "Limit edits to a demarked region of the file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static bool AclCheck(const Body *body, Seq *errors) +{ + bool success = true; + + if (BodyHasConstraint(body, "acl_directory_inherit") + && BodyHasConstraint(body, "acl_default")) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BODY, body, "An acl body cannot have both acl_directory_inherit and acl_default. Please use acl_default only")); + success = false; + } + if (BodyHasConstraint(body, "specify_inherit_aces") + && BodyHasConstraint(body, "specify_default_aces")) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BODY, body, "An acl body cannot have both specify_inherit_aces and specify_default_aces. Please use specify_default_aces only")); + success = false; + } + + return success; +} + +static const ConstraintSyntax acl_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("aces", "((user|group):[^:]+:[-=+,rwx()dtTabBpcoD]*(:(allow|deny))?)|((all|mask):[-=+,rwx()]*(:(allow|deny))?)", "Native settings for access control entry", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("acl_directory_inherit", "nochange,parent,specify,clear", "Access control list type for the affected file system", SYNTAX_STATUS_DEPRECATED), + ConstraintSyntaxNewOption("acl_default", "nochange,access,specify,clear", "How to apply default (inheritable) access control list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("acl_method", "append,overwrite", "Editing method for access control list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("acl_type", "generic,posix,ntfs", "Access control list type for the affected file system", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("specify_inherit_aces", "((user|group):[^:]+:[-=+,rwx()dtTabBpcoD]*(:(allow|deny))?)|((all|mask):[-=+,rwx()]*(:(allow|deny))?)", "Native settings for access control entry", SYNTAX_STATUS_DEPRECATED), + ConstraintSyntaxNewStringList("specify_default_aces", "((user|group):[^:]+:[-=+,rwx()dtTabBpcoD]*(:(allow|deny))?)|((all|mask):[-=+,rwx()]*(:(allow|deny))?)", "Native settings for access control entry", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("acl_inherit", CF_BOOL ",nochange", "Whether the object inherits its ACL from the parent (Windows only)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax acl_body = BodySyntaxNew("acl", acl_constraints, &AclCheck, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax changes_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("hash", "md5,sha1,sha224,sha256,sha384,sha512,best", "Hash files for change detection", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("report_changes", "all,stats,content,none", "Specify criteria for change warnings", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("update_hashes", "Update hash values immediately after change warning", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("report_diffs","Generate reports summarizing the major differences between individual text files", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax changes_body = BodySyntaxNew("changes", changes_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax depth_search_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewInt("depth", CF_VALRANGE, "Maximum depth level for search", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("exclude_dirs", ".*", "List of regexes of directory names NOT to include in depth search", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("include_basedir", "true/false include the start/root dir of the search results", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("include_dirs", ".*", "List of regexes of directory names to include in depth search", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("rmdeadlinks", "true/false remove links that point to nowhere. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("traverse_links", "true/false traverse symbolic links to directories. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("xdev", "When true files and directories on different devices from the promiser will be excluded from depth_search results. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax depth_search_body = BodySyntaxNew("depth_search", depth_search_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax edit_defaults_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("edit_backup", "true,false,timestamp,rotate", "Menu option for backup policy on edit changes. Default value: true", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("empty_file_before_editing", "Baseline memory model of file to zero/empty before commencing promised edits. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("inherit", "If true this causes the sub-bundle to inherit the private classes of its parent", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("max_file_size", CF_VALRANGE, "Do not edit files bigger than this number of bytes", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("recognize_join", "Join together lines that end with a backslash, up to 4kB limit. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("rotate", "0,99", "How many backups to store if 'rotate' edit_backup strategy is selected. Default value: 1", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax edit_defaults_body = BodySyntaxNew("edit_defaults", edit_defaults_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax delete_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("dirlinks", "delete,tidy,keep", "Menu option policy for dealing with symbolic links to directories during deletion", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("rmdirs", "true/false whether to delete empty directories during recursive deletion", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax delete_body = BodySyntaxNew("delete", delete_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax rename_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewBool("disable", "true/false automatically rename and remove permissions. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("disable_mode", CF_MODERANGE, "The permissions to set when a file is disabled", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("disable_suffix", "", "The suffix to add to files when disabling (.cfdisabled)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("newname", "", "The desired name for the current file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("rotate", "0,99", "Maximum number of file rotations to keep", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax rename_body = BodySyntaxNew("rename", rename_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax perms_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("bsdflags", CF_BSDFLAGRANGE, "List of menu options for bsd file system flags to set", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("groups", CF_USERRANGE, "List of acceptable groups of group ids, first is change target", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("mode", CF_MODERANGE, "File permissions (like posix chmod)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("owners", CF_USERRANGE, "List of acceptable owners or user ids, first is change target", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("rxdirs", "true/false add execute flag for directories if read flag is set", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax perms_body = BodySyntaxNew("perms", perms_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax file_select_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("leaf_name", "", "List of regexes that match an acceptable name", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("path_name", CF_ABSPATHRANGE, "List of pathnames to match acceptable target", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("search_mode", CF_MODERANGE, "A list of mode masks for acceptable file permissions", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("search_size", "0,inf", "Integer range of file sizes", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("search_owners", "", "List of acceptable user names or ids for the file, or regexes to match", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("search_groups", "", "List of acceptable group names or ids for the file, or regexes to match", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("search_bsdflags", CF_BSDFLAGRANGE, "String of flags for bsd file system flags expected set", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("ctime", CF_TIMERANGE, "Range of change times (ctime) for acceptable files", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("mtime", CF_TIMERANGE, "Range of modification times (mtime) for acceptable files", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("atime", CF_TIMERANGE, "Range of access times (atime) for acceptable files", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("exec_regex", CF_ANYSTRING, "Matches file if this regular expression matches any full line returned by the command", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("exec_program", CF_ABSPATHRANGE, "Execute this command on each file and match if the exit status is zero", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOptionList("file_types", "plain,reg,symlink,dir,socket,fifo,door,char,block", "List of acceptable file types from menu choices", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("issymlinkto", "", "List of regular expressions to match file objects", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("file_result", "[!*(leaf_name|path_name|file_types|mode|size|owner|group|atime|ctime|mtime|issymlinkto|exec_regex|exec_program|bsdflags)[|&.]*]*", + "Logical expression combining classes defined by file search criteria", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax file_select_body = BodySyntaxNew("file_select", file_select_constraints, NULL, SYNTAX_STATUS_NORMAL); + +/* Copy and link are really the same body and should have + non-overlapping patterns so that they are XOR but it's + okay that some names overlap (like source) as there is + no ambiguity in XOR */ + +static const ConstraintSyntax link_from_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("copy_patterns", "", "A set of patterns that should be copied and synchronized instead of linked", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("link_children", "true/false whether to link all directory's children to source originals. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("link_type", CF_LINKRANGE, "The type of link used to alias the file. Default value: symlink", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("source", CF_PATHRANGE, "The source file to which the link should point", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("when_linking_children", "override_file,if_no_such_file", "Policy for overriding existing files when linking directories of children", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("when_no_source", "force,delete,nop", "Behaviour when the source file to link to does not exist. Default value: nop", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax link_from_body = BodySyntaxNew("link_from", link_from_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax copy_from_constraints[] = +{ + /* We use CF_PATHRANGE due to collision with LINKTO_BODY and a bug lurking in + * a verification stage -- this attribute gets picked instead of another + * 'source' + */ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewString("source", CF_PATHRANGE, "Reference source file from which to copy", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("servers", "[A-Za-z0-9_.:\\-\\[\\]]+", "List of servers in order of preference from which to copy", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("collapse_destination_dir", "Copy files from subdirectories to the root destination directory. Default: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("compare", "atime,mtime,ctime,digest,hash,exists,binary", "Menu option policy for comparing source and image file attributes. Default: mtime or ctime differs", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("copy_backup", "true,false,timestamp", "Menu option policy for file backup/version control. Default value: true", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("encrypt", "true/false use encrypted data stream to connect to remote host. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("check_root", "true/false check permissions on the root directory when depth_search", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("copylink_patterns", "", "List of patterns matching files that should be copied instead of linked", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("copy_size", "0,inf", "Integer range of file sizes that may be copied", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("findertype", "MacOSX", "Menu option for default finder type on MacOSX", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("linkcopy_patterns", "", "List of patterns matching files that should be replaced with symbolic links", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("link_type", CF_LINKRANGE, "Menu option for type of links to use when copying. Default value: symlink", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("force_update", "true/false force copy update always. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("force_ipv4", "true/false force use of ipv4 on ipv6 enabled network. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("portnumber", "", "Port number or service name to connect to on server host", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("preserve", "true/false whether to preserve file permissions on copied file. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("purge", "true/false purge files on client that do not match files on server when a depth_search is used. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("stealth", "true/false whether to preserve time stamps on copied file. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("timeout", "1,3600", "Connection timeout, seconds", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("trustkey", "true/false trust public keys from remote server if previously unknown. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("type_check", "true/false compare file types before copying and require match", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("verify", "true/false verify transferred file by hashing after copy (resource penalty). Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("protocol_version", "1,classic,2,tls,3,cookie,latest", "CFEngine protocol version to use when connecting to the server. Default: undefined", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("missing_ok", "true/false Do not treat missing file as an error. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax copy_from_body = BodySyntaxNew("copy_from", copy_from_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax CF_FILES_BODIES[] = +{ + ConstraintSyntaxNewBody("acl", &acl_body, "Criteria for access control lists on file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("changes", &changes_body, "Criteria for change management", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("copy_from", ©_from_body, "Criteria for copying file from a source", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("create", "true/false whether to create non-existing file. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("delete", &delete_body, "Criteria for deleting files", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("content", CF_ANYSTRING, "Complete content the promised file should contain", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("depth_search", &depth_search_body, "Criteria for file depth searches", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("edit_defaults", &edit_defaults_body, "Default promise details for file edits", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBundle("edit_line", "Line editing model for file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("edit_template", CF_ABSPATHRANGE, "The name of a special CFEngine template file to expand", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("edit_template_string", CF_ANYSTRING, "Template string to expand", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBundle("edit_xml", "XML editing model for file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("file_select", &file_select_body, "Choose which files select in a search", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("file_type", "regular,fifo", "Type of file to create. Default value: regular", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("link_from", &link_from_body, "Criteria for linking file from a source", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("move_obstructions", "true/false whether to move obstructions to file-object creation. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("pathtype", "literal,regex,guess", "Menu option for interpreting promiser file object", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("perms", &perms_body, "Criteria for setting permissions on a file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("rename", &rename_body, "Criteria for renaming files", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("repository", CF_ABSPATHRANGE, "Name of a repository for versioning", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("touch", "true/false whether to touch time stamps on file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("transformer", CF_ABSPATHRANGE, "Command (with full path) used to transform current file (no shell wrapper used)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("template_method", "cfengine,inline_mustache,mustache", "", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewContainer("template_data", "", SYNTAX_STATUS_NORMAL), + + ConstraintSyntaxNewNull() +}; + +// edit_xml body syntax +const ConstraintSyntax CF_COMMON_XMLBODIES[] = +{ + ConstraintSyntaxNewString("build_xpath", "", "Build an XPath within the XML file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("select_xpath", "", "Select the XPath node in the XML file to edit", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax CF_INSERTTAGS_BODIES[] = +{ + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax CF_DELETETAGS_BODIES[] = +{ + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax CF_INSERTATTRIBUTES_BODIES[] = +{ + ConstraintSyntaxNewString("attribute_value", "", "Value of the attribute to be inserted into the XPath node of the XML file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax CF_DELETEATTRIBUTES_BODIES[] = +{ + ConstraintSyntaxNewNull() +}; + + +// Master Syntax for Files + +const PromiseTypeSyntax CF_FILES_PROMISE_TYPES[] = +{ + /* Body lists belonging to "files:" type in Agent */ + + PromiseTypeSyntaxNew("agent", "files", CF_FILES_BODIES, NULL, SYNTAX_STATUS_NORMAL), + + /* Body lists belonging to th edit_line sub-bundle of files: */ + + PromiseTypeSyntaxNew("edit_line", "*", CF_COMMON_EDITBODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_line", "delete_lines", CF_DELETELINES_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_line", "insert_lines", CF_INSERTLINES_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_line", "field_edits", CF_COLUMN_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_line", "replace_patterns", CF_REPLACE_BODIES, NULL, SYNTAX_STATUS_NORMAL), + + PromiseTypeSyntaxNew("edit_xml", "*", CF_COMMON_XMLBODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_xml", "build_xpath", CF_INSERTTAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_xml", "delete_tree", CF_DELETETAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_xml", "insert_tree", CF_INSERTTAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_xml", "delete_attribute", CF_DELETEATTRIBUTES_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_xml", "set_attribute", CF_INSERTATTRIBUTES_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_xml", "delete_text", CF_DELETETAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_xml", "set_text", CF_INSERTTAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNew("edit_xml", "insert_text", CF_INSERTTAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL), + + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_files.h b/libpromises/mod_files.h new file mode 100644 index 0000000000..cdc5807e4c --- /dev/null +++ b/libpromises/mod_files.h @@ -0,0 +1,34 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_FILES_H +#define CFENGINE_MOD_FILES_H + +#include + +extern const PromiseTypeSyntax CF_FILES_PROMISE_TYPES[]; +extern const ConstraintSyntax CF_COMMON_EDITBODIES[]; +extern const ConstraintSyntax CF_COMMON_XMLBODIES[]; + +#endif diff --git a/libpromises/mod_knowledge.c b/libpromises/mod_knowledge.c new file mode 100644 index 0000000000..6111a13fdd --- /dev/null +++ b/libpromises/mod_knowledge.c @@ -0,0 +1,97 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax association_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewString("forward_relationship", "", "Name of forward association between promiser topic and associates", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewString("backward_relationship", "", "Name of backward/inverse association from associates to promiser topic", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("associates", "", "List of associated topics by this forward relationship", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax association_body = BodySyntaxNew("association", association_constraints, NULL, SYNTAX_STATUS_REMOVED); + +static const ConstraintSyntax topics_constraints[] = +{ + ConstraintSyntaxNewBody("association", &association_body, "Declare associated topics", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("synonyms", "", "A list of words to be treated as equivalents in the defined context", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("generalizations", "", "A list of words to be treated as super-sets for the current topic, used when reasoning", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax occurrences_constraints[] = +{ + ConstraintSyntaxNewStringList("about_topics", "", "List of topics that the document or resource addresses", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("represents", "", "List of explanations for what relationship this document has to the topics it is about", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("representation", "literal,url,db,file,web,image,portal", "How to interpret the promiser string e.g. actual data or reference to data", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax things_constraints[] = +{ + ConstraintSyntaxNewStringList("synonyms", "", "A list of words to be treated as equivalents in the defined context", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("affects", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("belongs_to", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("causes", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("certainty", "certain,uncertain,possible", "Selects the level of certainty for the proposed knowledge, for use in inferential reasoning", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("determines", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("generalizations", "", "A list of words to be treated as super-sets for the current topic, used when reasoning", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("implements", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("involves", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_caused_by", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_connected_to", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_determined_by", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_followed_by", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_implemented_by", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_located_in", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_measured_by", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_part_of", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("is_preceded_by", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("measures", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("needs", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("provides", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("uses", "", "Special fixed relation for describing topics that are things", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewNull() +}; + +static const ConstraintSyntax inferences_constraints[] = +{ + ConstraintSyntaxNewStringList("precedents", "", "The foundational vector for a trinary inference", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewStringList("qualifiers", "", "The second vector in a trinary inference", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_KNOWLEDGE_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("knowledge", "inferences", inferences_constraints, NULL, SYNTAX_STATUS_REMOVED), + PromiseTypeSyntaxNew("knowledge", "things", things_constraints, NULL, SYNTAX_STATUS_REMOVED), + PromiseTypeSyntaxNew("knowledge", "topics", topics_constraints, NULL, SYNTAX_STATUS_REMOVED), + PromiseTypeSyntaxNew("knowledge", "occurrences", occurrences_constraints, NULL, SYNTAX_STATUS_REMOVED), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_knowledge.h b/libpromises/mod_knowledge.h new file mode 100644 index 0000000000..754a13a352 --- /dev/null +++ b/libpromises/mod_knowledge.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_KNOWLEDGE_H +#define CFENGINE_MOD_KNOWLEDGE_H + +#include + +extern const PromiseTypeSyntax CF_KNOWLEDGE_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_measurement.c b/libpromises/mod_measurement.c new file mode 100644 index 0000000000..f72e930e8c --- /dev/null +++ b/libpromises/mod_measurement.c @@ -0,0 +1,58 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax match_value_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + + /* Row models */ + ConstraintSyntaxNewString("select_line_matching", CF_ANYSTRING, "Regular expression for matching line location", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("select_line_number", CF_VALRANGE, "Read from the n-th line of the output (fixed format)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("extraction_regex", "", "Regular expression that should contain a single backreference for extracting a value", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("track_growing_file", "If true, cfengine remembers the position to which is last read when opening the file, and resets to the start if the file has since been truncated", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("select_multiline_policy", "average,sum,first,last", "Regular expression for matching line location", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax match_value_body = BodySyntaxNew("match_value", match_value_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax CF_MEASURE_BODIES[] = +{ + ConstraintSyntaxNewOption("stream_type", "pipe,file", "The datatype being collected.", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("data_type", "counter,int,real,string,slist", "The datatype being collected.", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("history_type", "weekly,scalar,static,log", "Whether the data can be seen as a time-series or just an isolated value", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("units", "", "The engineering dimensions of this value or a note about its intent used in plots", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("match_value", &match_value_body, "Criteria for extracting the measurement from a datastream", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_MEASUREMENT_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("monitor", "measurements", CF_MEASURE_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_measurement.h b/libpromises/mod_measurement.h new file mode 100644 index 0000000000..243a294d2e --- /dev/null +++ b/libpromises/mod_measurement.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_MEASUREMENT_H +#define CFENGINE_MOD_MEASUREMENT_H + +#include + +extern const PromiseTypeSyntax CF_MEASUREMENT_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_methods.c b/libpromises/mod_methods.c new file mode 100644 index 0000000000..a51e0a3436 --- /dev/null +++ b/libpromises/mod_methods.c @@ -0,0 +1,94 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include + +static const char *const POLICY_ERROR_METHODS_BUNDLE_ARITY = + "Conflicting arity in calling bundle %s, expected %d arguments, %d given"; + +static const ConstraintSyntax CF_METHOD_BODIES[] = +{ + ConstraintSyntaxNewBool("inherit", "If true this causes the sub-bundle to inherit the private classes of its parent", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBundle("usebundle", "Specify the name of a bundle to run as a parameterized method", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("useresult", CF_IDRANGE, "Specify the name of a local variable to contain any result/return value from the child", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static bool MethodsParseTreeCheck(const Promise *pp, Seq *errors) +{ + bool success = true; + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + const Constraint *cp = SeqAt(pp->conlist, i); + + // ensure: if call and callee are resolved, then they have matching arity + if (StringEqual(cp->lval, "usebundle")) + { + if (cp->rval.type == RVAL_TYPE_FNCALL) + { + // HACK: exploiting the fact that class-references and call-references are similar + FnCall *call = RvalFnCallValue(cp->rval); + ClassRef ref = ClassRefParse(call->name); + if (!ClassRefIsQualified(ref)) + { + ClassRefQualify(&ref, PromiseGetNamespace(pp)); + } + + const Bundle *callee = PolicyGetBundle(PolicyFromPromise(pp), ref.ns, "agent", ref.name); + if (!callee) + { + callee = PolicyGetBundle(PolicyFromPromise(pp), ref.ns, "common", ref.name); + } + + ClassRefDestroy(ref); + + if (callee) + { + if (RlistLen(call->args) != RlistLen(callee->args)) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, cp, + POLICY_ERROR_METHODS_BUNDLE_ARITY, + call->name, RlistLen(callee->args), RlistLen(call->args))); + success = false; + } + } + } + } + } + return success; +} + +const PromiseTypeSyntax CF_METHOD_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "methods", CF_METHOD_BODIES, &MethodsParseTreeCheck, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_methods.h b/libpromises/mod_methods.h new file mode 100644 index 0000000000..290bf5a05d --- /dev/null +++ b/libpromises/mod_methods.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_METHODS_H +#define CFENGINE_MOD_METHODS_H + +#include + +extern const PromiseTypeSyntax CF_METHOD_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_outputs.c b/libpromises/mod_outputs.c new file mode 100644 index 0000000000..634de7a4ad --- /dev/null +++ b/libpromises/mod_outputs.c @@ -0,0 +1,40 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax CF_OUTPUTS_BODIES[] = +{ + ConstraintSyntaxNewOption("output_level", "verbose,debug,inform", "Output level to observe for the named promise or bundle (meta-promise). Default value: verbose", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewOption("promiser_type", "promise,bundle", "Output level to observe for the named promise or bundle (meta-promise). Default value: promise", SYNTAX_STATUS_REMOVED), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_OUTPUTS_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "outputs", CF_OUTPUTS_BODIES, NULL, SYNTAX_STATUS_REMOVED), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_outputs.h b/libpromises/mod_outputs.h new file mode 100644 index 0000000000..097701c4c8 --- /dev/null +++ b/libpromises/mod_outputs.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_OUTPUTS_H +#define CFENGINE_MOD_OUTPUTS_H + +#include + +extern const PromiseTypeSyntax CF_OUTPUTS_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_packages.c b/libpromises/mod_packages.c new file mode 100644 index 0000000000..368c1f5410 --- /dev/null +++ b/libpromises/mod_packages.c @@ -0,0 +1,100 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax package_method_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewString("package_add_command", CF_PATHRANGE, "Command to install a package to the system", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_arch_regex", "", "Regular expression with one backreference to extract package architecture string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("package_changes", "individual,bulk", "Menu option - whether to group packages into a single aggregate command", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_delete_command", CF_PATHRANGE, "Command to remove a package from the system", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_delete_convention", "", "This is how the package manager expects the package to be referred to in the deletion part of a package update, e.g. $(name)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("package_file_repositories", "", "A list of machine-local directories to search for packages", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_installed_regex", "", "Regular expression which matches packages that are already installed", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_default_arch_command", CF_ABSPATHRANGE, "Command to detect the default packages' architecture", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_list_arch_regex", "", "Regular expression with one backreference to extract package architecture string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_list_command", CF_PATHRANGE, "Command to obtain a list of available packages", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_list_name_regex", "", "Regular expression with one backreference to extract package name string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_list_update_command", "", "Command to update the list of available packages (if any)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("package_list_update_ifelapsed", CF_INTRANGE, "The ifelapsed locking time in between updates of the package list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_list_version_regex", "", "Regular expression with one backreference to extract package version string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_name_convention", "", "This is how the package manager expects the package to be referred to, e.g. $(name).$(arch)", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_name_regex", "", "Regular expression with one backreference to extract package name string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_noverify_regex", "", "Regular expression to match verification failure output", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("package_noverify_returncode", CF_INTRANGE, "Integer return code indicating package verification failure", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_patch_arch_regex", "", "Regular expression with one backreference to extract update architecture string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_patch_command", CF_PATHRANGE, "Command to update to the latest patch release of an installed package", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_patch_installed_regex", "", "Regular expression which matches packages that are already installed", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_patch_list_command", CF_PATHRANGE, "Command to obtain a list of available patches or updates", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_patch_name_regex", "", "Regular expression with one backreference to extract update name string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_patch_version_regex", "", "Regular expression with one backreference to extract update version string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_update_command", CF_PATHRANGE, "Command to update to the latest version a currently installed package", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_verify_command", CF_PATHRANGE, "Command to verify the correctness of an installed package", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_version_regex", "", "Regular expression with one backreference to extract package version string", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_multiline_start", "", "Regular expression which matches the start of a new package in multiline output", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("package_commands_useshell", "Whether to use shell for commands in this body. Default value: true", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_version_less_command", CF_PATHRANGE, "Command to check whether first supplied package version is less than second one", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_version_equal_command", CF_PATHRANGE, "Command to check whether first supplied package version is equal to second one", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax package_method_body = BodySyntaxNew("package_method", package_method_constraints, NULL, SYNTAX_STATUS_NORMAL); + + +static const ConstraintSyntax package_module_constraints[] = +{ + ConstraintSyntaxNewInt("query_installed_ifelapsed", CF_INTRANGE, "The ifelapsed locking time in between updates of the installed package list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("query_updates_ifelapsed", CF_INTRANGE, "The ifelapsed locking time in between updates of the available updates list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("default_options", "", "Default options passed to package manager wrapper", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("interpreter", "", "Path to the interpreter to run the package manager wrapper with", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("module_path", "", "Non-standard path to the package manager wrapper", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; +static const BodySyntax package_module_body = BodySyntaxNew("package_module", package_module_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax packages_constraints[] = +{ + ConstraintSyntaxNewStringList("package_architectures", "", "Select the architecture for package selection", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("package_method", &package_method_body, "Criteria for installation and verification", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("package_policy", "add,delete,reinstall,update,addupdate,patch,verify", "Criteria for package installation/upgrade on the current system. Default value: verify", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("package_select", ">,<,==,!=,>=,<=", "A criterion for first acceptable match relative to \"package_version\"", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("package_version", "", "Version reference point for determining promised version", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("policy", "absent,present", "Criteria for package installation/upgrade on the current system", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("package_module", &package_module_body, "Package manager used for package related operations", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("version", "", "Version reference point for determining promised version", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("architecture", "", "Architecture type of package", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("options", "", "Additional options passed to package manager", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("additional_packages", "", "Additional packages processed with given package promiser", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_PACKAGES_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "packages", packages_constraints, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_packages.h b/libpromises/mod_packages.h new file mode 100644 index 0000000000..fc7bb4c011 --- /dev/null +++ b/libpromises/mod_packages.h @@ -0,0 +1,33 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_PACKAGES_H +#define CFENGINE_MOD_PACKAGES_H + +#include + +extern const PromiseTypeSyntax CF_PACKAGES_PROMISE_TYPES[]; + +#endif + diff --git a/libpromises/mod_process.c b/libpromises/mod_process.c new file mode 100644 index 0000000000..8c8992cd47 --- /dev/null +++ b/libpromises/mod_process.c @@ -0,0 +1,84 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax process_count_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewStringList("in_range_define", "", "List of classes to define if the matches are in range", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("match_range", CF_VALRANGE, "Integer range for acceptable number of matches for this process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("out_of_range_define", "", "List of classes to define if the matches are out of range", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax process_count_body = BodySyntaxNew("process_count", process_count_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax process_select_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewString("command", "", "Regular expression matching the command/cmd field of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("pid", CF_VALRANGE, "Range of integers matching the process id of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("pgid", CF_VALRANGE, "Range of integers matching the parent group id of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("ppid", CF_VALRANGE, "Range of integers matching the parent process id of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("priority", "-20,+20", "Range of integers matching the priority field (PRI/NI) of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("process_owner", "", "List of regexes matching the user of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("process_result", + "[(process_owner|pid|ppid||pgid|rsize|vsize|status|command|ttime|stime|tty|priority|threads)[|&!.]*]*", + "Boolean class expression returning the logical combination of classes set by a process selection test", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("rsize", CF_VALRANGE, "Range of integers matching the resident memory size of a process, in kilobytes", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("status", "", "Regular expression matching the status field of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("stime_range", CF_TIMERANGE, "Range of integers matching the start time of a process", SYNTAX_STATUS_NORMAL), + /* CF_VALRANGE insted of CF_TIMERANGE since ttime_range counts cumulative + * time, not absolute time since the epoch. See cumulative() that has + * maximum number of years 1000, that can easily surpass CF_TIMERANGE in + * seconds. */ + ConstraintSyntaxNewIntRange("ttime_range", CF_VALRANGE, "Range of integers matching the total elapsed time of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("tty", "", "Regular expression matching the tty field of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("threads", CF_VALRANGE, "Range of integers matching the threads (NLWP) field of a process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewIntRange("vsize", CF_VALRANGE, "Range of integers matching the virtual memory size of a process, in kilobytes", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax process_select_body = BodySyntaxNew("process_select", process_select_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax processes_constraints[] = +{ + ConstraintSyntaxNewBody("process_count", &process_count_body, "Criteria for constraining the number of processes matching other criteria", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("process_select", &process_select_body, "Criteria for matching processes in the system process table", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("process_stop", CF_ABSPATHRANGE, "A command used to stop a running process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("restart_class", CF_IDRANGE, + "A class to be defined globally if the process is not running, so that a command: rule can be referred to restart the process", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("signals", "(hup|int|trap|kill|pipe|cont|abrt|stop|quit|term|child|usr1|usr2|bus|segv|[0-9]+s?)", + "A list of strings representing signals to be sent to a process or sleep periods", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_PROCESS_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "processes", processes_constraints, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_process.h b/libpromises/mod_process.h new file mode 100644 index 0000000000..84b537ebda --- /dev/null +++ b/libpromises/mod_process.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_PROCESS_H +#define CFENGINE_MOD_PROCESS_H + +#include + +extern const PromiseTypeSyntax CF_PROCESS_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_report.c b/libpromises/mod_report.c new file mode 100644 index 0000000000..0f5d4c3660 --- /dev/null +++ b/libpromises/mod_report.c @@ -0,0 +1,57 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax printfile_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewString("file_to_print", CF_ABSPATHRANGE, "Path name to the file that is to be sent to standard output", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("number_of_lines", CF_INTRANGE, "Integer maximum number of lines to print from selected file", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax printfile_body = BodySyntaxNew("printfile", printfile_constraints, NULL, SYNTAX_STATUS_NORMAL); + +const ConstraintSyntax CF_REPORT_BODIES[] = +{ + ConstraintSyntaxNewString("friend_pattern", "", "Regular expression to keep selected hosts from the friends report list", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewReal("intermittency", "0,1", "Real number threshold [0,1] of intermittency about current peers, report above. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("lastseen", CF_VALRANGE, "Integer time threshold in hours since current peers were last seen, report absence", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("printfile", &printfile_body, "Quote part of a file to standard output", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("report_to_file", CF_ABSPATHRANGE, "The path and filename to which output should be appended", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("bundle_return_value_index", CF_IDRANGE, "The promiser is to be interpreted as a literal value that the caller can accept as a result for this bundle, i.e. a return value with array index defined by this attribute.", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("showstate", "", "List of services about which status reports should be reported to standard output", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_REPORT_PROMISE_TYPES[] = +{ + /* Body lists belonging to "reports:" type in Agent */ + + PromiseTypeSyntaxNew("agent", "reports", CF_REPORT_BODIES, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_report.h b/libpromises/mod_report.h new file mode 100644 index 0000000000..53046bed13 --- /dev/null +++ b/libpromises/mod_report.h @@ -0,0 +1,33 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_REPORT_H +#define CFENGINE_MOD_REPORT_H + +#include + +extern const PromiseTypeSyntax CF_REPORT_PROMISE_TYPES[]; +extern const ConstraintSyntax CF_REPORT_BODIES[]; + +#endif diff --git a/libpromises/mod_services.c b/libpromises/mod_services.c new file mode 100644 index 0000000000..8b91f0851e --- /dev/null +++ b/libpromises/mod_services.c @@ -0,0 +1,54 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax service_method_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewString("service_args", "", "Parameters for starting the service as command", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("service_autostart_policy", "none,boot_time,on_demand", "Should the service be started automatically by the OS", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBundle("service_bundle", "A bundle reference with two arguments (service_name,args) used if the service type is generic", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("service_dependence_chain", "ignore,start_parent_services,stop_child_services,all_related", "How to handle dependencies and dependent services", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("service_type", "windows,generic", "Service abstraction type", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax service_method_body = BodySyntaxNew("service_method", service_method_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax services_constraints[] = +{ + ConstraintSyntaxNewString("service_policy", "", "Policy for cfengine service status", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("service_dependencies", CF_IDRANGE, "A list of services on which the named service abstraction depends", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("service_method", &service_method_body, "Details of promise body for the service abtraction feature", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_SERVICES_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "services", services_constraints, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_services.h b/libpromises/mod_services.h new file mode 100644 index 0000000000..0ecf8217cc --- /dev/null +++ b/libpromises/mod_services.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_SERVICES_H +#define CFENGINE_MOD_SERVICES_H + +#include + +extern const PromiseTypeSyntax CF_SERVICES_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_storage.c b/libpromises/mod_storage.c new file mode 100644 index 0000000000..cd663a0f4a --- /dev/null +++ b/libpromises/mod_storage.c @@ -0,0 +1,67 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax volume_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewBool("check_foreign", "true/false verify storage that is mounted from a foreign system on this host. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("freespace", "[0-9]+[MBkKgGmb%]", "Absolute or percentage minimum disk space that should be available before warning", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("sensible_size", CF_VALRANGE, "Minimum size in bytes that should be used on a sensible-looking storage device", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("sensible_count", CF_VALRANGE, "Minimum number of files that should be defined on a sensible-looking storage device", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("scan_arrivals", "true/false generate pseudo-periodic disk change arrival distribution. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax volume_body = BodySyntaxNew("volume", volume_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax mount_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewBool("edit_fstab", "true/false add or remove entries to the file system table (\"fstab\"). Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewOption("mount_type", "nfs,nfs2,nfs3,nfs4,panfs,cifs", "Protocol type of remote file system", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("mount_source", CF_ABSPATHRANGE, "Path of remote file system to mount", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("mount_server", "", "Hostname or IP or remote file system server", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("mount_options", "", "List of option strings to add to the file system table (\"fstab\")", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("unmount", "true/false unmount a previously mounted filesystem. Default value: false", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax mount_body = BodySyntaxNew("mount", mount_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax storage_constraints[] = +{ + ConstraintSyntaxNewBody("mount", &mount_body, "Criteria for mounting foreign file systems", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("volume", &volume_body, "Criteria for monitoring/probing mounted volumes", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_STORAGE_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "storage", storage_constraints, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_storage.h b/libpromises/mod_storage.h new file mode 100644 index 0000000000..fdef15fe05 --- /dev/null +++ b/libpromises/mod_storage.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_STORAGE_H +#define CFENGINE_MOD_STORAGE_H + +#include + +extern const PromiseTypeSyntax CF_STORAGE_PROMISE_TYPES[]; + +#endif diff --git a/libpromises/mod_users.c b/libpromises/mod_users.c new file mode 100644 index 0000000000..a02687db94 --- /dev/null +++ b/libpromises/mod_users.c @@ -0,0 +1,58 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +static const ConstraintSyntax password_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewOption("format", "plaintext,hash", "The format of the given password, either plaintext or hash", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("data", "", "Password", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax password_body = BodySyntaxNew("password", password_constraints, NULL, SYNTAX_STATUS_NORMAL); + +static const ConstraintSyntax users_constraints[] = +{ + ConstraintSyntaxNewOption("policy", "present,absent,locked", "The promised state of a given user", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewInt("uid", CF_INTRANGE, "User id", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("password", &password_body, "User password", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("description", "", "User comment", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("group_primary", "", "User primary group", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewStringList("groups_secondary", ".*", "User additional groups", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("home_dir", CF_ABSPATHRANGE, "User home directory", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBundle("home_bundle", "Specify the name of a bundle to run when creating a user", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBool("home_bundle_inherit", "If true this causes the home_bundle to inherit the private classes of its parent", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("shell", CF_ABSPATHRANGE, "User shell", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +const PromiseTypeSyntax CF_USERS_PROMISE_TYPES[] = +{ + PromiseTypeSyntaxNew("agent", "users", users_constraints, NULL, SYNTAX_STATUS_NORMAL), + PromiseTypeSyntaxNewNull() +}; diff --git a/libpromises/mod_users.h b/libpromises/mod_users.h new file mode 100644 index 0000000000..6e60204d99 --- /dev/null +++ b/libpromises/mod_users.h @@ -0,0 +1,33 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_MOD_USERS_H +#define CFENGINE_MOD_USERS_H + +#include + +extern const PromiseTypeSyntax CF_USERS_PROMISE_TYPES[]; + +#endif + diff --git a/libpromises/modes.c b/libpromises/modes.c new file mode 100644 index 0000000000..5d6dc202da --- /dev/null +++ b/libpromises/modes.c @@ -0,0 +1,288 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +/***************************************************************/ + +enum modestate +{ + wild, + who, + which +}; + +enum modesort +{ + unknown, + numeric, + symbolic +}; + +/*******************************************************************/ + +static bool CheckModeState(enum modestate stateA, enum modestate stateB, enum modesort modeA, enum modesort modeB, + char ch); +static bool SetModeMask(char action, int value, int affected, mode_t *p, mode_t *m); + +/***************************************************************/ + +bool ParseModeString(const char *modestring, mode_t *plusmask, mode_t *minusmask) +{ + int affected = 0, value = 0, gotaction; + bool no_error = true; + char action = '='; + enum modestate state = wild; + enum modesort found_sort = unknown; /* Already found "sort" of mode */ + enum modesort sort = unknown; /* Sort of started but not yet finished mode */ + + *plusmask = *minusmask = 0; + + if (modestring == NULL) + { + return true; + } + + gotaction = false; + + for (const char *sp = modestring; true; sp++) + { + switch (*sp) + { + case 'a': + no_error = CheckModeState(who, state, symbolic, sort, *sp); + affected |= 07777; + sort = symbolic; + break; + + case 'u': + no_error = CheckModeState(who, state, symbolic, sort, *sp); + affected |= 04700; + sort = symbolic; + break; + + case 'g': + no_error = CheckModeState(who, state, symbolic, sort, *sp); + affected |= 02070; + sort = symbolic; + break; + + case 'o': + no_error = CheckModeState(who, state, symbolic, sort, *sp); + affected |= 00007; + sort = symbolic; + break; + + case '+': + case '-': + case '=': + if (gotaction) + { + Log(LOG_LEVEL_ERR, "Too many +-= in mode string"); + return false; + } + + no_error = CheckModeState(who, state, symbolic, sort, *sp); + action = *sp; + state = which; + gotaction = true; + sort = unknown; + break; + + case 'r': + no_error = CheckModeState(which, state, symbolic, sort, *sp); + value |= 0444 & affected; + sort = symbolic; + break; + + case 'w': + no_error = CheckModeState(which, state, symbolic, sort, *sp); + value |= 0222 & affected; + sort = symbolic; + break; + + case 'x': + no_error = CheckModeState(which, state, symbolic, sort, *sp); + value |= 0111 & affected; + sort = symbolic; + break; + + case 's': + no_error = CheckModeState(which, state, symbolic, sort, *sp); + value |= 06000 & affected; + sort = symbolic; + break; + + case 't': + no_error = CheckModeState(which, state, symbolic, sort, *sp); + value |= 01000; + sort = symbolic; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + no_error = CheckModeState(which, state, numeric, sort, *sp); + sort = numeric; + gotaction = true; + state = which; + affected = 07777; /* TODO: Hard-coded; see below */ + sscanf(sp, "%o", &value); + + if (value & S_IFMT) + { + Log(LOG_LEVEL_INFO, "Mode-Value is not entirely within the system's allowed permissions (octal %o) and will be filtered accordingly : %s", + S_IFMT, modestring); + } + + /* stat() returns the file types in the mode, but they + * can't be set. So we clear the file-type as per POSIX + * 2001 instead of erroring out, leaving just the + * permissions. */ + value &= ~S_IFMT; + + if (value > 07777) /* TODO: Hardcoded ! + Is this correct for all sorts of Unix ? + What about NT ? + Any (POSIX)-constants ?? + */ + { + Log(LOG_LEVEL_ERR, "Mode-Value too big : %s", modestring); + return false; + } + + while ((isdigit((int) *sp)) && (*sp != '\0')) + { + sp++; + } + sp--; + break; + + case ',': + if (!SetModeMask(action, value, affected, plusmask, minusmask)) + { + return false; + } + + if ((found_sort != unknown) && (found_sort != sort)) + { + Log(LOG_LEVEL_INFO, "Symbolic and numeric form for modes mixed"); + } + + found_sort = sort; + sort = unknown; + action = '='; + affected = 0; + value = 0; + gotaction = false; + state = who; + break; + + case '\0': + if ((state == who) || (value == 0)) + { + if ((strcmp(modestring, "0000") != 0) && (strcmp(modestring, "000") != 0)) + { + Log(LOG_LEVEL_ERR, "mode string is incomplete"); + return false; + } + } + + if (!SetModeMask(action, value, affected, plusmask, minusmask)) + { + return false; + } + + if ((found_sort != unknown) && (found_sort != sort)) + { + Log(LOG_LEVEL_INFO, "Symbolic and numeric form for modes mixed"); + } + + Log(LOG_LEVEL_DEBUG, + "Modestring [PLUS = %jo] [MINUS = %jo]", + (uintmax_t) *plusmask, (uintmax_t) *minusmask); + return true; + + default: + Log(LOG_LEVEL_ERR, "Invalid mode string (%s)", modestring); + return false; + } + } + + if (!no_error) + { + Log(LOG_LEVEL_ERR, "Error validating mode string %s", modestring); + } + + return no_error; +} + +/*********************************************************/ + +static bool CheckModeState(enum modestate stateA, enum modestate stateB, enum modesort sortA, enum modesort sortB, + char ch) +{ + if ((stateA != wild) && (stateB != wild) && (stateA != stateB)) + { + Log(LOG_LEVEL_ERR, "Mode string constant (%c) used out of context", ch); + return false; + } + + if ((sortA != unknown) && (sortB != unknown) && (sortA != sortB)) + { + Log(LOG_LEVEL_ERR, "Symbolic and numeric filemodes mixed within expression"); + return false; + } + + return true; +} + +/*********************************************************/ + +static bool SetModeMask(char action, int value, int affected, mode_t *p, mode_t *m) +{ + switch (action) + { + case '+': + *p |= value; + *m |= 0; + return true; + case '-': + *p |= 0; + *m |= value; + return true; + case '=': + *p |= value; + *m |= ((~value) & 07777 & affected); + return true; + default: + Log(LOG_LEVEL_ERR, "Mode directive %c is unknown", action); + return false; + } +} diff --git a/libpromises/monitoring_read.c b/libpromises/monitoring_read.c new file mode 100644 index 0000000000..de0aae753b --- /dev/null +++ b/libpromises/monitoring_read.c @@ -0,0 +1,329 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include + +#include /* FILE_SEPARATOR */ +#include + + +/* GLOBALS */ + +static const char *UNITS[CF_OBSERVABLES] = /* constant */ +{ + "average users per 2.5 mins", + "processes", + "processes", + "percent", + "jobs", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "packets", + "entries", + "entries", + "entries", + "entries", + "Celcius", + "Celcius", + "Celcius", + "Celcius", + "percent", + "percent", + "percent", + "percent", + "percent", + "packets", + "packets", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + "connections", + +}; + +time_t slots_load_time = 0; +MonitoringSlot *SLOTS[CF_OBSERVABLES - ob_spare]; + + +/*****************************************************************************/ + +void Nova_FreeSlot(MonitoringSlot *slot) +{ + if (slot) + { + free(slot->name); + free(slot->description); + free(slot->units); + free(slot); + } +} + +MonitoringSlot *Nova_MakeSlot(const char *name, const char *description, + const char *units, + double expected_minimum, double expected_maximum, + bool consolidable) +{ + MonitoringSlot *slot = xmalloc(sizeof(MonitoringSlot)); + + slot->name = xstrdup(name); + slot->description = xstrdup(description); + slot->units = xstrdup(units); + slot->expected_minimum = expected_minimum; + slot->expected_maximum = expected_maximum; + slot->consolidable = consolidable; + return slot; +} + +// This function is similar to cf-check/observables.c function GetObservableNames +// If we refactor for dynamic observables instead of hard coded and limited to 100 +// then we likely should change here and there or refactor to have +// this ts_key read/parse code in one shared place. +void Nova_LoadSlots(void) +{ + char filename[CF_BUFSIZE]; + int i; + + snprintf(filename, CF_BUFSIZE - 1, "%s%cts_key", GetStateDir(), FILE_SEPARATOR); + + FILE *f = safe_fopen(filename, "r"); + if (f == NULL) + { + return; + } + + int fd = fileno(f); + struct stat sb; + if ((fstat(fd, &sb) != 0) || + (sb.st_mtime <= slots_load_time)) + { + fclose(f); + return; + } + + slots_load_time = sb.st_mtime; + + for (i = 0; i < CF_OBSERVABLES; ++i) + { + if (i < ob_spare) + { + int r; + do + { + r = fgetc(f); + } while (r != (int)'\n' && r != EOF); + if (r == EOF) + { + break; + } + } + else + { + char line[CF_MAXVARSIZE]; + + char name[CF_MAXVARSIZE], desc[CF_MAXVARSIZE]; + char units[CF_MAXVARSIZE] = "unknown"; + double expected_min = 0.0; + double expected_max = 100.0; + int consolidable = true; + + if (fgets(line, CF_MAXVARSIZE, f) == NULL) + { + Log(LOG_LEVEL_ERR, "Error trying to read ts_key from file '%s'. (fgets: %s)", filename, GetErrorStr()); + break; + } + + int fields = sscanf(line, "%*d,%1023[^,],%1023[^,],%1023[^,],%lf,%lf,%d", + name, desc, units, &expected_min, &expected_max, &consolidable); + + if (fields == 2) + { + /* Old-style ts_key with name and description */ + } + else if (fields == 6) + { + /* New-style ts_key with additional parameters */ + } + else + { + Log(LOG_LEVEL_ERR, "Wrong line format in ts_key: %s", line); + } + + if (strcmp(name, "spare") != 0) + { + Nova_FreeSlot(SLOTS[i - ob_spare]); + SLOTS[i - ob_spare] = Nova_MakeSlot(name, desc, units, expected_min, expected_max, consolidable); + } + } + } + fclose(f); +} + + +bool NovaHasSlot(int idx) +{ + Nova_LoadSlots(); + + return idx < ob_spare || SLOTS[idx - ob_spare]; +} + +const char *NovaGetSlotName(int idx) +{ + Nova_LoadSlots(); + + return idx < ob_spare ? OBSERVABLES[idx][0] : SLOTS[idx - ob_spare]->name; +} + +const char *NovaGetSlotDescription(int idx) +{ + Nova_LoadSlots(); + + return idx < ob_spare ? OBSERVABLES[idx][1] : SLOTS[idx - ob_spare]->description; +} + +const char *NovaGetSlotUnits(int idx) +{ + Nova_LoadSlots(); + + return idx < ob_spare ? UNITS[idx] : SLOTS[idx - ob_spare]->units; +} + +// TODO: real expected minimum/maximum/consolidable for core slots + +double NovaGetSlotExpectedMinimum(int idx) +{ + Nova_LoadSlots(); + + return idx < ob_spare ? 0.0f : SLOTS[idx - ob_spare]->expected_minimum; +} + +double NovaGetSlotExpectedMaximum(int idx) +{ + Nova_LoadSlots(); + + return idx < ob_spare ? 100.0f : SLOTS[idx - ob_spare]->expected_maximum; +} + +bool NovaIsSlotConsolidable(int idx) +{ + Nova_LoadSlots(); + + return idx < ob_spare ? true : SLOTS[idx - ob_spare]->consolidable; +} + + + + +/* + * This function returns beginning of last Monday relative to 'time'. If 'time' + * is Monday, beginning of the same day is returned. + */ +time_t WeekBegin(time_t time) +{ + struct tm tm; + + gmtime_r(&time, &tm); + + /* Move back in time to reach Monday. */ + + time -= ((tm.tm_wday == 0 ? 6 : tm.tm_wday - 1) * SECONDS_PER_DAY); + + /* Move to the beginning of day */ + + time -= tm.tm_hour * SECONDS_PER_HOUR; + time -= tm.tm_min * SECONDS_PER_MINUTE; + time -= tm.tm_sec; + + return time; +} + +time_t SubtractWeeks(time_t time, int weeks) +{ + return time - weeks * SECONDS_PER_WEEK; +} + +time_t NextShift(time_t time) +{ + return time + SECONDS_PER_SHIFT; +} + + +void MakeTimekey(time_t time, char *result) +{ + /* Generate timekey for database */ + struct tm tm; + + gmtime_r(&time, &tm); + + snprintf(result, 64, "%d_%.3s_Lcycle_%d_%s", + tm.tm_mday, MONTH_TEXT[tm.tm_mon], (tm.tm_year + 1900) % 3, SHIFT_TEXT[tm.tm_hour / 6]); +} + +/* Returns true if entry was found, false otherwise */ +bool GetRecordForTime(CF_DB *db, time_t time, Averages *result) +{ + char timekey[CF_MAXVARSIZE]; + + MakeTimekey(time, timekey); + + return ReadDB(db, timekey, result, sizeof(Averages)); +} + diff --git a/libpromises/monitoring_read.h b/libpromises/monitoring_read.h new file mode 100644 index 0000000000..0acad5630d --- /dev/null +++ b/libpromises/monitoring_read.h @@ -0,0 +1,60 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#ifndef CFENGINE_MONITORING_READ_H +#define CFENGINE_MONITORING_READ_H + + +#include + + +extern MonitoringSlot *SLOTS[CF_OBSERVABLES - ob_spare]; + + +void Nova_FreeSlot(MonitoringSlot *slot); +MonitoringSlot *Nova_MakeSlot(const char *name, const char *description, + const char *units, + double expected_minimum, double expected_maximum, + bool consolidable); +void Nova_LoadSlots(void); +bool NovaHasSlot(int idx); +const char *NovaGetSlotName(int idx); +const char *NovaGetSlotDescription(int index); +const char *NovaGetSlotUnits(int index); +double NovaGetSlotExpectedMinimum(int index); +double NovaGetSlotExpectedMaximum(int index); +bool NovaIsSlotConsolidable(int index); +bool GetRecordForTime(CF_DB *db, time_t time, Averages *result); + + +/* - date-related functions - */ + +void MakeTimekey(time_t time, char *result); +time_t WeekBegin(time_t time); +time_t SubtractWeeks(time_t time, int weeks); +time_t NextShift(time_t time); + + +#endif diff --git a/libpromises/ornaments.c b/libpromises/ornaments.c new file mode 100644 index 0000000000..ca37e98d88 --- /dev/null +++ b/libpromises/ornaments.c @@ -0,0 +1,262 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include /* PromiseID */ + + +/** + * @brief Like StringAppend(), but replace characters '*' and '#' with their visible counterparts. + * @param buffer Buffer to be used. + * @param src Constant string to append + * @param n Total size of dst buffer. The string will be truncated if this is exceeded. + */ +static bool StringAppendPromise(char *dst, const char *src, size_t n) +{ + size_t i, j; + + if (n == 0) + { + return false; + } + + n--; + for (i = 0; i < n && dst[i]; i++) + { + } + for (j = 0; i < n && src[j]; i++, j++) + { + const char ch = src[j]; + switch (ch) + { + case CF_MANGLED_NS: + dst[i] = ':'; + break; + + case CF_MANGLED_SCOPE: + dst[i] = '.'; + break; + + default: + dst[i] = ch; + break; + } + } + dst[i] = '\0'; + return (i < n || !src[j]); +} + +/** + * @brief Like @c BufferAppendPromiseStr, but if @c str contains newlines + * and is longer than 2*N+3, then only copy an abbreviated version + * consisting of the first and last N characters, separated by @c `...` + * @param buffer Buffer to be used. + * @param str Constant string to append + * @param n Total size of dst buffer. The string will be truncated if this is exceeded. + * @param max_fragment Max. length of initial/final segment of @c str to keep + * @note 2*max_fragment+3 is the maximum length of the appended string (excl. terminating NULL) + * + */ +static bool StringAppendAbbreviatedPromise(char *dst, const char *src, size_t n, + const size_t max_fragment) +{ + /* check if `src` contains a new line (may happen for "insert_lines") */ + const char *const nl = strchr(src, '\n'); + if (nl == NULL) + { + return StringAppendPromise(dst, src, n); + } + else + { + /* `src` contains a newline: abbreviate it by taking the first and last few characters */ + static const char sep[] = "..."; + char abbr[sizeof(sep) + 2 * max_fragment]; + const size_t head = (nl > src + max_fragment) ? max_fragment : (size_t) (nl - src); + const char * last_line = strrchr(src, '\n') + 1; + assert(last_line); /* not max_fragmentULL, we know we have at least one '\n' */ + const size_t tail = strlen(last_line); + if (tail > max_fragment) + { + last_line += tail - max_fragment; + } + memcpy(abbr, src, head); + strcpy(abbr + head, sep); + strcat(abbr, last_line); + return StringAppendPromise(dst, abbr, n); + } +} + + +/*********************************************************************/ + + +void SpecialTypeBanner(TypeSequence type, int pass) +{ + if (type == TYPE_SEQUENCE_CONTEXTS) + { + Log(LOG_LEVEL_VERBOSE, "C: ........................................................."); + Log(LOG_LEVEL_VERBOSE, "C: BEGIN classes / conditions (pass %d)", pass); + } + if (type == TYPE_SEQUENCE_VARS) + { + Log(LOG_LEVEL_VERBOSE, "V: ........................................................."); + Log(LOG_LEVEL_VERBOSE, "V: BEGIN variables (pass %d)", pass); + } +} + +void PromiseBanner(EvalContext *ctx, const Promise *pp) +{ + char handle[CF_MAXVARSIZE]; + const char *sp; + + if ((sp = PromiseGetHandle(pp)) || (sp = PromiseID(pp))) + { + strlcpy(handle, sp, CF_MAXVARSIZE); + } + else + { + strcpy(handle, ""); + } + + Log(LOG_LEVEL_VERBOSE, "P: ........................................................."); + + if (strlen(handle) > 0) + { + Log(LOG_LEVEL_VERBOSE, "P: BEGIN promise '%s' of type \"%s\" (pass %d)", handle, PromiseGetPromiseType(pp), EvalContextGetPass(ctx)); + } + else + { + Log(LOG_LEVEL_VERBOSE, "P: BEGIN un-named promise of type \"%s\" (pass %d)", PromiseGetPromiseType(pp), EvalContextGetPass(ctx)); + } + + const size_t n = 2*CF_MAXFRAGMENT + 3; + char pretty_promise_name[n+1]; + pretty_promise_name[0] = '\0'; + StringAppendAbbreviatedPromise(pretty_promise_name, pp->promiser, n, CF_MAXFRAGMENT); + Log(LOG_LEVEL_VERBOSE, "P: Promiser/affected object: '%s'", pretty_promise_name); + + Rlist *params = NULL; + char *varclass; + FnCall *fp; + + if ((params = EvalContextGetBundleArgs(ctx))) + { + Writer *w = StringWriter(); + RlistWrite(w, params); + Log(LOG_LEVEL_VERBOSE, "P: From parameterized bundle: %s(%s)", PromiseGetBundle(pp)->name, StringWriterData(w)); + WriterClose(w); + } + else + { + Log(LOG_LEVEL_VERBOSE, "P: Part of bundle: %s", PromiseGetBundle(pp)->name); + } + + Log(LOG_LEVEL_VERBOSE, "P: Base context class: %s", pp->classes); + + if ((varclass = PromiseGetConstraintAsRval(pp, "if", RVAL_TYPE_SCALAR)) || (varclass = PromiseGetConstraintAsRval(pp, "ifvarclass", RVAL_TYPE_SCALAR))) + { + Log(LOG_LEVEL_VERBOSE, "P: \"if\" class condition: %s", varclass); + } + else if ((fp = (FnCall *)PromiseGetConstraintAsRval(pp, "if", RVAL_TYPE_FNCALL)) || (fp = (FnCall *)PromiseGetConstraintAsRval(pp, "ifvarclass", RVAL_TYPE_FNCALL))) + { + Writer *w = StringWriter(); + FnCallWrite(w, fp); + Log(LOG_LEVEL_VERBOSE, "P: \"if\" class condition: %s", StringWriterData(w)); + } + else if ((varclass = PromiseGetConstraintAsRval(pp, "unless", RVAL_TYPE_SCALAR))) + { + Log(LOG_LEVEL_VERBOSE, "P: \"unless\" class condition: %s", varclass); + } + else if ((fp = (FnCall *)PromiseGetConstraintAsRval(pp, "unless", RVAL_TYPE_FNCALL))) + { + Writer *w = StringWriter(); + FnCallWrite(w, fp); + Log(LOG_LEVEL_VERBOSE, "P: \"unless\" class condition: %s", StringWriterData(w)); + } + + Log(LOG_LEVEL_VERBOSE, "P: Stack path: %s", EvalContextStackToString(ctx)); + + if (pp->comment) + { + Log(LOG_LEVEL_VERBOSE, "P:\n"); + Log(LOG_LEVEL_VERBOSE, "P: Comment: %s", pp->comment); + } +} + +void Legend() +{ + Log(LOG_LEVEL_VERBOSE, "----------------------------------------------------------------"); + Log(LOG_LEVEL_VERBOSE, "PREFIX LEGEND:"); + Log(LOG_LEVEL_VERBOSE, " V: variable or parameter new definition in scope"); + Log(LOG_LEVEL_VERBOSE, " C: class/context new definition "); + Log(LOG_LEVEL_VERBOSE, " B: bundle start/end execution marker"); + Log(LOG_LEVEL_VERBOSE, " P: promise execution output "); + Log(LOG_LEVEL_VERBOSE, " A: accounting output "); + Log(LOG_LEVEL_VERBOSE, " T: time measurement for stated object (promise or bundle)"); + Log(LOG_LEVEL_VERBOSE, "----------------------------------------------------------------"); +} + +void Banner(const char *s) +{ + Log(LOG_LEVEL_VERBOSE, "----------------------------------------------------------------"); + Log(LOG_LEVEL_VERBOSE, " %s ", s); + Log(LOG_LEVEL_VERBOSE, "----------------------------------------------------------------"); + +} + +void BundleBanner(const Bundle *bp, const Rlist *params) +{ + Log(LOG_LEVEL_VERBOSE, "B: *****************************************************************"); + + if (params) + { + Writer *w = StringWriter(); + RlistWrite(w, params); + Log(LOG_LEVEL_VERBOSE, "B: BEGIN bundle %s(%s)", bp->name, StringWriterData(w)); + WriterClose(w); + } + else + { + Log(LOG_LEVEL_VERBOSE, "B: BEGIN bundle %s", bp->name); + } + + Log(LOG_LEVEL_VERBOSE, "B: *****************************************************************"); +} + +void EndBundleBanner(const Bundle *bp) +{ + if (bp == NULL) + { + return; + } + + Log(LOG_LEVEL_VERBOSE, "B: *****************************************************************"); + Log(LOG_LEVEL_VERBOSE, "B: END bundle %s", bp->name); + Log(LOG_LEVEL_VERBOSE, "B: *****************************************************************"); +} diff --git a/libpromises/ornaments.h b/libpromises/ornaments.h new file mode 100644 index 0000000000..2688127ce9 --- /dev/null +++ b/libpromises/ornaments.h @@ -0,0 +1,42 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_ORNAMENTS_H +#define CFENGINE_ORNAMENTS_H + +/* + * Various ornaments in output + */ + +#include +#include +#include + +void SpecialTypeBanner(TypeSequence type, int pass); +void PromiseBanner(EvalContext *ctx, const Promise *pp); +void Banner(const char *s); +void Legend(); +void BundleBanner(const Bundle *bp, const Rlist *params); +void EndBundleBanner(const Bundle *bp); +#endif diff --git a/libpromises/parser.c b/libpromises/parser.c new file mode 100644 index 0000000000..f1b661e0fe --- /dev/null +++ b/libpromises/parser.c @@ -0,0 +1,195 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#include +#include +#include + +#include + +int yyparse(void); + +ParserState PARSER_STATE = { 0 }; /* GLOBAL_X */ + +extern FILE *yyin; + +static void ParserStateReset(ParserState *p, bool discard) +{ + assert(p != NULL); + p->agent_type = AGENT_TYPE_COMMON; + p->warnings = PARSER_WARNING_ALL; + p->policy = NULL; + + int i = CF_MAX_NESTING; + while (i-- > 0) /* Clear stacks from top down */ + { + if (discard) + { + free(p->currentfnid[i]); + RlistDestroy(p->giveargs[i]); + FnCallDestroy(p->currentfncall[i]); + } + else + { + assert(!p->currentfnid[i]); + assert(!p->giveargs[i]); + assert(!p->currentfncall[i]); + } + p->currentfnid[i] = NULL; + p->giveargs[i] = NULL; + p->currentfncall[i] = NULL; + } + + free(p->current_line); + p->current_line = NULL; + p->line_no = 1; + p->line_pos = 1; + p->error_count = 0; + p->warning_count = 0; + p->list_nesting = 0; + p->arg_nesting = 0; + + free(p->current_namespace); + p->current_namespace = xstrdup("default"); + + p->currentid[0] = '\0'; + if (p->currentstring) + { + free(p->currentstring); + } + p->currentstring = NULL; + p->currenttype[0] = '\0'; + if (p->currentclasses) + { + free(p->currentclasses); + } + p->currentclasses = NULL; + p->currentRlist = NULL; + p->currentpromise = NULL; + p->currentbody = NULL; + if (p->promiser) + { + free(p->promiser); + } + p->promiser = NULL; + p->blockid[0] = '\0'; + p->blocktype[0] = '\0'; + RvalDestroy(p->rval); + p->rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE); +} + +static void ParserStateClean(ParserState *p) +{ + free(p->current_namespace); + p->current_namespace = NULL; +} + +Policy *ParserParseFile(AgentType agent_type, const char *path, unsigned int warnings, unsigned int warnings_error) +{ + ParserStateReset(&PARSER_STATE, false); + + PARSER_STATE.agent_type = agent_type; + PARSER_STATE.policy = PolicyNew(); + + PARSER_STATE.warnings = warnings; + PARSER_STATE.warnings_error = warnings_error; + + strlcpy(PARSER_STATE.filename, path, CF_MAXVARSIZE); + + yyin = safe_fopen(path, "rt"); + if (yyin == NULL) + { + Log(LOG_LEVEL_ERR, "While opening file '%s' for parsing. (fopen: %s)", path, GetErrorStr()); + DoCleanupAndExit(EXIT_FAILURE); + } + + while (!feof(yyin)) + { + yyparse(); + + if (ferror(yyin)) + { + perror("cfengine"); + DoCleanupAndExit(EXIT_FAILURE); + } + } + + fclose(yyin); + + if (PARSER_STATE.error_count > 0) + { + PolicyDestroy(PARSER_STATE.policy); + ParserStateReset(&PARSER_STATE, true); + ParserStateClean(&PARSER_STATE); + return NULL; + } + + Policy *policy = PARSER_STATE.policy; + ParserStateReset(&PARSER_STATE, false); + ParserStateClean(&PARSER_STATE); + return policy; +} + +int ParserWarningFromString(const char *warning_str) +{ + if (strcmp("deprecated", warning_str) == 0) + { + return PARSER_WARNING_DEPRECATED; + } + else if (strcmp("removed", warning_str) == 0) + { + return PARSER_WARNING_REMOVED; + } + else if (strcmp("sanity-check", warning_str) == 0) + { + return PARSER_WARNING_SANITY_CHECK; + } + else if (strcmp("all", warning_str) == 0) + { + return PARSER_WARNING_ALL; + } + else + { + return -1; + } +} + +const char *ParserWarningToString(unsigned int warning) +{ + switch (warning) + { + case PARSER_WARNING_DEPRECATED: + return "deprecated"; + case PARSER_WARNING_REMOVED: + return "removed"; + case PARSER_WARNING_SANITY_CHECK: + return "sanity-check"; + + default: + ProgrammingError("Invalid parser warning: %u", warning); + } +} diff --git a/libpromises/parser.h b/libpromises/parser.h new file mode 100644 index 0000000000..0bd023a7d7 --- /dev/null +++ b/libpromises/parser.h @@ -0,0 +1,52 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PARSER_H +#define CFENGINE_PARSER_H + +#include + +#define PARSER_WARNING_DEPRECATED (1 << 0) +#define PARSER_WARNING_REMOVED (1 << 1) +#define PARSER_WARNING_SANITY_CHECK (1 << 2) + +#define PARSER_WARNING_ALL 0xfffffff + +/** + * @return warning code, or -1 if not a valid warning + */ +int ParserWarningFromString(const char *warning_str); +const char *ParserWarningToString(unsigned int warning); + +/** + * @brief Parse a CFEngine file to create a Policy DOM + * @param agent_type Which agent is parsing the file. The parser will ignore elements not pertitent to its type + * @param path Path of file to parse + * @param warnings Bitfield of which warnings should be recorded + * @param warnings_error Bitfield of which warnings should be counted as errors + * @return + */ +Policy *ParserParseFile(AgentType agent_type, const char *path, unsigned int warnings, unsigned int warnings_error); + +#endif diff --git a/libpromises/parser_helpers.h b/libpromises/parser_helpers.h new file mode 100644 index 0000000000..bc0f6269b0 --- /dev/null +++ b/libpromises/parser_helpers.h @@ -0,0 +1,72 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +// This header contains types and (inline) functions used +// when parsing policy. It is used in a few files related +// to parsing, for example cf3lex.l (flex generated lexer), +// cf3parse.y (yacc generated parser) and syntax.c +// It doesn't really contain the grammar, or syntax +// description just some helper functions, to enable those. +// The intention is to move a lot of logic (C code) out of +// the lex and yacc files to make them easier to understand +// and maintain. + +// It could maybe be combined with parser_state.h, but +// there is a distinction, this file does not include anything +// about the state of the parser + +#ifndef CF_PARSER_HELPERS_H +#define CF_PARSER_HELPERS_H + +#include // debug_abort_if_reached() + +// Blocks are the top level elements of a policy file +// (excluding macros). + +// Currently there are 3 types of blocks; bundle, body, promise +typedef enum +{ + PARSER_BLOCK_BUNDLE = 1, + PARSER_BLOCK_BODY = 2, + PARSER_BLOCK_PROMISE = 3, +} ParserBlock; + +static inline const char *ParserBlockString(ParserBlock b) +{ + switch (b) + { + case PARSER_BLOCK_BUNDLE: + return "bundle"; + case PARSER_BLOCK_BODY: + return "body"; + case PARSER_BLOCK_PROMISE: + return "promise"; + default: + break; + } + debug_abort_if_reached(); + return "ERROR"; +} + +#endif // CF_PARSER_HELPERS_H diff --git a/libpromises/parser_state.h b/libpromises/parser_state.h new file mode 100644 index 0000000000..22dd090903 --- /dev/null +++ b/libpromises/parser_state.h @@ -0,0 +1,99 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PARSER_STATE_H +#define CFENGINE_PARSER_STATE_H + +#include +#include +#include +#include // ParserBlock + +#define CF_MAX_NESTING 10 + +typedef struct +{ + AgentType agent_type; + + ParserBlock block; // enum for bundle/body + char blocktype[CF_MAXVARSIZE]; + char blockid[CF_MAXVARSIZE]; + + char filename[CF_MAXVARSIZE]; + char *current_line; + int line_pos; + int line_no; + int error_count; + + int warning_count; + int warnings; // bitfield of warnings not considered to be an error + int warnings_error; // bitfield of warnings considered to be an error + + int if_depth; + + int arg_nesting; + int list_nesting; + + char lval[CF_MAXVARSIZE]; + Rval rval; + bool references_body; + + char *promiser; + void *promisee; + + char *current_namespace; + char currentid[CF_MAXVARSIZE]; + char currenttype[CF_MAXVARSIZE]; + char *currentstring; + char *currentclasses; + char *currentvarclasses; + + Policy *policy; + + Bundle *currentbundle; + Body *currentbody; + Promise *currentpromise; + BundleSection *currentstype; + Rlist *useargs; + + Rlist *currentRlist; + + char *currentfnid[CF_MAX_NESTING]; + Rlist *giveargs[CF_MAX_NESTING]; + FnCall *currentfncall[CF_MAX_NESTING]; + + struct OffsetState + { + size_t current; + size_t last_id; + size_t last_string; + size_t last_block_id; + size_t last_promise_guard_id; + size_t last_class_id; + } offsets; +} ParserState; + +extern ParserState PARSER_STATE; + +#endif diff --git a/libpromises/patches.c b/libpromises/patches.c new file mode 100644 index 0000000000..d62177a7d4 --- /dev/null +++ b/libpromises/patches.c @@ -0,0 +1,222 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* + Contains any fixes which need to be made because of lack of OS support on a + given platform These are conditionally compiled, pending extensions or + developments in the OS concerned. + + FIXME: move to the libcompat/ directory or to the appropriate source file. +*/ + +#include + +#include + +#ifdef __ANDROID__ +# include /* File_Copy() */ +#endif + +static char *cf_format_strtimestamp(struct tm *tm, char *buf); + +/*********************************************************/ + +#ifndef HAVE_SETNETGRENT +#if SETNETGRENT_RETURNS_INT +int +#else +void +#endif +setnetgrent(const char *netgroup) +{ +#if SETNETGRENT_RETURNS_INT + return 0; +#endif +} + +#endif + +/**********************************************************/ + +#ifndef HAVE_GETNETGRENT + +int getnetgrent(char **machinep, char **userp, char **domainp) +{ + *machinep = NULL; + *userp = NULL; + *domainp = NULL; + return 0; +} + +#endif + +/***********************************************************/ + +#ifndef HAVE_ENDNETGRENT +#if ENDNETGRENT_RETURNS_INT +int +#else +void +#endif +endnetgrent(void) +{ +#if ENDNETGRENT_RETURNS_INT + return 1; +#endif +} + +#endif + +/***********************************************************/ + +#ifndef HAVE_SETEGID + +int setegid(gid_t gid) +{ +# ifdef HAVE_SETREGID + return setregid(-1, gid); +# else + Log(LOG_LEVEL_VERBOSE, "(This system does not have setregid (patches.c)"); + return -1; +# endif +} + +#endif + +/*******************************************************************/ + +bool IsPrivileged() +{ +#ifdef _WIN32 + return true; +#else + return (getuid() == 0); +#endif +} + +/* + * This function converts passed time_t value to string timestamp used + * throughout the system. By sheer coincidence this timestamp has the same + * format as ctime(3) output on most systems (but NT differs in definition of + * ctime format, so those are not identical there). + * + * Buffer passed should be at least 26 bytes long (including the trailing zero). + * + * Please use this function instead of (non-portable and deprecated) ctime_r or + * (non-threadsafe) ctime. + */ + +/*******************************************************************/ + +char *cf_strtimestamp_local(const time_t time, char *buf) +{ + struct tm tm; + + if (localtime_r(&time, &tm) == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Unable to parse passed timestamp. (localtime_r: %s)", GetErrorStr()); + return NULL; + } + + return cf_format_strtimestamp(&tm, buf); +} + +/*******************************************************************/ + +char *cf_strtimestamp_utc(const time_t time, char *buf) +{ + struct tm tm; + + if (gmtime_r(&time, &tm) == NULL) + { + Log(LOG_LEVEL_VERBOSE, "Unable to parse passed timestamp. (gmtime_r: %s)", GetErrorStr()); + return NULL; + } + + return cf_format_strtimestamp(&tm, buf); +} + +/*******************************************************************/ + +static char *cf_format_strtimestamp(struct tm *tm, char *buf) +{ + /* Security checks */ + if ((tm->tm_year < -2899) || (tm->tm_year > 8099)) + { + Log(LOG_LEVEL_ERR, "Unable to format timestamp: passed year is out of range: %d", tm->tm_year + 1900); + return NULL; + } + +/* There is no easy way to replicate ctime output by using strftime */ + + if (snprintf(buf, 26, "%3.3s %3.3s %2d %02d:%02d:%02d %04d", + DAY_TEXT[tm->tm_wday ? (tm->tm_wday - 1) : 6], MONTH_TEXT[tm->tm_mon], + tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_year + 1900) >= 26) + { + Log(LOG_LEVEL_ERR, "Unable to format timestamp: passed values are out of range"); + return NULL; + } + + return buf; +} + +/*******************************************************************/ + +bool LinkOrCopy(const char *from, const char *to, int sym) +/** + * Creates symlink to file on platforms supporting it, copies on + * others. + **/ +{ + +#ifdef __MINGW32__ // only copy on Windows for now + + if (!CopyFile(from, to, TRUE)) + { + return false; + } + +#elif __ANDROID__ /* link() not supported on ANDROID platform */ + return File_Copy(from, to); +#else /* !__MINGW32__ */ + + if (sym) + { + if (symlink(from, to) == -1) + { + return false; + } + } + else // hardlink + { + if (link(from, to) == -1) + { + return false; + } + } + +#endif /* !__MINGW32__ */ + + return true; +} diff --git a/libpromises/pipes.c b/libpromises/pipes.c new file mode 100644 index 0000000000..8e0718138d --- /dev/null +++ b/libpromises/pipes.c @@ -0,0 +1,278 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include +#include + +bool PipeTypeIsOk(const char *type) +{ + if (type[0] != 'r' && type[0] != 'w') + { + return false; + } + else if (type[1] != 't' && type[1] != '+') + { + if (type[1] == '\0') + { + return true; + } + else + { + return false; + } + } + else if (type[2] == '\0' || type[2] == 't') + { + return true; + } + else + { + return false; + } +} + +/*******************************************************************/ +/* Pipe read/write interface, originally in package modules */ +/*******************************************************************/ + +Rlist *PipeReadData(const IOData *io, int pipe_timeout_secs, int pipe_termination_check_secs) +{ + char buff[CF_BUFSIZE] = {0}; + + Buffer *data = BufferNew(); + if (!data) + { + Log(LOG_LEVEL_VERBOSE, + "Unable to allocate buffer for handling pipe responses."); + return NULL; + } + + int timeout_seconds_left = pipe_timeout_secs; + + while (!IsPendingTermination() && timeout_seconds_left > 0) + { + int fd = PipeIsReadWriteReady(io, pipe_termination_check_secs); + + if (fd < 0) + { + Log(LOG_LEVEL_DEBUG, + "Error reading data from application pipe %d", fd); + break; + } + else if (fd == io->read_fd) + { + ssize_t res = read(fd, buff, sizeof(buff) - 1); + if (res == -1) + { + if (errno == EINTR) + { + continue; + } + else + { + Log(LOG_LEVEL_ERR, + "Unable to read output from application pipe: %s", + GetErrorStr()); + BufferDestroy(data); + return NULL; + } + } + else if (res == 0) /* reached EOF */ + { + break; + } + Log(LOG_LEVEL_DEBUG, "Data read from application pipe: %zd [%s]", + res, buff); + + BufferAppendString(data, buff); + memset(buff, 0, sizeof(buff)); + } + else if (fd == 0) /* timeout */ + { + timeout_seconds_left -= pipe_termination_check_secs; + continue; + } + } + + char *read_string = BufferClose(data); + +#ifdef __MINGW32__ + bool detect_crlf = true; +#else + bool detect_crlf = false; +#endif + + Rlist *response_lines = RlistFromStringSplitLines(read_string, detect_crlf); + free(read_string); + + return response_lines; +} + +ssize_t PipeWrite(IOData *io, const char *data) +{ + /* If there is nothing to write close writing end of pipe. */ + if (data == NULL || strlen(data) == 0) + { + if (io->write_fd >= 0) + { + cf_pclose_full_duplex_side(io->write_fd); + io->write_fd = -1; + } + return 0; + } + + ssize_t wrt = write(io->write_fd, data, strlen(data)); + + /* Make sure to close write_fd after sending all data. */ + if (io->write_fd >= 0) + { + cf_pclose_full_duplex_side(io->write_fd); + io->write_fd = -1; + } + return wrt; +} + +int PipeWriteData(const char *base_cmd, const char *args, const char *data) +{ + assert(base_cmd); + assert(args); + + char *command = StringFormat("%s %s", base_cmd, args); + IOData io = cf_popen_full_duplex(command, false, true); + free(command); + + if (io.write_fd == -1 || io.read_fd == -1) + { + Log(LOG_LEVEL_VERBOSE, "Error occurred while opening pipes for " + "communication with application '%s'.", base_cmd); + return -1; + } + + Log(LOG_LEVEL_DEBUG, "Opened fds %d and %d for command '%s'.", + io.read_fd, io.write_fd, args); + + int res = 0; + int written = PipeWrite(&io, data); + if (written < 0) + { + Log(LOG_LEVEL_ERR, "Failed to write to pipe (fd %d): %s", + io.write_fd, GetErrorStr()); + res = -1; + } + else if ((size_t) written != strlen(data)) + { + Log(LOG_LEVEL_VERBOSE, + "Was not able to send whole data to application '%s'.", + base_cmd); + res = -1; + } + + /* If script returns non 0 status */ + int close = cf_pclose_full_duplex(&io); + if (close != EXIT_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Application '%s' returned with non zero return code: %d", + base_cmd, close); + res = -1; + } + return res; +} + +/* In some cases the response is expected to be not filled out. Some requests + will have response filled only in case of errors. */ +int PipeReadWriteData(const char *base_cmd, const char *args, const char *request, + Rlist **response, int pipe_timeout_secs, int pipe_termination_check_secs) +{ + assert(base_cmd); + assert(args); + + char *command = StringFormat("%s %s", base_cmd, args); + IOData io = cf_popen_full_duplex(command, false, true); + + if (io.write_fd == -1 || io.read_fd == -1) + { + Log(LOG_LEVEL_INFO, "Some error occurred while communicating with %s", command); + free(command); + return -1; + } + + Log(LOG_LEVEL_DEBUG, "Opened fds %d and %d for command '%s'.", + io.read_fd, io.write_fd, command); + + int written = PipeWrite(&io, request); + if (written < 0) { + Log(LOG_LEVEL_ERR, "Failed to write to pipe (fd %d): %s", + io.write_fd, GetErrorStr()); + return -1; + } + else if ((size_t) written != strlen(request)) + { + Log(LOG_LEVEL_VERBOSE, "Couldn't send whole data to application '%s'.", + base_cmd); + free(command); + return -1; + } + + /* We can have some error message here. */ + Rlist *res = PipeReadData(&io, pipe_timeout_secs, pipe_termination_check_secs); + + /* If script returns non 0 status */ + int close = cf_pclose_full_duplex(&io); + if (close != EXIT_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Command '%s' returned with non zero return code: %d", + command, close); + free(command); + RlistDestroy(res); + return -1; + } + + free(command); + *response = res; + return 0; +} + +IOData cf_popen_full_duplex_streams( + const char *command, bool capture_stderr, bool require_full_path) +{ + IOData ret = cf_popen_full_duplex( + command, capture_stderr, require_full_path); + + // On windows, these streams are already set up correctly: + if (ret.read_stream == NULL) + { + ret.read_stream = fdopen(ret.read_fd, "r"); + } + if (ret.write_stream == NULL) + { + ret.write_stream = fdopen(ret.write_fd, "w"); + } + + return ret; +} diff --git a/libpromises/pipes.h b/libpromises/pipes.h new file mode 100644 index 0000000000..5961ed0140 --- /dev/null +++ b/libpromises/pipes.h @@ -0,0 +1,75 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PIPES_H +#define CFENGINE_PIPES_H + +#include + +typedef struct +{ + int write_fd; + int read_fd; + FILE *write_stream; + FILE *read_stream; +} IOData; + +typedef enum OutputSelect +{ + OUTPUT_SELECT_BOTH, + OUTPUT_SELECT_STDOUT, + OUTPUT_SELECT_STDERR, +} OutputSelect; + +IOData cf_popen_full_duplex(const char *command, bool capture_stderr, bool require_full_path); +IOData cf_popen_full_duplex_streams(const char *command, bool capture_stderr, bool require_full_path); +int cf_pclose_full_duplex(IOData *data); +int cf_pclose_full_duplex_side(int fd); + +FILE *cf_popen(const char *command, const char *type, bool capture_stderr); +FILE *cf_popen_select(const char *command, const char *type, OutputSelect output_select); +FILE *cf_popensetuid(const char *command, const Seq *arglist, const char *type, uid_t uid, gid_t gid, char *chdirv, char *chrootv, int background); +FILE *cf_popen_sh(const char *command, const char *type); +FILE *cf_popen_sh_select(const char *command, const char *type, OutputSelect output_select); +FILE *cf_popen_shsetuid(const char *command, const char *type, uid_t uid, gid_t gid, char *chdirv, char *chrootv, int background); +int cf_pclose(FILE *pp); +void cf_pclose_nowait(FILE *pp); +bool PipeToPid(pid_t *pid, FILE *pp); +bool PipeTypeIsOk(const char *type); + +int PipeIsReadWriteReady(const IOData *io, int timeout_sec); +Rlist *PipeReadData(const IOData *io, int pipe_timeout_secs, int pipe_termination_check_secs); +ssize_t PipeWrite(IOData *io, const char *data); +int PipeWriteData(const char *base_cmd, const char *args, const char *data); +int PipeReadWriteData(const char *base_command, const char *args, const char *request, + Rlist **response, int pipe_timeout_secs, int pipe_termination_check_secs); + +#ifdef __MINGW32__ +FILE *cf_popen_powershell(const char *command, const char *type); +FILE *cf_popen_powershell_select(const char *command, const char *type, OutputSelect output_select); +FILE *cf_popen_powershell_setuid(const char *command, const char *type, uid_t uid, gid_t gid, char *chdirv, char *chrootv, + int background); +#endif + +#endif diff --git a/libpromises/pipes_unix.c b/libpromises/pipes_unix.c new file mode 100644 index 0000000000..a9aece3778 --- /dev/null +++ b/libpromises/pipes_unix.c @@ -0,0 +1,1112 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool CfSetuid(uid_t uid, gid_t gid); + +static int cf_pwait(pid_t pid); + +static pid_t *CHILDREN = NULL; /* GLOBAL_X */ +static int MAX_FD = 2048; /* GLOBAL_X */ /* Max number of simultaneous pipes */ + + +static void ChildrenFDInit() +{ + ThreadLock(cft_count); + if (CHILDREN == NULL) /* first time */ + { + CHILDREN = xcalloc(MAX_FD, sizeof(pid_t)); + } + + ThreadUnlock(cft_count); +} + +/*****************************************************************************/ + +/* This leaks memory and is not thread-safe! To be used only when you are + * about to exec() or _exit(), and only async-signal-safe code is allowed. */ +static void ChildrenFDUnsafeClose() +{ + /* GenericCreatePipeAndFork() must have been called to init this. */ + assert(CHILDREN != NULL); + + for (int i = 0; i < MAX_FD; i++) + { + if (CHILDREN[i] > 0) + { + close(i); + } + } + CHILDREN = NULL; /* leaks on purpose */ +} + +/* This is the original safe version, but not signal-handler-safe. + It's currently unused. */ +#if 0 +static void ChildrenFDClose() +{ + ThreadLock(cft_count); + int i; + for (i = 0; i < MAX_FD; i++) + { + if (CHILDREN[i] > 0) + { + close(i); + } + } + free(CHILDREN); + CHILDREN = NULL; + ThreadUnlock(cft_count); +} +#endif + +static void ChildOutputSelectDupClose(int pd[2], OutputSelect output_select) +{ + close(pd[0]); // Don't need output from parent + + if (pd[1] != 1) // TODO: When is pd[1] == 1 ??? + { + if ((output_select == OUTPUT_SELECT_BOTH) + || (output_select == OUTPUT_SELECT_STDOUT)) + { + // close our(child) stdout(1) and open (pd[1]) as stdout + dup2(pd[1], 1); + // Subsequent stdout output will go to parent (pd[1]) + } + else + { + // Close / discard stdout + int nullfd = open(NULLFILE, O_WRONLY); + dup2(nullfd, 1); + close(nullfd); + } + + if ((output_select == OUTPUT_SELECT_BOTH) + || (output_select == OUTPUT_SELECT_STDERR)) + { + // close our(child) stderr(2) and open (pd[1]) as stderr + dup2(pd[1], 2); + // Subsequent stderr output will go to parent (pd[1]) + } + else + { + // Close / discard stderr + int nullfd = open(NULLFILE, O_WRONLY); + dup2(nullfd, 2); + close(nullfd); + } + + close(pd[1]); + } +} + +/*****************************************************************************/ + +static void ChildrenFDSet(int fd, pid_t pid) +{ + int new_max = 0; + + if (fd >= MAX_FD) + { + Log(LOG_LEVEL_WARNING, + "File descriptor %d of child %jd higher than MAX_FD, check for defunct children", + fd, (intmax_t) pid); + new_max = fd + 32; + } + + ThreadLock(cft_count); + + if (new_max) + { + CHILDREN = xrealloc(CHILDREN, new_max * sizeof(pid_t)); + MAX_FD = new_max; + } + + CHILDREN[fd] = pid; + ThreadUnlock(cft_count); +} + +/*****************************************************************************/ + +typedef struct +{ + const char *type; + int pipe_desc[2]; +} IOPipe; + +static pid_t GenericCreatePipeAndFork(IOPipe *pipes) +{ + for (int i = 0; i < 2; i++) + { + if (pipes[i].type && !PipeTypeIsOk(pipes[i].type)) + { + errno = EINVAL; + return -1; + } + } + + ChildrenFDInit(); + + /* Create pair of descriptors to this process. */ + if (pipes[0].type && pipe(pipes[0].pipe_desc) < 0) + { + return -1; + } + + /* Create second pair of descriptors (if exists) to this process. + * This will allow full I/O operations. */ + if (pipes[1].type && pipe(pipes[1].pipe_desc) < 0) + { + close(pipes[0].pipe_desc[0]); + close(pipes[0].pipe_desc[1]); + return -1; + } + + pid_t pid = -1; + + if ((pid = fork()) == (pid_t) -1) + { + /* One pipe will be always here. */ + close(pipes[0].pipe_desc[0]); + close(pipes[0].pipe_desc[1]); + + /* Second pipe is optional so we have to check existence. */ + if (pipes[1].type) + { + close(pipes[1].pipe_desc[0]); + close(pipes[1].pipe_desc[1]); + } + return -1; + } + + /* Ignore SIGCHLD, by setting the handler to SIG_DFL. NOTE: this is + * different than setting to SIG_IGN. In the latter case no zombies are + * generated ever and you can't wait() for the child to finish. */ + struct sigaction sa = { + .sa_handler = SIG_DFL, + }; + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, NULL); + + if (pid == 0) /* child */ + { + /* WARNING only call async-signal-safe functions in child. */ + + /* The fork()ed child is always single-threaded, but we are only + * allowed to call async-signal-safe functions (man 3p fork). */ + + // Redmine #2971: reset SIGPIPE signal handler in the child to have a + // sane behavior of piped commands within child + signal(SIGPIPE, SIG_DFL); + + /* The child should always accept all signals after it has exec'd, + * else the child might be unkillable! (happened in ENT-3147). */ + sigset_t sigmask; + sigemptyset(&sigmask); + sigprocmask(SIG_SETMASK, &sigmask, NULL); + } + + ALARM_PID = (pid != 0 ? pid : -1); + + return pid; +} + +/*****************************************************************************/ + +static pid_t CreatePipeAndFork(const char *type, int *pd) +{ + IOPipe pipes[2]; + pipes[0].type = type; + pipes[1].type = NULL; /* We don't want to create this one. */ + + pid_t pid = GenericCreatePipeAndFork(pipes); + + pd[0] = pipes[0].pipe_desc[0]; + pd[1] = pipes[0].pipe_desc[1]; + + return pid; +} + +/*****************************************************************************/ + +static pid_t CreatePipesAndFork(const char *type, int *pd, int *pdb) +{ + IOPipe pipes[2]; + /* Both pipes MUST have the same type. */ + pipes[0].type = type; + pipes[1].type = type; + + pid_t pid = GenericCreatePipeAndFork(pipes); + + pd[0] = pipes[0].pipe_desc[0]; + pd[1] = pipes[0].pipe_desc[1]; + pdb[0] = pipes[1].pipe_desc[0]; + pdb[1] = pipes[1].pipe_desc[1]; + return pid; +} + +/*****************************************************************************/ + +IOData cf_popen_full_duplex(const char *command, bool capture_stderr, bool require_full_path) +{ + /* For simplifying reading and writing directions */ + const int READ=0, WRITE=1; + int child_pipe[2]; /* From child to parent */ + int parent_pipe[2]; /* From parent to child */ + pid_t pid; + + char **argv = ArgSplitCommand(command, NULL); + + fflush(NULL); /* Empty file buffers */ + pid = CreatePipesAndFork("r+t", child_pipe, parent_pipe); + + if (pid == (pid_t) -1) + { + Log(LOG_LEVEL_ERR, "Couldn't fork child process: %s", GetErrorStr()); + ArgFree(argv); + return (IOData) {-1, -1, NULL, NULL}; + } + + else if (pid > 0) // parent + { + close(child_pipe[WRITE]); + close(parent_pipe[READ]); + + IOData io_desc; + io_desc.write_fd = parent_pipe[WRITE]; + io_desc.read_fd = child_pipe[READ]; + io_desc.read_stream = NULL; + io_desc.write_stream = NULL; + + ChildrenFDSet(parent_pipe[WRITE], pid); + ChildrenFDSet(child_pipe[READ], pid); + ArgFree(argv); + return io_desc; + } + else // child + { + close(child_pipe[READ]); + close(parent_pipe[WRITE]); + + /* Open stdin from parant process and stdout from child */ + if (dup2(parent_pipe[READ], 0) == -1 || dup2(child_pipe[WRITE],1) == -1) + { + Log(LOG_LEVEL_ERR, "Can not execute dup2: %s", GetErrorStr()); + _exit(EXIT_FAILURE); + } + + if (capture_stderr) + { + /* Merge stdout/stderr */ + if(dup2(child_pipe[WRITE], 2) == -1) + { + Log(LOG_LEVEL_ERR, "Can not execute dup2 for merging stderr: %s", + GetErrorStr()); + _exit(EXIT_FAILURE); + } + } + else + { + /* leave stderr open */ + } + + close(child_pipe[WRITE]); + close(parent_pipe[READ]); + + ChildrenFDUnsafeClose(); + + int res; + if (require_full_path) + { + res = execv(argv[0], argv); + } + else + { + res = execvp(argv[0], argv); + } + + if (res == -1) + { + /* NOTE: exec functions return only when error have occurred. */ + Log(LOG_LEVEL_ERR, "Couldn't run '%s'. (%s: %s)", + argv[0], + require_full_path ? "execv" : "execvp", + GetErrorStr()); + } + + /* We shouldn't reach this point */ + _exit(EXIT_FAILURE); + } +} + +FILE *cf_popen_select(const char *command, const char *type, OutputSelect output_select) +{ + int pd[2]; + pid_t pid; + FILE *pp = NULL; + + char **argv = ArgSplitCommand(command, NULL); + + pid = CreatePipeAndFork(type, pd); + if (pid == (pid_t) -1) + { + ArgFree(argv); + return NULL; + } + + if (pid == 0) /* child */ + { + /* WARNING only call async-signal-safe functions in child. */ + + switch (*type) + { + case 'r': + ChildOutputSelectDupClose(pd, output_select); + break; + + case 'w': + + close(pd[1]); + + if (pd[0] != 0) + { + dup2(pd[0], 0); + close(pd[0]); + } + } + + ChildrenFDUnsafeClose(); + + if (execv(argv[0], argv) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't run '%s'. (execv: %s)", argv[0], GetErrorStr()); + } + + _exit(EXIT_FAILURE); + } + else /* parent */ + { + switch (*type) + { + case 'r': + + close(pd[1]); + + if ((pp = fdopen(pd[0], type)) == NULL) + { + cf_pwait(pid); + ArgFree(argv); + return NULL; + } + break; + + case 'w': + + close(pd[0]); + + if ((pp = fdopen(pd[1], type)) == NULL) + { + cf_pwait(pid); + ArgFree(argv); + return NULL; + } + } + + ChildrenFDSet(fileno(pp), pid); + ArgFree(argv); + return pp; + } + + ProgrammingError("Unreachable code"); + return NULL; +} + +FILE *cf_popen(const char *command, const char *type, bool capture_stderr) +{ + return cf_popen_select( + command, + type, + capture_stderr ? OUTPUT_SELECT_BOTH : OUTPUT_SELECT_STDOUT); +} + +/*****************************************************************************/ + +/* + * WARNING: this is only allowed to be called from single-threaded code, + * because of the safe_chdir() call in the forked child. + */ +FILE *cf_popensetuid(const char *command, const Seq *arglist, + const char *type, uid_t uid, gid_t gid, char *chdirv, + char *chrootv, ARG_UNUSED int background) +{ + int pd[2]; + pid_t pid; + FILE *pp = NULL; + + char **argv = ArgSplitCommand(command, arglist); + + pid = CreatePipeAndFork(type, pd); + if (pid == (pid_t) -1) + { + ArgFree(argv); + return NULL; + } + + if (pid == 0) /* child */ + { + /* WARNING only call async-signal-safe functions in child. */ + + switch (*type) + { + case 'r': + + close(pd[0]); /* Don't need output from parent */ + + if (pd[1] != 1) + { + dup2(pd[1], 1); /* Attach pp=pd[1] to our stdout */ + dup2(pd[1], 2); /* Merge stdout/stderr */ + close(pd[1]); + } + + break; + + case 'w': + + close(pd[1]); + + if (pd[0] != 0) + { + dup2(pd[0], 0); + close(pd[0]); + } + } + + ChildrenFDUnsafeClose(); + + if (chrootv && (strlen(chrootv) != 0)) + { + if (chroot(chrootv) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't chroot to '%s'. (chroot: %s)", chrootv, GetErrorStr()); + _exit(EXIT_FAILURE); + } + } + + if (chdirv && (strlen(chdirv) != 0)) + { + if (safe_chdir(chdirv) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't chdir to '%s'. (chdir: %s)", chdirv, GetErrorStr()); + _exit(EXIT_FAILURE); + } + } + + if (!CfSetuid(uid, gid)) + { + _exit(EXIT_FAILURE); + } + + if (execv(argv[0], argv) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't run '%s'. (execv: %s)", argv[0], GetErrorStr()); + } + + _exit(EXIT_FAILURE); + } + else /* parent */ + { + switch (*type) + { + case 'r': + + close(pd[1]); + + if ((pp = fdopen(pd[0], type)) == NULL) + { + cf_pwait(pid); + ArgFree(argv); + return NULL; + } + break; + + case 'w': + + close(pd[0]); + + if ((pp = fdopen(pd[1], type)) == NULL) + { + cf_pwait(pid); + ArgFree(argv); + return NULL; + } + } + + ChildrenFDSet(fileno(pp), pid); + ArgFree(argv); + return pp; + } + + ProgrammingError("Unreachable code"); + return NULL; +} + +/*****************************************************************************/ +/* Shell versions of commands - not recommended for security reasons */ +/*****************************************************************************/ + +FILE *cf_popen_sh_select(const char *command, const char *type, OutputSelect output_select) +{ + int pd[2]; + pid_t pid; + FILE *pp = NULL; + + pid = CreatePipeAndFork(type, pd); + if (pid == (pid_t) -1) + { + return NULL; + } + + if (pid == 0) /* child */ + { + /* WARNING only call async-signal-safe functions in child. */ + + switch (*type) + { + case 'r': + ChildOutputSelectDupClose(pd, output_select); + + break; + + case 'w': + + close(pd[1]); + + if (pd[0] != 0) + { + dup2(pd[0], 0); + close(pd[0]); + } + } + + ChildrenFDUnsafeClose(); + + execl(SHELL_PATH, "sh", "-c", command, NULL); + + Log(LOG_LEVEL_ERR, "Couldn't run: '%s' (execl: %s)", command, GetErrorStr()); + _exit(EXIT_FAILURE); + } + else /* parent */ + { + switch (*type) + { + case 'r': + + close(pd[1]); + + if ((pp = fdopen(pd[0], type)) == NULL) + { + cf_pwait(pid); + return NULL; + } + break; + + case 'w': + + close(pd[0]); + + if ((pp = fdopen(pd[1], type)) == NULL) + { + cf_pwait(pid); + return NULL; + } + } + + ChildrenFDSet(fileno(pp), pid); + return pp; + } + + ProgrammingError("Unreachable code"); + return NULL; +} + +FILE *cf_popen_sh(const char *command, const char *type) +{ + return cf_popen_sh_select(command, type, OUTPUT_SELECT_BOTH); +} + +/******************************************************************************/ + +/* + * WARNING: this is only allowed to be called from single-threaded code, + * because of the safe_chdir() call in the forked child. + */ +FILE *cf_popen_shsetuid(const char *command, const char *type, + uid_t uid, gid_t gid, char *chdirv, char *chrootv, + ARG_UNUSED int background) +{ + int pd[2]; + pid_t pid; + FILE *pp = NULL; + + pid = CreatePipeAndFork(type, pd); + if (pid == (pid_t) -1) + { + return NULL; + } + + if (pid == 0) /* child */ + { + /* WARNING only call async-signal-safe functions in child. */ + + switch (*type) + { + case 'r': + + close(pd[0]); /* Don't need output from parent */ + + if (pd[1] != 1) + { + dup2(pd[1], 1); /* Attach pp=pd[1] to our stdout */ + dup2(pd[1], 2); /* Merge stdout/stderr */ + close(pd[1]); + } + + break; + + case 'w': + + close(pd[1]); + + if (pd[0] != 0) + { + dup2(pd[0], 0); + close(pd[0]); + } + } + + ChildrenFDUnsafeClose(); + + if (chrootv && (strlen(chrootv) != 0)) + { + if (chroot(chrootv) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't chroot to '%s'. (chroot: %s)", chrootv, GetErrorStr()); + _exit(EXIT_FAILURE); + } + } + + if (chdirv && (strlen(chdirv) != 0)) + { + if (safe_chdir(chdirv) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't chdir to '%s'. (chdir: %s)", chdirv, GetErrorStr()); + _exit(EXIT_FAILURE); + } + } + + if (!CfSetuid(uid, gid)) + { + _exit(EXIT_FAILURE); + } + + execl(SHELL_PATH, "sh", "-c", command, NULL); + + Log(LOG_LEVEL_ERR, "Couldn't run: '%s' (execl: %s)", command, GetErrorStr()); + _exit(EXIT_FAILURE); + } + else /* parent */ + { + switch (*type) + { + case 'r': + + close(pd[1]); + + if ((pp = fdopen(pd[0], type)) == NULL) + { + cf_pwait(pid); + return NULL; + } + break; + + case 'w': + + close(pd[0]); + + if ((pp = fdopen(pd[1], type)) == NULL) + { + cf_pwait(pid); + return NULL; + } + } + + ChildrenFDSet(fileno(pp), pid); + return pp; + } + + ProgrammingError("Unreachable code"); + return NULL; +} + +static int cf_pwait(pid_t pid) +{ + Log(LOG_LEVEL_DEBUG, + "cf_pwait - waiting for process %jd", (intmax_t) pid); + + int status; + while (waitpid(pid, &status, 0) < 0) + { + if (errno != EINTR) + { + Log(LOG_LEVEL_ERR, + "Waiting for child PID %jd failed (waitpid: %s)", + (intmax_t) pid, GetErrorStr()); + return -1; + } + } + + if (!WIFEXITED(status)) + { + Log(LOG_LEVEL_VERBOSE, + "Child PID %jd exited abnormally (%s)", (intmax_t) pid, + WIFSIGNALED(status) ? "signalled" : ( + WIFSTOPPED(status) ? "stopped" : ( + WIFCONTINUED(status) ? "continued" : "unknown" ))); + return -1; + } + + int retcode = WEXITSTATUS(status); + + Log(LOG_LEVEL_DEBUG, "cf_pwait - process %jd exited with code: %d", + (intmax_t) pid, retcode); + return retcode; +} + +/*******************************************************************/ + +/** + * Closes the pipe without waiting the child. + * + * @param pp pipe to the child process + */ +void cf_pclose_nowait(FILE *pp) +{ + if (fclose(pp) == EOF) + { + Log(LOG_LEVEL_ERR, + "Could not close the pipe to the executed subcommand (fclose: %s)", + GetErrorStr()); + } +} + +/** + * Closes the pipe and wait()s for PID of the child, + * in order to reap the zombies. + */ +int cf_pclose(FILE *pp) +{ + int fd = fileno(pp); + pid_t pid; + + ThreadLock(cft_count); + + if (CHILDREN == NULL) /* popen hasn't been called */ + { + ThreadUnlock(cft_count); + fclose(pp); + return -1; + } + + ALARM_PID = -1; + + if (fd >= MAX_FD) + { + ThreadUnlock(cft_count); + Log(LOG_LEVEL_ERR, + "File descriptor %d of child higher than MAX_FD in cf_pclose!", + fd); + fclose(pp); + return -1; + } + + pid = CHILDREN[fd]; + CHILDREN[fd] = 0; + ThreadUnlock(cft_count); + + if (fclose(pp) == EOF) + { + Log(LOG_LEVEL_ERR, + "Could not close the pipe to the executed subcommand (fclose: %s)", + GetErrorStr()); + } + + return cf_pwait(pid); +} + +int cf_pclose_full_duplex_side(int fd) +{ + ThreadLock(cft_count); + + if (CHILDREN == NULL) /* popen hasn't been called */ + { + ThreadUnlock(cft_count); + close(fd); + return -1; + } + + if (fd >= MAX_FD) + { + ThreadUnlock(cft_count); + Log(LOG_LEVEL_ERR, + "File descriptor %d of child higher than MAX_FD in cf_pclose_full_duplex_side!", + fd); + } + else + { + CHILDREN[fd] = 0; + ThreadUnlock(cft_count); + } + return close(fd); +} + + +/* We are assuming that read_fd part will be always open at this point. */ +int cf_pclose_full_duplex(IOData *data) +{ + assert(data != NULL); + ThreadLock(cft_count); + + if (CHILDREN == NULL) + { + ThreadUnlock(cft_count); + if (data->read_stream != NULL) + { + // fclose closes the underlying fd / socket, + // but we need the fd for handling of processes below. + assert(data->read_fd >= 0); + fclose(data->read_stream); + } + else if (data->read_fd >= 0) + { + close(data->read_fd); + } + + if (data->write_stream != NULL) + { + // fclose closes the underlying fd / socket, + // but we need the fd for handling of processes below. + assert(data->write_fd >= 0); + fclose(data->write_stream); + } + else if (data->write_fd >= 0) + { + close(data->write_fd); + } + return -1; + } + + ALARM_PID = -1; + pid_t pid = 0; + + /* Safe as pipes[1] is -1 if not initialized */ + if (data->read_fd >= MAX_FD || data->write_fd >= MAX_FD) + { + ThreadUnlock(cft_count); + Log(LOG_LEVEL_ERR, + "File descriptor %d of child higher than MAX_FD in cf_pclose!", + data->read_fd > data->write_fd ? data->read_fd : data->write_fd); + } + else + { + pid = CHILDREN[data->read_fd]; + if (data->write_fd >= 0) + { + assert(pid == CHILDREN[data->write_fd]); + CHILDREN[data->write_fd] = 0; + } + CHILDREN[data->read_fd] = 0; + ThreadUnlock(cft_count); + } + + if (data->read_stream != NULL) + { + // Stream is open, fclose it + if (fclose(data->read_stream) != 0) + { + return -1; + } + } + else + { + // No stream, just close fd + if (close(data->read_fd) != 0) + { + return -1; + } + } + + if (data->write_fd >= 0) + { + // write fd needs to be closed + if (data->write_stream != NULL) + { + // Stream is open, fclose it + if (fclose(data->write_stream) != 0) + { + return -1; + } + } + else + { + // No stream, close fd directly + if (close(data->write_fd) != 0) + { + return -1; + } + } + } + + if (pid == 0) + { + return -1; + } + + return cf_pwait(pid); +} + +bool PipeToPid(pid_t *pid, FILE *pp) +{ + int fd = fileno(pp); + ThreadLock(cft_count); + + if (CHILDREN == NULL) /* popen hasn't been called */ + { + ThreadUnlock(cft_count); + return false; + } + + *pid = CHILDREN[fd]; + ThreadUnlock(cft_count); + + return true; +} + +/*******************************************************************/ + +static bool CfSetuid(uid_t uid, gid_t gid) +{ + struct passwd *pw; + + if (gid != (gid_t) - 1) + { + Log(LOG_LEVEL_VERBOSE, "Changing gid to %ju", (uintmax_t)gid); + + if (setgid(gid) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't set gid to '%ju'. (setgid: %s)", (uintmax_t)gid, GetErrorStr()); + return false; + } + + /* Now eliminate any residual privileged groups */ + + if ((pw = getpwuid(uid)) == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to get login groups when dropping privilege to '%ju'. (getpwuid: %s)", (uintmax_t)uid, GetErrorStr()); + return false; + } + + if (initgroups(pw->pw_name, pw->pw_gid) == -1) + { + Log(LOG_LEVEL_ERR, "Unable to set login groups when dropping privilege to '%s=%ju'. (initgroups: %s)", pw->pw_name, + (uintmax_t)uid, GetErrorStr()); + return false; + } + } + + if (uid != (uid_t) - 1) + { + Log(LOG_LEVEL_VERBOSE, "Changing uid to '%ju'", (uintmax_t)uid); + + if (setuid(uid) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't set uid to '%ju'. (setuid: %s)", (uintmax_t)uid, GetErrorStr()); + return false; + } + } + + return true; +} + +/* For Windows we need a different method because select() does not */ +/* work with non-socket file descriptors. */ +int PipeIsReadWriteReady(const IOData *io, int timeout_sec) +{ + fd_set rset; + FD_ZERO(&rset); + FD_SET(io->read_fd, &rset); + + struct timeval tv = { + .tv_sec = timeout_sec, + .tv_usec = 0, + }; + + Log(LOG_LEVEL_DEBUG, + "PipeIsReadWriteReady: wait max %ds for data on fd %d", + timeout_sec, io->read_fd); + + int ret = select(io->read_fd + 1, &rset, NULL, NULL, &tv); + + if (ret < 0) + { + Log(LOG_LEVEL_VERBOSE, "Failed checking for data (select: %s)", + GetErrorStr()); + return -1; + } + else if (FD_ISSET(io->read_fd, &rset)) + { + return io->read_fd; + } + else if (ret == 0) + { + /* timeout_sec has elapsed but no data was available. */ + return 0; + } + else + { + UnexpectedError("select() returned > 0 but our only fd is not set!"); + return -1; + } +} diff --git a/libpromises/policy.c b/libpromises/policy.c new file mode 100644 index 0000000000..4a9b553d27 --- /dev/null +++ b/libpromises/policy.c @@ -0,0 +1,3352 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *const POLICY_ERROR_BUNDLE_NAME_RESERVED = + "Use of a reserved container name as a bundle name \"%s\""; +static const char *const POLICY_ERROR_BUNDLE_REDEFINITION = + "Duplicate definition of bundle %s with type %s"; +static const char *const POLICY_ERROR_BODY_REDEFINITION = + "Duplicate definition of body %s with type %s"; +static const char *const POLICY_ERROR_BODY_UNDEFINED = + "Undefined body %s with type %s"; +static const char *const POLICY_ERROR_BODY_CONTROL_ARGS = + "Control bodies cannot take arguments, body %s control"; +static const char *const POLICY_ERROR_PROMISE_UNCOMMENTED = + "Promise is missing a comment attribute, and comments are required " + "by policy"; +static const char *const POLICY_ERROR_PROMISE_DUPLICATE_HANDLE = + "Duplicate promise handle %s found"; +static const char *const POLICY_ERROR_LVAL_INVALID = + "Promise type %s has unknown attribute %s"; +static const char *const POLICY_ERROR_PROMISE_ATTRIBUTE_NOT_SUPPORTED = + "Common attribute '%s' not supported for custom promises, use '%s' instead (%s promises)"; +static const char *const POLICY_ERROR_PROMISE_TYPE_UNSUPPORTED = + "Promise type '%s' not supported by '%s' bundle type"; + +static const char *const POLICY_ERROR_CONSTRAINT_TYPE_MISMATCH = + "Type mismatch in constraint: %s"; + +static const char *const POLICY_ERROR_EMPTY_VARREF = + "Empty variable reference"; + +//************************************************************************ + +static void BundleDestroy(Bundle *bundle); +static void BodyDestroy(Body *body); +static SyntaxTypeMatch ConstraintCheckType(const Constraint *cp); +static bool PromiseCheck(const Promise *pp, Seq *errors); + +/*************************************************************************/ + +/** + * @brief Return a default bundle name for this method/service + */ +Rval DefaultBundleConstraint(const Promise *pp, char *promisetype) +{ + static char name[CF_BUFSIZE]; + snprintf(name, CF_BUFSIZE, "%s_%s", promisetype, CanonifyName(pp->promiser)); + return (Rval) { name, RVAL_TYPE_SCALAR }; +} + +/*************************************************************************/ + +const char *NamespaceDefault(void) +{ + return "default"; +} + +/*************************************************************************/ + +Policy *PolicyNew(void) +{ + Policy *policy = xcalloc(1, sizeof(Policy)); + + policy->release_id = NULL; + policy->bundles = SeqNew(100, BundleDestroy); + policy->bodies = SeqNew(100, BodyDestroy); + policy->custom_promise_types = SeqNew(20, BodyDestroy); + policy->policy_files_hashes = NULL; + + return policy; +} + +/*************************************************************************/ + +int PolicyCompare(const void *a, const void *b) +{ + return a - b; +} + +/*************************************************************************/ + +void PolicyDestroy(Policy *policy) +{ + if (policy != NULL) + { + SeqDestroy(policy->bundles); + SeqDestroy(policy->bodies); + SeqDestroy(policy->custom_promise_types); + free(policy->release_id); + if (policy->policy_files_hashes != NULL) + { + StringMapDestroy(policy->policy_files_hashes); + } + + free(policy); + } +} + +/*************************************************************************/ + +static unsigned ConstraintHash(const Constraint *cp, unsigned seed) +{ + unsigned hash = seed; + + hash = StringHash(cp->lval, hash); + hash = StringHash(cp->classes, hash); + hash = RvalHash(cp->rval, hash); + + return hash; +} + +/*************************************************************************/ + +static unsigned BodyHash(const Body *body, unsigned seed) +{ + unsigned hash = seed; + for (size_t i = 0; i < SeqLength(body->conlist); i++) + { + const Constraint *cp = SeqAt(body->conlist, i); + hash = ConstraintHash(cp, hash); + } + + return hash; +} +/*************************************************************************/ + +static unsigned PromiseHash(const Promise *pp, unsigned seed) +{ + unsigned hash = seed; + + hash = StringHash(pp->promiser, seed); + hash = RvalHash(pp->promisee, hash); + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + const Constraint *cp = SeqAt(pp->conlist, i); + hash = ConstraintHash(cp, hash); + } + + return hash; +} + +/*************************************************************************/ + +static unsigned BundleSectionHash(const BundleSection *section, unsigned seed) +{ + unsigned hash = seed; + + hash = StringHash(section->promise_type, hash); + for (size_t i = 0; i < SeqLength(section->promises); i++) + { + const Promise *pp = SeqAt(section->promises, i); + hash = PromiseHash(pp, hash); + } + + return hash; +} + +/*************************************************************************/ + +static unsigned BundleHash(const Bundle *bundle, unsigned seed) +{ + unsigned hash = seed; + + hash = StringHash(bundle->type, hash); + hash = StringHash(bundle->ns, hash); + hash = StringHash(bundle->name, hash); + hash = RlistHash(bundle->args, hash); + + for (size_t i = 0; i < SeqLength(bundle->sections); i++) + { + const BundleSection *section = SeqAt(bundle->sections, i); + hash = BundleSectionHash(section, hash); + } + + return hash; +} + +/*************************************************************************/ + +unsigned PolicyHash(const Policy *policy) +{ + unsigned hash = 0; + + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + const Body *body = SeqAt(policy->bodies, i); + hash = BodyHash(body, hash); + } + + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + const Bundle *bundle = SeqAt(policy->bundles, i); + hash = BundleHash(bundle, hash); + } + + return hash; +} + +/*************************************************************************/ + +StringSet *PolicySourceFiles(const Policy *policy) +{ + StringSet *files = StringSetNew(); + + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + const Bundle *bp = SeqAt(policy->bundles, i); + if (bp->source_path) + { + StringSetAdd(files, xstrdup(bp->source_path)); + } + } + + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + const Bundle *bp = SeqAt(policy->bodies, i); + if (bp->source_path) + { + StringSetAdd(files, xstrdup(bp->source_path)); + } + } + + return files; +} + +/*************************************************************************/ + +/** + * Get hash digest of the given policy file. + * + * @param policy Policy that is supposed to contain (have loaded) the file + * @param policy_file_path Absolute path of the policy file to get the digest for + * @return Hash digest of the given policy file or %NULL if unknown + * @note The returned hash digest is owned by the policy, **do not free it**. + */ +const char *PolicyGetPolicyFileHash(const Policy *policy, const char *policy_file_path) +{ + return StringMapGet(policy->policy_files_hashes, policy_file_path); +} + +/*************************************************************************/ + +static const char *StripNamespace(const char *full_symbol) +{ + char *sep = strchr(full_symbol, CF_NS); + + if (sep == NULL) + { + return full_symbol; + } + else + { + return sep + 1; + } +} + +/*************************************************************************/ + +/** + * @brief Query a policy for a body + * @param policy The policy to query + * @param ns Namespace filter (optionally NULL) + * @param type Body type filter + * @param name Body name filter + * @return Body child object if found, otherwise NULL + */ +Body *PolicyGetBody(const Policy *policy, const char *ns, const char *type, const char *name) +{ + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + Body *bp = SeqAt(policy->bodies, i); + const char *body_symbol = StripNamespace(bp->name); + + if (strcmp(bp->type, type) == 0 && + strcmp(body_symbol, name) == 0) + { + // allow namespace to be optionally matched + if (ns && strcmp(bp->ns, ns) != 0) + { + continue; + } + + return bp; + } + } + + return NULL; +} + +/*************************************************************************/ + +/** + * @brief Query a policy for a bundle + * @param policy The policy to query + * @param ns Namespace filter (optionally NULL) + * @param type Bundle type filter + * @param name Bundle name filter + * @return Bundle child object if found, otherwise NULL + */ +Bundle *PolicyGetBundle(const Policy *policy, const char *ns, const char *type, const char *name) +{ + const char *bundle_symbol = StripNamespace(name); + + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + Bundle *bp = SeqAt(policy->bundles, i); + + if ((type == NULL || strcmp(bp->type, type) == 0) + && + ((strcmp(bp->name, bundle_symbol) == 0) || + (strcmp(bp->name, name) == 0))) + { + // allow namespace to be optionally matched + if (ns && strcmp(bp->ns, ns) != 0) + { + continue; + } + + return bp; + } + } + + return NULL; +} + +/*************************************************************************/ + +/** + * @brief Check to see if a policy is runnable (contains body common control) + * @param policy Policy to check + * @return True if policy is runnable + */ +bool PolicyIsRunnable(const Policy *policy) +{ + return PolicyGetBody(policy, NULL, "common", "control") != NULL; +} + +/*************************************************************************/ + +/** + * @brief Merge two partial policy objects. The memory for the child objects of the original policies are transferred to the new parent. + * @param a + * @param b + * @return Merged policy + */ +Policy *PolicyMerge(Policy *a, Policy *b) +{ + assert(a != NULL); + assert(b != NULL); + + Policy *result = PolicyNew(); + + SeqAppendSeq(result->bundles, a->bundles); + SeqSoftDestroy(a->bundles); + SeqAppendSeq(result->bundles, b->bundles); + SeqSoftDestroy(b->bundles); + + for (size_t i = 0; i < SeqLength(result->bundles); i++) + { + Bundle *bp = SeqAt(result->bundles, i); + bp->parent_policy = result; + } + + SeqAppendSeq(result->bodies, a->bodies); + SeqSoftDestroy(a->bodies); + SeqAppendSeq(result->bodies, b->bodies); + SeqSoftDestroy(b->bodies); + + for (size_t i = 0; i < SeqLength(result->bodies); i++) + { + Body *bdp = SeqAt(result->bodies, i); + bdp->parent_policy = result; + } + + SeqAppendSeq(result->custom_promise_types, a->custom_promise_types); + SeqSoftDestroy(a->custom_promise_types); + SeqAppendSeq(result->custom_promise_types, b->custom_promise_types); + SeqSoftDestroy(b->custom_promise_types); + + for (size_t i = 0; i < SeqLength(result->custom_promise_types); i++) + { + Body *bdp = SeqAt(result->custom_promise_types, i); + bdp->parent_policy = result; + } + + StringMap *extra_hashes = NULL; + if (a->policy_files_hashes != NULL) + { + result->policy_files_hashes = a->policy_files_hashes; + a->policy_files_hashes = NULL; + extra_hashes = b->policy_files_hashes; + b->policy_files_hashes = NULL; + } + else if (b->policy_files_hashes != NULL) + { + result->policy_files_hashes = b->policy_files_hashes; + b->policy_files_hashes = NULL; + } + else + { + result->policy_files_hashes = NULL; + } + + if (extra_hashes != NULL) + { + MapIterator it = MapIteratorInit(extra_hashes->impl); + MapKeyValue *item; + while ((item = MapIteratorNext(&it)) != NULL) + { + /* Move data and duplicate just the keys (which are always owned by + the map). */ + StringMapInsert(result->policy_files_hashes, + xstrdup((char*) item->key), (char*) item->value); + } + /* Destroy only the map and the keys, data was moved. */ + StringMapSoftDestroy(extra_hashes); + } + + /* Should result take over a release_id ? */ + free(a->release_id); + free(b->release_id); + free(a); + free(b); + + return result; +} + +/*************************************************************************/ + +const char *ConstraintGetNamespace(const Constraint *cp) +{ + switch (cp->type) + { + case POLICY_ELEMENT_TYPE_BODY: + return cp->parent.body->ns; + + case POLICY_ELEMENT_TYPE_PROMISE: + return cp->parent.promise->parent_section->parent_bundle->ns; + + default: + ProgrammingError("Constraint has parent type: %d", cp->type); + } +} + +/*************************************************************************/ + +/** + * @brief Convenience function to get the policy object associated with a promise + * @param promise + * @return Policy object + */ +const Policy *PolicyFromPromise(const Promise *promise) +{ + assert(promise); + + BundleSection *section = promise->parent_section; + assert(section != NULL); + + Bundle *bundle = section->parent_bundle; + assert(bundle); + + return bundle->parent_policy; +} + +char *BundleQualifiedName(const Bundle *bundle) +{ + assert(bundle); + if (!bundle) + { + return NULL; + } + + if (bundle->name) + { + const char *ns = bundle->ns ? bundle->ns : NamespaceDefault(); + return StringConcatenate(3, ns, ":", bundle->name); // CF_NS == ':' + } + + return NULL; +} + +static bool RvalTypeCheckDataType(RvalType rval_type, DataType expected_datatype) +{ + if (rval_type == RVAL_TYPE_FNCALL) + { + return true; + } + + switch (expected_datatype) + { + case CF_DATA_TYPE_BODY: + case CF_DATA_TYPE_BUNDLE: + return rval_type == RVAL_TYPE_SCALAR; + + case CF_DATA_TYPE_CONTEXT: + case CF_DATA_TYPE_COUNTER: + case CF_DATA_TYPE_INT: + case CF_DATA_TYPE_INT_RANGE: + case CF_DATA_TYPE_OPTION: + case CF_DATA_TYPE_REAL: + case CF_DATA_TYPE_REAL_RANGE: + case CF_DATA_TYPE_STRING: + return rval_type == RVAL_TYPE_SCALAR; + + case CF_DATA_TYPE_CONTEXT_LIST: + case CF_DATA_TYPE_INT_LIST: + case CF_DATA_TYPE_OPTION_LIST: + case CF_DATA_TYPE_REAL_LIST: + case CF_DATA_TYPE_STRING_LIST: + return (rval_type == RVAL_TYPE_SCALAR) || (rval_type == RVAL_TYPE_LIST); + + case CF_DATA_TYPE_CONTAINER: + return (rval_type == RVAL_TYPE_CONTAINER); + + default: + ProgrammingError("Unhandled expected datatype in switch: %d", expected_datatype); + } +} + +/*************************************************************************/ + +/* Check if a constraint's syntax is correct according to its promise_type and + lvalue. +*/ +static bool ConstraintCheckSyntax(const Constraint *constraint, Seq *errors) +{ + assert(constraint != NULL); + if (constraint->type != POLICY_ELEMENT_TYPE_PROMISE) + { + ProgrammingError("Attempted to check the syntax for a constraint" + " not belonging to a promise"); + } + + const BundleSection *section = constraint->parent.promise->parent_section; + const Bundle *bundle = section->parent_bundle; + + /* Check if lvalue is valid for the bundle's specific section. */ + const PromiseTypeSyntax *promise_type_syntax = PromiseTypeSyntaxGet(bundle->type, section->promise_type); + for (size_t i = 0; promise_type_syntax->constraints[i].lval != NULL; i++) + { + const ConstraintSyntax *body_syntax = &promise_type_syntax->constraints[i]; + if (strcmp(body_syntax->lval, constraint->lval) == 0) + { + if (!RvalTypeCheckDataType(constraint->rval.type, body_syntax->dtype)) + { + SeqAppend(errors, + PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, constraint, + POLICY_ERROR_CONSTRAINT_TYPE_MISMATCH, constraint->lval)); + return false; + } + return true; + } + } + /* FIX: Call a VerifyConstraint() hook for the specific promise_type, defined + in verify_TYPE.c, that checks for promise_type-specific constraint syntax. */ + + /* Check if lvalue is valid for all bodies. */ + for (size_t i = 0; CF_COMMON_BODIES[i].lval != NULL; i++) + { + if (strcmp(constraint->lval, CF_COMMON_BODIES[i].lval) == 0) + { + if (!RvalTypeCheckDataType(constraint->rval.type, CF_COMMON_BODIES[i].dtype)) + { + SeqAppend(errors, + PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, constraint, + POLICY_ERROR_CONSTRAINT_TYPE_MISMATCH, constraint->lval)); + return false; + } + return true; + } + } + for (size_t i = 0; CF_COMMON_EDITBODIES[i].lval != NULL; i++) + { + if (strcmp(constraint->lval, CF_COMMON_EDITBODIES[i].lval) == 0) + { + if (!RvalTypeCheckDataType(constraint->rval.type, CF_COMMON_EDITBODIES[i].dtype)) + { + SeqAppend(errors, + PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, constraint, + POLICY_ERROR_CONSTRAINT_TYPE_MISMATCH, constraint->lval)); + return false; + } + return true; + } + } + for (size_t i = 0; CF_COMMON_XMLBODIES[i].lval != NULL; i++) + { + if (strcmp(constraint->lval, CF_COMMON_XMLBODIES[i].lval) == 0) + { + if (!RvalTypeCheckDataType(constraint->rval.type, CF_COMMON_XMLBODIES[i].dtype)) + { + SeqAppend(errors, + PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, constraint, + POLICY_ERROR_CONSTRAINT_TYPE_MISMATCH, constraint->lval)); + return false; + } + return true; + } + } + + /* lval is unknown for this promise type */ + SeqAppend(errors, + PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, constraint, + POLICY_ERROR_LVAL_INVALID, + constraint->parent.promise->parent_section->promise_type, + constraint->lval)); + + return false; +} + +/*************************************************************************/ + +static bool PolicyCheckPromiseType(const BundleSection *section, Seq *errors) +{ + assert(section != NULL); + assert(section->parent_bundle); + bool success = true; + + for (size_t i = 0; i < SeqLength(section->promises); i++) + { + const Promise *pp = SeqAt(section->promises, i); + success &= PromiseCheck(pp, errors); + } + + return success; +} + +/*************************************************************************/ + +static inline bool PolicyCheckBundleSections(Seq * sections, Seq *errors) +{ + bool success = true; + const size_t length = SeqLength(sections); + for (size_t i = 0; i < length; i++) + { + const BundleSection *section = SeqAt(sections, i); + success &= PolicyCheckPromiseType(section, errors); + } + return success; +} + +static bool PolicyCheckBundle(const Bundle *bundle, Seq *errors) +{ + assert(bundle); + bool success = true; + + // ensure no reserved bundle names are used + { + static const char *const reserved_names[] = + { "sys", "const", "mon", "edit", "match", "mon", "this", NULL }; + if (IsStrIn(bundle->name, reserved_names)) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BUNDLE, bundle, + POLICY_ERROR_BUNDLE_NAME_RESERVED, bundle->name)); + success = false; + } + } + + success &= PolicyCheckBundleSections(bundle->sections, errors); + success &= PolicyCheckBundleSections(bundle->custom_sections, errors); + + return success; +} + +static bool PolicyCheckBody(const Body *body, Seq *errors) +{ + bool success = true; + + if (strcmp("control", body->name) == 0) + { + if (RlistLen(body->args) > 0) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BODY, body, + POLICY_ERROR_BODY_CONTROL_ARGS, + body->type)); + success = false; + } + } + + for (size_t i = 0; i < SeqLength(body->conlist); i++) + { + Constraint *cp = SeqAt(body->conlist, i); + SyntaxTypeMatch err = ConstraintCheckType(cp); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, cp, + POLICY_ERROR_CONSTRAINT_TYPE_MISMATCH, + cp->lval)); + success = false; + } + } + + const BodySyntax *body_syntax = BodySyntaxGet(PARSER_BLOCK_BODY, body->type); + assert(body_syntax && "Should have been checked at parse time"); + if (body_syntax->check_body) + { + success &= body_syntax->check_body(body, errors); + } + + return success; +} + +/*************************************************************************/ + +/* Get the syntax of a constraint according to its promise_type and lvalue. + Make sure you've already checked the constraint's validity. +*/ +static const ConstraintSyntax *ConstraintGetSyntax(const Constraint *constraint) +{ + if (constraint->type != POLICY_ELEMENT_TYPE_PROMISE) + { + ProgrammingError("Attempted to get the syntax for a constraint not belonging to a promise"); + } + + const Promise *promise = constraint->parent.promise; + const BundleSection *section = promise->parent_section; + const Bundle *bundle = section->parent_bundle; + + const PromiseTypeSyntax *promise_type_syntax = PromiseTypeSyntaxGet(bundle->type, section->promise_type); + + /* Check if lvalue is valid for the bundle's specific section. */ + for (size_t i = 0; promise_type_syntax->constraints[i].lval != NULL; i++) + { + const ConstraintSyntax *body_syntax = &promise_type_syntax->constraints[i]; + if (strcmp(body_syntax->lval, constraint->lval) == 0) + { + return body_syntax; + } + } + + /* Check if lvalue is valid for all bodies. */ + for (size_t i = 0; CF_COMMON_BODIES[i].lval != NULL; i++) + { + if (strcmp(constraint->lval, CF_COMMON_BODIES[i].lval) == 0) + { + return &CF_COMMON_BODIES[i]; + } + } + for (size_t i = 0; CF_COMMON_EDITBODIES[i].lval != NULL; i++) + { + if (strcmp(constraint->lval, CF_COMMON_EDITBODIES[i].lval) == 0) + { + return &CF_COMMON_EDITBODIES[i]; + } + } + for (size_t i = 0; CF_COMMON_XMLBODIES[i].lval != NULL; i++) + { + if (strcmp(constraint->lval, CF_COMMON_XMLBODIES[i].lval) == 0) + { + return &CF_COMMON_XMLBODIES[i]; + } + } + + /* Syntax must have been checked first during PolicyCheckPartial(). */ + ProgrammingError("ConstraintGetSyntax() was called for constraint with " + "invalid lvalue: %s", constraint->lval); + return NULL; +} + +/*************************************************************************/ + +/** + * @return A reference to the full symbol value of the Rval regardless of type, e.g. "foo:bar""foo:bar" + */ +static const char *RvalFullSymbol(const Rval *rval) +{ + switch (rval->type) + { + case RVAL_TYPE_SCALAR: + return rval->item; + break; + + case RVAL_TYPE_FNCALL: + return ((FnCall *)rval->item)->name; + + default: + ProgrammingError("Cannot get full symbol value from Rval of type %c", rval->type); + return NULL; + } +} + +/** + * @return A copy of the namespace component of a qualified name, or NULL. e.g. "foo:bar" -> "foo" + */ +char *QualifiedNameNamespaceComponent(const char *qualified_name) +{ + if (strchr(qualified_name, CF_NS)) + { + char ns[256] = { 0 }; + sscanf(qualified_name, "%255[^:]", ns); + + return xstrdup(ns); + } + else + { + return NULL; + } +} + +/** + * @return A copy of the symbol compoent of a qualified name, or NULL. e.g. "foo:bar" -> "bar" + */ +char *QualifiedNameScopeComponent(const char *qualified_name) +{ + char *sep = strchr(qualified_name, CF_NS); + if (sep) + { + return xstrdup(sep + 1); + } + else + { + return xstrdup(qualified_name); + } +} + +static bool PolicyCheckUndefinedBodies(const Policy *policy, Seq *errors) +{ + bool success = true; + + for (size_t bpi = 0; bpi < SeqLength(policy->bundles); bpi++) + { + Bundle *bundle = SeqAt(policy->bundles, bpi); + + for (size_t sti = 0; sti < SeqLength(bundle->sections); sti++) + { + BundleSection *section = SeqAt(bundle->sections, sti); + + for (size_t ppi = 0; ppi < SeqLength(section->promises); ppi++) + { + Promise *promise = SeqAt(section->promises, ppi); + + for (size_t cpi = 0; cpi < SeqLength(promise->conlist); cpi++) + { + Constraint *constraint = SeqAt(promise->conlist, cpi); + + const ConstraintSyntax *syntax = ConstraintGetSyntax(constraint); + if (syntax->dtype == CF_DATA_TYPE_BODY) + { + char *ns = QualifiedNameNamespaceComponent(RvalFullSymbol(&constraint->rval)); + char *symbol = QualifiedNameScopeComponent(RvalFullSymbol(&constraint->rval)); + + Body *referenced_body = PolicyGetBody(policy, ns, constraint->lval, symbol); + if (!referenced_body) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, constraint, + POLICY_ERROR_BODY_UNDEFINED, symbol, constraint->lval)); + success = false; + } + + free(ns); + free(symbol); + } + } // constraints + } // promises + } // promise_types + } // bundles + + return success; +} + +static bool PolicyCheckRequiredComments(const EvalContext *ctx, const Policy *policy, Seq *errors) +{ + const Body *common_control = PolicyGetBody(policy, NULL, "common", "control"); + if (common_control) + { + bool require_comments = ConstraintsGetAsBoolean(ctx, "require_comments", common_control->conlist); + if (!require_comments) + { + return true; + } + + bool success = true; + + for (size_t bpi = 0; bpi < SeqLength(policy->bundles); bpi++) + { + Bundle *bundle = SeqAt(policy->bundles, bpi); + + for (size_t sti = 0; sti < SeqLength(bundle->sections); sti++) + { + BundleSection *section = SeqAt(bundle->sections, sti); + + for (size_t ppi = 0; ppi < SeqLength(section->promises); ppi++) + { + Promise *promise = SeqAt(section->promises, ppi); + + bool promise_has_comment = false; + for (size_t cpi = 0; cpi < SeqLength(promise->conlist); cpi++) + { + Constraint *constraint = SeqAt(promise->conlist, cpi); + + if (strcmp(constraint->lval, "comment") == 0) + { + promise_has_comment = true; + break; + } + } // constraints + + if (!promise_has_comment) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, promise, + POLICY_ERROR_PROMISE_UNCOMMENTED)); + success = false; + } + } // promises + } // promise_types + } // bundles + + return success; + } + else + { + return true; + } +} + +bool PolicyCheckDuplicateHandles(const Policy *policy, Seq *errors) +{ + bool success = true; + + Map *recorded = MapNew(StringHash_untyped, StringEqual_untyped, NULL, NULL); + + for (size_t bpi = 0; bpi < SeqLength(policy->bundles); bpi++) + { + Bundle *bundle = SeqAt(policy->bundles, bpi); + + for (size_t sti = 0; sti < SeqLength(bundle->sections); sti++) + { + BundleSection *section = SeqAt(bundle->sections, sti); + + for (size_t ppi = 0; ppi < SeqLength(section->promises); ppi++) + { + Promise *promise = SeqAt(section->promises, ppi); + const char *handle = PromiseGetHandle(promise); + + if (handle) + { + if (IsCf3VarString(handle)) + { + // can't check dynamic handles + continue; + } + + const Promise *other_promise = MapGet(recorded, handle); + if (other_promise) + { + // Need to make this smarter by comparing parsed expressions for equivalency. + if (strcmp(promise->classes, other_promise->classes) == 0) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_PROMISE, promise, + POLICY_ERROR_PROMISE_DUPLICATE_HANDLE, handle)); + success = false; + } + } + else + { + MapInsert(recorded, (void *)handle, (void *)promise); + } + } + } + } + } + + MapDestroy(recorded); + + return success; +} + +/** + * @brief Check a runnable policy DOM for errors + * @param policy Policy to check + * @param errors Sequence of PolicyError to append errors to + * @param ignore_missing_bundles Whether to ignore missing bundle references + * @return True if no new errors are found + */ +bool PolicyCheckRunnable(const EvalContext *ctx, const Policy *policy, Seq *errors) +{ + bool success = true; + + success &= PolicyCheckRequiredComments(ctx, policy, errors); + success &= PolicyCheckUndefinedBodies(policy, errors); + success &= PolicyCheckDuplicateHandles(policy, errors); + + return success; +} + +/** + * @brief Check a partial policy DOM for errors + * @param policy Policy to check + * @param errors Sequence of PolicyError to append errors to + * @return True if no new errors are found + */ +bool PolicyCheckPartial(const Policy *policy, Seq *errors) +{ + bool success = true; + + // ensure bundle names are not duplicated + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + Bundle *bp = SeqAt(policy->bundles, i); + + for (size_t j = 0; j < SeqLength(policy->bundles); j++) + { + Bundle *bp2 = SeqAt(policy->bundles, j); + + if (bp != bp2 + && strcmp(bp->type, bp2->type) == 0 + && strcmp(bp->ns, bp2->ns) == 0 + && strcmp(bp->name, bp2->name) == 0) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BUNDLE, bp, + POLICY_ERROR_BUNDLE_REDEFINITION, + bp->name, bp->type)); + success = false; + } + } + } + + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + Bundle *bp = SeqAt(policy->bundles, i); + success &= PolicyCheckBundle(bp, errors); + } + + + // ensure body names are not duplicated + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + const Body *bp = SeqAt(policy->bodies, i); + + for (size_t j = 0; j < SeqLength(policy->bodies); j++) + { + const Body *bp2 = SeqAt(policy->bodies, j); + + if (bp != bp2 + && strcmp(bp->type, bp2->type) == 0 + && strcmp(bp->ns, bp2->ns) == 0 + && strcmp(bp->name, bp2->name) == 0) + { + if (strcmp(bp->type,"file") != 0) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BODY, bp, + POLICY_ERROR_BODY_REDEFINITION, + bp->name, bp->type)); + success = false; + } + } + } + } + + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + const Body *body = SeqAt(policy->bodies, i); + success &= PolicyCheckBody(body, errors); + + } + + success &= PolicyCheckDuplicateHandles(policy, errors); + + return success; +} + +/*************************************************************************/ + +PolicyError *PolicyErrorNew(PolicyElementType type, const void *subject, const char *error_msg, ...) +{ + PolicyError *error = xmalloc(sizeof(PolicyError)); + + error->type = type; + error->subject = subject; + + va_list args; + va_start(args, error_msg); + xvasprintf(&error->message, error_msg, args); + va_end(args); + + return error; +} + +/*************************************************************************/ + +void PolicyErrorDestroy(PolicyError *error) +{ + free(error->message); + free(error); +} + +/*************************************************************************/ + +static SourceOffset PolicyElementSourceOffset(PolicyElementType type, const void *element) +{ + assert(element); + + switch (type) + { + case POLICY_ELEMENT_TYPE_POLICY: + { + return (SourceOffset) { 0 }; + } + + case POLICY_ELEMENT_TYPE_BUNDLE: + { + const Bundle *bundle = (const Bundle *)element; + return bundle->offset; + } + + case POLICY_ELEMENT_TYPE_BODY: + { + const Body *body = (const Body *)element; + return body->offset; + } + + case POLICY_ELEMENT_TYPE_BUNDLE_SECTION: + { + const BundleSection *section = (const BundleSection *) element; + return section->offset; + } + + case POLICY_ELEMENT_TYPE_PROMISE: + { + const Promise *promise = (const Promise *)element; + return promise->offset; + } + + case POLICY_ELEMENT_TYPE_CONSTRAINT: + { + const Constraint *constraint = (const Constraint *)element; + return constraint->offset; + } + + default: + assert(false && "Invalid policy element"); + return (SourceOffset) { 0 }; + } +} + +/*************************************************************************/ + +static const char *PolicyElementSourceFile(PolicyElementType type, const void *element) +{ + assert(element); + + switch (type) + { + case POLICY_ELEMENT_TYPE_POLICY: + return ""; + + case POLICY_ELEMENT_TYPE_BUNDLE: + { + const Bundle *bundle = (const Bundle *)element; + return bundle->source_path; + } + + case POLICY_ELEMENT_TYPE_BODY: + { + const Body *body = (const Body *)element; + return body->source_path; + } + + case POLICY_ELEMENT_TYPE_BUNDLE_SECTION: + { + const BundleSection *section = (const BundleSection *) element; + return PolicyElementSourceFile(POLICY_ELEMENT_TYPE_BUNDLE, section->parent_bundle); + } + + case POLICY_ELEMENT_TYPE_PROMISE: + { + const Promise *promise = (const Promise *)element; + return PolicyElementSourceFile(POLICY_ELEMENT_TYPE_BUNDLE_SECTION, promise->parent_section); + } + + case POLICY_ELEMENT_TYPE_CONSTRAINT: + { + const Constraint *constraint = (const Constraint *)element; + switch (constraint->type) + { + case POLICY_ELEMENT_TYPE_BODY: + return PolicyElementSourceFile(POLICY_ELEMENT_TYPE_BODY, constraint->parent.body); + + case POLICY_ELEMENT_TYPE_PROMISE: + return PolicyElementSourceFile(POLICY_ELEMENT_TYPE_PROMISE, constraint->parent.promise); + + default: + assert(false && "Constraint has invalid parent element type"); + return NULL; + } + } + + default: + assert(false && "Invalid policy element"); + return NULL; + } +} + +/*************************************************************************/ + +void PolicyErrorWrite(Writer *writer, const PolicyError *error) +{ + SourceOffset offset = PolicyElementSourceOffset(error->type, error->subject); + const char *path = PolicyElementSourceFile(error->type, error->subject); + + // FIX: need to track columns in SourceOffset + WriterWriteF(writer, "%s:%zu:%zu: error: %s\n", path, offset.line, (size_t)0, error->message); +} + +static char *PolicyErrorToString(const PolicyError *error) +{ + SourceOffset offset = PolicyElementSourceOffset(error->type, error->subject); + const char *path = PolicyElementSourceFile(error->type, error->subject); + + Writer *msg = StringWriter(); + WriterWriteF(msg, "%s:%zu:%zu: %s.", + path, offset.line, + (size_t)0, error->message); + + if (error->type == POLICY_ELEMENT_TYPE_CONSTRAINT) + { + const Constraint *cp = error->subject; + WriterWrite(msg, " Given attribute value '"); + RvalWrite(msg, cp->rval); + WriterWriteChar(msg, '\''); + } + + return StringWriterClose(msg); +} + +/*************************************************************************/ + +void BundleSectionDestroy(BundleSection *section) +{ + if (section != NULL) + { + SeqDestroy(section->promises); + + free(section->promise_type); + free(section); + } +} + +Bundle *PolicyAppendBundle(Policy *policy, + const char *ns, const char *name, const char *type, + const Rlist *args, const char *source_path) +{ + Bundle *bundle = xcalloc(1, sizeof(Bundle)); + + bundle->parent_policy = policy; + + SeqAppend(policy->bundles, bundle); + + bundle->name = xstrdup(name); + bundle->type = xstrdup(type); + bundle->ns = xstrdup(ns); + bundle->args = RlistCopy(args); + bundle->source_path = SafeStringDuplicate(source_path); + bundle->sections = SeqNew(10, BundleSectionDestroy); + bundle->custom_sections = SeqNew(10, BundleSectionDestroy); + + return bundle; +} + +/*******************************************************************/ + +Body *PolicyAppendBody(Policy *policy, const char *ns, const char *name, + const char *type, Rlist *args, const char *source_path, + bool is_custom) +{ + Body *body = xcalloc(1, sizeof(Body)); + body->parent_policy = policy; + + SeqAppend(policy->bodies, body); + + body->name = xstrdup(name); + body->type = xstrdup(type); + body->ns = xstrdup(ns); + body->args = RlistCopy(args); + body->source_path = SafeStringDuplicate(source_path); + body->conlist = SeqNew(10, ConstraintDestroy); + body->is_custom = is_custom; + + // TODO: move to standard callback + if (strcmp("service_method", body->name) == 0) + { + Rlist *bundle_args = NULL; + RlistAppendRval(&bundle_args, RvalNew("$(this.promiser)", RVAL_TYPE_SCALAR)); + RlistAppendRval(&bundle_args, RvalNew("$(this.service_policy)", RVAL_TYPE_SCALAR)); + + FnCall *service_bundle = FnCallNew("standard_services", bundle_args); + BodyAppendConstraint(body, "service_bundle", (Rval) { service_bundle, RVAL_TYPE_FNCALL }, "any", false); + } + + return body; +} + +/*******************************************************************/ + +Body *PolicyAppendPromiseBlock( + Policy *policy, + const char *ns, + const char *name, + const char *type, + Rlist *args, + const char *source_path) +{ + assert(policy != NULL); + + Body *body = xcalloc(1, sizeof(Body)); + body->parent_policy = policy; + + SeqAppend(policy->custom_promise_types, body); + + body->name = xstrdup(name); + body->type = xstrdup(type); + body->ns = xstrdup(ns); + body->args = RlistCopy(args); + body->source_path = SafeStringDuplicate(source_path); + body->conlist = SeqNew(10, ConstraintDestroy); + + return body; +} + +/*******************************************************************/ + +BundleSection *BundleAppendSection(Bundle *bundle, const char *promise_type) +{ + if (bundle == NULL) + { + ProgrammingError("Attempt to add a type without a bundle"); + } + + // TODO: review SeqLookup + for (size_t i = 0; i < SeqLength(bundle->sections); i++) + { + BundleSection *existing = SeqAt(bundle->sections, i); + if (strcmp(existing->promise_type, promise_type) == 0) + { + return existing; + } + } + for (size_t i = 0; i < SeqLength(bundle->custom_sections); i++) + { + BundleSection *existing = SeqAt(bundle->custom_sections, i); + if (strcmp(existing->promise_type, promise_type) == 0) + { + return existing; + } + } + + BundleSection *section = xcalloc(1, sizeof(BundleSection)); + + section->parent_bundle = bundle; + section->promise_type = xstrdup(promise_type); + section->promises = SeqNew(10, PromiseDestroy); + + if (IsBuiltInPromiseType(promise_type)) + { + SeqAppend(bundle->sections, section); + } + else + { + SeqAppend(bundle->custom_sections, section); + } + + return section; +} + +/*******************************************************************/ + +Promise *BundleSectionAppendPromise(BundleSection *section, const char *promiser, Rval promisee, const char *classes, const char *varclasses) +{ + assert(promiser && "Missing promiser"); + assert(section != NULL && "Missing promise type"); + + Promise *pp = xcalloc(1, sizeof(Promise)); + + pp->promiser = xstrdup(promiser); + + if (classes && strlen(classes) > 0) + { + pp->classes = xstrdup(classes); + } + else + { + pp->classes = xstrdup("any"); + } + + SeqAppend(section->promises, pp); + + pp->parent_section = section; + + pp->promisee = promisee; + pp->conlist = SeqNew(10, ConstraintDestroy); + pp->org_pp = pp; + + if (varclasses != NULL) + { + PromiseAppendConstraint(pp, "ifvarclass", RvalNew(varclasses, RVAL_TYPE_SCALAR), true); + } + + return pp; +} + +static void BundleDestroy(Bundle *bundle) +{ + if (bundle != NULL) + { + free(bundle->name); + free(bundle->type); + free(bundle->ns); + free(bundle->source_path); + + RlistDestroy(bundle->args); + SeqDestroy(bundle->sections); + SeqDestroy(bundle->custom_sections); + + free(bundle); + } +} + +static void BodyDestroy(Body *body) +{ + if (body) + { + free(body->name); + free(body->type); + free(body->ns); + free(body->source_path); + + RlistDestroy(body->args); + SeqDestroy(body->conlist); + free(body); + } +} + + +void PromiseDestroy(Promise *pp) +{ + if (pp) + { + free(pp->promiser); + + if (pp->promisee.item) + { + RvalDestroy(pp->promisee); + } + + free(pp->classes); + free(pp->comment); + + SeqDestroy(pp->conlist); + + free(pp); + } +} + +/*******************************************************************/ + +static Constraint *ConstraintNew(const char *lval, Rval rval, const char *classes, bool references_body) +{ + Constraint *cp = xcalloc(1, sizeof(Constraint)); + + cp->lval = SafeStringDuplicate(lval); + cp->rval = rval; + + cp->classes = SafeStringDuplicate(classes); + cp->references_body = references_body; + + return cp; +} + +Constraint *PromiseAppendConstraint(Promise *pp, const char *lval, Rval rval, bool references_body) +{ + Constraint *cp = ConstraintNew(lval, rval, "any", references_body); + cp->type = POLICY_ELEMENT_TYPE_PROMISE; + cp->parent.promise = pp; + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *old_cp = SeqAt(pp->conlist, i); + if (strcmp(old_cp->lval, lval) == 0) + { + if (strcmp(old_cp->lval, "ifvarclass") == 0 || + strcmp(old_cp->lval, "if") == 0) + { + // merge two if/ifvarclass promise attributes this + // only happens in a variable context when we have a + // scalar already in the attribute (old_cp) + switch (rval.type) + { + case RVAL_TYPE_FNCALL: // case 1: merge FnCall with scalar + { + char * rval_string = RvalToString(old_cp->rval); + Log(LOG_LEVEL_DEBUG, "PromiseAppendConstraint: merging PREVIOUS %s string context rval %s", old_cp->lval, rval_string); + Log(LOG_LEVEL_DEBUG, "PromiseAppendConstraint: merging NEW %s rval %s", old_cp->lval, rval_string); + free(rval_string); + + Rlist *synthetic_args = NULL; + RlistAppendScalar(&synthetic_args, RvalScalarValue(old_cp->rval)); + + // append the old Rval (a function call) under the arguments of the new one + RlistAppend(&synthetic_args, rval.item, RVAL_TYPE_FNCALL); + + Rval replacement = (Rval) { FnCallNew("and", synthetic_args), RVAL_TYPE_FNCALL }; + rval_string = RvalToString(replacement); + Log(LOG_LEVEL_DEBUG, "PromiseAppendConstraint: MERGED %s rval %s", old_cp->lval, rval_string); + free(rval_string); + + // overwrite the old Constraint rval with its replacement + RvalDestroy(cp->rval); + cp->rval = replacement; + } + break; + + case RVAL_TYPE_SCALAR: // case 2: merge scalar with scalar + { + Buffer *grow = BufferNew(); + BufferAppendF(grow, "(%s).(%s)", + RvalScalarValue(old_cp->rval), + RvalScalarValue(rval)); + RvalDestroy(cp->rval); + rval = RvalNew(BufferData(grow), RVAL_TYPE_SCALAR); + BufferDestroy(grow); + cp->rval = rval; + } + break; + + default: + ProgrammingError("PromiseAppendConstraint: unexpected rval type: %c", rval.type); + break; + } + } + SeqSet(pp->conlist, i, cp); + return cp; + } + } + + SeqAppend(pp->conlist, cp); + return cp; +} + +Constraint *BodyAppendConstraint(Body *body, const char *lval, Rval rval, const char *classes, + bool references_body) +{ + Constraint *cp = ConstraintNew(lval, rval, classes, references_body); + cp->type = POLICY_ELEMENT_TYPE_BODY; + cp->parent.body = body; + + for (size_t i = 0; i < SeqLength(body->conlist); i++) + { + Constraint *old_cp = SeqAt(body->conlist, i); + if (strcmp(old_cp->lval, lval) == 0 && strcmp(old_cp->classes, classes) == 0) + { + SeqSet(body->conlist, i, cp); + return cp; + } + } + + SeqAppend(body->conlist, cp); + + return cp; +} + +/*******************************************************************/ + +const BundleSection *BundleGetSection(const Bundle *bp, const char *promise_type) +{ + // TODO: hiding error, remove and see what will crash + if (bp == NULL) + { + return NULL; + } + + for (size_t i = 0; i < SeqLength(bp->sections); i++) + { + BundleSection *sp = SeqAt(bp->sections, i); + + if (strcmp(promise_type, sp->promise_type) == 0) + { + return sp; + } + } + + return NULL; +} + +/****************************************************************************/ + +static Buffer *EscapeQuotes(const char *raw, Buffer *out) +{ + const char *spf; + + for (spf = raw; *spf != '\0'; spf++) + { + switch (*spf) + { + case '\'': + case '\"': + BufferAppendChar(out, '\\'); + break; + + default: + break; + } + BufferAppendChar(out, *spf); + } + + return out; +} + +/** + * Converts the given attribute rval to a JSON object. + * + * @return A JsonElement of type JSON_ELEMENT_TYPE_CONTAINER + */ +static JsonElement *AttributeValueToJson(Rval rval, bool symbolic_reference) +{ + switch (rval.type) + { + case RVAL_TYPE_CONTAINER: + { + return JsonCopy(RvalContainerValue(rval)); + } + + case RVAL_TYPE_SCALAR: + { + Buffer *buffer = BufferNewWithCapacity(strlen(rval.item)); + + EscapeQuotes((const char *) rval.item, buffer); + + JsonElement *json_attribute = JsonObjectCreate(10); + + if (symbolic_reference) + { + JsonObjectAppendString(json_attribute, "type", "symbol"); + } + else + { + JsonObjectAppendString(json_attribute, "type", "string"); + } + JsonObjectAppendString(json_attribute, "value", BufferData(buffer)); + + BufferDestroy(buffer); + + return json_attribute; + } + + + case RVAL_TYPE_LIST: + { + Rlist *rp = NULL; + JsonElement *list = JsonArrayCreate(10); + + JsonElement *json_attribute = JsonObjectCreate(10); + JsonObjectAppendString(json_attribute, "type", "list"); + + for (rp = (Rlist *) rval.item; rp != NULL; rp = rp->next) + { + JsonArrayAppendObject(list, AttributeValueToJson(rp->val, false)); + } + + JsonObjectAppendArray(json_attribute, "value", list); + return json_attribute; + } + + case RVAL_TYPE_FNCALL: + { + Rlist *argp = NULL; + FnCall *call = (FnCall *) rval.item; + + JsonElement *json_attribute = JsonObjectCreate(10); + JsonObjectAppendString(json_attribute, "type", "functionCall"); + JsonObjectAppendString(json_attribute, "name", call->name); + + { + JsonElement *arguments = JsonArrayCreate(10); + + for (argp = call->args; argp != NULL; argp = argp->next) + { + JsonArrayAppendObject(arguments, AttributeValueToJson(argp->val, false)); + } + + JsonObjectAppendArray(json_attribute, "arguments", arguments); + } + + return json_attribute; + } + + case RVAL_TYPE_NOPROMISEE: + ProgrammingError("Attempted to export attribute of type: %c", rval.type); + return NULL; + } + + assert(false); + return NULL; +} + +static JsonElement *CreateContextAsJson(const char *name, const char *children_name, JsonElement *children) +{ + JsonElement *json = JsonObjectCreate(10); + + JsonObjectAppendString(json, "name", name); + JsonObjectAppendArray(json, children_name, children); + + return json; +} + +static JsonElement *BodyContextsToJson(const Seq *constraints) +{ + JsonElement *json_contexts = JsonArrayCreate(10); + JsonElement *json_attributes = JsonArrayCreate(10); + char *current_context = "any"; + + for (size_t i = 0; i < SeqLength(constraints); i++) + { + Constraint *cp = SeqAt(constraints, i); + + JsonElement *json_attribute = JsonObjectCreate(10); + + if (strcmp(current_context, cp->classes) != 0) + { + JsonArrayAppendObject(json_contexts, + CreateContextAsJson(current_context, + "attributes", json_attributes)); + json_attributes = JsonArrayCreate(10); + current_context = cp->classes; + } + + JsonObjectAppendInteger(json_attribute, "line", cp->offset.line); + + JsonObjectAppendString(json_attribute, "lval", cp->lval); + JsonObjectAppendObject(json_attribute, "rval", AttributeValueToJson(cp->rval, false)); + JsonArrayAppendObject(json_attributes, json_attribute); + } + + JsonArrayAppendObject(json_contexts, + CreateContextAsJson(current_context, + "attributes", json_attributes)); + + return json_contexts; +} + +static JsonElement *BundleContextsToJson(const Seq *promises) +{ + JsonElement *json_contexts = JsonArrayCreate(10); + JsonElement *json_promises = JsonArrayCreate(10); + char *current_context = NULL; + + for (size_t ppi = 0; ppi < SeqLength(promises); ppi++) + { + Promise *pp = SeqAt(promises, ppi); + + if (!current_context) + { + current_context = pp->classes; + } + + JsonElement *json_promise = JsonObjectCreate(10); + + if (strcmp(current_context, pp->classes) != 0) + { + JsonArrayAppendObject(json_contexts, + CreateContextAsJson(current_context, + "promises", json_promises)); + json_promises = JsonArrayCreate(10); + current_context = pp->classes; + } + + JsonObjectAppendInteger(json_promise, "line", pp->offset.line); + + { + JsonElement *json_promise_attributes = JsonArrayCreate(10); + + for (size_t k = 0; k < SeqLength(pp->conlist); k++) + { + Constraint *cp = SeqAt(pp->conlist, k); + + JsonElement *json_attribute = JsonObjectCreate(10); + + JsonObjectAppendInteger(json_attribute, "line", cp->offset.line); + + JsonObjectAppendString(json_attribute, "lval", cp->lval); + JsonElement *json_rval = AttributeValueToJson(cp->rval, cp->references_body); + if (JsonGetContainerType(json_rval) == JSON_CONTAINER_TYPE_ARRAY) + { + JsonObjectAppendArray(json_attribute, "rval", json_rval); + } + else + { + JsonObjectAppendObject(json_attribute, "rval", json_rval); + } + JsonArrayAppendObject(json_promise_attributes, json_attribute); + } + + JsonObjectAppendString(json_promise, "promiser", pp->promiser); + + switch (pp->promisee.type) + { + case RVAL_TYPE_SCALAR: + JsonObjectAppendString(json_promise, "promisee", pp->promisee.item); + break; + + case RVAL_TYPE_LIST: + { + JsonElement *promisee_list = JsonArrayCreate(10); + for (const Rlist *rp = pp->promisee.item; rp; rp = rp->next) + { + JsonArrayAppendString(promisee_list, RlistScalarValue(rp)); + } + JsonObjectAppendArray(json_promise, "promisee", promisee_list); + } + break; + + default: + break; + } + + JsonObjectAppendArray(json_promise, "attributes", json_promise_attributes); + } + JsonArrayAppendObject(json_promises, json_promise); + } + + if (JsonLength(json_promises) > 0) + { + JsonArrayAppendObject(json_contexts, + CreateContextAsJson(current_context, + "promises", json_promises)); + } + + return json_contexts; +} + +/** + * @brief Serialize a bundle as JSON + * @param bundle The bundle to serialize + * @return A JsonElement representing the input bundle + */ +JsonElement *BundleToJson(const Bundle *bundle) +{ + JsonElement *json_bundle = JsonObjectCreate(10); + + if (bundle->source_path) + { + JsonObjectAppendString(json_bundle, "sourcePath", bundle->source_path); + } + JsonObjectAppendInteger(json_bundle, "line", bundle->offset.line); + + JsonObjectAppendString(json_bundle, "namespace", bundle->ns); + JsonObjectAppendString(json_bundle, "name", bundle->name); + JsonObjectAppendString(json_bundle, "bundleType", bundle->type); + + { + JsonElement *json_args = JsonArrayCreate(10); + Rlist *argp = NULL; + + for (argp = bundle->args; argp != NULL; argp = argp->next) + { + JsonArrayAppendString(json_args, RlistScalarValue(argp)); + } + + JsonObjectAppendArray(json_bundle, "arguments", json_args); + } + + { + JsonElement *json_promise_types = JsonArrayCreate(10); + + for (size_t i = 0; i < SeqLength(bundle->sections); i++) + { + const BundleSection *sp = SeqAt(bundle->sections, i); + + JsonElement *json_promise_type = JsonObjectCreate(10); + + JsonObjectAppendInteger(json_promise_type, "line", sp->offset.line); + JsonObjectAppendString(json_promise_type, "name", sp->promise_type); + JsonObjectAppendArray(json_promise_type, "contexts", BundleContextsToJson(sp->promises)); + + JsonArrayAppendObject(json_promise_types, json_promise_type); + } + + JsonObjectAppendArray(json_bundle, "promiseTypes", json_promise_types); + } + + return json_bundle; +} + +/** + * @brief Serialize a body as JSON + * @param body The body to serialize + * @return A JsonElement representing the input body + */ +JsonElement *BodyToJson(const Body *body) +{ + JsonElement *json_body = JsonObjectCreate(10); + + if (body->source_path) + { + JsonObjectAppendString(json_body, "sourcePath", body->source_path); + } + JsonObjectAppendInteger(json_body, "line", body->offset.line); + + JsonObjectAppendString(json_body, "namespace", body->ns); + JsonObjectAppendString(json_body, "name", body->name); + JsonObjectAppendString(json_body, "bodyType", body->type); + + { + JsonElement *json_args = JsonArrayCreate(10); + Rlist *argp = NULL; + + for (argp = body->args; argp != NULL; argp = argp->next) + { + JsonArrayAppendString(json_args, RlistScalarValue(argp)); + } + + JsonObjectAppendArray(json_body, "arguments", json_args); + } + + JsonObjectAppendArray(json_body, "contexts", BodyContextsToJson(body->conlist)); + + return json_body; +} + +/** + * @brief Serialize a policy as JSON + * @param policy The policy to serialize + * @return A JsonElement representing the input policy + */ +JsonElement *PolicyToJson(const Policy *policy) +{ + JsonElement *json_policy = JsonObjectCreate(10); + + { + JsonElement *json_bundles = JsonArrayCreate(10); + + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + const Bundle *bp = SeqAt(policy->bundles, i); + JsonArrayAppendObject(json_bundles, BundleToJson(bp)); + } + + JsonObjectAppendArray(json_policy, "bundles", json_bundles); + } + + { + JsonElement *json_bodies = JsonArrayCreate(10); + + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + const Body *bdp = SeqAt(policy->bodies, i); + + JsonArrayAppendObject(json_bodies, BodyToJson(bdp)); + } + + JsonObjectAppendArray(json_policy, "bodies", json_bodies); + } + + return json_policy; +} + +/****************************************************************************/ + + +static void IndentPrint(Writer *writer, int indent_level) +{ + static const int PRETTY_PRINT_SPACES_PER_INDENT = 2; + + int i = 0; + + for (i = 0; i < PRETTY_PRINT_SPACES_PER_INDENT * indent_level; i++) + { + WriterWriteChar(writer, ' '); + } +} + +static void AttributeToString(Writer *writer, Constraint *attribute, bool symbolic_reference) +{ + WriterWriteF(writer, "%s => ", attribute->lval); + if (symbolic_reference) + { + RvalWrite(writer, attribute->rval); + } + else + { + RvalWriteQuoted(writer, attribute->rval); + } +} + + +static void ArgumentsToString(Writer *writer, Rlist *args) +{ + Rlist *argp = NULL; + + WriterWriteChar(writer, '('); + for (argp = args; argp != NULL; argp = argp->next) + { + WriterWriteF(writer, "%s", RlistScalarValue(argp)); + + if (argp->next != NULL) + { + WriterWrite(writer, ", "); + } + } + WriterWriteChar(writer, ')'); +} + + +void BodyToString(Writer *writer, Body *body) +{ + char *current_class = NULL; + + WriterWriteF(writer, "body %s %s", body->type, body->name); + ArgumentsToString(writer, body->args); + WriterWrite(writer, "\n{"); + + for (size_t i = 0; i < SeqLength(body->conlist); i++) + { + Constraint *cp = SeqAt(body->conlist, i); + + if (current_class == NULL || strcmp(cp->classes, current_class) != 0) + { + current_class = cp->classes; + + if (strcmp(current_class, "any") == 0) + { + WriterWrite(writer, "\n"); + } + else + { + WriterWriteF(writer, "\n\n%s::", current_class); + } + } + + IndentPrint(writer, 1); + AttributeToString(writer, cp, false); + WriterWriteChar(writer, ';'); + WriterWriteChar(writer, '\n'); + } + + WriterWrite(writer, "\n}\n"); +} + + +void BundleToString(Writer *writer, Bundle *bundle) +{ + WriterWriteF(writer, "bundle %s %s", bundle->type, bundle->name); + ArgumentsToString(writer, bundle->args); + WriterWrite(writer, "\n{"); + + for (size_t i = 0; i < SeqLength(bundle->sections); i++) + { + BundleSection *section = SeqAt(bundle->sections, i); + + WriterWriteF(writer, "\n%s:\n", section->promise_type); + + char *current_class = NULL; + for (size_t ppi = 0; ppi < SeqLength(section->promises); ppi++) + { + Promise *pp = SeqAt(section->promises, ppi); + + if (current_class == NULL || strcmp(pp->classes, current_class) != 0) + { + current_class = pp->classes; + IndentPrint(writer, 1); + WriterWriteF(writer, "%s::\n", current_class); + } + + IndentPrint(writer, 2); + ScalarWrite(writer, pp->promiser, true, false); + + /* FIX: add support + * + if (pp->promisee != NULL) + { + fprintf(out, "%s", pp->promisee); + } + */ + + for (size_t k = 0; k < SeqLength(pp->conlist); k++) + { + Constraint *cp = SeqAt(pp->conlist, k); + + IndentPrint(writer, 4); + AttributeToString(writer, cp, cp->references_body); + if (k < SeqLength(pp->conlist)-1) + { + WriterWriteChar(writer, ','); + WriterWriteChar(writer, '\n'); + } + } + WriterWriteChar(writer, ';'); + WriterWriteChar(writer, '\n'); + } + + if (i == (SeqLength(bundle->sections) - 1)) + { + WriterWriteChar(writer, '\n'); + } + } + + WriterWrite(writer, "\n}\n"); +} + +/** + * @brief Pretty-print a policy + * @param policy The policy to print + * @param writer Writer to write into + */ +void PolicyToString(const Policy *policy, Writer *writer) +{ + for (size_t i = 0; i < SeqLength(policy->bundles); i++) + { + Bundle *bundle = SeqAt(policy->bundles, i); + BundleToString(writer, bundle); + WriterWriteChar(writer, '\n'); + } + + for (size_t i = 0; i < SeqLength(policy->bodies); i++) + { + Body *body = SeqAt(policy->bodies, i); + BodyToString(writer, body); + WriterWriteChar(writer, '\n'); + } + +} + +//***************************************************************************** + +static Rval RvalFromJson(JsonElement *json_rval) +{ + const char *type = JsonObjectGetAsString(json_rval, "type"); + + if (strcmp("string", type) == 0 || strcmp("symbol", type) == 0) + { + const char *value = JsonObjectGetAsString(json_rval, "value"); + return ((Rval) { xstrdup(value), RVAL_TYPE_SCALAR }); + } + else if (strcmp("list", type) == 0) + { + JsonElement *json_list = JsonObjectGetAsArray(json_rval, "value"); + Rlist *rlist = NULL; + + for (size_t i = 0; i < JsonLength(json_list); i++) + { + Rval list_value = RvalFromJson(JsonArrayGetAsObject(json_list, i)); + RlistAppend(&rlist, list_value.item, list_value.type); + RvalDestroy(list_value); + } + + return ((Rval) { rlist, RVAL_TYPE_LIST }); + } + else if (strcmp("functionCall", type) == 0) + { + const char *name = JsonObjectGetAsString(json_rval, "name"); + JsonElement *json_args = JsonObjectGetAsArray(json_rval, "arguments"); + Rlist *args = NULL; + + for (size_t i = 0; i < JsonLength(json_args); i++) + { + JsonElement *json_arg = JsonArrayGetAsObject(json_args, i); + Rval arg = RvalFromJson(json_arg); + + RlistAppend(&args, arg.item, arg.type); + RvalDestroy(arg); + } + + FnCall *fn = FnCallNew(name, args); + + return ((Rval) { fn, RVAL_TYPE_FNCALL }); + } + else + { + ProgrammingError("Unexpected rval type: %s", type); + } +} + +static Constraint *PromiseAppendConstraintJson(Promise *promise, JsonElement *json_constraint) +{ + const char *lval = JsonObjectGetAsString(json_constraint, "lval"); + + JsonElement *json_rval = JsonObjectGetAsObject(json_constraint, "rval"); + const char *type = JsonObjectGetAsString(json_rval, "type"); + + Rval rval = RvalFromJson(json_rval); + + Constraint *cp = PromiseAppendConstraint(promise, lval, rval, (strcmp("symbol", type) == 0)); + + return cp; +} + +static Promise *BundleSectionAppendPromiseJson(BundleSection *section, JsonElement *json_promise, const char *context) +{ + const char *promiser = JsonObjectGetAsString(json_promise, "promiser"); + + Promise *promise = BundleSectionAppendPromise(section, promiser, (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, context, NULL); + + JsonElement *json_attributes = JsonObjectGetAsArray(json_promise, "attributes"); + for (size_t i = 0; i < JsonLength(json_attributes); i++) + { + JsonElement *json_attribute = JsonArrayGetAsObject(json_attributes, i); + PromiseAppendConstraintJson(promise, json_attribute); + } + + return promise; +} + +static BundleSection *BundleAppendSectionJson(Bundle *bundle, JsonElement *json_promise_type) +{ + const char *name = JsonObjectGetAsString(json_promise_type, "name"); + + BundleSection *section = BundleAppendSection(bundle, name); + + JsonElement *json_contexts = JsonObjectGetAsArray(json_promise_type, "contexts"); + for (size_t i = 0; i < JsonLength(json_contexts); i++) + { + JsonElement *json_context = JsonArrayGetAsObject(json_contexts, i); + + const char *context = JsonObjectGetAsString(json_context, "name"); + + JsonElement *json_context_promises = JsonObjectGetAsArray(json_context, "promises"); + for (size_t j = 0; j < JsonLength(json_context_promises); j++) + { + JsonElement *json_promise = JsonArrayGetAsObject(json_context_promises, j); + BundleSectionAppendPromiseJson(section, json_promise, context); + } + } + + return section; +} + +static Bundle *PolicyAppendBundleJson(Policy *policy, JsonElement *json_bundle) +{ + const char *ns = JsonObjectGetAsString(json_bundle, "namespace"); + const char *name = JsonObjectGetAsString(json_bundle, "name"); + const char *type = JsonObjectGetAsString(json_bundle, "bundleType"); + const char *source_path = JsonObjectGetAsString(json_bundle, "sourcePath"); + + Rlist *args = NULL; + { + JsonElement *json_args = JsonObjectGetAsArray(json_bundle, "arguments"); + for (size_t i = 0; i < JsonLength(json_args); i++) + { + RlistAppendScalar(&args, JsonArrayGetAsString(json_args, i)); + } + } + + Bundle *bundle = PolicyAppendBundle(policy, ns, name, type, args, source_path); + + { + JsonElement *json_promise_types = JsonObjectGetAsArray(json_bundle, "promiseTypes"); + for (size_t i = 0; i < JsonLength(json_promise_types); i++) + { + JsonElement *json_promise_type = JsonArrayGetAsObject(json_promise_types, i); + BundleAppendSectionJson(bundle, json_promise_type); + } + } + + return bundle; +} + +static Constraint *BodyAppendConstraintJson(Body *body, JsonElement *json_constraint, const char *context) +{ + const char *lval = JsonObjectGetAsString(json_constraint, "lval"); + + JsonElement *json_rval = JsonObjectGetAsObject(json_constraint, "rval"); + const char *type = JsonObjectGetAsString(json_rval, "type"); + + Rval rval = RvalFromJson(json_rval); + + Constraint *cp = BodyAppendConstraint(body, lval, rval, context, (strcmp("symbol", type) == 0)); + + return cp; +} + +static Body *PolicyAppendBodyJson(Policy *policy, JsonElement *json_body) +{ + const char *ns = JsonObjectGetAsString(json_body, "namespace"); + const char *name = JsonObjectGetAsString(json_body, "name"); + const char *type = JsonObjectGetAsString(json_body, "bodyType"); + const char *source_path = JsonObjectGetAsString(json_body, "sourcePath"); + + Rlist *args = NULL; + { + JsonElement *json_args = JsonObjectGetAsArray(json_body, "arguments"); + for (size_t i = 0; i < JsonLength(json_args); i++) + { + RlistAppendScalar(&args, JsonArrayGetAsString(json_args, i)); + } + } + + Body *body = PolicyAppendBody(policy, ns, name, type, args, source_path, false); + RlistDestroy(args); // It's copied by PolicyAppendBody() + { + JsonElement *json_contexts = JsonObjectGetAsArray(json_body, "contexts"); + for (size_t i = 0; i < JsonLength(json_contexts); i++) + { + JsonElement *json_context = JsonArrayGetAsObject(json_contexts, i); + const char *context = JsonObjectGetAsString(json_context, "name"); + + { + JsonElement *json_attributes = JsonObjectGetAsArray(json_context, "attributes"); + for (size_t j = 0; j < JsonLength(json_attributes); j++) + { + JsonElement *json_attribute = JsonArrayGetAsObject(json_attributes, j); + BodyAppendConstraintJson(body, json_attribute, context); + } + } + } + } + + return body; +} + +/** + * @brief Deserialize a policy from JSON + * @param json_policy JSON to deserialize + * @return A policy DOM + */ +Policy *PolicyFromJson(JsonElement *json_policy) +{ + Policy *policy = PolicyNew(); + + JsonElement *json_bundles = JsonObjectGetAsArray(json_policy, "bundles"); + JsonElement *json_bodies = JsonObjectGetAsArray(json_policy, "bodies"); + + if ((json_bundles == NULL) && (json_bodies == NULL)) + { + return NULL; + } + + if (json_bundles != NULL) + { + for (size_t i = 0; i < JsonLength(json_bundles); i++) + { + JsonElement *json_bundle = JsonArrayGetAsObject(json_bundles, i); + PolicyAppendBundleJson(policy, json_bundle); + } + } + if (json_bodies != NULL) + { + for (size_t i = 0; i < JsonLength(json_bodies); i++) + { + JsonElement *json_body = JsonArrayGetAsObject(json_bodies, i); + PolicyAppendBodyJson(policy, json_body); + } + } + + return policy; +} + +/** + * @brief A sequence of constraints matching the l-value. + * @param body Body to query + * @param lval l-value to match + * @return Sequence of pointers to the constraints. Destroying it does not alter the DOM. + */ +Seq *BodyGetConstraint(Body *body, const char *lval) +{ + Seq *matches = SeqNew(5, NULL); + + for (size_t i = 0; i < SeqLength(body->conlist); i++) + { + Constraint *cp = SeqAt(body->conlist, i); + if (strcmp(cp->lval, lval) == 0) + { + SeqAppend(matches, cp); + } + } + + return matches; +} + +bool BodyHasConstraint(const Body *body, const char *lval) +{ + for (size_t i = 0; i < SeqLength(body->conlist); i++) + { + Constraint *cp = SeqAt(body->conlist, i); + if (StringEqual(lval, cp->lval)) + { + return true; + } + } + + return false; +} + +/** + * @brief Get the context of the given constraint + * @param cp + * @return context. never returns NULL. + */ +const char *ConstraintContext(const Constraint *cp) +{ + switch (cp->type) + { + case POLICY_ELEMENT_TYPE_BODY: + return cp->classes; + + case POLICY_ELEMENT_TYPE_BUNDLE: + return cp->parent.promise->classes; + + default: + ProgrammingError("Constraint had parent element type: %d", cp->type); + return NULL; + } +} + +/** + * @brief Returns the first effective constraint from a list of candidates, depending on evaluation state. + * @param constraints The list of potential candidates + * @return The effective constraint, or NULL if none are found. + */ +Constraint *EffectiveConstraint(const EvalContext *ctx, Seq *constraints) +{ + for (size_t i = 0; i < SeqLength(constraints); i++) + { + Constraint *cp = SeqAt(constraints, i); + + const char *context = ConstraintContext(cp); + if (IsDefinedClass(ctx, context)) + { + return cp; + } + } + + return NULL; +} + +void ConstraintDestroy(Constraint *cp) +{ + if (cp) + { + RvalDestroy(cp->rval); + free(cp->lval); + free(cp->classes); + + free(cp); + } +} + +/*****************************************************************************/ + +/** + * @brief Get the boolean value of the first effective constraint found matching, from a promise + * @return true/false + * @note Returns #false if no matching constraint is found + */ +int PromiseGetConstraintAsBoolean(const EvalContext *ctx, const char *lval, const Promise *pp) +{ + return PromiseGetConstraintAsBooleanWithDefault(ctx, lval, pp, false, false); +} + +/** + * @brief Get the boolean value of the first effective constraint found matching, from a promise + * @return true/false + */ +int PromiseGetConstraintAsBooleanWithDefault(const EvalContext *ctx, const char *lval, const Promise *pp, + int default_val, bool with_warning) +{ + assert(pp != NULL); + + int retval = CF_UNDEFINED; + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + + if (strcmp(cp->lval, lval) == 0) + { + if (IsDefinedClass(ctx, cp->classes)) + { + if (retval != CF_UNDEFINED) + { + Log(LOG_LEVEL_ERR, "Multiple '%s' (boolean) constraints break this promise", lval); + PromiseRef(LOG_LEVEL_ERR, pp); + } + } + else + { + continue; + } + + if (cp->rval.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, "Type mismatch on rhs - expected type %c for boolean constraint '%s'", + cp->rval.type, lval); + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Aborted"); + } + + if (strcmp(cp->rval.item, "true") == 0 || strcmp(cp->rval.item, "yes") == 0) + { + retval = true; + continue; + } + + if (strcmp(cp->rval.item, "false") == 0 || strcmp(cp->rval.item, "no") == 0) + { + retval = false; + } + } + } + + if (retval == CF_UNDEFINED) + { + if (with_warning) + { + Log(LOG_LEVEL_WARNING, + "Using the default value '%s' for attribute %s (promiser: %s, file: %s:%zd), please set it explicitly", + default_val ? "true" : "false", lval, + pp->promiser, PromiseGetBundle(pp)->source_path, pp->offset.line); + } + retval = default_val; + } + + return retval; +} + +/*****************************************************************************/ + +/** + * @brief Get the trinary boolean value of the first effective constraint found matching + * @param lval + * @param constraints + * @return True/false, or CF_UNDEFINED if not found + */ +int ConstraintsGetAsBoolean(const EvalContext *ctx, const char *lval, const Seq *constraints) +{ + int retval = CF_UNDEFINED; + + for (size_t i = 0; i < SeqLength(constraints); i++) + { + Constraint *cp = SeqAt(constraints, i); + + if (strcmp(cp->lval, lval) == 0) + { + if (IsDefinedClass(ctx, cp->classes)) + { + if (retval != CF_UNDEFINED) + { + Log(LOG_LEVEL_ERR, "Multiple '%s' (boolean) body constraints break this promise", lval); + } + } + else + { + continue; + } + + if (cp->rval.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, "Type mismatch - expected type %c for boolean constraint '%s'", + cp->rval.type, lval); + FatalError(ctx, "Aborted"); + } + + if (strcmp(cp->rval.item, "true") == 0 || strcmp(cp->rval.item, "yes") == 0) + { + retval = true; + continue; + } + + if (strcmp(cp->rval.item, "false") == 0 || strcmp(cp->rval.item, "no") == 0) + { + retval = false; + } + } + } + + if (retval == CF_UNDEFINED) + { + retval = false; + } + + return retval; +} + +/*****************************************************************************/ + +bool PromiseBundleOrBodyConstraintExists(const EvalContext *ctx, const char *lval, const Promise *pp) +{ + int retval = CF_UNDEFINED; + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + const Constraint *cp = SeqAt(pp->conlist, i); + + if (strcmp(cp->lval, lval) == 0) + { + if (IsDefinedClass(ctx, cp->classes)) + { + if (retval != CF_UNDEFINED) + { + Log(LOG_LEVEL_ERR, "Multiple '%s' constraints break this promise", lval); + PromiseRef(LOG_LEVEL_ERR, pp); + } + } + else + { + continue; + } + + if (!(cp->rval.type == RVAL_TYPE_FNCALL || cp->rval.type == RVAL_TYPE_SCALAR)) + { + Log(LOG_LEVEL_ERR, + "Anomalous type mismatch - type %c for bundle constraint '%s' did not match internals", + cp->rval.type, lval); + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Aborted"); + } + + return true; + } + } + + return false; +} + +static inline bool CheckScalarNotEmptyVarRef(const char *scalar) +{ + return (!StringEqual("$()", scalar) && !StringEqual("${}", scalar)); +} + +static bool ValidateCustomPromise(const Promise *pp, Seq *errors) +{ + assert(pp != NULL); + assert(pp->parent_section != NULL); + assert(errors != NULL); + + const char *const promise_type = PromiseGetPromiseType(pp); + bool valid = true; + Seq *attributes = pp->conlist; + const size_t length = SeqLength(attributes); + for (size_t i = 0; i < length; ++i) + { + Constraint *attribute = SeqAt(attributes, i); + const char *name = attribute->lval; + if (StringEqual(name, "ifvarclass")) + { + SeqAppend( + errors, + PolicyErrorNew( + POLICY_ELEMENT_TYPE_PROMISE, + pp, + POLICY_ERROR_PROMISE_ATTRIBUTE_NOT_SUPPORTED, + name, + "if", + promise_type)); + valid = false; + } + } + + // TODO: If we are running --full-check, spawn promise module and perform + // validate operation. https://northerntech.atlassian.net/browse/CFE-3430 + + return valid; +} + +static bool PromiseCheck(const Promise *pp, Seq *errors) +{ + assert(pp != NULL); + + bool success = true; + + if (!CheckScalarNotEmptyVarRef(pp->promiser)) + { + SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_PROMISE, pp, + POLICY_ERROR_EMPTY_VARREF)); + success = false; + } + + const bool is_builtin = IsBuiltInPromiseType(PromiseGetPromiseType(pp)); + + const PromiseTypeSyntax *pts = PromiseTypeSyntaxGet(pp->parent_section->parent_bundle->type, + PromiseGetPromiseType(pp)); + + if (is_builtin) + { + if(pts == NULL) + { + // No promise type syntax to check for a built in promise + // This should be an error, for example if trying to use + // methods promises outside agent bundles + SeqAppend(errors, + PolicyErrorNew(POLICY_ELEMENT_TYPE_BUNDLE_SECTION, pp->parent_section, + POLICY_ERROR_PROMISE_TYPE_UNSUPPORTED, + pp->parent_section->promise_type, + pp->parent_section->parent_bundle->type)); + return false; + } + + // check if promise's constraints are valid + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *constraint = SeqAt(pp->conlist, i); + success &= ConstraintCheckSyntax(constraint, errors); + } + } + + if (pts == NULL) + { + assert(!is_builtin); + success &= ValidateCustomPromise(pp, errors); + } + else if (pts->check_promise) + { + assert(is_builtin); + success &= pts->check_promise(pp, errors); + } + + return success; +} + +const char *PromiseGetNamespace(const Promise *pp) +{ + return pp->parent_section->parent_bundle->ns; +} + +const Bundle *PromiseGetBundle(const Promise *pp) +{ + return pp->parent_section->parent_bundle; +} + +const Policy *PromiseGetPolicy(const Promise *pp) +{ + return PromiseGetBundle(pp)->parent_policy; +} + +static void BundlePath(Writer *w, const Bundle *bp) +{ + WriterWriteChar(w, '/'); + WriterWrite(w, bp->ns); + WriterWriteChar(w, '/'); + WriterWrite(w, bp->name); +} + +static void PromiseTypePath(Writer *w, const BundleSection *section) +{ + BundlePath(w, section->parent_bundle); + WriterWriteChar(w, '/'); + WriterWrite(w, section->promise_type); +} + +/** + * @brief Write a string describing the promise location in policy, e.g. /default/foo/packages/'emacs' + */ +void PromisePath(Writer *w, const Promise *pp) +{ + PromiseTypePath(w, pp->parent_section); + WriterWriteChar(w, '/'); + WriterWriteChar(w, '\''); + WriterWrite(w, pp->promiser); + WriterWriteChar(w, '\''); +} + +/** + * @brief Return handle of the promise. + * @param pp + * @return Promise handle or NULL if no handle is provided + */ +const char *PromiseGetHandle(const Promise *pp) +{ + return (const char *)PromiseGetImmediateRvalValue("handle", pp, RVAL_TYPE_SCALAR); +} + +/** + * @brief Get the int value of the first effective constraint found matching, from a promise + * @param lval + * @param pp + * @return Int value, or CF_NOINT + */ +int PromiseGetConstraintAsInt(const EvalContext *ctx, const char *lval, const Promise *pp) +{ + int retval = CF_NOINT; + const Constraint *cp = PromiseGetConstraint(pp, lval); + if (cp) + { + if (cp->rval.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, + "Anomalous type mismatch - expected type for int constraint %s did not match internals", lval); + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Aborted"); + } + + retval = (int) IntFromString((char *) cp->rval.item); + } + + return retval; +} + +/*****************************************************************************/ + +/** + * @brief Get the real value of the first effective constraint found matching, from a promise + * @return true if value could be extracted + */ +bool PromiseGetConstraintAsReal(const EvalContext *ctx, const char *lval, const Promise *pp, double *value_out) +{ + const Constraint *cp = PromiseGetConstraint(pp, lval); + if (cp) + { + if (cp->rval.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, + "Anomalous type mismatch - expected type for int constraint '%s' did not match internals", lval); + FatalError(ctx, "Aborted"); + } + + *value_out = DoubleFromString((char *) cp->rval.item, value_out); + return true; + } + + return false; +} + +/*****************************************************************************/ + +/** + * @return true if successful + */ +static bool Str2Mode(const char *s, mode_t *mode_out) +{ + int a = CF_UNDEFINED; + + if (s == NULL) + { + *mode_out = (mode_t)0; + return true; + } + + sscanf(s, "%o", &a); + + if (a == CF_UNDEFINED) + { + return false; + } + + *mode_out = (mode_t)a; + return true; +} + +/** + * @brief Get the octal value of the first effective constraint found matching, from a promise + * @param lval + * @param list + * @return Double value, or 077 if not found + */ +mode_t PromiseGetConstraintAsOctal(const EvalContext *ctx, const char *lval, const Promise *pp) +{ + mode_t retval = 077; + +// We could handle units here, like kb,b,mb + + const Constraint *cp = PromiseGetConstraint(pp, lval); + if (cp) + { + if (cp->rval.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, + "Anomalous type mismatch - expected type for int constraint %s did not match internals", lval); + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Aborted"); + } + + if (!Str2Mode(cp->rval.item, &retval)) + { + Log(LOG_LEVEL_ERR, "Error reading assumed octal value '%s'", (const char *)cp->rval.item); + PromiseRef(LOG_LEVEL_ERR, pp); + } + } + + return retval; +} + +/*****************************************************************************/ + +#ifdef __MINGW32__ + +uid_t PromiseGetConstraintAsUid(const EvalContext *ctx, const char *lval, const Promise *pp) +{ // we use sids on windows instead + return CF_SAME_OWNER; +} + +#else /* !__MINGW32__ */ + +/** + * @brief Get the uid value of the first effective constraint found matching, from a promise + * @param lval + * @param pp + * @return Uid value, or CF_SAME_OWNER if not found + */ +uid_t PromiseGetConstraintAsUid(const EvalContext *ctx, const char *lval, const Promise *pp) +{ + int retval = CF_SAME_OWNER; + char buffer[CF_MAXVARSIZE]; + + const Constraint *cp = PromiseGetConstraint(pp, lval); + if (cp) + { + if (cp->rval.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, + "Anomalous type mismatch - expected type for owner constraint %s did not match internals", + lval); + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Aborted"); + } + + retval = Str2Uid((char *) cp->rval.item, buffer, pp); + } + + return retval; +} + +#endif /* !__MINGW32__ */ + +/*****************************************************************************/ + +#ifdef __MINGW32__ + +gid_t PromiseGetConstraintAsGid(const EvalContext *ctx, char *lval, const Promise *pp) +{ // not applicable on windows: processes have no group + return CF_SAME_GROUP; +} + +#else /* !__MINGW32__ */ + +/** + * @brief Get the uid value of the first effective constraint found matching, from a promise + * @param lval + * @param pp + * @return Gid value, or CF_SAME_GROUP if not found + */ +gid_t PromiseGetConstraintAsGid(const EvalContext *ctx, char *lval, const Promise *pp) +{ + int retval = CF_SAME_GROUP; + char buffer[CF_MAXVARSIZE]; + + const Constraint *cp = PromiseGetConstraint(pp, lval); + if (cp) + { + if (cp->rval.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, + "Anomalous type mismatch - expected type for group constraint '%s' did not match internals", + lval); + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Aborted"); + } + + retval = Str2Gid((char *) cp->rval.item, buffer, pp); + } + + return retval; +} +#endif /* !__MINGW32__ */ + +/*****************************************************************************/ + +/** + * @brief Get the Rlist value of the first effective constraint found matching, from a promise + * @param lval + * @param list + * @return Rlist or NULL if not found (note: same as empty list) + */ +// FIX: promise constrained classed? +Rlist *PromiseGetConstraintAsList(const EvalContext *ctx, const char *lval, const Promise *pp) +{ + const Constraint *cp = PromiseGetConstraint(pp, lval); + if (cp) + { + if (cp->rval.type != RVAL_TYPE_LIST) + { + Log(LOG_LEVEL_ERR, "Type mismatch on rhs - expected type for list constraint '%s'", lval); + PromiseRef(LOG_LEVEL_ERR, pp); + FatalError(ctx, "Aborted"); + } + + return RvalRlistValue(cp->rval); + } + + return NULL; +} + +/** + * @brief Get the first effective constraint from the promise, also does some checking + * @param promise + * @param lval + * @return Effective constraint if found, otherwise NULL + */ +Constraint *PromiseGetConstraint(const Promise *pp, const char *lval) +{ + if (!pp) + { + return NULL; + } + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + + if (strcmp(cp->lval, lval) == 0) + { + return cp; + } + } + + return NULL; +} + +Constraint *PromiseGetConstraintWithType(const Promise *pp, const char *lval, RvalType type) +{ + assert(pp); + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + if (cp->rval.type != type) { + continue; + } + + if (strcmp(cp->lval, lval) == 0) + { + return cp; + } + } + + return NULL; +} + +/** + * @brief Get the first constraint from the promise + * + * Kill this function with fire once we have separated promise constraints and body constraints. + * + * @param promise + * @param lval + * @return Constraint if found, otherwise NULL + */ +Constraint *PromiseGetImmediateConstraint(const Promise *pp, const char *lval) +{ + if (pp == NULL) + { + return NULL; + } + + for (size_t i = 0; i < SeqLength(pp->conlist); ++i) + { + Constraint *cp = SeqAt(pp->conlist, i); + + if (strcmp(cp->lval, lval) == 0) + { + /* It would be nice to check whether the constraint we have asked + for is defined in promise (not in referenced body), but there + seem to be no way to do it easily. + + Checking for absence of classes does not work, as constrains + obtain classes defined on promise itself. + */ + + return cp; + } + } + + return NULL; +} + +/** + * @brief Get the Rval value of the first constraint that matches the given + * type. Checks that this constraint does not have any contexts attached. + * + * Kill this function with fire once we have separated body constraints and bundle constraints. + * + * @param lval + * @param promise + * @param type + * @return Rval value if found, NULL otherwise + */ +void *PromiseGetImmediateRvalValue(const char *lval, const Promise *pp, RvalType rtype) +{ + const Constraint *constraint = PromiseGetImmediateConstraint(pp, lval); + + if (constraint && constraint->rval.type == rtype) + { + return constraint->rval.item; + } + else + { + return NULL; + } +} + +/*****************************************************************************/ + +/** + * @brief Get the Rval value of the first effective constraint that matches the given type + * @param lval + * @param promise + * @param type + * @return Rval value if found, NULL otherwise + */ +void *PromiseGetConstraintAsRval(const Promise *pp, const char *lval, RvalType rtype) +{ + const Constraint *constraint = PromiseGetConstraint(pp, lval); + + if (constraint && constraint->rval.type == rtype) + { + return constraint->rval.item; + } + else + { + return NULL; + } +} + +/*****************************************************************************/ + +/* + * Check promise constraints while iterating through all slist/containers + * combinations, called from ExpandPromiseAndDo() during PRE-EVAL. This is + * currently running also in cf-promises, but it's enough if such errors are + * detected in the agent run. + * + * TODO remove CommonEvalPromise() actuator, that all it does is call this + * function, and call this one at the beginning of the main actuators, like + * KeepAgentPromise(). + */ +void PromiseRecheckAllConstraints(const EvalContext *ctx, const Promise *pp) +{ + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + SyntaxTypeMatch err = ConstraintCheckType(cp); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + PolicyError *error = + PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, cp, + "In attribute '%s', %s", + cp->lval, SyntaxTypeMatchToString(err)); + char *error_str = PolicyErrorToString(error); + + Log(LOG_LEVEL_ERR, "%s", error_str); + PolicyErrorDestroy(error); + free(error_str); + + FatalError(ctx, "Cannot continue"); + } + } + + /* Check and warn for non-convergence, see commits + 4f8c19b84327b8f3c2e269173196282ccedfdad9 and + 30c109d22e170a781a647b04b4b0a4a2f7244871. */ + if (strcmp(PromiseGetPromiseType(pp), "insert_lines") == 0) + { + /* TODO without static var, actually remove this check from here + * completely, do it at the end of PRE-EVAL promise iterations + * (ExpandPromiseAndDo() after the main loop). */ + static Item *EDIT_ANCHORS = NULL; /* GLOBAL_X */ + + const char *sp = PromiseGetConstraintAsRval(pp, "select_line_matching", + RVAL_TYPE_SCALAR); + if (sp != NULL && !IsExpandable(sp)) + { + /* Avoid adding twice the same select_line_matching anchor. */ + const char *bun = PromiseGetBundle(pp)->name; + const Item *ptr = ReturnItemInClass(EDIT_ANCHORS, sp, bun); + if (ptr != NULL) + { + Log(LOG_LEVEL_INFO, + "insert_lines promise uses the same select_line_matching anchor '%s' as another promise." + " This will lead to non-convergent behaviour unless 'empty_file_before_editing' is set", + sp); + PromiseRef(LOG_LEVEL_INFO, pp); + return; + } + + PrependItem(&EDIT_ANCHORS, sp, bun); + } + } +} + +/*****************************************************************************/ + +static SyntaxTypeMatch ConstraintCheckType(const Constraint *cp) +{ + // Check class + for (size_t i = 0; CF_CLASSBODY[i].lval != NULL; i++) + { + if (strcmp(cp->lval, CF_CLASSBODY[i].lval) == 0) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(cp->lval, cp->rval, CF_CLASSBODY[i].dtype, CF_CLASSBODY[i].range.validation_string, 0); + if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED) + { + return err; + } + } + } + + if (cp->type == POLICY_ELEMENT_TYPE_PROMISE) + { + BundleSection *section = cp->parent.promise->parent_section; + + for (size_t i = 0; i < (size_t) CF3_MODULES; i++) + { + const PromiseTypeSyntax *ssp = CF_ALL_PROMISE_TYPES[i]; + if (!ssp) + { + continue; + } + + for (size_t j = 0; ssp[j].bundle_type != NULL; j++) + { + PromiseTypeSyntax ss = ssp[j]; + + if (ss.promise_type != NULL) + { + if (strcmp(ss.promise_type, section->promise_type) == 0) + { + const ConstraintSyntax *bs = ss.constraints; + + for (size_t l = 0; bs[l].lval != NULL; l++) + { + // No validation for CF_DATA_TYPE_BUNDLE here + // see: PolicyCheckUndefinedBundles() etc. + if (bs[l].dtype == CF_DATA_TYPE_BODY) + { + const ConstraintSyntax *bs2 = bs[l].range.body_type_syntax->constraints; + + for (size_t m = 0; bs2[m].lval != NULL; m++) + { + if (strcmp(cp->lval, bs2[m].lval) == 0) + { + return CheckConstraintTypeMatch(cp->lval, cp->rval, bs2[m].dtype, bs2[m].range.validation_string, 0); + } + } + } + + if (strcmp(cp->lval, bs[l].lval) == 0) + { + return CheckConstraintTypeMatch(cp->lval, cp->rval, bs[l].dtype, bs[l].range.validation_string, 0); + } + } + } + } + } + } + } + +/* Now check the functional modules - extra level of indirection */ + + for (size_t i = 0; CF_COMMON_BODIES[i].lval != NULL; i++) + { + if (CF_COMMON_BODIES[i].dtype == CF_DATA_TYPE_BODY) + { + continue; + } + + if (strcmp(cp->lval, CF_COMMON_BODIES[i].lval) == 0) + { + return CheckConstraintTypeMatch(cp->lval, cp->rval, CF_COMMON_BODIES[i].dtype, CF_COMMON_BODIES[i].range.validation_string, 0); + } + } + + return SYNTAX_TYPE_MATCH_OK; +} + +/** + * @brief Check whether the promise type is allowed one + */ +/* FIXME: need to be done automatically */ +bool BundleTypeCheck(const char *name) +{ + /* FIXME: export size of CF_AGENTTYPES somewhere */ + for (int i = 0; strcmp(CF_AGENTTYPES[i], "") != 0; ++i) + { + if (!strcmp(CF_AGENTTYPES[i], name)) + { + return true; + } + } + + if (!strcmp("knowledge", name)) + { + return true; + } + + if (!strcmp("edit_line", name)) + { + return true; + } + + if (!strcmp("edit_xml", name)) + { + return true; + } + + return false; +} + +bool PolicyHasCustomPromiseType(const Policy *policy, const char *name) +{ + assert(policy != NULL); + Seq *types = policy->custom_promise_types; + const size_t length = SeqLength(types); + for (size_t i = 0; i < length; ++i) + { + Body *type = SeqAt(types, i); + if (StringEqual(name, type->name)) + { + return true; + } + } + return false; +} diff --git a/libpromises/policy.h b/libpromises/policy.h new file mode 100644 index 0000000000..3d9124b432 --- /dev/null +++ b/libpromises/policy.h @@ -0,0 +1,240 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_POLICY_H +#define CFENGINE_POLICY_H + +#include + +#include +#include +#include +#include + +typedef enum +{ + POLICY_ELEMENT_TYPE_POLICY, + POLICY_ELEMENT_TYPE_BUNDLE, + POLICY_ELEMENT_TYPE_BODY, + POLICY_ELEMENT_TYPE_BUNDLE_SECTION, + POLICY_ELEMENT_TYPE_PROMISE, + POLICY_ELEMENT_TYPE_CONSTRAINT +} PolicyElementType; + +typedef struct +{ + PolicyElementType type; + const void *subject; + char *message; +} PolicyError; + +struct Policy_ +{ + char *release_id; + + Seq *bundles; + Seq *bodies; + Seq *custom_promise_types; + StringMap *policy_files_hashes; +}; + +typedef struct +{ + size_t start; + size_t end; + size_t line; + size_t context; +} SourceOffset; + +struct Bundle_ +{ + Policy *parent_policy; + + char *type; + char *name; + char *ns; + Rlist *args; + + Seq *sections; + Seq *custom_sections; + + char *source_path; + SourceOffset offset; +}; + +struct Body_ +{ + Policy *parent_policy; + + char *type; + char *name; + char *ns; + Rlist *args; + + Seq *conlist; + bool is_custom; + + char *source_path; + SourceOffset offset; +}; + +struct BundleSection_ +{ + Bundle *parent_bundle; + + char *promise_type; + Seq *promises; + + SourceOffset offset; +}; + +struct Promise_ +{ + BundleSection *parent_section; + + char *classes; + char *comment; + char *promiser; + Rval promisee; + Seq *conlist; + + const Promise *org_pp; /* A ptr to the unexpanded raw promise */ + + SourceOffset offset; +}; + +struct Constraint_ +{ + PolicyElementType type; + union { + Promise *promise; + Body *body; + } parent; + + char *lval; + Rval rval; + + char *classes; + bool references_body; + + SourceOffset offset; +}; + +const char *NamespaceDefault(void); + +Policy *PolicyNew(void); +int PolicyCompare(const void *a, const void *b); +void PolicyDestroy(Policy *policy); +unsigned PolicyHash(const Policy *policy); + +StringSet *PolicySourceFiles(const Policy *policy); +const char *PolicyGetPolicyFileHash(const Policy *policy, const char *policy_file_path); + +Policy *PolicyMerge(Policy *a, Policy *b); +Body *PolicyGetBody(const Policy *policy, const char *ns, const char *type, const char *name); +Bundle *PolicyGetBundle(const Policy *policy, const char *ns, const char *type, const char *name); +bool PolicyIsRunnable(const Policy *policy); +const Policy *PolicyFromPromise(const Promise *promise); +char *BundleQualifiedName(const Bundle *bundle); + +PolicyError *PolicyErrorNew(PolicyElementType type, const void *subject, const char *error_msg, ...); +void PolicyErrorDestroy(PolicyError *error); +void PolicyErrorWrite(Writer *writer, const PolicyError *error); + +bool PolicyCheckPartial(const Policy *policy, Seq *errors); +bool PolicyCheckRunnable(const EvalContext *ctx, const Policy *policy, Seq *errors); + +Bundle *PolicyAppendBundle(Policy *policy, const char *ns, const char *name, const char *type, const Rlist *args, const char *source_path); +Body *PolicyAppendBody(Policy *policy, const char *ns, const char *name, + const char *type, Rlist *args, const char *source_path, + bool is_custom); +Body *PolicyAppendPromiseBlock(Policy *policy, const char *ns, const char *name, const char *type, Rlist *args, const char *source_path); + +JsonElement *PolicyToJson(const Policy *policy); +JsonElement *BundleToJson(const Bundle *bundle); +JsonElement *BodyToJson(const Body *body); +Policy *PolicyFromJson(JsonElement *json_policy); +void PolicyToString(const Policy *policy, Writer *writer); + +BundleSection *BundleAppendSection(Bundle *bundle, const char *promise_type); +const BundleSection *BundleGetSection(const Bundle *bp, const char *promise_type); + +Constraint *BodyAppendConstraint(Body *body, const char *lval, Rval rval, const char *classes, bool references_body); +Seq *BodyGetConstraint(Body *body, const char *lval); +bool BodyHasConstraint(const Body *body, const char *lval); + +const char *ConstraintGetNamespace(const Constraint *cp); + +Promise *BundleSectionAppendPromise(BundleSection *section, const char *promiser, Rval promisee, const char *classes, const char *varclasses); +void BundleSectionDestroy(BundleSection *section); + +void PromiseDestroy(Promise *pp); + +Constraint *PromiseAppendConstraint(Promise *promise, const char *lval, Rval rval, bool references_body); + +const char *PromiseGetNamespace(const Promise *pp); +const Bundle *PromiseGetBundle(const Promise *pp); +const Policy *PromiseGetPolicy(const Promise *pp); + +static inline const char *PromiseGetPromiseType(const Promise *pp) +{ + assert(pp != NULL); + return pp->parent_section->promise_type; +} + +void PromisePath(Writer *w, const Promise *pp); +const char *PromiseGetHandle(const Promise *pp); +int PromiseGetConstraintAsInt(const EvalContext *ctx, const char *lval, const Promise *pp); +bool PromiseGetConstraintAsReal(const EvalContext *ctx, const char *lval, const Promise *list, double *value_out); +mode_t PromiseGetConstraintAsOctal(const EvalContext *ctx, const char *lval, const Promise *list); +uid_t PromiseGetConstraintAsUid(const EvalContext *ctx, const char *lval, const Promise *pp); +gid_t PromiseGetConstraintAsGid(const EvalContext *ctx, char *lval, const Promise *pp); +Rlist *PromiseGetConstraintAsList(const EvalContext *ctx, const char *lval, const Promise *pp); +int PromiseGetConstraintAsBoolean(const EvalContext *ctx, const char *lval, const Promise *list); +int PromiseGetConstraintAsBooleanWithDefault(const EvalContext *ctx, const char *lval, const Promise *pp, + int default_val, bool with_warning); +Constraint *PromiseGetConstraintWithType(const Promise *promise, const char *lval, RvalType type); +Constraint *PromiseGetImmediateConstraint(const Promise *promise, const char *lval); +void *PromiseGetConstraintAsRval(const Promise *promise, const char *lval, RvalType type); +Constraint *PromiseGetConstraint(const Promise *promise, const char *lval); + +bool PromiseBundleOrBodyConstraintExists(const EvalContext *ctx, const char *lval, const Promise *pp); + +void PromiseRecheckAllConstraints(const EvalContext *ctx, const Promise *pp); + +void ConstraintDestroy(Constraint *cp); +int ConstraintsGetAsBoolean(const EvalContext *ctx, const char *lval, const Seq *constraints); +const char *ConstraintContext(const Constraint *cp); +Constraint *EffectiveConstraint(const EvalContext *ctx, Seq *constraints); + +void *PromiseGetImmediateRvalValue(const char *lval, const Promise *pp, RvalType rtype); + +char *QualifiedNameNamespaceComponent(const char *qualified_name); +char *QualifiedNameScopeComponent(const char *qualified_name); +bool BundleTypeCheck(const char *name); +Rval DefaultBundleConstraint(const Promise *pp, char *promisetype); + +bool PolicyHasCustomPromiseType(const Policy *policy, const char *name); + +#endif diff --git a/libpromises/process_aix.c b/libpromises/process_aix.c new file mode 100644 index 0000000000..e8fc3fb0f0 --- /dev/null +++ b/libpromises/process_aix.c @@ -0,0 +1,87 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include + +#include + +/* + * AIX 5.3 is missing this declaration + */ +#ifndef HAVE_GETPROCS64 +int getprocs64(void *procsinfo, int sizproc, void *fdsinfo, int sizfd, pid_t *index, int count); +#endif +static bool FillProcEntry(struct procentry64* pe, pid_t pid) +{ + pid_t nextpid = pid; + int ret = getprocs64(pe, sizeof(*pe), NULL, 0, &nextpid, 1); + + /* + * getprocs64 may + * - return -1 => we can't access this process (EPERM) + * - return 0 => end of process table, no such process + * - return another process' info => no such process + */ + return ret == 1 && pe->pi_pid == pid; +} + +time_t GetProcessStartTime(pid_t pid) +{ + struct procentry64 pe; + + if (FillProcEntry(&pe, pid)) + { + return pe.pi_start; + } + else + { + return PROCESS_START_TIME_UNKNOWN; + } +} + +ProcessState GetProcessState(pid_t pid) +{ + struct procentry64 pe; + + if (FillProcEntry(&pe, pid)) + { + switch (pe.pi_state) + { + case SSTOP: + return PROCESS_STATE_STOPPED; + case SZOMB: + return PROCESS_STATE_ZOMBIE; + default: + return PROCESS_STATE_RUNNING; + } + } + else + { + return PROCESS_STATE_DOES_NOT_EXIST; + } +} diff --git a/libpromises/process_freebsd.c b/libpromises/process_freebsd.c new file mode 100644 index 0000000000..b5fda4734c --- /dev/null +++ b/libpromises/process_freebsd.c @@ -0,0 +1,108 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include + +#include +#include +#include + +typedef struct +{ + time_t starttime; + char state; +} ProcessStat; + +static bool GetProcessStat(pid_t pid, ProcessStat *state) +{ + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; + struct kinfo_proc psinfo; + size_t len = sizeof(psinfo); + + if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &psinfo, &len, NULL, 0) == 0) + { + state->starttime = psinfo.ki_start.tv_sec; + + switch(psinfo.ki_stat) + { + case SRUN: + case SIDL: + state->state = 'R'; + break; + case SSTOP: + state->state = 'T'; + break; + case SSLEEP: + state->state = 'S'; + break; + case SZOMB: + state->state = 'Z'; + break; + default: + state->state = 'X'; + } + } + else + { + return false; + } + return true; +} + +time_t GetProcessStartTime(pid_t pid) +{ + ProcessStat st; + if (GetProcessStat(pid, &st)) + { + return st.starttime; + } + else + { + return PROCESS_START_TIME_UNKNOWN; + } +} + +ProcessState GetProcessState(pid_t pid) +{ + ProcessStat st; + if (GetProcessStat(pid, &st)) + { + switch (st.state) + { + case 'T': + return PROCESS_STATE_STOPPED; + case 'Z': + return PROCESS_STATE_ZOMBIE; + default: + return PROCESS_STATE_RUNNING; + } + } + else + { + return PROCESS_STATE_DOES_NOT_EXIST; + } +} diff --git a/libpromises/process_hpux.c b/libpromises/process_hpux.c new file mode 100644 index 0000000000..f4b7d01b1f --- /dev/null +++ b/libpromises/process_hpux.c @@ -0,0 +1,64 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#include + +time_t GetProcessStartTime(pid_t pid) +{ + struct pst_status proc; + + if (pstat_getproc(&proc, sizeof(proc), 0, pid) > 0) + { + return proc.pst_start; + } + else + { + return PROCESS_START_TIME_UNKNOWN; + } +} + +ProcessState GetProcessState(pid_t pid) +{ + struct pst_status proc; + + if (pstat_getproc(&proc, sizeof(proc), 0, pid) > 0) + { + switch (proc.pst_stat) + { + case PS_STOP: + return PROCESS_STATE_STOPPED; + case PS_ZOMBIE: + return PROCESS_STATE_ZOMBIE; + default: + return PROCESS_STATE_RUNNING; + } + } + else + { + return PROCESS_STATE_DOES_NOT_EXIST; + } +} diff --git a/libpromises/process_lib.h b/libpromises/process_lib.h new file mode 100644 index 0000000000..0daad4c92d --- /dev/null +++ b/libpromises/process_lib.h @@ -0,0 +1,61 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PROCESS_H +#define CFENGINE_PROCESS_H + +#include + + +/* TODO move to libutils, once windows implementation is merged. */ + + +#define PROCESS_START_TIME_UNKNOWN ((time_t) 0) + + +/** + * Obtain start time of specified process. + * + * @return start time (Unix timestamp) of specified process + * @return PROCESS_START_TIME_UNKNOWN if start time cannot be determined + */ +time_t GetProcessStartTime(pid_t pid); + +/** + * Gracefully kill the process with pid #pid and start time #process_start_time. + * + * Under Unix this will send SIGINT, then SIGTERM and then SIGKILL if process + * does not exit. + * + * #process_start_time may be PROCESS_START_TIME_UNKNOWN, which will disable + * safety check for killing right process. + * + * @return true if process was killed successfully, false otherwise. + * @return false if killing failed for any reason, or if the PID wasn't + * present in the first place. + */ +bool GracefulTerminate(pid_t pid, time_t process_start_time); + + +#endif diff --git a/libpromises/process_linux.c b/libpromises/process_linux.c new file mode 100644 index 0000000000..5bd833b008 --- /dev/null +++ b/libpromises/process_linux.c @@ -0,0 +1,160 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include + + +typedef struct +{ + time_t starttime; + char state; +} ProcessStat; + +static bool GetProcessStat(pid_t pid, ProcessStat *state) +{ + char filename[64]; + xsnprintf(filename, sizeof(filename), "/proc/%jd/stat", (intmax_t) pid); + + int fd; + for (;;) + { + if ((fd = open(filename, O_RDONLY)) != -1) + { + break; + } + + if (errno == EINTR) + { + continue; + } + + if (errno == ENOENT || errno == ENOTDIR) + { + return false; + } + + if (errno == EACCES) + { + return false; + } + + assert (fd != -1 && "Unable to open /proc//stat"); + } + + char stat[CF_BUFSIZE]; + int res = FullRead(fd, stat, sizeof(stat) - 1); /* -1 for the '\0', below */ + close(fd); + + if (res < 0) + { + return false; + } + assert(res < CF_BUFSIZE); + stat[res] = '\0'; /* read() doesn't '\0'-terminate */ + + /* stat entry is of form: () + * To avoid choking on weird task names, we search for the closing + * parenthesis first: */ + char *p = memrchr(stat, ')', res); + if (p == NULL) + { + /* Wrong field format! */ + return false; + } + + p++; // Skip the parenthesis + + char proc_state[2]; + unsigned long long starttime; + + if (sscanf(p, + "%1s" /* state */ + "%*s" /* ppid */ + "%*s" /* pgrp */ + "%*s" /* session */ + "%*s" /* tty_nr */ + "%*s" /* tpgid */ + "%*s" /* flags */ + "%*s" /* minflt */ + "%*s" /* cminflt */ + "%*s" /* majflt */ + "%*s" /* cmajflt */ + "%*s" /* utime */ + "%*s" /* stime */ + "%*s" /* cutime */ + "%*s" /* cstime */ + "%*s" /* priority */ + "%*s" /* nice */ + "%*s" /* num_threads */ + "%*s" /* itrealvalue */ + "%llu" /* starttime */, + proc_state, + &starttime) != 2) + { + return false; + } + + state->state = proc_state[0]; + state->starttime = (time_t)(starttime / sysconf(_SC_CLK_TCK)); + + return true; +} + +time_t GetProcessStartTime(pid_t pid) +{ + ProcessStat st; + if (GetProcessStat(pid, &st)) + { + return st.starttime; + } + else + { + return PROCESS_START_TIME_UNKNOWN; + } +} + +ProcessState GetProcessState(pid_t pid) +{ + ProcessStat st; + if (GetProcessStat(pid, &st)) + { + switch (st.state) + { + case 'T': + return PROCESS_STATE_STOPPED; + case 'Z': + return PROCESS_STATE_ZOMBIE; + default: + return PROCESS_STATE_RUNNING; + } + } + else + { + return PROCESS_STATE_DOES_NOT_EXIST; + } +} diff --git a/libpromises/process_solaris.c b/libpromises/process_solaris.c new file mode 100644 index 0000000000..cab4dad87d --- /dev/null +++ b/libpromises/process_solaris.c @@ -0,0 +1,167 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include + +/* + * procfs.h is not 64-bit off_t clean, but the only affected structure is + * priovec, which we don't use. Hence we may work around #error in sys/procfs.h + * by lying that we are not compiling with large file support (while we do). + */ +#define _FILE_OFFSET_BITS 32 + +#include + +static bool GetProcessPsinfo(pid_t pid, psinfo_t *psinfo) +{ + char filename[CF_BUFSIZE]; + snprintf(filename, CF_BUFSIZE, "/proc/%d/psinfo", (int)pid); + + int fd = open(filename, O_RDONLY); + if (fd == -1) + { + return false; + } + + int res = FullRead(fd, psinfo, sizeof(*psinfo)); + close(fd); + return res == sizeof(*psinfo); +} + +time_t GetProcessStartTime(pid_t pid) +{ + psinfo_t psinfo; + if (GetProcessPsinfo(pid, &psinfo)) + { + return psinfo.pr_start.tv_sec; + } + else + { + return PROCESS_START_TIME_UNKNOWN; + } +} + +static bool GetProcessPstatus(pid_t pid, pstatus_t *pstatus) +{ + char filename[CF_BUFSIZE]; + snprintf(filename, CF_BUFSIZE, "/proc/%jd/status", (intmax_t) pid); + + int fd = open(filename, O_RDONLY); + if (fd == -1) + { + return false; + } + + int res = FullRead(fd, pstatus, sizeof(*pstatus)); + close(fd); + return res == sizeof(*pstatus); +} + +ProcessState GetProcessState(pid_t pid) +{ +/* This is the documented way to check for zombie process, on Solaris 9 + * however I couldn't get it to work: Killing a child process and then reading + * /proc/PID/psinfo *before* reaping the dead child, resulted in + * psinfo.pr_nlwp == 1 and psinfo.pr_lwp.pr_lwpid == 1. + */ + +#if 0 + psinfo_t psinfo; + if (GetProcessPsinfo(pid, &psinfo)) + { + if (psinfo.pr_nlwp == 0 && + psinfo.pr_lwp.pr_lwpid == 0) + { + return PROCESS_STATE_ZOMBIE; + } + else + { + /* Then, we must read the "status" file to get + * pstatus.pr_lwp.pr_flags, because the psinfo.pr_lwp.pr_flag is + * deprecated. */ + pstatus_t pstatus; + if (GetProcessPstatus(pid &pstatus)) + { + if (pstatus.pr_lwp.pr_flags & PR_STOPPED) + { + return PROCESS_STATE_STOPPED; + } + else + { + return PROCESS_STATE_RUNNING; + } + } + } + } + + return PROCESS_STATE_DOES_NOT_EXIST; +#endif + + + + /* HACK WARNING: By experimentation I figured out that on Solaris 9 there + is no clear way to figure out if a process is zombie, but if the + "status" file is not there while the "psinfo" file is, then it's most + probably a zombie. */ + + pstatus_t pstatus; + bool success = GetProcessPstatus(pid, &pstatus); + + if (!success && errno == ENOENT) /* file does not exist */ + { + psinfo_t psinfo; + if (GetProcessPsinfo(pid, &psinfo)) + { + /* /proc/PID/psinfo exists, /proc/PID/status does not exist */ + return PROCESS_STATE_ZOMBIE; + } + else /* Neither status nor psinfo could be opened */ + { + return PROCESS_STATE_DOES_NOT_EXIST; + } + } + + /* Read the flags from status, since reading it from + psinfo is deprecated. */ + if (success) + { + if (pstatus.pr_lwp.pr_flags & PR_STOPPED) + { + return PROCESS_STATE_STOPPED; + } + else + { + return PROCESS_STATE_RUNNING; + } + } + + /* If we reach this point it's probably because /proc/PID/status couldn't + * be opened because of EPERM. We can't do anything to that process, so we + * might as well pretend it does not exist. */ + + return PROCESS_STATE_DOES_NOT_EXIST; +} diff --git a/libpromises/process_unix.c b/libpromises/process_unix.c new file mode 100644 index 0000000000..4193d14827 --- /dev/null +++ b/libpromises/process_unix.c @@ -0,0 +1,282 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + + +#include + +#include +#include +#include +#include + + +#define SLEEP_POLL_TIMEOUT_NS 10000000 + + +/* + * Wait until process specified by #pid is stopped due to SIGSTOP signal. + * + * @returns true if process has come to stop during #timeout_ns nanoseconds, + * false if the process cannot be found or failed to stop during #timeout_ns + * nanoseconds. + * + * FIXME: Only timeouts < 1s are supported + */ +static bool ProcessWaitUntilStopped(pid_t pid, long timeout_ns) +{ + while (timeout_ns > 0) + { + switch (GetProcessState(pid)) + { + case PROCESS_STATE_RUNNING: + break; /* retry in a while */ + case PROCESS_STATE_STOPPED: + return true; + case PROCESS_STATE_ZOMBIE: + /* There is not much we can do by waiting a zombie process. It + * will never change to a stopped state. */ + return false; + case PROCESS_STATE_DOES_NOT_EXIST: + return false; + } + + struct timespec ts = { + .tv_sec = 0, + .tv_nsec = MIN(SLEEP_POLL_TIMEOUT_NS, timeout_ns), + }; + + while (nanosleep(&ts, &ts) < 0) + { + if (errno != EINTR) + { + ProgrammingError("Invalid timeout for nanosleep"); + } + } + + timeout_ns = MAX(0, timeout_ns - SLEEP_POLL_TIMEOUT_NS); + } + + return false; +} + +/* + * Currently only timeouts < 1s are supported + */ +static bool ProcessWaitUntilExited(pid_t pid, long timeout_ns) +{ + assert(timeout_ns < 1000000000); + + while (timeout_ns > 0) + { + switch (GetProcessState(pid)) + { + case PROCESS_STATE_RUNNING: + break; /* retry in a while */ + case PROCESS_STATE_DOES_NOT_EXIST: + return true; + case PROCESS_STATE_ZOMBIE: + /* There is not much we can do by waiting a zombie process. It's + the responsibility of the caller to reap the child so we're + considering it has already exited. */ + return true; + case PROCESS_STATE_STOPPED: + /* Almost the same case with a zombie process, but it will + * respond only to signals that can't be caught. */ + return false; + } + + struct timespec ts = { + .tv_sec = 0, + .tv_nsec = MIN(SLEEP_POLL_TIMEOUT_NS, timeout_ns), + }; + + Log(LOG_LEVEL_DEBUG, + "PID %jd still alive after signalling, waiting for %lu ms...", + (intmax_t) pid, ts.tv_nsec / 1000000); + + while (nanosleep(&ts, &ts) < 0) + { + if (errno != EINTR) + { + ProgrammingError("Invalid timeout for nanosleep"); + } + } + + timeout_ns = MAX(0, timeout_ns - SLEEP_POLL_TIMEOUT_NS); + } + + return false; +} + +/* A timeout (in nanoseconds) to wait for process to stop (pause) or exit. + * Note that it's important that it does not overflow 32 bits; no more than + * nine 9s in a row, i.e. one second. */ +#define STOP_WAIT_TIMEOUT 999999999L + +/* + * Safely kill process by checking that the process is the right one by matching + * process start time. + * + * The algorithm: + * + * 1. Check that the process has the same start time as stored in lock. If it + * is not, return, as we know for sure this is a wrong process. (This step + * is an optimization to avoid sending SIGSTOP/SIGCONT to wrong processes). + * + * 2. Send SIGSTOP to the process. + * + * 3. Poll process state until it is stopped. + * + * Now the process is stopped, so we may examine it and not be afraid that it + * will exit and another one with the same PID will appear. + * + * 4. Check that the process has the same start time as provided. + * If it is, send the signal to the process. + * + * 5. Send SIGCONT to the process, so it may continue. + * + * + * Returns 0 on success, -1 on error. Error code is signalled through errno. + * + * ERRORS + * + * EINVAL An invalid signal was specified. + * EPERM The process does not have permission to send the signal. + * ESRCH The pid does not exist or its start time does not match expected one. + */ +static int SafeKill(pid_t pid, time_t expected_start_time, int signal) +{ + /* Preliminary check: in case process start time is different already, we + * are sure we don't want to STOP it or kill it. */ + + time_t pid_start_time = GetProcessStartTime(pid); + + if (pid_start_time != expected_start_time) + { + errno = ESRCH; + return -1; + } + + /* Now to avoid race conditions we need to stop process so it won't exit + * voluntarily while we are working on it */ + + if (kill(pid, SIGSTOP) < 0) + { + return -1; + } + + if (!ProcessWaitUntilStopped(pid, STOP_WAIT_TIMEOUT)) + { + /* Ensure the process is started again in case of timeout or error, so + * we don't leave SIGSTOP'ed processes around on overloaded or + * misconfigured machine */ + kill(pid, SIGCONT); + errno = ESRCH; + return -1; + } + + /* Here process has stopped, so we may interrogate it without race conditions */ + + pid_start_time = GetProcessStartTime(pid); + + if (pid_start_time != expected_start_time) + { + /* This is a wrong process, let it continue */ + kill(pid, SIGCONT); + errno = ESRCH; + return -1; + } + + /* We've got a right process, signal it and let it continue */ + + int ret = kill(pid, signal); + int saved_errno = errno; + + /* + * We don't check return value of SIGCONT, as the process may have been + * terminated already by previous kill. Moreover, what would we do with the + * return code? + */ + kill(pid, SIGCONT); + + errno = saved_errno; + return ret; +} + +static int Kill(pid_t pid, time_t process_start_time, int signal) +{ + if (process_start_time == PROCESS_START_TIME_UNKNOWN) + { + /* We don't know when the process has started, do a plain kill(2) */ + return kill(pid, signal); + } + else + { + return SafeKill(pid, process_start_time, signal); + } +} + + +bool GracefulTerminate(pid_t pid, time_t process_start_time) +{ + /* We can't allow to kill ourselves. First it does not make sense, and + * second, once SafeKill() sends SIGSTOP, we will just freeze forever. */ + if (pid == getpid()) + { + Log(LOG_LEVEL_WARNING, + "Ignoring request to kill ourself (pid %jd)!", + (intmax_t) pid); + return false; + } + + if (Kill(pid, process_start_time, SIGINT) < 0) + { + /* If we failed to kill the process return error. If the process + * doesn't even exist (errno==ESRCH), again return error, we shouldn't + * have signalled the PID in the first place. */ + return false; + } + + if (ProcessWaitUntilExited(pid, STOP_WAIT_TIMEOUT)) + { + return true; + } + + if (Kill(pid, process_start_time, SIGTERM) < 0) + { + return errno == ESRCH; + } + + if (ProcessWaitUntilExited(pid, STOP_WAIT_TIMEOUT)) + { + return true; + } + + if (Kill(pid, process_start_time, SIGKILL) < 0) + { + return errno == ESRCH; + } + + return true; +} diff --git a/libpromises/process_unix_priv.h b/libpromises/process_unix_priv.h new file mode 100644 index 0000000000..1c23957738 --- /dev/null +++ b/libpromises/process_unix_priv.h @@ -0,0 +1,54 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PROCESS_PRIV_H +#define CFENGINE_PROCESS_PRIV_H + +/* + * Unix-like OS should provide implementations of the following functions. + */ + +/* + * GetProcessStartTime (see process_lib.h) + */ + +typedef enum +{ + PROCESS_STATE_RUNNING, + PROCESS_STATE_STOPPED, + PROCESS_STATE_ZOMBIE, + PROCESS_STATE_DOES_NOT_EXIST +} ProcessState; + +/* + * Obtain process state. + * + * @return PROCESS_STATE_RUNNING if process exists and is running, + * @return PROCESS_STATE_STOPPED if process exists and has been stopped by SIGSTOP signal, + * @return PROCESS_STATE_ZOMBIE if process exists and is zombie, + * @return PROCESS_STATE_DOES_NOT_EXIST if process cannot be found. + */ +ProcessState GetProcessState(pid_t pid); + +#endif diff --git a/libpromises/process_unix_stub.c b/libpromises/process_unix_stub.c new file mode 100644 index 0000000000..04235bcaab --- /dev/null +++ b/libpromises/process_unix_stub.c @@ -0,0 +1,52 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include + +time_t GetProcessStartTime(ARG_UNUSED pid_t pid) +{ + Log(LOG_LEVEL_VERBOSE, + "No platform-specific code for obtaining process start time. - " + "Falling back to no PID double-checking on kill()"); + + return PROCESS_START_TIME_UNKNOWN; +} + +ProcessState GetProcessState(pid_t pid) +{ + Log(LOG_LEVEL_DEBUG, + "No platform-specific code for obtaining process state. - " + "Falling back to no PID double-checking on kill()"); + + if (kill(pid, 0) < 0 && errno == ESRCH) + { + return PROCESS_STATE_DOES_NOT_EXIST; + } + + /* It might be RUNNING, STOPPED or ZOMBIE, but we can't tell the + * difference without platform specific code. */ + return PROCESS_STATE_RUNNING; +} diff --git a/libpromises/processes_select.c b/libpromises/processes_select.c new file mode 100644 index 0000000000..3f14114ca6 --- /dev/null +++ b/libpromises/processes_select.c @@ -0,0 +1,1731 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include + +#include +#include +#include +#include +#include +#include /* Chop */ +#include /* CompileRegex,StringMatchWithPrecompiledRegex,StringMatchFull */ +#include +#include // SetUmask(), RestoreUmask() +#include +#include +#include +#include +#include +#include +#include + +# ifdef HAVE_GETZONEID +#include +#define MAX_ZONENAME_SIZE 64 +# endif + +#ifdef _WIN32 +#define TABLE_STORAGE +#else +#define TABLE_STORAGE static +#endif +TABLE_STORAGE Item *PROCESSTABLE = NULL; + +typedef enum +{ + /* + * In this mode, all columns must be present by the occurrence of at least + * one non-whitespace character. + */ + PCA_AllColumnsPresent, + /* + * In this mode, if a process is a zombie, and if there is nothing but + * whitespace in the byte columns directly below a header, that column is + * skipped. + * + * This means that very little text shifting will be tolerated, preferably + * none, or small enough that all column entries still remain under their + * own header. It also means that a zombie process must be detectable by + * reading the 'Z' state directly below the 'S' or 'ST' header. + */ + PCA_ZombieSkipEmptyColumns, +} PsColumnAlgorithm; + +/* + Ideally this should be autoconf-tested, but it's really hard to do so. See + SplitProcLine() to see the exact effects this has. +*/ +static const PsColumnAlgorithm PS_COLUMN_ALGORITHM[] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = PCA_AllColumnsPresent, + [PLATFORM_CONTEXT_OPENVZ] = PCA_AllColumnsPresent, /* virt_host_vz_vzps */ + [PLATFORM_CONTEXT_HP] = PCA_AllColumnsPresent, /* hpux */ + [PLATFORM_CONTEXT_AIX] = PCA_ZombieSkipEmptyColumns, /* aix */ + [PLATFORM_CONTEXT_LINUX] = PCA_AllColumnsPresent, /* linux */ + [PLATFORM_CONTEXT_BUSYBOX] = PCA_AllColumnsPresent, /* linux */ + [PLATFORM_CONTEXT_SOLARIS] = PCA_AllColumnsPresent, /* solaris >= 11 */ + [PLATFORM_CONTEXT_SUN_SOLARIS] = PCA_AllColumnsPresent, /* solaris < 11 */ + [PLATFORM_CONTEXT_FREEBSD] = PCA_AllColumnsPresent, /* freebsd */ + [PLATFORM_CONTEXT_NETBSD] = PCA_AllColumnsPresent, /* netbsd */ + [PLATFORM_CONTEXT_CRAYOS] = PCA_AllColumnsPresent, /* cray */ + [PLATFORM_CONTEXT_WINDOWS_NT] = PCA_AllColumnsPresent, /* NT - cygnus */ + [PLATFORM_CONTEXT_SYSTEMV] = PCA_AllColumnsPresent, /* unixware */ + [PLATFORM_CONTEXT_OPENBSD] = PCA_AllColumnsPresent, /* openbsd */ + [PLATFORM_CONTEXT_CFSCO] = PCA_AllColumnsPresent, /* sco */ + [PLATFORM_CONTEXT_DARWIN] = PCA_AllColumnsPresent, /* darwin */ + [PLATFORM_CONTEXT_QNX] = PCA_AllColumnsPresent, /* qnx */ + [PLATFORM_CONTEXT_DRAGONFLY] = PCA_AllColumnsPresent, /* dragonfly */ + [PLATFORM_CONTEXT_MINGW] = PCA_AllColumnsPresent, /* mingw */ + [PLATFORM_CONTEXT_VMWARE] = PCA_AllColumnsPresent, /* vmware */ + [PLATFORM_CONTEXT_ANDROID] = PCA_AllColumnsPresent, /* android */ +}; + +#if defined(__sun) || defined(TEST_UNIT_TEST) +static StringMap *UCB_PS_MAP = NULL; +// These will be tried in order. +static const char *UCB_STYLE_PS[] = { + "/usr/ucb/ps", + "/bin/ps", + NULL +}; +static const char *UCB_STYLE_PS_ARGS = "axwww"; +static const PsColumnAlgorithm UCB_STYLE_PS_COLUMN_ALGORITHM = PCA_ZombieSkipEmptyColumns; +#endif + +static bool SelectProcRangeMatch(char *name1, char *name2, int min, int max, char **names, char **line); +static bool SelectProcRegexMatch(const char *name1, const char *name2, const char *regex, bool anchored, char **colNames, char **line); +static bool SplitProcLine(const char *proc, + time_t pstime, + char **names, + int *start, + int *end, + PsColumnAlgorithm pca, + char **line); +static bool SelectProcTimeCounterRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line); +static bool SelectProcTimeAbsRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line); +static int GetProcColumnIndex(const char *name1, const char *name2, char **names); +static void GetProcessColumnNames(const char *proc, char **names, int *start, int *end); +static int ExtractPid(char *psentry, char **names, int *end); +static void ApplyPlatformExtraTable(char **names, char **columns); + +/***************************************************************************/ + +static bool SelectProcess(const char *procentry, + time_t pstime, + char **names, + int *start, + int *end, + const char *process_regex, + const ProcessSelect *a, + bool attrselect) +{ + bool result = true; + char *column[CF_PROCCOLS]; + Rlist *rp; + + assert(process_regex); + assert(a != NULL); + + StringSet *process_select_attributes = StringSetNew(); + + memset(column, 0, sizeof(column)); + + if (!SplitProcLine(procentry, pstime, names, start, end, + PS_COLUMN_ALGORITHM[VPSHARDCLASS], column)) + { + result = false; + goto cleanup; + } + + ApplyPlatformExtraTable(names, column); + + for (int i = 0; names[i] != NULL; i++) + { + LogDebug(LOG_MOD_PS, "In SelectProcess, COL[%s] = '%s'", + names[i], column[i]); + } + + if (!SelectProcRegexMatch("CMD", "COMMAND", process_regex, false, names, column)) + { + result = false; + goto cleanup; + } + + if (!attrselect) + { + // If we are not considering attributes, then the matching is done. + goto cleanup; + } + + for (rp = a->owner; rp != NULL; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_FNCALL) + { + Log(LOG_LEVEL_VERBOSE, + "Function call '%s' in process_select body was not resolved, skipping", + RlistFnCallValue(rp)->name); + } + else if (SelectProcRegexMatch("USER", "UID", RlistScalarValue(rp), true, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("process_owner")); + break; + } + } + + if (SelectProcRangeMatch("PID", "PID", a->min_pid, a->max_pid, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("pid")); + } + + if (SelectProcRangeMatch("PPID", "PPID", a->min_ppid, a->max_ppid, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("ppid")); + } + + if (SelectProcRangeMatch("PGID", "PGID", a->min_pgid, a->max_pgid, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("pgid")); + } + + if (SelectProcRangeMatch("VSZ", "SZ", a->min_vsize, a->max_vsize, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("vsize")); + } + + if (SelectProcRangeMatch("RSS", "RSS", a->min_rsize, a->max_rsize, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("rsize")); + } + + if (SelectProcTimeCounterRangeMatch("TIME", "TIME", a->min_ttime, a->max_ttime, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("ttime")); + } + + if (SelectProcTimeAbsRangeMatch + ("STIME", "START", a->min_stime, a->max_stime, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("stime")); + } + + if (SelectProcRangeMatch("NI", "PRI", a->min_pri, a->max_pri, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("priority")); + } + + if (SelectProcRangeMatch("NLWP", "NLWP", a->min_thread, a->max_thread, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("threads")); + } + + if (SelectProcRegexMatch("S", "STAT", a->status, true, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("status")); + } + + if (SelectProcRegexMatch("CMD", "COMMAND", a->command, true, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("command")); + } + + if (SelectProcRegexMatch("TTY", "TTY", a->tty, true, names, column)) + { + StringSetAdd(process_select_attributes, xstrdup("tty")); + } + + if (!a->process_result) + { + if (StringSetSize(process_select_attributes) == 0) + { + result = EvalProcessResult("", process_select_attributes); + } + else + { + Writer *w = StringWriter(); + StringSetIterator iter = StringSetIteratorInit(process_select_attributes); + char *attr = StringSetIteratorNext(&iter); + WriterWrite(w, attr); + + while ((attr = StringSetIteratorNext(&iter))) + { + WriterWriteChar(w, '.'); + WriterWrite(w, attr); + } + + result = EvalProcessResult(StringWriterData(w), process_select_attributes); + WriterClose(w); + } + } + else + { + result = EvalProcessResult(a->process_result, process_select_attributes); + } + +cleanup: + StringSetDestroy(process_select_attributes); + + for (int i = 0; column[i] != NULL; i++) + { + free(column[i]); + } + + return result; +} + +Item *SelectProcesses(const char *process_name, const ProcessSelect *a, bool attrselect) +{ + assert(a != NULL); + const Item *processes = PROCESSTABLE; + Item *result = NULL; + + if (processes == NULL) + { + return result; + } + + char *names[CF_PROCCOLS]; + int start[CF_PROCCOLS]; + int end[CF_PROCCOLS]; + + GetProcessColumnNames(processes->name, names, start, end); + + /* TODO: use actual time of ps-run, as time(NULL) may be later. */ + time_t pstime = time(NULL); + + for (Item *ip = processes->next; ip != NULL; ip = ip->next) + { + if (NULL_OR_EMPTY(ip->name)) + { + continue; + } + + if (!SelectProcess(ip->name, pstime, names, start, end, process_name, a, attrselect)) + { + continue; + } + + pid_t pid = ExtractPid(ip->name, names, end); + + if (pid == -1) + { + Log(LOG_LEVEL_VERBOSE, "Unable to extract pid while looking for %s", process_name); + continue; + } + + PrependItem(&result, ip->name, ""); + result->counter = (int)pid; + } + + for (int i = 0; i < CF_PROCCOLS; i++) + { + free(names[i]); + } + + return result; +} + +static bool SelectProcRangeMatch(char *name1, char *name2, int min, int max, char **names, char **line) +{ + int i; + long value; + + if ((min == CF_NOINT) || (max == CF_NOINT)) + { + return false; + } + + if ((i = GetProcColumnIndex(name1, name2, names)) != -1) + { + value = IntFromString(line[i]); + + if (value == CF_NOINT) + { + Log(LOG_LEVEL_INFO, "Failed to extract a valid integer from '%s' => '%s' in process list", names[i], + line[i]); + return false; + } + + if ((min <= value) && (value <= max)) + { + return true; + } + else + { + return false; + } + } + + return false; +} + +/***************************************************************************/ + +static long TimeCounter2Int(const char *s) +{ + long days, hours, minutes, seconds; + + if (s == NULL) + { + return CF_NOINT; + } + + /* If we match dd-hh:mm[:ss], believe it: */ + int got = sscanf(s, "%ld-%ld:%ld:%ld", &days, &hours, &minutes, &seconds); + if (got > 2) + { + /* All but perhaps seconds set */ + } + /* Failing that, try matching hh:mm[:ss] */ + else if (1 < (got = sscanf(s, "%ld:%ld:%ld", &hours, &minutes, &seconds))) + { + /* All but days and perhaps seconds set */ + days = 0; + got++; + } + else + { + Log(LOG_LEVEL_ERR, + "Unable to parse 'ps' time field as [dd-]hh:mm[:ss], got '%s'", + s); + return CF_NOINT; + } + assert(got > 2); /* i.e. all but maybe seconds have been set */ + /* Clear seconds if unset: */ + if (got < 4) + { + seconds = 0; + } + + LogDebug(LOG_MOD_PS, "TimeCounter2Int:" + " Parsed '%s' as elapsed time '%ld-%02ld:%02ld:%02ld'", + s, days, hours, minutes, seconds); + + /* Convert to seconds: */ + return ((days * 24 + hours) * 60 + minutes) * 60 + seconds; +} + +static bool SelectProcTimeCounterRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line) +{ + if ((min == CF_NOINT) || (max == CF_NOINT)) + { + return false; + } + + int i = GetProcColumnIndex(name1, name2, names); + if (i != -1) + { + time_t value = (time_t) TimeCounter2Int(line[i]); + + if (value == CF_NOINT) + { + Log(LOG_LEVEL_INFO, + "Failed to extract a valid integer from %s => '%s' in process list", + names[i], line[i]); + return false; + } + + if ((min <= value) && (value <= max)) + { + Log(LOG_LEVEL_VERBOSE, "Selection filter matched counter range" + " '%s/%s' = '%s' in [%jd,%jd] (= %jd secs)", + name1, name2, line[i], (intmax_t)min, (intmax_t)max, (intmax_t)value); + return true; + } + else + { + LogDebug(LOG_MOD_PS, "Selection filter REJECTED counter range" + " '%s/%s' = '%s' in [%jd,%jd] (= %jd secs)", + name1, name2, line[i], + (intmax_t) min, (intmax_t) max, (intmax_t) value); + return false; + } + } + + return false; +} + +static time_t TimeAbs2Int(const char *s) +{ + if (s == NULL) + { + return CF_NOINT; + } + + struct tm tm; + localtime_r(&CFSTARTTIME, &tm); + tm.tm_sec = 0; + tm.tm_isdst = -1; + + /* Try various ways to parse s: */ + char word[4]; /* Abbreviated month name */ + long ns[3]; /* Miscellaneous numbers, diverse readings */ + int got = sscanf(s, "%2ld:%2ld:%2ld", ns, ns + 1, ns + 2); + if (1 < got) /* Hr:Min[:Sec] */ + { + tm.tm_hour = ns[0]; + tm.tm_min = ns[1]; + if (got == 3) + { + tm.tm_sec = ns[2]; + } + } + /* or MMM dd (the %ld shall ignore any leading space) */ + else if (sscanf(s, "%3[a-zA-Z]%ld", word, ns) == 2 && + /* Only match if word is a valid month text: */ + 0 < (ns[1] = Month2Int(word))) + { + int month = ns[1] - 1; + if (tm.tm_mon < month) + { + /* Wrapped around */ + tm.tm_year--; + } + tm.tm_mon = month; + tm.tm_mday = ns[0]; + tm.tm_hour = 0; + tm.tm_min = 0; + } + /* or just year, or seconds since 1970 */ + else if (sscanf(s, "%ld", ns) == 1) + { + if (ns[0] > 9999) + { + /* Seconds since 1970. + * + * This is the amended value SplitProcLine() replaces + * start time with if it's imprecise and a better value + * can be calculated from elapsed time. + */ + return (time_t)ns[0]; + } + /* else year, at most four digits; either 4-digit CE or + * already relative to 1900. */ + + memset(&tm, 0, sizeof(tm)); + tm.tm_year = ns[0] < 999 ? ns[0] : ns[0] - 1900; + tm.tm_isdst = -1; + } + else + { + return CF_NOINT; + } + + return mktime(&tm); +} + +static bool SelectProcTimeAbsRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line) +{ + int i; + time_t value; + + if ((min == CF_NOINT) || (max == CF_NOINT)) + { + return false; + } + + if ((i = GetProcColumnIndex(name1, name2, names)) != -1) + { + value = TimeAbs2Int(line[i]); + + if (value == CF_NOINT) + { + Log(LOG_LEVEL_INFO, "Failed to extract a valid integer from %c => '%s' in process list", name1[i], + line[i]); + return false; + } + + if ((min <= value) && (value <= max)) + { + Log(LOG_LEVEL_VERBOSE, "Selection filter matched absolute '%s/%s' = '%s(%jd)' in [%jd,%jd]", name1, name2, line[i], (intmax_t)value, + (intmax_t)min, (intmax_t)max); + return true; + } + else + { + return false; + } + } + + return false; +} + +/***************************************************************************/ + +static bool SelectProcRegexMatch(const char *name1, const char *name2, + const char *regex, bool anchored, + char **colNames, char **line) +{ + int i; + + if (regex == NULL) + { + return false; + } + + if ((i = GetProcColumnIndex(name1, name2, colNames)) != -1) + { + if (anchored) + { + return StringMatchFull(regex, line[i]); + } + else + { + size_t s, e; + return StringMatch(regex, line[i], &s, &e); + } + } + + return false; +} + +static void PrintStringIndexLine(int prefix_spaces, int len) +{ + char arrow_str[CF_BUFSIZE]; + arrow_str[0] = '^'; + arrow_str[1] = '\0'; + char index_str[CF_BUFSIZE]; + index_str[0] = '0'; + index_str[1] = '\0'; + for (int lineindex = 10; lineindex <= len; lineindex += 10) + { + char num[PRINTSIZE(lineindex)]; + xsnprintf(num, sizeof(num), "%10d", lineindex); + strlcat(index_str, num, sizeof(index_str)); + strlcat(arrow_str, " ^", sizeof(arrow_str)); + } + + // Prefix the beginning of the indexes with the given number. + LogDebug(LOG_MOD_PS, "%*s%s", prefix_spaces, "", arrow_str); + LogDebug(LOG_MOD_PS, "%*s%s", prefix_spaces, "Index: ", index_str); +} + +static void MaybeFixStartTime(const char *line, + time_t pstime, + char **names, + char **fields) +{ + /* Since start times can be very imprecise (e.g. just a past day's + * date, or a past year), calculate a better value from elapsed + * time, if available: */ + int k = GetProcColumnIndex("ELAPSED", "ELAPSED", names); + if (k != -1) + { + const long elapsed = TimeCounter2Int(fields[k]); + if (elapsed != CF_NOINT) /* Only use if parsed successfully ! */ + { + int j = GetProcColumnIndex("STIME", "START", names), ns[3]; + /* Trust the reported value if it matches hh:mm[:ss], though: */ + if (sscanf(fields[j], "%d:%d:%d", ns, ns + 1, ns + 2) < 2) + { + time_t value = pstime - (time_t) elapsed; + + LogDebug(LOG_MOD_PS, + "processes: Replacing parsed start time %s with %s", + fields[j], ctime(&value)); + + free(fields[j]); + xasprintf(fields + j, "%ld", value); + } + } + else if (fields[k]) + { + Log(LOG_LEVEL_VERBOSE, + "Parsing problem was in ELAPSED field of '%s'", + line); + } + } +} + +/*******************************************************************/ +/* fields must be char *fields[CF_PROCCOLS] in fact. */ +/* pstime should be the time at which ps was run. */ + +static bool SplitProcLine(const char *line, + time_t pstime, + char **names, + int *start, + int *end, + PsColumnAlgorithm pca, + char **fields) +{ + if (line == NULL || line[0] == '\0') + { + return false; + } + + const size_t checklen = strlen(line); + if (checklen > INT_MAX) { + Log(LOG_LEVEL_ERR, "Proc line too long (%zu > %d)", checklen, INT_MAX); + return false; + } + int linelen = (int) checklen; + + if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) + { + LogDebug(LOG_MOD_PS, "Parsing ps line: '%s'", line); + // Makes the entry line up with the line above. + PrintStringIndexLine(18, linelen); + } + + /* + All platforms have been verified to not produce overlapping fields with + currently used ps tools, and hence we can parse based on space separation + (with some caveats, see below). + + Dates may have spaces in them, like "May 4", or not, like "May4". Prefer + to match a date without spaces as long as it contains a number, but fall + back to parsing letters followed by space(s) and a date. + + Commands will also have extra spaces, but it is always the last field, so + we just include spaces at this point in the parsing. + + An additional complication is that some platforms (only AIX is known at + the time of writing) can have empty columns when a process is a + zombie. The plan is to match for this by checking the range between start + and end directly below the header (same byte position). If the whole range + is whitespace we consider the entry missing. The columns are (presumably) + guaranteed to line up correctly for this, since zombie processes do not + have memory usage which can produce large, potentially alignment-altering + numbers. However, we cannot do this whitespace check in general, because + non-zombie processes may shift columns in a way that leaves some columns + apparently (but not actually) empty. Zombie processes have state Z and + command on AIX. Similarly processes marked with command + also have missing columns and need to be skipped. (AIX only). + + Take these two examples: + +AIX: + USER PID PPID PGID %CPU %MEM VSZ NI S STIME TIME COMMAND + root 1 0 0 0.0 0.0 784 20 A Nov 28 00:00:00 /etc/init + root 1835344 1 1835344 0.0 0.0 944 20 A Nov 28 00:00:00 /usr/lib/errdemon + root 2097594 1 1638802 0.0 0.0 596 20 A Nov 28 00:00:05 /usr/sbin/syncd 60 + root 3408328 1 3408328 0.0 0.0 888 20 A Nov 28 00:00:00 /usr/sbin/srcmstr + root 4325852 3408328 4325852 0.0 0.0 728 20 A Nov 28 00:00:00 /usr/sbin/syslogd + root 4784534 3408328 4784534 0.0 0.0 1212 20 A Nov 28 00:00:00 sendmail: accepting connections + root 5898690 1 5898690 0.0 0.0 1040 20 A Nov 28 00:00:00 /usr/sbin/cron + 6095244 8913268 8913268 20 Z 00:00:00 + root 6160866 3408328 6160866 0.0 0.0 1612 20 A Nov 28 00:00:00 /opt/rsct/bin/IBM.ServiceRMd + 6750680 17826152 17826152 20 Z 00:00:00 + root 7143692 3408328 7143692 0.0 0.0 476 20 A Nov 28 00:00:00 /var/perf/pm/bin/pmperfrec + root 7340384 8651136 8651136 0.0 0.0 500 20 A Nov 28 00:00:00 [trspoolm] + root 7602560 8978714 7602560 0.0 0.0 636 20 A Nov 28 00:00:00 sshd: u0013628 [priv] + 7733720 - - - A - + +Solaris 9: + USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND + jenkins 29769 0.0 0.0 810 2976 pts/1 S 07:22:43 0:00 /usr/bin/perl ../../ps.pl + jenkins 29835 - - 0 0 ? Z - 0:00 + jenkins 10026 0.0 0.3 30927 143632 ? S Jan_21 01:18:58 /usr/jdk/jdk1.6.0_45/bin/java -jar slave.jar + + Due to how the process state 'S' is shifted under the 'S' header in the + second example, it is not possible to separate between this and a missing + column. Counting spaces is no good, because commands can contain an + arbitrary number of spaces, and there is no way to know the byte position + where a command begins. Hence the only way is to base this algorithm on + platform and only do the "empty column detection" when: + * The platform is known to produce empty columns for zombie processes + (see PCA_ZombieSkipEmptyColumns) + * The platform is known to not shift columns when the process is a + zombie. + * It is a zombie / exiting / idle process + (These states provide almost no useful info in ps output) + */ + + bool skip = false; + + if (pca == PCA_ZombieSkipEmptyColumns) + { + // Find out if the process is a zombie. + for (int field = 0; names[field] && !skip; field++) + { + if (strcmp(names[field], "S") == 0 || + strcmp(names[field], "ST") == 0) + { + // Check for zombie state. + for (int pos = start[field]; pos <= end[field] && pos < linelen && !skip; pos++) + { + // 'Z' letter with word boundary on each side. + if (isspace(line[pos - 1]) + && line[pos] == 'Z' + && (isspace(line[pos + 1]) + || line[pos + 1] == '\0')) + { + LogDebug(LOG_MOD_PS, "Detected zombie process, " + "skipping parsing of empty ps fields."); + skip = true; + } + } + } + else if (strcmp(names[field], "COMMAND") == 0) + { + // Check for exiting or idle state. + for (int pos = start[field]; pos <= end[field] && pos < linelen && !skip; pos++) + { + if (!isspace(line[pos])) // Skip spaces + { + if (strncmp(line + pos, "", 9) == 0 || + strncmp(line + pos, "", 6) == 0) + { + LogDebug(LOG_MOD_PS, "Detected exiting/idle process, " + "skipping parsing of empty ps fields."); + skip = true; + } + else + { + break; + } + } + } + } + } + } + + int field = 0; + int pos = 0; + while (names[field]) + { + // Some sanity checks. + if (pos >= linelen) + { + if (pca == PCA_ZombieSkipEmptyColumns && skip) + { + LogDebug(LOG_MOD_PS, "Assuming '%s' field is empty, " + "since ps line '%s' is not long enough to reach under its " + "header.", names[field], line); + fields[field] = xstrdup(""); + field++; + continue; + } + else + { + Log(LOG_LEVEL_ERR, "ps output line '%s' is shorter than its " + "associated header.", line); + return false; + } + } + + bool cmd = (strcmp(names[field], "CMD") == 0 || + strcmp(names[field], "COMMAND") == 0); + bool stime = !cmd && (strcmp(names[field], "STIME") == 0); + + // Equal boolean results, either both must be true, or both must be + // false. IOW we must either both be at the last field, and it must be + // CMD, or none of those. | + // v + if ((names[field + 1] != NULL) == cmd) + { + Log(LOG_LEVEL_ERR, "Last field of ps output '%s' is not " + "CMD/COMMAND.", line); + return false; + } + + // If zombie/exiting, check if field is empty. + if (pca == PCA_ZombieSkipEmptyColumns && skip) + { + int empty_pos = start[field]; + bool empty = true; + while (empty_pos <= end[field]) + { + if (!isspace(line[empty_pos])) + { + empty = false; + break; + } + empty_pos++; + } + if (empty) + { + LogDebug(LOG_MOD_PS, "Detected empty" + " '%s' field between positions %d and %d", + names[field], start[field], end[field]); + fields[field] = xstrdup(""); + pos = end[field] + 1; + field++; + continue; + } + else + { + LogDebug(LOG_MOD_PS, "Detected non-empty " + "'%s' field between positions %d and %d", + names[field], start[field], end[field]); + } + } + + // Preceding space. + while (isspace(line[pos])) + { + pos++; + } + + // Field. + int last = pos; + if (cmd) + { + // Last field, slurp up the rest, but discard trailing whitespace. + last = linelen; + while (last > pos && isspace(line[last - 1])) + { + last--; + } + } + else if (stime) + { + while (isalpha(line[last])) + { + last++; + } + if (isspace(line[last])) + { + // In this case we expect spaces followed by a number. + // It means what we first read was the month, now is the date. + do + { + last++; + } while (isspace(line[last])); + if (!isdigit(line[last])) + { + char fmt[200]; + xsnprintf(fmt, sizeof(fmt), "Unable to parse STIME entry in ps " + "output line '%%s': Expected day number after " + "'%%.%ds'", (last - 1) - pos); + Log(LOG_LEVEL_ERR, fmt, line, line + pos); + return false; + } + } + while (line[last] && !isspace(line[last])) + { + last++; + } + } + else + { + // Generic fields + while (line[last] && !isspace(line[last])) + { + last++; + } + } + + // Make a copy and store in fields. + fields[field] = xstrndup(line + pos, last - pos); + LogDebug(LOG_MOD_PS, "'%s' field '%s'" + " extracted from between positions %d and %d", + names[field], fields[field], pos, last - 1); + + pos = last; + field++; + } + + MaybeFixStartTime(line, pstime, names, fields); + + return true; +} + +/*******************************************************************/ + +static int GetProcColumnIndex(const char *name1, const char *name2, char **names) +{ + for (int i = 0; names[i] != NULL; i++) + { + if (strcmp(names[i], name1) == 0 || + strcmp(names[i], name2) == 0) + { + return i; + } + } + + LogDebug(LOG_MOD_PS, "Process column %s/%s" + " was not supported on this system", + name1, name2); + + return -1; +} + +/**********************************************************************************/ + +bool IsProcessNameRunning(char *procNameRegex) +{ + char *colHeaders[CF_PROCCOLS]; + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + bool matched = false; + int i; + + memset(colHeaders, 0, sizeof(colHeaders)); + + if (PROCESSTABLE == NULL) + { + Log(LOG_LEVEL_ERR, "IsProcessNameRunning: PROCESSTABLE is empty"); + return false; + } + /* TODO: use actual time of ps-run, not time(NULL), which may be later. */ + time_t pstime = time(NULL); + + GetProcessColumnNames(PROCESSTABLE->name, colHeaders, start, end); + + for (const Item *ip = PROCESSTABLE->next; !matched && ip != NULL; ip = ip->next) // iterate over ps lines + { + char *lineSplit[CF_PROCCOLS]; + memset(lineSplit, 0, sizeof(lineSplit)); + + if (NULL_OR_EMPTY(ip->name)) + { + continue; + } + + if (!SplitProcLine(ip->name, pstime, colHeaders, start, end, + PS_COLUMN_ALGORITHM[VPSHARDCLASS], lineSplit)) + { + Log(LOG_LEVEL_ERR, "IsProcessNameRunning: Could not split process line '%s'", ip->name); + goto loop_cleanup; + } + + ApplyPlatformExtraTable(colHeaders, lineSplit); + + if (SelectProcRegexMatch("CMD", "COMMAND", procNameRegex, true, colHeaders, lineSplit)) + { + matched = true; + } + + loop_cleanup: + for (i = 0; lineSplit[i] != NULL; i++) + { + free(lineSplit[i]); + } + } + + for (i = 0; colHeaders[i] != NULL; i++) + { + free(colHeaders[i]); + } + + return matched; +} + + +static void GetProcessColumnNames(const char *proc, char **names, int *start, int *end) +{ + char title[16]; + int col, offset = 0; + + if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) + { + LogDebug(LOG_MOD_PS, "Parsing ps line: '%s'", proc); + // Makes the entry line up with the line above. + PrintStringIndexLine(18, strlen(proc)); + } + + for (col = 0; col < CF_PROCCOLS; col++) + { + start[col] = end[col] = -1; + names[col] = NULL; + } + + col = 0; + + for (const char *sp = proc; *sp != '\0'; sp++) + { + offset = sp - proc; + + if (isspace((unsigned char) *sp)) + { + if (start[col] != -1) + { + LogDebug(LOG_MOD_PS, "End of '%s' is %d", title, offset - 1); + end[col++] = offset - 1; + if (col >= CF_PROCCOLS) /* No space for more columns. */ + { + size_t blank = strspn(sp, " \t\r\n\f\v"); + if (sp[blank]) /* i.e. that wasn't everything. */ + { + /* If this happens, we have more columns in + * our ps output than space to store them. + * Update the #define CF_PROCCOLS (last seen + * in libpromises/cf3.defs.h) to a bigger + * number ! */ + Log(LOG_LEVEL_ERR, + "Process table lacks space for last columns: %s", + sp + blank); + } + break; + } + } + } + else if (start[col] == -1) + { + if (col == 0) + { + // The first column always extends all the way to the left. + start[col] = 0; + } + else + { + start[col] = offset; + } + + if (sscanf(sp, "%15s", title) == 1) + { + LogDebug(LOG_MOD_PS, "Start of '%s' is at offset: %d", + title, offset); + LogDebug(LOG_MOD_PS, "Col[%d] = '%s'", col, title); + + names[col] = xstrdup(title); + } + } + } + + if (end[col] == -1) + { + LogDebug(LOG_MOD_PS, "End of '%s' is %d", title, offset); + end[col] = offset; + } +} + +#ifndef _WIN32 +static const char *GetProcessOptions(void) +{ + +# ifdef __linux__ + if (strncmp(VSYSNAME.release, "2.4", 3) == 0) + { + // No threads on 2.4 kernels, so omit nlwp + return "-eo user,pid,ppid,pgid,pcpu,pmem,vsz,ni,rss:9,stime,etime,time,args"; + } +# endif + + return VPSOPTS[VPSHARDCLASS]; +} +#endif + +static int ExtractPid(char *psentry, char **names, int *end) +{ + int offset = 0; + + for (int col = 0; col < CF_PROCCOLS; col++) + { + if (strcmp(names[col], "PID") == 0) + { + if (col > 0) + { + offset = end[col - 1]; + } + break; + } + } + + for (const char *sp = psentry + offset; *sp != '\0'; sp++) /* if first field contains alpha, skip */ + { + /* If start with alphanum then skip it till the first space */ + + if (isalnum((unsigned char) *sp)) + { + while (*sp != ' ' && *sp != '\0') + { + sp++; + } + } + + while (*sp == ' ' || *sp == '\t') + { + sp++; + } + + int pid; + if (sscanf(sp, "%d", &pid) == 1 && pid != -1) + { + return pid; + } + } + + return -1; +} + +# ifndef _WIN32 +# ifdef HAVE_GETZONEID +/* ListLookup with the following return semantics + * -1 if the first argument is smaller than the second + * 0 if the arguments are equal + * 1 if the first argument is bigger than the second + */ +int PidListCompare(const void *pid1, const void *pid2, ARG_UNUSED void *user_data) +{ + int p1 = (intptr_t)(void *)pid1; + int p2 = (intptr_t)(void *)pid2; + + if (p1 < p2) + { + return -1; + } + else if (p1 > p2) + { + return 1; + } + return 0; +} +/* Load processes using zone-aware ps + * to obtain solaris list of global + * process ids for root and non-root + * users to lookup later */ +int ZLoadProcesstable(Seq *pidlist, Seq *rootpidlist) +{ + + char *names[CF_PROCCOLS]; + int start[CF_PROCCOLS]; + int end[CF_PROCCOLS]; + + const char *pscmd = "/usr/bin/ps -Aleo zone,user,pid"; + + FILE *psf = cf_popen(pscmd, "r", false); + if (psf == NULL) + { + Log(LOG_LEVEL_ERR, "ZLoadProcesstable: Couldn't open the process list with command %s.", pscmd); + return false; + } + + size_t pbuff_size = CF_BUFSIZE; + char *pbuff = xmalloc(pbuff_size); + bool header = true; + + while (true) + { + ssize_t res = CfReadLine(&pbuff, &pbuff_size, psf); + if (res == -1) + { + if (!feof(psf)) + { + Log(LOG_LEVEL_ERR, "IsGlobalProcess(char **, int): Unable to read process list with command '%s'. (fread: %s)", pscmd, GetErrorStr()); + cf_pclose(psf); + free(pbuff); + return false; + } + else + { + break; + } + } + Chop(pbuff, pbuff_size); + if (header) /* This line is the header. */ + { + GetProcessColumnNames(pbuff, &names[0], start, end); + } + else + { + int pid = ExtractPid(pbuff, &names[0], end); + + size_t zone_offset = strspn(pbuff, " "); + size_t zone_end_offset = strcspn(pbuff + zone_offset, " ") + zone_offset; + size_t user_offset = strspn(pbuff + zone_end_offset, " ") + zone_end_offset; + size_t user_end_offset = strcspn(pbuff + user_offset, " ") + user_offset; + bool is_global = (zone_end_offset - zone_offset == 6 + && strncmp(pbuff + zone_offset, "global", 6) == 0); + bool is_root = (user_end_offset - user_offset == 4 + && strncmp(pbuff + user_offset, "root", 4) == 0); + + if (is_global && is_root) + { + SeqAppend(rootpidlist, (void*)(intptr_t)pid); + } + else if (is_global && !is_root) + { + SeqAppend(pidlist, (void*)(intptr_t)pid); + } + } + + header = false; + } + cf_pclose(psf); + free(pbuff); + return true; +} +bool PidInSeq(Seq *list, int pid) +{ + void *res = SeqLookup(list, (void *)(intptr_t)pid, PidListCompare); + int result = (intptr_t)(void*)res; + + if (result == pid) + { + return true; + } + return false; +} +/* return true if the process with + * pid is in the global zone */ +int IsGlobalProcess(int pid, Seq *pidlist, Seq *rootpidlist) +{ + if (PidInSeq(pidlist, pid) || PidInSeq(rootpidlist, pid)) + { + return true; + } + else + { + return false; + } +} +void ZCopyProcessList(Item **dest, const Item *source, Seq *pidlist, char **names, int *end) +{ + int gpid = ExtractPid(source->name, names, end); + + if (PidInSeq(pidlist, gpid)) + { + PrependItem(dest, source->name, ""); + } +} +# endif /* HAVE_GETZONEID */ + +static void CheckPsLineLimitations(void) +{ +#ifdef __hpux + FILE *ps_fd; + int ret; + char limit[21]; + char *buf = NULL; + size_t bufsize = 0; + + ps_fd = safe_fopen("/etc/default/ps", "r"); + if (!ps_fd) + { + Log(LOG_LEVEL_VERBOSE, "Could not open '/etc/default/ps' " + "to check ps line length limitations."); + return; + } + + while (true) + { + ret = CfReadLine(&buf, &bufsize, ps_fd); + if (ret < 0) + { + break; + } + + ret = sscanf(buf, "DEFAULT_CMD_LINE_WIDTH = %20[0-9]", limit); + + if (ret == 1) + { + if (atoi(limit) < 1024) + { + Log(LOG_LEVEL_VERBOSE, "ps line length limit is less than 1024. " + "Consider adjusting the DEFAULT_CMD_LINE_WIDTH setting in /etc/default/ps " + "in order to guarantee correct process matching."); + } + break; + } + } + + free(buf); + fclose(ps_fd); +#endif // __hpux +} +#endif // _WIN32 + +const char *GetProcessTableLegend(void) +{ + if (PROCESSTABLE) + { + // First entry in the table is legend. + return PROCESSTABLE->name; + } + else + { + return ""; + } +} + +#if defined(__sun) || defined(TEST_UNIT_TEST) +static FILE *OpenUcbPsPipe(void) +{ + for (int i = 0; UCB_STYLE_PS[i]; i++) + { + struct stat statbuf; + if (stat(UCB_STYLE_PS[i], &statbuf) < 0) + { + Log(LOG_LEVEL_VERBOSE, + "%s not found, cannot be used for extra process information", + UCB_STYLE_PS[i]); + continue; + } + if (!(statbuf.st_mode & 0111)) + { + Log(LOG_LEVEL_VERBOSE, + "%s not executable, cannot be used for extra process information", + UCB_STYLE_PS[i]); + continue; + } + + char *ps_cmd; + xasprintf(&ps_cmd, "%s %s", UCB_STYLE_PS[i], + UCB_STYLE_PS_ARGS); + + FILE *cmd = cf_popen(ps_cmd, "rt", false); + if (!cmd) + { + Log(LOG_LEVEL_WARNING, "Could not execute \"%s\", extra process " + "information not available. " + "Process command line length may be limited to 80 characters.", + ps_cmd); + } + + free(ps_cmd); + + return cmd; + } + + Log(LOG_LEVEL_VERBOSE, "No eligible tool for extra process information " + "found. Skipping."); + + return NULL; +} + +static void ReadFromUcbPsPipe(FILE *cmd) +{ + char *names[CF_PROCCOLS]; + memset(names, 0, sizeof(names)); + int start[CF_PROCCOLS]; + int end[CF_PROCCOLS]; + char *line = NULL; + size_t linesize = 0; + bool header = true; + time_t pstime = time(NULL); + int pidcol = -1; + int cmdcol = -1; + while (CfReadLine(&line, &linesize, cmd) > 0) + { + if (header) + { + GetProcessColumnNames(line, names, start, end); + + for (int i = 0; names[i]; i++) + { + if (strcmp(names[i], "PID") == 0) + { + pidcol = i; + } + else if (strcmp(names[i], "COMMAND") == 0 + || strcmp(names[i], "CMD") == 0) + { + cmdcol = i; + } + } + + if (pidcol < 0 || cmdcol < 0) + { + Log(LOG_LEVEL_ERR, + "Could not find PID and/or CMD/COMMAND column in " + "ps output: \"%s\"", line); + break; + } + + header = false; + continue; + } + + + char *columns[CF_PROCCOLS]; + memset(columns, 0, sizeof(columns)); + if (!SplitProcLine(line, pstime, names, start, end, + UCB_STYLE_PS_COLUMN_ALGORITHM, columns)) + { + Log(LOG_LEVEL_WARNING, + "Not able to parse ps output: \"%s\"", line); + } + + StringMapInsert(UCB_PS_MAP, columns[pidcol], columns[cmdcol]); + // We avoid strdup'ing these strings by claiming ownership here. + columns[pidcol] = NULL; + columns[cmdcol] = NULL; + + for (int i = 0; i < CF_PROCCOLS; i++) + { + // There may be some null entries here, but since we "steal" + // strings in the section above, we may have set some of them to + // NULL and there may be following non-NULL fields. + free(columns[i]); + } + } + + if (!feof(cmd) && ferror(cmd)) + { + Log(LOG_LEVEL_ERR, "Error while reading output from ps: %s", + GetErrorStr()); + } + + for (int i = 0; names[i] && i < CF_PROCCOLS; i++) + { + free(names[i]); + } + + free(line); +} + +static void ClearPlatformExtraTable(void) +{ + if (UCB_PS_MAP) + { + StringMapDestroy(UCB_PS_MAP); + UCB_PS_MAP = NULL; + } +} + +static void LoadPlatformExtraTable(void) +{ + if (UCB_PS_MAP) + { + return; + } + + UCB_PS_MAP = StringMapNew(); + + FILE *cmd = OpenUcbPsPipe(); + if (!cmd) + { + return; + } + ReadFromUcbPsPipe(cmd); + if (cf_pclose(cmd) != 0) + { + Log(LOG_LEVEL_WARNING, "Command returned non-zero while gathering " + "extra process information."); + // Make an empty map, in this case. The information can't be trusted. + StringMapClear(UCB_PS_MAP); + } +} + +static void ApplyPlatformExtraTable(char **names, char **columns) +{ + int pidcol = -1; + + for (int i = 0; names[i] && columns[i]; i++) + { + if (strcmp(names[i], "PID") == 0) + { + pidcol = i; + break; + } + } + + if (pidcol == -1 || !StringMapHasKey(UCB_PS_MAP, columns[pidcol])) + { + return; + } + + for (int i = 0; names[i] && columns[i]; i++) + { + if (strcmp(names[i], "COMMAND") == 0 || strcmp(names[i], "CMD") == 0) + { + free(columns[i]); + columns[i] = xstrdup(StringMapGet(UCB_PS_MAP, columns[pidcol])); + break; + } + } +} + +#else +static inline void LoadPlatformExtraTable(void) +{ +} + +static inline void ClearPlatformExtraTable(void) +{ +} + +static inline void ApplyPlatformExtraTable(ARG_UNUSED char **names, ARG_UNUSED char **columns) +{ +} +#endif + +#ifndef _WIN32 +bool LoadProcessTable() +{ + FILE *prp; + char pscomm[CF_MAXLINKSIZE]; + Item *rootprocs = NULL; + Item *otherprocs = NULL; + + + if (PROCESSTABLE) + { + Log(LOG_LEVEL_VERBOSE, "Reusing cached process table"); + return true; + } + + LoadPlatformExtraTable(); + + CheckPsLineLimitations(); + + const char *psopts = GetProcessOptions(); + + snprintf(pscomm, CF_MAXLINKSIZE, "%s %s", VPSCOMM[VPSHARDCLASS], psopts); + + Log(LOG_LEVEL_VERBOSE, "Observe process table with %s", pscomm); + + if ((prp = cf_popen(pscomm, "r", false)) == NULL) + { + Log(LOG_LEVEL_ERR, "Couldn't open the process list with command '%s'. (popen: %s)", pscomm, GetErrorStr()); + return false; + } + + size_t vbuff_size = CF_BUFSIZE; + char *vbuff = xmalloc(vbuff_size); + +# ifdef HAVE_GETZONEID + + char *names[CF_PROCCOLS]; + int start[CF_PROCCOLS]; + int end[CF_PROCCOLS]; + Seq *pidlist = SeqNew(1, NULL); + Seq *rootpidlist = SeqNew(1, NULL); + bool global_zone = IsGlobalZone(); + + if (global_zone) + { + int res = ZLoadProcesstable(pidlist, rootpidlist); + + if (res == false) + { + Log(LOG_LEVEL_ERR, "Unable to load solaris zone process table."); + return false; + } + } + +# endif + + ARG_UNUSED bool header = true; /* used only if HAVE_GETZONEID */ + + for (;;) + { + ssize_t res = CfReadLine(&vbuff, &vbuff_size, prp); + if (res == -1) + { + if (!feof(prp)) + { + Log(LOG_LEVEL_ERR, "Unable to read process list with command '%s'. (fread: %s)", pscomm, GetErrorStr()); + cf_pclose(prp); + free(vbuff); + return false; + } + else + { + break; + } + } + Chop(vbuff, vbuff_size); + +# ifdef HAVE_GETZONEID + + if (global_zone) + { + if (header) + { /* this is the banner so get the column header names for later use*/ + GetProcessColumnNames(vbuff, &names[0], start, end); + } + else + { + int gpid = ExtractPid(vbuff, names, end); + + if (!IsGlobalProcess(gpid, pidlist, rootpidlist)) + { + continue; + } + } + } + +# endif + AppendItem(&PROCESSTABLE, vbuff, ""); + + header = false; + } + + cf_pclose(prp); + +/* Now save the data */ + const char* const statedir = GetStateDir(); + + snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_procs", statedir, FILE_SEPARATOR); + + RawSaveItemList(PROCESSTABLE, vbuff, NewLineMode_Unix); + +# ifdef HAVE_GETZONEID + if (global_zone) /* pidlist and rootpidlist are empty if we're not in the global zone */ + { + Item *ip = PROCESSTABLE; + while (ip != NULL) + { + ZCopyProcessList(&rootprocs, ip, rootpidlist, names, end); + ip = ip->next; + } + ReverseItemList(rootprocs); + ip = PROCESSTABLE; + while (ip != NULL) + { + ZCopyProcessList(&otherprocs, ip, pidlist, names, end); + ip = ip->next; + } + ReverseItemList(otherprocs); + } + else +# endif + { + CopyList(&rootprocs, PROCESSTABLE); + CopyList(&otherprocs, PROCESSTABLE); + + while (DeleteItemNotContaining(&rootprocs, "root")) + { + } + + while (DeleteItemContaining(&otherprocs, "root")) + { + } + } + if (otherprocs) + { + PrependItem(&rootprocs, otherprocs->name, NULL); + } + + // TODO: Change safe_fopen() to default to 0600, then remove this. + const mode_t old_umask = SetUmask(0077); + + snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_rootprocs", statedir, FILE_SEPARATOR); + RawSaveItemList(rootprocs, vbuff, NewLineMode_Unix); + DeleteItemList(rootprocs); + + snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_otherprocs", statedir, FILE_SEPARATOR); + RawSaveItemList(otherprocs, vbuff, NewLineMode_Unix); + DeleteItemList(otherprocs); + + RestoreUmask(old_umask); + + free(vbuff); + return true; +} +# endif + +void ClearProcessTable(void) +{ + ClearPlatformExtraTable(); + + DeleteItemList(PROCESSTABLE); + PROCESSTABLE = NULL; +} diff --git a/libpromises/processes_select.h b/libpromises/processes_select.h new file mode 100644 index 0000000000..317d86582e --- /dev/null +++ b/libpromises/processes_select.h @@ -0,0 +1,42 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PROCESSES_SELECT_H +#define CFENGINE_PROCESSES_SELECT_H + +#include + +#ifdef _WIN32 +extern Item *PROCESSTABLE; +#endif + +bool LoadProcessTable(void); +void ClearProcessTable(void); + +Item *SelectProcesses(const char *process_name, const ProcessSelect *a, bool attrselect); +bool IsProcessNameRunning(char *procNameRegex); + +const char *GetProcessTableLegend(void); + +#endif diff --git a/libpromises/promises.c b/libpromises/promises.c new file mode 100644 index 0000000000..4c917ef581 --- /dev/null +++ b/libpromises/promises.c @@ -0,0 +1,941 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void AddDefaultBodiesToPromise(EvalContext *ctx, Promise *promise, const PromiseTypeSyntax *syntax); + +void CopyBodyConstraintsToPromise(EvalContext *ctx, Promise *pp, + const Body *bp) +{ + for (size_t k = 0; k < SeqLength(bp->conlist); k++) + { + Constraint *scp = SeqAt(bp->conlist, k); + + if (IsDefinedClass(ctx, scp->classes)) + { + Rval returnval = ExpandPrivateRval(ctx, NULL, "body", + scp->rval.item, scp->rval.type); + PromiseAppendConstraint(pp, scp->lval, returnval, false); + } + } +} + +/** + * Get a map that rewrites body according to parameters. + * + * @NOTE make sure you free the returned map with JsonDestroy(). + */ +static JsonElement *GetBodyRewriter(const EvalContext *ctx, + const Body *current_body, + const Rval *called_rval, + bool in_inheritance_chain) +{ + size_t given_args = 0; + JsonElement *arg_rewriter = JsonObjectCreate(2); + + if (called_rval == NULL) + { + // nothing needed, this is not an inherit_from rval + } + else if (called_rval->type == RVAL_TYPE_SCALAR) + { + // We leave the parameters as they were. + + // Unless the current body matches the + // parameters of the inherited body, there + // will be unexpanded variables. But the + // alternative is to match up body and fncall + // arguments, which is not trivial. + } + else if (called_rval->type == RVAL_TYPE_FNCALL) + { + const Rlist *call_args = RvalFnCallValue(*called_rval)->args; + const Rlist *body_args = current_body->args; + + given_args = RlistLen(call_args); + + while (call_args != NULL && + body_args != NULL) + { + JsonObjectAppendString(arg_rewriter, + RlistScalarValue(body_args), + RlistScalarValue(call_args)); + call_args = call_args->next; + body_args = body_args->next; + } + } + + size_t required_args = RlistLen(current_body->args); + // only check arguments for inherited bodies + if (in_inheritance_chain && required_args != given_args) + { + FatalError(ctx, + "Argument count mismatch for body " + "(gave %zu arguments) vs. inherited body '%s:%s' " + "(requires %zu arguments)", + given_args, + current_body->ns, current_body->name, required_args); + } + + return arg_rewriter; +} + +/** + * Appends expanded bodies to the promise #pcopy. It expands the bodies based + * on arguments, inheritance, and it can optionally flatten the '@' slists and + * expand the variables in the body according to the EvalContext. + */ +static void AppendExpandedBodies(EvalContext *ctx, Promise *pcopy, + const Seq *bodies_and_args, + bool flatten_slists, bool expand_body_vars) +{ + size_t ba_len = SeqLength(bodies_and_args); + + /* Iterate over all parent bodies, and finally over the body of the + * promise itself, expanding arguments. We have already reversed the Seq + * so we start with the most distant parent in the inheritance tree. */ + for (size_t i = 0; i < ba_len; i += 2) + { + const Rval *called_rval = SeqAt(bodies_and_args, i); + const Body *current_body = SeqAt(bodies_and_args, i + 1); + bool in_inheritance_chain= (ba_len - i > 2); + + JsonElement *arg_rewriter = + GetBodyRewriter(ctx, current_body, called_rval, + in_inheritance_chain); + + size_t constraints_num = SeqLength(current_body->conlist); + for (size_t k = 0; k < constraints_num; k++) + { + const Constraint *scp = SeqAt(current_body->conlist, k); + + // we don't copy the inherit_from attribute or associated call + if (strcmp("inherit_from", scp->lval) == 0) + { + continue; + } + + if (IsDefinedClass(ctx, scp->classes)) + { + /* We copy the Rval expanding all, including inherited, + * body arguments. */ + Rval newrv = RvalCopyRewriter(scp->rval, arg_rewriter); + + /* Expand '@' slists. */ + if (flatten_slists && newrv.type == RVAL_TYPE_LIST) + { + RlistFlatten(ctx, (Rlist **) &newrv.item); + } + + /* Expand body vars; note it has to happen ONLY ONCE. */ + if (expand_body_vars) + { + Rval newrv2 = ExpandPrivateRval(ctx, NULL, "body", + newrv.item, newrv.type); + RvalDestroy(newrv); + newrv = newrv2; + } + + /* PromiseAppendConstraint() overwrites existing constraints, + thus inheritance just works, as it correctly overwrites + parents' constraints. */ + Constraint *scp_copy = + PromiseAppendConstraint(pcopy, scp->lval, + newrv, false); + scp_copy->offset = scp->offset; + + char *rval_s = RvalToString(scp->rval); + char *rval_exp_s = RvalToString(scp_copy->rval); + Log(LOG_LEVEL_DEBUG, "DeRefCopyPromise(): " + "expanding constraint '%s': '%s' -> '%s'", + scp->lval, rval_s, rval_exp_s); + free(rval_exp_s); + free(rval_s); + } + } /* for all body constraints */ + + JsonDestroy(arg_rewriter); + } +} + +static Rval GetExpandedBodyAsContainer(EvalContext *ctx, + const Seq *bodies_and_args, + bool flatten_slists, + bool expand_body_vars) +{ + const size_t ba_len = SeqLength(bodies_and_args); + JsonElement *body = JsonObjectCreate(ba_len / 2); + + /* Iterate over all parent bodies, and finally over the body of the + * promise itself, expanding arguments. We have already reversed the Seq + * so we start with the most distant parent in the inheritance tree. */ + for (size_t i = 0; i < ba_len; i += 2) + { + const Rval *called_rval = SeqAt(bodies_and_args, i); + const Body *current_body = SeqAt(bodies_and_args, i + 1); + bool in_inheritance_chain = (ba_len - i > 2); + + JsonElement *arg_rewriter = + GetBodyRewriter(ctx, current_body, called_rval, + in_inheritance_chain); + + const size_t constraints_num = SeqLength(current_body->conlist); + for (size_t k = 0; k < constraints_num; k++) + { + const Constraint *scp = SeqAt(current_body->conlist, k); + + // we don't copy the inherit_from attribute or associated call + if (StringEqual("inherit_from", scp->lval)) + { + continue; + } + + if (IsDefinedClass(ctx, scp->classes)) + { + /* We copy the Rval expanding all, including inherited, + * body arguments. */ + Rval newrv = RvalCopyRewriter(scp->rval, arg_rewriter); + + /* Expand '@' slists. */ + if (flatten_slists && newrv.type == RVAL_TYPE_LIST) + { + RlistFlatten(ctx, (Rlist **) &newrv.item); + } + + /* Expand body vars; note it has to happen ONLY ONCE. */ + if (expand_body_vars) + { + Rval newrv2 = ExpandPrivateRval(ctx, NULL, "body", + newrv.item, newrv.type); + RvalDestroy(newrv); + newrv = newrv2; + } + + /* JsonObjectAppendElement() overwrites existing constraints, + thus inheritance just works, as it correctly overwrites + parents' constraints. */ + JsonObjectAppendElement(body, scp->lval, RvalToJson(newrv)); + + if (WouldLog(LOG_LEVEL_DEBUG)) + { + char *rval_s = RvalToString(scp->rval); + char *rval_exp_s = RvalToString(newrv); + Log(LOG_LEVEL_DEBUG, "DeRefCopyPromise(): " + "expanding constraint '%s': '%s' -> '%s'", + scp->lval, rval_s, rval_exp_s); + free(rval_exp_s); + free(rval_s); + } + } + } /* for all body constraints */ + + JsonDestroy(arg_rewriter); + } + + return RvalNew((void *) body, RVAL_TYPE_CONTAINER); +} + +/** + * Copies the promise, expanding the constraints. + * + * 1. copy the promise itself + * 2. copy constraints: copy the bodies expanding arguments passed + * (arg_rewrite), copy the bundles, copy the rest of the constraints + * 3. flatten '@' slists everywhere + * 4. handle body inheritance + */ +Promise *DeRefCopyPromise(EvalContext *ctx, const Promise *pp) +{ + Log(LOG_LEVEL_DEBUG, + "DeRefCopyPromise(): promiser:'%s'", + SAFENULL(pp->promiser)); + + Promise *pcopy = xcalloc(1, sizeof(Promise)); + + if (pp->promiser) + { + pcopy->promiser = xstrdup(pp->promiser); + } + + /* Copy promisee (if not NULL) while expanding '@' slists. */ + pcopy->promisee = RvalCopy(pp->promisee); + if (pcopy->promisee.type == RVAL_TYPE_LIST) + { + RlistFlatten(ctx, (Rlist **) &pcopy->promisee.item); + } + + if (pp->promisee.item != NULL) + { + char *promisee_string = RvalToString(pp->promisee); + + CF_ASSERT(pcopy->promisee.item != NULL, + "DeRefCopyPromise: Failed to copy promisee: %s", + promisee_string); + Log(LOG_LEVEL_DEBUG, "DeRefCopyPromise(): " + "expanded promisee: '%s'", + promisee_string); + free(promisee_string); + } + + assert(pp->classes); + pcopy->classes = xstrdup(pp->classes); + pcopy->parent_section = pp->parent_section; + pcopy->offset.line = pp->offset.line; + pcopy->comment = pp->comment ? xstrdup(pp->comment) : NULL; + pcopy->conlist = SeqNew(10, ConstraintDestroy); + pcopy->org_pp = pp->org_pp; + pcopy->offset = pp->offset; + +/* No further type checking should be necessary here, already done by CheckConstraintTypeMatch */ + + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + const Policy *policy = PolicyFromPromise(pp); + + /* bodies_and_args: Do we have body to expand, possibly with arguments? + * At position 0 we'll have the body, then its rval, then the same for + * each of its inherit_from parents. */ + Seq *bodies_and_args = NULL; + const Rlist *args = NULL; + const char *body_reference = NULL; + + /* A body template reference could look like a scalar or fn to the parser w/w () */ + switch (cp->rval.type) + { + case RVAL_TYPE_SCALAR: + if (cp->references_body) + { + body_reference = RvalScalarValue(cp->rval); + bodies_and_args = EvalContextResolveBodyExpression(ctx, policy, body_reference, cp->lval); + } + args = NULL; + break; + case RVAL_TYPE_FNCALL: + body_reference = RvalFnCallValue(cp->rval)->name; + bodies_and_args = EvalContextResolveBodyExpression(ctx, policy, body_reference, cp->lval); + args = RvalFnCallValue(cp->rval)->args; + break; + default: + break; + } + + /* First case is: we have a body to expand lval = body(args). */ + + if (bodies_and_args != NULL && + SeqLength(bodies_and_args) > 0) + { + const Body *bp = SeqAt(bodies_and_args, 0); + assert(bp != NULL); + + SeqReverse(bodies_and_args); // when we iterate, start with the furthest parent + + EvalContextStackPushBodyFrame(ctx, pcopy, bp, args); + + if (strcmp(bp->type, cp->lval) != 0) + { + Log(LOG_LEVEL_ERR, + "Body type mismatch for body reference '%s' in promise " + "at line %zu of file '%s', '%s' does not equal '%s'", + body_reference, pp->offset.line, + PromiseGetBundle(pp)->source_path, bp->type, cp->lval); + } + + Log(LOG_LEVEL_DEBUG, "DeRefCopyPromise(): " + "copying body %s: '%s'", + cp->lval, body_reference); + + if (IsDefinedClass(ctx, cp->classes) && !bp->is_custom) + { + /* For new package promises we need to have name of the + * package_manager body. */ + char body_name[strlen(cp->lval) + 6]; + xsnprintf(body_name, sizeof(body_name), "%s_name", cp->lval); + PromiseAppendConstraint(pcopy, body_name, + (Rval) {xstrdup(bp->name), RVAL_TYPE_SCALAR }, false); + + /* Keep the referent body type as a boolean for convenience + * when checking later. */ + PromiseAppendConstraint(pcopy, cp->lval, + (Rval) {xstrdup("true"), RVAL_TYPE_SCALAR }, false); + } + + if (bp->args) /* There are arguments to insert */ + { + if (!args) + { + Log(LOG_LEVEL_ERR, + "Argument mismatch for body reference '%s' in promise " + "at line %zu of file '%s'", + body_reference, pp->offset.line, + PromiseGetBundle(pp)->source_path); + } + + if (bp->is_custom) + { + PromiseAppendConstraint(pcopy, cp->lval, + GetExpandedBodyAsContainer(ctx, bodies_and_args, + false, true), false); + } + else + { + AppendExpandedBodies(ctx, pcopy, bodies_and_args, + false, true); + } + } + else /* No body arguments or body undeclared */ + { + if (args) /* body undeclared */ + { + Log(LOG_LEVEL_ERR, + "Apparent body '%s' was undeclared or could " + "have incorrect args, but used in a promise near " + "line %zu of %s (possible unquoted literal value)", + RvalScalarValue(cp->rval), pp->offset.line, + PromiseGetBundle(pp)->source_path); + } + else /* no body arguments, but maybe the inherited bodies have */ + { + if (bp->is_custom) + { + PromiseAppendConstraint(pcopy, cp->lval, + GetExpandedBodyAsContainer(ctx, bodies_and_args, + true, false), false); + } + else + { + AppendExpandedBodies(ctx, pcopy, bodies_and_args, + true, false); + } + } + } + + EvalContextStackPopFrame(ctx); + SeqDestroy(bodies_and_args); + } + else /* constraint is not a body */ + { + if (cp->references_body) + { + // assume this is a typed bundle (e.g. edit_line) + const Bundle *callee = + EvalContextResolveBundleExpression(ctx, policy, + body_reference, + cp->lval); + if (!callee) + { + // otherwise, assume this is a method-type call + callee = EvalContextResolveBundleExpression(ctx, policy, + body_reference, + "agent"); + if (!callee) + { + callee = EvalContextResolveBundleExpression(ctx, policy, + body_reference, + "common"); + } + } + + if (callee == NULL && + cp->rval.type != RVAL_TYPE_FNCALL && + strcmp("ifvarclass", cp->lval) != 0 && + strcmp("if", cp->lval) != 0) + { + char *rval_string = RvalToString(cp->rval); + Log(LOG_LEVEL_ERR, + "Apparent bundle '%s' was undeclared, but " + "used in a promise near line %zu of %s " + "(possible unquoted literal value)", + rval_string, pp->offset.line, + PromiseGetBundle(pp)->source_path); + free(rval_string); + } + + Log(LOG_LEVEL_DEBUG, + "DeRefCopyPromise(): copying bundle: '%s'", + body_reference); + } + else + { + Log(LOG_LEVEL_DEBUG, + "DeRefCopyPromise(): copying constraint: '%s'", + cp->lval); + } + + /* For all non-body constraints: copy the Rval expanding the + * '@' list variables. */ + + if (IsDefinedClass(ctx, cp->classes)) + { + Rval newrv = RvalCopy(cp->rval); + if (newrv.type == RVAL_TYPE_LIST) + { + RlistFlatten(ctx, (Rlist **) &newrv.item); + } + + PromiseAppendConstraint(pcopy, cp->lval, newrv, false); + } + } + + } /* for all constraints */ + + // Add default body for promise body types that are not present + char *bundle_type = pcopy->parent_section->parent_bundle->type; + const char *promise_type = PromiseGetPromiseType(pcopy); + const PromiseTypeSyntax *syntax = PromiseTypeSyntaxGet(bundle_type, promise_type); + AddDefaultBodiesToPromise(ctx, pcopy, syntax); + + // Add default body for global body types that are not present + const PromiseTypeSyntax *global_syntax = PromiseTypeSyntaxGet("*", "*"); + AddDefaultBodiesToPromise(ctx, pcopy, global_syntax); + + return pcopy; +} + +// Try to add default bodies to promise for every body type found in syntax +static void AddDefaultBodiesToPromise(EvalContext *ctx, Promise *promise, const PromiseTypeSyntax *syntax) +{ + // do nothing if syntax is not defined + if (syntax == NULL) { + return; + } + + // iterate over possible constraints + for (int i = 0; syntax->constraints[i].lval; i++) + { + // of type body + if(syntax->constraints[i].dtype == CF_DATA_TYPE_BODY) { + const char *constraint_type = syntax->constraints[i].lval; + // if there is no matching body in this promise + if(!PromiseBundleOrBodyConstraintExists(ctx, constraint_type, promise)) { + const Policy *policy = PolicyFromPromise(promise); + // default format is _ + char* default_body_name = StringConcatenate(3, PromiseGetPromiseType(promise), "_", constraint_type); + const Body *bp = EvalContextFindFirstMatchingBody(policy, constraint_type, "bodydefault", default_body_name); + if(bp) { + Log(LOG_LEVEL_VERBOSE, "Using the default body: %60s", default_body_name); + CopyBodyConstraintsToPromise(ctx, promise, bp); + } + free(default_body_name); + } + } + } +} + +/*****************************************************************************/ + +static bool EvaluateConstraintIteration(EvalContext *ctx, const Constraint *cp, Rval *rval_out) +{ + assert(cp->type == POLICY_ELEMENT_TYPE_PROMISE); + const Promise *pp = cp->parent.promise; + + if (!IsDefinedClass(ctx, cp->classes)) + { + return false; + } + + if (ExpectedDataType(cp->lval) == CF_DATA_TYPE_BUNDLE) + { + *rval_out = ExpandBundleReference(ctx, NULL, "this", cp->rval); + } + else + { + *rval_out = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL, + "this", cp->rval, false, pp); + } + + return true; +} + +/** + @brief Helper function to determine whether the Rval of ifvarclass/if/unless is defined. + If the Rval is a function, call that function. +*/ +static ExpressionValue CheckVarClassExpression(const EvalContext *ctx, const Constraint *cp, Promise *pcopy) +{ + assert(ctx); + assert(cp); + assert(pcopy); + + /* + This might fail to expand if there are unexpanded variables in function arguments + (in which case the function won't be called at all), but the function still returns true. + + If expansion fails for other reasons, assume that we don't know this class. + */ + Rval final; + if (!EvaluateConstraintIteration((EvalContext*)ctx, cp, &final)) + { + return EXPRESSION_VALUE_ERROR; + } + + char *classes = NULL; + PromiseAppendConstraint(pcopy, cp->lval, final, false); + switch (final.type) + { + case RVAL_TYPE_SCALAR: + classes = RvalScalarValue(final); + break; + + case RVAL_TYPE_FNCALL: + Log(LOG_LEVEL_DEBUG, "Function call in class expression did not succeed"); + break; + + default: + break; + } + + if (classes == NULL) + { + return EXPRESSION_VALUE_ERROR; + } + // sanity check for unexpanded variables + if (strchr(classes, '$') || strchr(classes, '@')) + { + Log(LOG_LEVEL_DEBUG, "Class expression did not evaluate"); + return EXPRESSION_VALUE_ERROR; + } + + return CheckClassExpression(ctx, classes); +} + +/* Expands "$(this.promiser)" comment if present. Writes the result to pp. */ +static void DereferenceAndPutComment(Promise* pp, const char *comment) +{ + free(pp->comment); + + char *sp; + if ((sp = strstr(comment, "$(this.promiser)")) != NULL || + (sp = strstr(comment, "${this.promiser}")) != NULL) + { + char *s; + int this_len = strlen("$(this.promiser)"); + int this_offset = sp - comment; + xasprintf(&s, "%.*s%s%s", + this_offset, comment, pp->promiser, + &comment[this_offset + this_len]); + + pp->comment = s; + } + else + { + pp->comment = xstrdup(comment); + } +} + +Promise *ExpandDeRefPromise(EvalContext *ctx, const Promise *pp, bool *excluded) +{ + assert(pp != NULL); + assert(pp->parent_section != NULL); + assert(pp->promiser != NULL); + assert(pp->classes != NULL); + assert(excluded != NULL); + + *excluded = false; + + Rval returnval = ExpandPrivateRval(ctx, PromiseGetNamespace(pp), + "this", pp->promiser, RVAL_TYPE_SCALAR); + if (returnval.item == NULL) + { + assert(returnval.type == RVAL_TYPE_LIST || + returnval.type == RVAL_TYPE_NOPROMISEE); + /* TODO Log() empty slist, promise skipped? */ + *excluded = true; + return NULL; + } + Promise *pcopy = xcalloc(1, sizeof(Promise)); + pcopy->promiser = RvalScalarValue(returnval); + + /* TODO remove the conditions here for fixing redmine#7880. */ + if (!StringEqual("storage", PromiseGetPromiseType(pp))) + { + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser", pcopy->promiser, + CF_DATA_TYPE_STRING, "source=promise"); + } + + if (pp->promisee.item) + { + pcopy->promisee = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL, "this", pp->promisee, true, pp); + } + else + { + pcopy->promisee = (Rval) {NULL, RVAL_TYPE_NOPROMISEE }; + } + + pcopy->classes = xstrdup(pp->classes); + pcopy->parent_section = pp->parent_section; + pcopy->offset.line = pp->offset.line; + pcopy->comment = pp->comment ? xstrdup(pp->comment) : NULL; + pcopy->conlist = SeqNew(10, ConstraintDestroy); + pcopy->org_pp = pp->org_pp; + + // if this is a class promise, check if it is already set, if so, skip + if (strcmp("classes", PromiseGetPromiseType(pp)) == 0) + { + if (IsDefinedClass(ctx, CanonifyName(pcopy->promiser))) + { + Log(LOG_LEVEL_DEBUG, + "Skipping evaluation of classes promise as class '%s' is already set", + CanonifyName(pcopy->promiser)); + + *excluded = true; + return pcopy; + } + } + + /* Look for 'if'/'ifvarclass' exclusion. */ + { + /* We need to make sure to check both 'if' and 'ifvarclass' constraints. */ + bool checked_if = false; + const Constraint *ifvarclass = PromiseGetConstraint(pp, "ifvarclass"); + if (!ifvarclass) + { + ifvarclass = PromiseGetConstraint(pp, "if"); + checked_if = true; + } + + // if - Skip if false or error: + while (ifvarclass != NULL) + { + if (CheckVarClassExpression(ctx, ifvarclass, pcopy) != EXPRESSION_VALUE_TRUE) + { + if (LogGetGlobalLevel() >= LOG_LEVEL_VERBOSE) + { + char *ifvarclass_string = RvalToString(ifvarclass->rval); + Log(LOG_LEVEL_VERBOSE, "Skipping promise '%s'" + " because constraint '%s => %s' is not met", + pp->promiser, ifvarclass->lval, ifvarclass_string); + free(ifvarclass_string); + } + *excluded = true; + return pcopy; + } + if (!checked_if) + { + ifvarclass = PromiseGetConstraint(pp, "if"); + checked_if = true; + } + else + { + ifvarclass = NULL; + } + } + } + + /* Look for 'unless' exclusion. */ + { + const Constraint *unless = PromiseGetConstraint(pp, "unless"); + + // unless - Skip if true or error: + if (unless != NULL) + { + // If the rval is a function, CheckVarClassExpression will call it + // It will evaluate the class expression as well: + const ExpressionValue value = CheckVarClassExpression(ctx, unless, pcopy); + // If it returns EXPRESSION_VALUE_ERROR, it most likely means there + // are unexpanded variables in the rval (possibly in function calls) + + if ((EvalContextGetPass(ctx) == CF_DONEPASSES-1) + && (value == EXPRESSION_VALUE_ERROR)) + { + char *unless_string = RvalToString(unless->rval); + // The rval is most likely a string or a function call, + // with unexpanded variables, for example: + // unless => "$(no_such_var)" + // We default to NOT skipping (since if would skip). + Log(LOG_LEVEL_VERBOSE, + "Not skipping %s promise '%s' with constraint '%s => %s' in last evaluation pass (since if would skip)", + PromiseGetPromiseType(pp), + pp->promiser, + unless->lval, + unless_string); + free(unless_string); + } + else if (value != EXPRESSION_VALUE_FALSE) + { + if (LogGetGlobalLevel() >= LOG_LEVEL_VERBOSE) + { + char *unless_string = RvalToString(unless->rval); + Log(LOG_LEVEL_VERBOSE, + "Skipping promise '%s' because constraint '%s => %s' is not met", + pp->promiser, unless->lval, unless_string); + free(unless_string); + } + *excluded = true; + return pcopy; + } + } + } + + /* Look for depends_on exclusion. */ + { + const Constraint *depends_on = PromiseGetConstraint(pp, "depends_on"); + if (depends_on) + { + Rval final; + if (EvaluateConstraintIteration(ctx, depends_on, &final)) + { + PromiseAppendConstraint(pcopy, depends_on->lval, final, false); + + if (MissingDependencies(ctx, pcopy)) + { + *excluded = true; + return pcopy; + } + } + } + } + + /* NOTE: We have to undefine the '$(this.promiser)' variable for a 'files' + * promise now because it is later defined for the individual + * expansions of the promise and so it has to be left unexpanded in + * the constraints/attributes. It is, however, defined above just like + * for any other promise so that it can be used in the common + * if/ifvarclass/unless checking. */ + if (StringEqual(PromiseGetPromiseType(pp), "files")) + { + EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser"); + } + + /* Evaluate all constraints. */ + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + + // special constraints ifvarclass and depends_on are evaluated before the rest of the constraints + if (strcmp(cp->lval, "ifvarclass") == 0 || + strcmp(cp->lval, "if") == 0 || + strcmp(cp->lval, "unless") == 0 || + strcmp(cp->lval, "depends_on") == 0) + { + continue; + } + + Rval final; + if (!EvaluateConstraintIteration(ctx, cp, &final)) + { + continue; + } + + PromiseAppendConstraint(pcopy, cp->lval, final, false); + + if (strcmp(cp->lval, "comment") == 0) + { + if (final.type != RVAL_TYPE_SCALAR) + { + Log(LOG_LEVEL_ERR, "Comments can only be scalar objects, not '%s' in '%s'", + RvalTypeToString(final.type), pp->promiser); + } + else + { + assert(final.item != NULL); /* it's SCALAR type */ + DereferenceAndPutComment(pcopy, final.item); + } + } + } + + return pcopy; +} + +void PromiseRef(LogLevel level, const Promise *pp) +{ + if (pp == NULL) + { + return; + } + + if (PromiseGetBundle(pp)->source_path) + { + Log(level, "Promise belongs to bundle '%s' in file '%s' near line %zu", PromiseGetBundle(pp)->name, + PromiseGetBundle(pp)->source_path, pp->offset.line); + } + else + { + Log(level, "Promise belongs to bundle '%s' near line %zu", PromiseGetBundle(pp)->name, + pp->offset.line); + } + + if (pp->comment) + { + Log(level, "Comment is '%s'", pp->comment); + } + + switch (pp->promisee.type) + { + case RVAL_TYPE_SCALAR: + Log(level, "This was a promise to '%s'", (char *)(pp->promisee.item)); + break; + case RVAL_TYPE_LIST: + { + Writer *w = StringWriter(); + RlistWrite(w, pp->promisee.item); + char *p = StringWriterClose(w); + Log(level, "This was a promise to '%s'", p); + free(p); + break; + } + default: + break; + } +} + +/*******************************************************************/ + +/* Old legacy function from Enterprise, TODO remove static string. */ +const char *PromiseID(const Promise *pp) +{ + static char id[CF_MAXVARSIZE]; + char vbuff[CF_MAXVARSIZE]; + const char *handle = PromiseGetHandle(pp); + + if (handle) + { + snprintf(id, CF_MAXVARSIZE, "%s", CanonifyName(handle)); + } + else if (pp && PromiseGetBundle(pp)->source_path) + { + snprintf(vbuff, CF_MAXVARSIZE, "%s", ReadLastNode(PromiseGetBundle(pp)->source_path)); + snprintf(id, CF_MAXVARSIZE, "promise_%s_%zu", CanonifyName(vbuff), pp->offset.line); + } + else + { + snprintf(id, CF_MAXVARSIZE, "unlabelled_promise"); + } + + return id; +} diff --git a/libpromises/promises.h b/libpromises/promises.h new file mode 100644 index 0000000000..3c64eb4b8d --- /dev/null +++ b/libpromises/promises.h @@ -0,0 +1,43 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PROMISES_H +#define CFENGINE_PROMISES_H + + +#include + +#include +#include + + +Promise *DeRefCopyPromise(EvalContext *ctx, const Promise *pp); +Promise *ExpandDeRefPromise(EvalContext *ctx, const Promise *pp, bool *excluded); +void PromiseRef(LogLevel level, const Promise *pp); +void CopyBodyConstraintsToPromise(EvalContext *ctx, Promise *pp, + const Body *bp); +const char *PromiseID(const Promise *pp); + + +#endif diff --git a/libpromises/prototypes3.h b/libpromises/prototypes3.h new file mode 100644 index 0000000000..52032955fd --- /dev/null +++ b/libpromises/prototypes3.h @@ -0,0 +1,126 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_PROTOTYPES3_H +#define CFENGINE_PROTOTYPES3_H + +#include +#include +#include +#include + +bool BootstrapAllowed(void); + +/* Versions */ + +const char *Version(void); +const char *NameVersion(void); + +/* cfparse.y */ + +void yyerror(const char *s); + +/* agent.c */ + +PromiseResult ScheduleAgentOperations(EvalContext *ctx, const Bundle *bp); + +/* Only for agent.c */ + +void ConnectionsInit(void); +void ConnectionsCleanup(void); + +/* client_protocol.c */ + +void SetSkipIdentify(bool enabled); + +/* enterprise_stubs.c */ + +ENTERPRISE_VOID_FUNC_1ARG_DECLARE(void, Nova_Initialize, EvalContext *, ctx); +ENTERPRISE_FUNC_1ARG_DECLARE(int, CfSessionKeySize, char, c); +ENTERPRISE_FUNC_0ARG_DECLARE(char, CfEnterpriseOptions); +ENTERPRISE_FUNC_1ARG_DECLARE(const EVP_CIPHER *, CfengineCipher, char, type); +ENTERPRISE_VOID_FUNC_1ARG_DECLARE(void, EnterpriseContext, EvalContext *, ctx); +ENTERPRISE_FUNC_0ARG_DECLARE(const char *, GetConsolePrefix); +ENTERPRISE_FUNC_6ARG_DECLARE(char *, GetRemoteScalar, EvalContext *, ctx, char *, proto, char *, handle, const char *, server, int, encrypted, char *, rcv); +ENTERPRISE_VOID_FUNC_2ARG_DECLARE(void, LogTotalCompliance, const char *, version, int, background_tasks); +#if defined(__MINGW32__) +ENTERPRISE_FUNC_4ARG_DECLARE(bool, GetRegistryValue, const char *, key, char *, name, char *, buf, int, bufSz); +#endif +ENTERPRISE_FUNC_6ARG_DECLARE(void *, CfLDAPValue, char *, uri, char *, dn, char *, filter, char *, name, char *, scope, char *, sec); +ENTERPRISE_FUNC_6ARG_DECLARE(void *, CfLDAPList, char *, uri, char *, dn, char *, filter, char *, name, char *, scope, char *, sec); +ENTERPRISE_FUNC_8ARG_DECLARE(void *, CfLDAPArray, EvalContext *, ctx, const Bundle *, caller, char *, array, char *, uri, char *, dn, char *, filter, char *, scope, char *, sec); +ENTERPRISE_FUNC_8ARG_DECLARE(void *, CfRegLDAP, EvalContext *, ctx, char *, uri, char *, dn, char *, filter, char *, name, char *, scope, char *, regex, char *, sec); +ENTERPRISE_VOID_FUNC_3ARG_DECLARE(void, CacheUnreliableValue, char *, caller, char *, handle, char *, buffer); +ENTERPRISE_FUNC_3ARG_DECLARE(int, RetrieveUnreliableValue, char *, caller, char *, handle, char *, buffer); +ENTERPRISE_FUNC_3ARG_DECLARE(bool, TranslatePath, const char *, from, char *, to, size_t, to_size); +ENTERPRISE_FUNC_4ARG_DECLARE(bool, ListHostsWithClass, EvalContext *, ctx, Rlist **, return_list, char *, class_name, char *, return_format); + +ENTERPRISE_VOID_FUNC_2ARG_DECLARE(void, CheckAndSetHAState, const char *, workdir, EvalContext *, ctx); +ENTERPRISE_VOID_FUNC_0ARG_DECLARE(void, ReloadHAConfig); + +ENTERPRISE_VOID_FUNC_2ARG_DECLARE(void, Nova_ClassHistoryAddContextName, const StringSet *, list, const char *, context_name); +ENTERPRISE_VOID_FUNC_2ARG_DECLARE(void, Nova_ClassHistoryEnable, StringSet **, list, bool, enable); + +/* manual.c */ + +void TexinfoManual(EvalContext *ctx, const char *source_dir, const char *output_file); + +/* modes.c */ + +bool ParseModeString(const char *modestring, mode_t *plusmask, mode_t *minusmask); + +/* patches.c */ + +bool IsPrivileged(void); +char *cf_strtimestamp_local(const time_t time, char *buf); +char *cf_strtimestamp_utc(const time_t time, char *buf); +int cf_closesocket(int sd); + +#if !defined(__MINGW32__) +#define OpenNetwork() /* noop */ +#define CloseNetwork() /* noop */ +#else +void OpenNetwork(void); +void CloseNetwork(void); +#endif + +bool LinkOrCopy(const char *from, const char *to, int sym); + +/* storage_tools.c */ + +off_t GetDiskUsage(char *file, CfSize type); + +/* verify_reports.c */ + +PromiseResult VerifyReportPromise(EvalContext *ctx, const Promise *pp); + +/* cf-key */ + +ENTERPRISE_FUNC_1ARG_DECLARE(bool, LicenseInstall, char *, path_source); + +/* cf-serverd */ + +ENTERPRISE_FUNC_0ARG_DECLARE(size_t, EnterpriseGetMaxCfHubProcesses); + +#endif diff --git a/libpromises/rlist.c b/libpromises/rlist.c new file mode 100644 index 0000000000..ce9e7f7281 --- /dev/null +++ b/libpromises/rlist.c @@ -0,0 +1,1740 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include /* StringHash */ +#include /* StringMatchWithPrecompiledRegex,CompileRegex */ +#include +#include +#include +#include +#include /* IsCf3VarString */ + + +static Rlist *RlistPrependRval(Rlist **start, Rval rval); + +static char SECRET_RVAL[] = "************"; + +RvalType DataTypeToRvalType(DataType datatype) +{ + switch (datatype) + { + case CF_DATA_TYPE_BODY: + case CF_DATA_TYPE_BUNDLE: + case CF_DATA_TYPE_CONTEXT: + case CF_DATA_TYPE_COUNTER: + case CF_DATA_TYPE_INT: + case CF_DATA_TYPE_INT_RANGE: + case CF_DATA_TYPE_OPTION: + case CF_DATA_TYPE_REAL: + case CF_DATA_TYPE_REAL_RANGE: + case CF_DATA_TYPE_STRING: + return RVAL_TYPE_SCALAR; + + case CF_DATA_TYPE_CONTEXT_LIST: + case CF_DATA_TYPE_INT_LIST: + case CF_DATA_TYPE_OPTION_LIST: + case CF_DATA_TYPE_REAL_LIST: + case CF_DATA_TYPE_STRING_LIST: + return RVAL_TYPE_LIST; + + case CF_DATA_TYPE_CONTAINER: + return RVAL_TYPE_CONTAINER; + + case CF_DATA_TYPE_NONE: + return RVAL_TYPE_NOPROMISEE; + } + + ProgrammingError("DataTypeToRvalType, unhandled"); +} + +bool RlistValueIsType(const Rlist *rlist, RvalType type) +{ + return (rlist != NULL && + rlist->val.type == type); +} + +char *RlistScalarValue(const Rlist *rlist) +{ + if (rlist->val.type != RVAL_TYPE_SCALAR) + { + ProgrammingError("Rlist value contains type %c instead of expected scalar", rlist->val.type); + } + + return rlist->val.item; +} + +char *RlistScalarValueSafe(const Rlist *rlist) +{ + if (rlist->val.type != RVAL_TYPE_SCALAR) + { + return "[not printable]"; + } + + return RlistScalarValue(rlist); +} + +/*******************************************************************/ + +FnCall *RlistFnCallValue(const Rlist *rlist) +{ + if (rlist->val.type != RVAL_TYPE_FNCALL) + { + ProgrammingError("Rlist value contains type %c instead of expected FnCall", rlist->val.type); + } + + return rlist->val.item; +} + +/*******************************************************************/ + +Rlist *RlistRlistValue(const Rlist *rlist) +{ + if (rlist->val.type != RVAL_TYPE_LIST) + { + ProgrammingError("Rlist value contains type %c instead of expected List", rlist->val.type); + } + + return rlist->val.item; +} + +/*******************************************************************/ + +char *RvalScalarValue(Rval rval) +{ + if (rval.type != RVAL_TYPE_SCALAR) + { + ProgrammingError("Internal error: Rval contains type %c instead of expected scalar", rval.type); + } + + return rval.item; +} + +/*******************************************************************/ + +FnCall *RvalFnCallValue(Rval rval) +{ + if (rval.type != RVAL_TYPE_FNCALL) + { + ProgrammingError("Rval contains type %c instead of expected FnCall", rval.type); + } + + return rval.item; +} + +/*******************************************************************/ + +Rlist *RvalRlistValue(Rval rval) +{ + if (rval.type != RVAL_TYPE_LIST) + { + ProgrammingError("Rval contain type %c instead of expected List", rval.type); + } + + return rval.item; +} + +/*******************************************************************/ + +JsonElement *RvalContainerValue(Rval rval) +{ + if (rval.type != RVAL_TYPE_CONTAINER) + { + ProgrammingError("Rval contain type %c instead of expected container", rval.type); + } + + return rval.item; +} + + +const char *RvalTypeToString(RvalType type) +{ + switch (type) + { + case RVAL_TYPE_CONTAINER: + return "data"; + case RVAL_TYPE_FNCALL: + return "call"; + case RVAL_TYPE_LIST: + return "list"; + case RVAL_TYPE_NOPROMISEE: + return "null"; + case RVAL_TYPE_SCALAR: + return "scalar"; + } + + assert(false && "never reach"); + return NULL; +} + +Rlist *RlistKeyIn(Rlist *list, const char *key) +{ + for (Rlist *rp = list; rp != NULL; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_SCALAR && + strcmp(RlistScalarValue(rp), key) == 0) + { + return rp; + } + } + + return NULL; +} + +/*******************************************************************/ + +bool RlistMatchesRegexRlist(const Rlist *list, const Rlist *search) +/* + Returns true if "list" contains all the regular expressions in + "search". Non-scalars in "list" and "search" are skipped. +*/ +{ + for (const Rlist *rp = search; rp != NULL; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_SCALAR && + // check for the current element in the search list + !RlistMatchesRegex(list, RlistScalarValue(search))) + { + return false; + } + } + + return true; +} + +bool RlistMatchesRegex(const Rlist *list, const char *regex) +/* + Returns true if any of the "list" of strings matches "regex". + Non-scalars in "list" are skipped. +*/ +{ + if (regex == NULL || list == NULL) + { + return false; + } + + Regex *rx = CompileRegex(regex); + if (!rx) + { + return false; + } + + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_SCALAR && + StringMatchFullWithPrecompiledRegex(rx, RlistScalarValue(rp))) + { + RegexDestroy(rx); + return true; + } + } + + RegexDestroy(rx); + return false; +} + +bool RlistIsNullList(const Rlist *list) +{ + return (list == NULL); +} + +bool RlistIsInListOfRegex(const Rlist *list, const char *str) +/* + Returns true if any of the "list" of regular expressions matches "str". + Non-scalars in "list" are skipped. +*/ +{ + if (str == NULL || list == NULL) + { + return false; + } + + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_SCALAR && + StringMatchFull(RlistScalarValue(rp), str)) + { + return true; + } + } + + return false; +} + +bool RlistContainsString(const Rlist *list, const char *string) +{ + assert(string != NULL); + + if (list == NULL) + { + // Empty Rlist is represented as a NULL pointer + return false; + } + + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + if (rp->val.type == RVAL_TYPE_SCALAR && + StringEqual(RlistScalarValue(rp), string)) + { + return true; + } + } + return false; +} + +/*******************************************************************/ + +static Rval RvalCopyScalar(Rval rval) +{ + assert(rval.type == RVAL_TYPE_SCALAR); + const char * src = rval.item ? rval.item : ""; + + return (Rval) {xstrdup(src), RVAL_TYPE_SCALAR}; +} + +Rlist *RlistAppendRval(Rlist **start, Rval rval) +{ + Rlist *rp = xmalloc(sizeof(Rlist)); + + rp->val = rval; + rp->next = NULL; + + if (*start == NULL) + { + *start = rp; + } + else + { + Rlist *lp = *start; + while (lp->next != NULL) + { + lp = lp->next; + } + + lp->next = rp; + } + + return rp; +} + +/* Inserts an Rlist node with value #rval, right after the rlist node #node. */ +void RlistInsertAfter(Rlist *node, Rval rval) +{ + assert(node != NULL); + + Rlist new_node = { .val = rval, + .next = node->next }; + + node->next = xmemdup(&new_node, sizeof(new_node)); +} + +Rval RvalNewRewriter(const void *item, RvalType type, JsonElement *map) +{ + switch (type) + { + case RVAL_TYPE_SCALAR: + if (map != NULL && JsonLength(map) > 0 && // do we have a rewrite map? + (strstr(item, "$(") || strstr(item, "${"))) // are there unresolved variable references? + { + // TODO: replace with BufferSearchAndReplace when the + // string_replace code is merged. + // Sorry about the CF_BUFSIZE ugliness. + int max_size = 10*CF_BUFSIZE+1; + char *buffer_from = xmalloc(max_size); + char *buffer_to = xmalloc(max_size); + + Buffer *format = BufferNew(); + StringCopy(item, buffer_from, max_size); + buffer_to[0] = '\0'; + + for (int iteration = 0; iteration < 10; iteration++) + { + bool replacement_made = false; + int var_start = -1; + char closing_brace = 0; + for (int c = 0; buffer_from[c] != '\0'; c++) + { + if (buffer_from[c] == '$') + { + if (buffer_from[c+1] == '(') + { + closing_brace = ')'; + } + else if (buffer_from[c+1] == '{') + { + closing_brace = '}'; + } + + if (closing_brace) + { + c++; + var_start = c-1; + } + } + else if (var_start >= 0 && buffer_from[c] == closing_brace) + { + char saved = buffer_from[c]; + buffer_from[c] = '\0'; + + const char *repl = JsonObjectGetAsString(map, buffer_from + var_start + 2); + buffer_from[c] = saved; + + if (repl) + { + // Before the replacement. + memcpy(buffer_to, buffer_from, var_start); + + // The actual replacement. + int repl_len = strlen(repl); + memcpy(buffer_to + var_start, repl, repl_len); + + // The text after. + strlcpy(buffer_to + var_start + repl_len, buffer_from + c + 1, max_size - var_start - repl_len); + + // Reset location to immediately after the replacement. + c = var_start + repl_len - 1; + var_start = -1; + StringCopy(buffer_to, buffer_from, max_size); + closing_brace = 0; + replacement_made = true; + } + } + } + + if (!replacement_made) + { + break; + } + } + + char* ret; + if (buffer_to[0] == '\0') { + // If nothing has been written to buffer_to, we pass the input verbatim. + // This function only evaluates body parameters, but the body can also reference the global + // context. + ret = xstrdup(buffer_from); + } else { + ret = xstrdup(buffer_to); + } + + BufferDestroy(format); + free(buffer_to); + free(buffer_from); + + return (Rval) { ret, RVAL_TYPE_SCALAR }; + } + else + { + return (Rval) { xstrdup(item), RVAL_TYPE_SCALAR }; + } + + case RVAL_TYPE_FNCALL: + return (Rval) { FnCallCopyRewriter(item, map), RVAL_TYPE_FNCALL }; + + case RVAL_TYPE_LIST: + return (Rval) { RlistCopyRewriter(item, map), RVAL_TYPE_LIST }; + + case RVAL_TYPE_CONTAINER: + return (Rval) { JsonCopy(item), RVAL_TYPE_CONTAINER }; + + case RVAL_TYPE_NOPROMISEE: + return ((Rval) {NULL, type}); + } + + assert(false); + return ((Rval) { NULL, RVAL_TYPE_NOPROMISEE }); +} + +Rval RvalNew(const void *item, RvalType type) +{ + return RvalNewRewriter(item, type, NULL); +} + +Rval RvalNewSecret() +{ + return ((Rval) {SECRET_RVAL, RVAL_TYPE_SCALAR}); +} + +Rval RvalCopyRewriter(Rval rval, JsonElement *map) +{ + return RvalNewRewriter(rval.item, rval.type, map); +} + +Rval RvalCopy(Rval rval) +{ + return RvalNew(rval.item, rval.type); +} + +/*******************************************************************/ + +Rlist *RlistCopyRewriter(const Rlist *rp, JsonElement *map) +{ + Rlist *start = NULL; + + while (rp != NULL) + { + RlistAppendRval(&start, RvalCopyRewriter(rp->val, map)); + rp = rp->next; + } + + return start; +} + +Rlist *RlistCopy(const Rlist *rp) +{ + return RlistCopyRewriter(rp, NULL); +} + +/*******************************************************************/ + +void RlistDestroy(Rlist *rl) +/* Delete an rlist and all its references */ +{ + while (rl != NULL) + { + Rlist *next = rl->next; + + if (rl->val.item) + { + RvalDestroy(rl->val); + } + + free(rl); + rl = next; + } +} + +void RlistDestroy_untyped(void *rl) +{ + RlistDestroy(rl); +} + +/*******************************************************************/ + +Rlist *RlistAppendScalarIdemp(Rlist **start, const char *scalar) +{ + if (RlistKeyIn(*start, scalar)) + { + return NULL; + } + + return RlistAppendScalar(start, scalar); +} + +Rlist *RlistPrependScalarIdemp(Rlist **start, const char *scalar) +{ + if (RlistKeyIn(*start, scalar)) + { + return NULL; + } + + return RlistPrepend(start, scalar, RVAL_TYPE_SCALAR); +} + +Rlist *RlistAppendScalar(Rlist **start, const char *scalar) +{ + return RlistAppendRval(start, RvalCopyScalar((Rval) { (char *)scalar, RVAL_TYPE_SCALAR })); +} + +Rlist *RlistAppendString(Rlist **start, const char *string) +{ + Rlist *l = RlistAppendScalar(start, string); + + assert(RlistContainsString(l, string)); + + return l; +} + +// NOTE: Copies item, does NOT take ownership +Rlist *RlistAppend(Rlist **start, const void *item, RvalType type) +{ + return RlistAppendAllTypes(start, item, type, false); +} + +// See fncall.c for the usage of allow_all_types. +Rlist *RlistAppendAllTypes(Rlist **start, const void *item, RvalType type, bool allow_all_types) +{ + Rlist *lp = *start; + + switch (type) + { + case RVAL_TYPE_SCALAR: + return RlistAppendScalar(start, item); + + case RVAL_TYPE_FNCALL: + break; + + case RVAL_TYPE_LIST: + if (allow_all_types) + { + JsonElement* store = JsonArrayCreate(RlistLen(item)); + for (const Rlist *rp = item; rp; rp = rp->next) + { + JsonArrayAppendElement(store, RvalToJson(rp->val)); + } + + return RlistAppendRval(start, (Rval) { store, RVAL_TYPE_CONTAINER }); + } + + for (const Rlist *rp = item; rp; rp = rp->next) + { + lp = RlistAppendRval(start, RvalCopy(rp->val)); + } + + return lp; + + case RVAL_TYPE_CONTAINER: + if (allow_all_types) + { + return RlistAppendRval(start, (Rval) { JsonCopy((JsonElement*) item), RVAL_TYPE_CONTAINER }); + } + + // fall through + + default: + Log(LOG_LEVEL_DEBUG, "Cannot append %c to rval-list '%s'", type, (char *) item); + return NULL; + } + + Rlist *rp = xmalloc(sizeof(Rlist)); + + rp->val = RvalNew(item, type); + rp->next = NULL; + + if (*start == NULL) + { + *start = rp; + } + else + { + for (lp = *start; lp->next != NULL; lp = lp->next) + { + } + + lp->next = rp; + } + + return rp; +} + +/*******************************************************************/ + +static Rlist *RlistPrependRval(Rlist **start, Rval rval) +{ + Rlist *rp = xmalloc(sizeof(Rlist)); + + rp->next = *start; + rp->val = rval; + + *start = rp; + + return rp; +} + +Rlist *RlistPrepend(Rlist **start, const void *item, RvalType type) +{ + switch (type) + { + case RVAL_TYPE_LIST: + { + Rlist *lp = NULL; + for (const Rlist *rp = item; rp; rp = rp->next) + { + lp = RlistPrependRval(start, RvalCopy(rp->val)); + } + return lp; + } + + case RVAL_TYPE_SCALAR: + case RVAL_TYPE_FNCALL: + case RVAL_TYPE_CONTAINER: + case RVAL_TYPE_NOPROMISEE: + return RlistPrependRval(start, RvalNew(item, type)); + } + + assert(false); + return NULL; +} + +/*******************************************************************/ + +int RlistLen(const Rlist *start) +{ + int count = 0; + + for (const Rlist *rp = start; rp != NULL; rp = rp->next) + { + count++; + } + + return count; +} + +/*******************************************************************/ + +Rlist *RlistParseShown(const char *string) +{ + Rlist *newlist = NULL, *splitlist, *rp; + +/* Parse a string representation generated by ShowList and turn back into Rlist */ + + splitlist = RlistFromSplitString(string, ','); + + for (rp = splitlist; rp != NULL; rp = rp->next) + { + char value[CF_MAXVARSIZE] = { 0 }; + sscanf(RlistScalarValue(rp), "%*[{ '\"]%255[^'\"}]", value); + RlistAppendScalar(&newlist, value); + } + + RlistDestroy(splitlist); + return newlist; +} + +/*******************************************************************/ + +typedef enum +{ + ST_OPENED, + ST_PRECLOSED, + ST_CLOSED, + ST_IO, + ST_ELM1, + ST_ELM2, + ST_END1, + ST_END2, + ST_SEP, + ST_ERROR +} state; + +#define CLASS_BLANK(x) (((x)==' ')||((x)=='\t')) +#define CLASS_START1(x) (((x)=='\'')) +#define CLASS_START2(x) (((x)=='"')) +#define CLASS_END1(x) ((CLASS_START1(x))) +#define CLASS_END2(x) ((CLASS_START2(x))) +#define CLASS_BRA1(x) (((x)=='{')) +#define CLASS_BRA2(x) (((x)=='}')) +#define CLASS_SEP(x) (((x)==',')) +#define CLASS_EOL(x) (((x)=='\0')) + +#define CLASS_ANY0(x) ((!CLASS_BLANK(x))&&(!CLASS_BRA1(x))) +#define CLASS_ANY1(x) ((!CLASS_BLANK(x))&&(!CLASS_START1(x))&&(!CLASS_START2(x))) +#define CLASS_ANY2(x) ((!CLASS_END1(x))) +#define CLASS_ANY3(x) ((!CLASS_END2(x))) +#define CLASS_ANY4(x) ((!CLASS_BLANK(x))&&(!CLASS_SEP(x))&&(!CLASS_BRA2(x))) +#define CLASS_ANY5(x) ((!CLASS_BLANK(x))&&(!CLASS_SEP(x))&&(!CLASS_BRA2(x))) +#define CLASS_ANY6(x) ((!CLASS_BLANK(x))&&(!CLASS_START2(x))&&(!CLASS_START2(x))) +#define CLASS_ANY7(x) ((!CLASS_BLANK(x))&&(!CLASS_EOL(x))) + +/** + @brief parse elements in a list passed through use_module + + @param[in] str: is the string to parse + @param[out] newlist: rlist of elements found + + @retval 0: successful > 0: failed + */ +static int LaunchParsingMachine(const char *str, Rlist **newlist) +{ + const char *s = str; + state current_state = ST_OPENED; + int ret; + + Buffer *buf = BufferNewWithCapacity(CF_MAXVARSIZE); + + assert(newlist); + + while (current_state != ST_CLOSED && *s) + { + switch(current_state) + { + case ST_ERROR: + Log(LOG_LEVEL_ERR, "Parsing error : Malformed string"); + ret = 1; + goto clean; + case ST_OPENED: + if (CLASS_BLANK(*s)) + { + current_state = ST_OPENED; + } + else if (CLASS_BRA1(*s)) + { + current_state = ST_IO; + } + else if (CLASS_ANY0(*s)) + { + current_state = ST_ERROR; + } + s++; + break; + case ST_IO: + if (CLASS_BLANK(*s)) + { + current_state = ST_IO; + } + else if (CLASS_START1(*s)) + { + BufferClear(buf); + current_state = ST_ELM1; + } + else if (CLASS_START2(*s)) + { + BufferClear(buf); + current_state = ST_ELM2; + } + else if (CLASS_ANY1(*s)) + { + current_state = ST_ERROR; + } + s++; + break; + case ST_ELM1: + if (CLASS_END1(*s)) + { + RlistAppendScalar(newlist, BufferData(buf)); + BufferClear(buf); + current_state = ST_END1; + } + else if (CLASS_ANY2(*s)) + { + BufferAppendChar(buf, *s); + current_state = ST_ELM1; + } + s++; + break; + case ST_ELM2: + if (CLASS_END2(*s)) + { + RlistAppendScalar(newlist, BufferData(buf)); + BufferClear(buf); + current_state = ST_END2; + } + else if (CLASS_ANY3(*s)) + { + BufferAppendChar(buf, *s); + current_state = ST_ELM2; + } + s++; + break; + case ST_END1: + if (CLASS_SEP(*s)) + { + current_state = ST_SEP; + } + else if (CLASS_BRA2(*s)) + { + current_state = ST_PRECLOSED; + } + else if (CLASS_BLANK(*s)) + { + current_state = ST_END1; + } + else if (CLASS_ANY4(*s)) + { + current_state = ST_ERROR; + } + s++; + break; + case ST_END2: + if (CLASS_SEP(*s)) + { + current_state = ST_SEP; + } + else if (CLASS_BRA2(*s)) + { + current_state = ST_PRECLOSED; + } + else if (CLASS_BLANK(*s)) + { + current_state = ST_END2; + } + else if (CLASS_ANY5(*s)) + { + current_state = ST_ERROR; + } + s++; + break; + case ST_SEP: + if (CLASS_BLANK(*s)) + { + current_state = ST_SEP; + } + else if (CLASS_START1(*s)) + { + current_state = ST_ELM1; + } + else if (CLASS_START2(*s)) + { + current_state = ST_ELM2; + } + else if (CLASS_ANY6(*s)) + { + current_state = ST_ERROR; + } + s++; + break; + case ST_PRECLOSED: + if (CLASS_BLANK(*s)) + { + current_state = ST_PRECLOSED; + } + else if (CLASS_EOL(*s)) + { + current_state = ST_CLOSED; + } + else if (CLASS_ANY7(*s)) + { + current_state = ST_ERROR; + } + s++; + break; + default: + Log(LOG_LEVEL_ERR, "Parsing logic error: unknown state"); + ret = 2; + goto clean; + break; + } + } + + if (current_state != ST_CLOSED && current_state != ST_PRECLOSED ) + { + Log(LOG_LEVEL_ERR, "Parsing error : Malformed string (unexpected end of input)"); + ret = 3; + goto clean; + } + + BufferDestroy(buf); + return 0; + +clean: + BufferDestroy(buf); + RlistDestroy(*newlist); + assert(ret != 0); + return ret; +} + +Rlist *RlistParseString(const char *string) +{ + Rlist *newlist = NULL; + if (LaunchParsingMachine(string, &newlist)) + { + return NULL; + } + + return newlist; +} + +/*******************************************************************/ + +void RvalDestroy(Rval rval) +{ + if (rval.item == NULL || rval.item == SECRET_RVAL) + { + return; + } + + switch (rval.type) + { + case RVAL_TYPE_SCALAR: + free(RvalScalarValue(rval)); + return; + + case RVAL_TYPE_LIST: + RlistDestroy(RvalRlistValue(rval)); + return; + + case RVAL_TYPE_FNCALL: + FnCallDestroy(RvalFnCallValue(rval)); + break; + + case RVAL_TYPE_CONTAINER: + JsonDestroy(RvalContainerValue(rval)); + break; + + case RVAL_TYPE_NOPROMISEE: + return; + } +} + +/*********************************************************************/ + +void RlistDestroyEntry(Rlist **liststart, Rlist *entry) +{ + if (entry != NULL) + { + if (entry->val.item) + { + free(entry->val.item); + } + + Rlist *sp = entry->next; + + if (entry == *liststart) + { + *liststart = sp; + } + else + { + Rlist *rp = *liststart; + while (rp->next != entry) + { + rp = rp->next; + } + + assert(rp && rp->next == entry); + rp->next = sp; + } + + free(entry); + } +} + +/*******************************************************************/ + +/* Copies a -delimited unit from into a new entry in . + * + * \ is not counted as the separator, but copied to the new entry + * as . No other escape sequences are supported. + * + * Returns the number of bytes read out of ; this may be more + * than the length of the new entry in . The new entry is + * prepended; the caller can reverse once built. + */ +static size_t SubStrnCopyChr(Rlist **to, const char *from, char sep, char lstrip) +{ + assert(from && from[0]); + size_t offset = 0; + + while (lstrip != '\0' && from[0] == lstrip && from[0] != '\0') + { + /* Skip over all instances of the 'lstrip' character (e.g. ' ') if + * specified */ + from++; + offset++; + } + if (from[0] == '\0') + { + /* Reached the end already so there's nothing to add to the result list, + just tell the caller how far they can move. */ + return offset; + } + + const char *end = from; + size_t escapes = 0; + while (end && end[0] && end[0] != sep) + { + end = strchr(end, sep); + assert(end == NULL || end[0] == sep); + if (end && end > from && end[-1] == '\\') + { + escapes++; + end++; + } + } + + size_t consume = (end == NULL) ? strlen(from) : (size_t)(end - from); + assert(consume >= escapes); + char copy[1 + consume - escapes], *dst = copy; + + for (const char *src = from; src[0] != '\0' && src[0] != sep; src++) + { + if (src[0] == '\\' && src[1] == sep) + { + src++; /* Skip over the backslash so we copy the sep */ + } + dst++[0] = src[0]; + } + assert(dst + 1 == copy + sizeof(copy)); + *dst = '\0'; + + /* Prepend to the list and reverse when done, costing O(len), + * instead of appending, which costs O(len**2). */ + RlistPrependRval(to, RvalCopyScalar((Rval) { copy, RVAL_TYPE_SCALAR })); + return offset + consume; +} + +Rlist *RlistFromSplitString(const char *string, char sep) +/* Splits a string on a separator - e.g. "," - into a linked list of + * separate items. Supports escaping separators - e.g. "\," isn't a + * separator, it contributes a simple "," in a list entry. */ +{ + if (string == NULL || string[0] == '\0') + { + return NULL; + } + Rlist *liststart = NULL; + + for (const char *sp = string; *sp != '\0';) + { + sp += SubStrnCopyChr(&liststart, sp, sep, '\0'); + assert((size_t) (sp - string) <= strlen(string)); + if (*sp) + { + assert(*sp == sep && (sp == string || sp[-1] != '\\')); + sp++; + } + } + + RlistReverse(&liststart); + return liststart; +} + +/** + * Splits the given string into lines. On Windows, both \n and \r\n newlines are + * detected. Escaped newlines are respected/ignored too. + * + * @param detect_crlf whether to try to detect and respect "\r\n" line endings + * @return: an #Rlist where items are the individual lines **without** the + * trailing newline character(s) + * @note: Free the result with RlistDestroy() + * @warning: This function doesn't work properly if @string uses "\r\n" newlines + * and contains '\r' characters that are not part of any "\r\n" + * sequence because it first splits @string on '\r'. + */ +Rlist *RlistFromStringSplitLines(const char *string, bool detect_crlf) +{ + if (string == NULL || string[0] == '\0') + { + return NULL; + } + + if (!detect_crlf || (strstr(string, "\r\n") == NULL)) + { + return RlistFromSplitString(string, '\n'); + } + + /* else we split on '\r' just like RlistFromSplitString() above, but + * strip leading '\n' in every chunk, thus effectively split on \r\n. See + * the warning in the function's documentation.*/ + Rlist *liststart = NULL; + + for (const char *sp = string; *sp != '\0';) + { + sp += SubStrnCopyChr(&liststart, sp, '\r', '\n'); + assert((size_t) (sp - string) <= strlen(string)); + if (*sp) + { + assert(*sp == '\r' && (sp == string || sp[-1] != '\\')); + sp++; + } + } + + RlistReverse(&liststart); + return liststart; +} + +/*******************************************************************/ + +Rlist *RlistFromSplitRegex(const char *string, const char *regex, size_t max_entries, bool allow_blanks) +{ + assert(string); + if (!string) + { + return NULL; + } + + const char *sp = string; + size_t entry_count = 0; + size_t start = 0; + size_t end = 0; + Rlist *result = NULL; + Buffer *buffer = BufferNewWithCapacity(CF_MAXVARSIZE); + + Regex *rx = CompileRegex(regex); + if (rx) + { + while ((entry_count < max_entries) && + StringMatchWithPrecompiledRegex(rx, sp, &start, &end)) + { + if (end == 0) + { + break; + } + + BufferClear(buffer); + BufferAppend(buffer, sp, start); + + if (allow_blanks || BufferSize(buffer) > 0) + { + RlistAppendScalar(&result, BufferData(buffer)); + entry_count++; + } + + sp += end; + } + + RegexDestroy(rx); + } + + if (entry_count < max_entries) + { + BufferClear(buffer); + size_t remaining = strlen(sp); + BufferAppend(buffer, sp, remaining); + + if ((allow_blanks && sp != string) || BufferSize(buffer) > 0) + { + RlistAppendScalar(&result, BufferData(buffer)); + } + } + + BufferDestroy(buffer); + + return result; +} + +/*******************************************************************/ +/* + * Splits string on regex, returns a list of (at most max) fragments. + * + * NOTE: in contrast with RlistFromSplitRegex() this one will produce at most max number of elements; + * last element will contain everything that lefts from original string (we use everything after + * the (max-1)-th separator as the final list element, including any separators that may be embedded in it) + */ +Rlist *RlistFromRegexSplitNoOverflow(const char *string, const char *regex, int max) +{ + Rlist *liststart = NULL; + char node[CF_MAXVARSIZE]; + size_t start, end; + int count = 0; + + assert(max > 0); // ensured by FnCallStringSplit() before calling us + assert(string != NULL); // ensured by FnCallStringSplit() before calling us + + const char *sp = string; + // We will avoid compiling regex multiple times. + Regex *pattern = CompileRegex(regex); + + if (pattern == NULL) + { + Log(LOG_LEVEL_DEBUG, "Error compiling regex from '%s'", regex); + return NULL; + } + + while (count < max - 1 && + StringMatchWithPrecompiledRegex(pattern, sp, &start, &end)) + { + size_t len = start; + if (len >= CF_MAXVARSIZE) + { + len = CF_MAXVARSIZE - 1; + Log(LOG_LEVEL_WARNING, + "Segment in string_split() is %zu bytes and will be truncated to %zu bytes", + start, + len); + } + memcpy(node, sp, len); + node[len] = '\0'; + RlistAppendScalar(&liststart, node); + count++; + + sp += end; + } + + assert(count < max); + RlistAppendScalar(&liststart, sp); + + RegexDestroy(pattern); + + return liststart; +} + +Rlist *RlistLast(Rlist *start) +{ + if (start == NULL) + { + return NULL; + } + Rlist *rp = start; + while (rp->next != NULL) + { + rp = rp->next; + } + return rp; +} + +void RlistFilter(Rlist **list, + bool (*KeepPredicate)(void *, void *), void *predicate_user_data, + void (*DestroyItem)(void *)) +{ + assert(KeepPredicate); + Rlist *start = *list, *prev = NULL, *next; + + for (Rlist *rp = start; rp; rp = next) + { + next = rp->next; + if (KeepPredicate(RlistScalarValue(rp), predicate_user_data)) + { + prev = rp; + } + else + { + if (prev) + { + prev->next = next; + } + else + { + assert(rp == *list); + *list = next; + } + + if (DestroyItem) + { + DestroyItem(rp->val.item); + rp->val.item = NULL; + } + + rp->next = NULL; + RlistDestroy(rp); + } + } +} + +void RlistReverse(Rlist **list) +{ + Rlist *prev = NULL; + while (*list) + { + Rlist *tmp = *list; + *list = (*list)->next; + tmp->next = prev; + prev = tmp; + } + *list = prev; +} + +void RlistWrite(Writer *writer, const Rlist *list) +{ + WriterWrite(writer, " {"); + + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + RvalWriteQuoted(writer, rp->val); + + if (rp->next != NULL) + { + WriterWriteChar(writer, ','); + } + } + + WriterWriteChar(writer, '}'); +} + +void ScalarWrite(Writer *writer, const char *s, bool quote, bool raw) +{ + if (quote) + { + WriterWriteChar(writer, '"'); + } + for (; *s; s++) + { + if (*s == '"' && !raw) + { + WriterWriteChar(writer, '\\'); + } + WriterWriteChar(writer, *s); + } + if (quote) + { + WriterWriteChar(writer, '"'); + } +} + +static void RvalWriteParts(Writer *writer, const void* item, RvalType type, bool quote, bool raw) +{ + if (item == NULL) + { + return; + } + + switch (type) + { + case RVAL_TYPE_SCALAR: + ScalarWrite(writer, item, quote, raw); + break; + + case RVAL_TYPE_LIST: + RlistWrite(writer, item); + break; + + case RVAL_TYPE_FNCALL: + FnCallWrite(writer, item); + break; + + case RVAL_TYPE_NOPROMISEE: + WriterWrite(writer, "(no-one)"); + break; + + case RVAL_TYPE_CONTAINER: + JsonWrite(writer, item, 0); + break; + } +} + +void RvalWrite(Writer *writer, Rval rval) +{ + RvalWriteParts(writer, rval.item, rval.type, false, false); +} + +void RvalWriteQuoted(Writer *writer, Rval rval) +{ + RvalWriteParts(writer, rval.item, rval.type, true, false); +} + +void RvalWriteRaw(Writer *writer, Rval rval) +{ + RvalWriteParts(writer, rval.item, rval.type, false, true); +} + +char *RvalToString(Rval rval) +{ + Writer *w = StringWriter(); + RvalWrite(w, rval); + return StringWriterClose(w); +} + +char *RlistToString(const Rlist *rlist) +{ + Writer *w = StringWriter(); + RlistWrite(w, rlist); + return StringWriterClose(w); +} + +unsigned RvalHash(Rval rval, unsigned seed) +{ + switch (rval.type) + { + case RVAL_TYPE_SCALAR: + return StringHash(RvalScalarValue(rval), seed); + case RVAL_TYPE_FNCALL: + return FnCallHash(RvalFnCallValue(rval), seed); + case RVAL_TYPE_LIST: + return RlistHash(RvalRlistValue(rval), seed); + case RVAL_TYPE_NOPROMISEE: + /* TODO modulus operation is biasing results. */ + return (seed + 1); + default: + ProgrammingError("Unhandled case in switch: %d", rval.type); + } +} + +unsigned int RlistHash(const Rlist *list, unsigned seed) +{ + unsigned hash = seed; + for (const Rlist *rp = list; rp; rp = rp->next) + { + hash = RvalHash(rp->val, hash); + } + return hash; +} + +unsigned int RlistHash_untyped(const void *list, unsigned seed) +{ + return RlistHash(list, seed); +} + + +static JsonElement *FnCallToJson(const FnCall *fp) +{ + assert(fp); + + JsonElement *object = JsonObjectCreate(3); + + JsonObjectAppendString(object, "name", fp->name); + JsonObjectAppendString(object, "type", "function-call"); + + JsonElement *argsArray = JsonArrayCreate(5); + + for (Rlist *rp = fp->args; rp != NULL; rp = rp->next) + { + switch (rp->val.type) + { + case RVAL_TYPE_SCALAR: + JsonArrayAppendString(argsArray, RlistScalarValue(rp)); + break; + + case RVAL_TYPE_FNCALL: + JsonArrayAppendObject(argsArray, FnCallToJson(RlistFnCallValue(rp))); + break; + + default: + assert(false && "Unknown argument type"); + break; + } + } + JsonObjectAppendArray(object, "arguments", argsArray); + + return object; +} + +static JsonElement *RlistToJson(Rlist *list) +{ + JsonElement *array = JsonArrayCreate(RlistLen(list)); + + for (Rlist *rp = list; rp; rp = rp->next) + { + switch (rp->val.type) + { + case RVAL_TYPE_SCALAR: + JsonArrayAppendString(array, RlistScalarValue(rp)); + break; + + case RVAL_TYPE_LIST: + JsonArrayAppendArray(array, RlistToJson(RlistRlistValue(rp))); + break; + + case RVAL_TYPE_FNCALL: + JsonArrayAppendObject(array, FnCallToJson(RlistFnCallValue(rp))); + break; + + default: + ProgrammingError("Unsupported item type in rlist"); + break; + } + } + + return array; +} + +JsonElement *RvalToJson(Rval rval) +{ + /* Only empty Rlist can be NULL. */ + assert(rval.item || rval.type == RVAL_TYPE_LIST); + + switch (rval.type) + { + case RVAL_TYPE_SCALAR: + return JsonStringCreate(RvalScalarValue(rval)); + case RVAL_TYPE_LIST: + return RlistToJson(RvalRlistValue(rval)); + case RVAL_TYPE_FNCALL: + return FnCallToJson(RvalFnCallValue(rval)); + case RVAL_TYPE_CONTAINER: + return JsonCopy(RvalContainerValue(rval)); + case RVAL_TYPE_NOPROMISEE: + assert(false); + return JsonObjectCreate(1); + } + + assert(false); + return NULL; +} + +/** + * @brief Flattens an Rlist by expanding naked scalar list-variable + * members. Flattening is only one-level deep. + */ +void RlistFlatten(EvalContext *ctx, Rlist **list) +{ + Rlist *next; + for (Rlist *rp = *list; rp != NULL; rp = next) + { + next = rp->next; + + if (rp->val.type == RVAL_TYPE_SCALAR && + IsNakedVar(RlistScalarValue(rp), '@')) + { + char naked[CF_MAXVARSIZE]; + GetNaked(naked, RlistScalarValue(rp)); + + /* Make sure there are no inner expansions to take place, like if + * rp was "@{blah_$(blue)}". */ + if (!IsExpandable(naked)) + { + Log(LOG_LEVEL_DEBUG, + "Flattening slist: %s", RlistScalarValue(rp)); + + VarRef *ref = VarRefParse(naked); + DataType value_type; + const void *value = EvalContextVariableGet(ctx, ref, &value_type); + VarRefDestroy(ref); + + if (value_type == CF_DATA_TYPE_NONE) + { + assert(value == NULL); + continue; /* undefined variable */ + } + + if (DataTypeToRvalType(value_type) != RVAL_TYPE_LIST) + { + Log(LOG_LEVEL_WARNING, + "'%s' failed - variable is not list but %s", + RlistScalarValue(rp), DataTypeToString(value_type)); + continue; + } + + /* NOTE: Remember that value can be NULL as an empty Rlist. */ + + /* at_node: just a mnemonic name for the + list node with @{blah}. */ + Rlist *at_node = rp; + Rlist *insert_after = at_node; + for (const Rlist *rp2 = value; rp2 != NULL; rp2 = rp2->next) + { + assert(insert_after != NULL); + + RlistInsertAfter(insert_after, RvalCopy(rp2->val)); + insert_after = insert_after->next; + } + + /* Make sure we won't miss any element. */ + assert(insert_after->next == next); + RlistDestroyEntry(list, at_node); /* Delete @{blah} entry */ + + char *list_s = RlistToString(*list); + Log(LOG_LEVEL_DEBUG, "Flattened slist: %s", list_s); + free(list_s); + } + } + } +} + +bool RlistEqual(const Rlist *list1, const Rlist *list2) +{ + const Rlist *rp1, *rp2; + + for (rp1 = list1, rp2 = list2; rp1 != NULL && rp2 != NULL; rp1 = rp1->next, rp2 = rp2->next) + { + if (rp1->val.item != NULL && + rp2->val.item != NULL) + { + if (rp1->val.type == RVAL_TYPE_FNCALL || rp2->val.type == RVAL_TYPE_FNCALL) + { + return false; // inconclusive + } + + const Rlist *rc1 = rp1; + const Rlist *rc2 = rp2; + + // Check for list nesting with { fncall(), "x" ... } + + if (rp1->val.type == RVAL_TYPE_LIST) + { + rc1 = rp1->val.item; + } + + if (rp2->val.type == RVAL_TYPE_LIST) + { + rc2 = rp2->val.item; + } + + if (IsCf3VarString(rc1->val.item) || IsCf3VarString(rp2->val.item)) + { + return false; // inconclusive + } + + if (strcmp(rc1->val.item, rc2->val.item) != 0) + { + return false; + } + } + else if ((rp1->val.item != NULL && rp2->val.item == NULL) || + (rp1->val.item == NULL && rp2->val.item != NULL)) + { + return false; + } + else + { + assert(rp1->val.item == NULL && rp2->val.item == NULL); + } + } + // return false if lengths are different + return (rp1 == NULL && rp2 == NULL); +} + +bool RlistEqual_untyped(const void *list1, const void *list2) +{ + return RlistEqual(list1, list2); +} + +/*******************************************************************/ + +static void RlistAppendContainerPrimitive(Rlist **list, const JsonElement *primitive) +{ + assert(JsonGetElementType(primitive) == JSON_ELEMENT_TYPE_PRIMITIVE); + + switch (JsonGetPrimitiveType(primitive)) + { + case JSON_PRIMITIVE_TYPE_BOOL: + RlistAppendScalar(list, JsonPrimitiveGetAsBool(primitive) ? "true" : "false"); + break; + case JSON_PRIMITIVE_TYPE_INTEGER: + { + char *str = StringFromLong(JsonPrimitiveGetAsInteger(primitive)); + RlistAppendScalar(list, str); + free(str); + } + break; + case JSON_PRIMITIVE_TYPE_REAL: + { + char *str = StringFromDouble(JsonPrimitiveGetAsReal(primitive)); + RlistAppendScalar(list, str); + free(str); + } + break; + case JSON_PRIMITIVE_TYPE_STRING: + RlistAppendScalar(list, JsonPrimitiveGetAsString(primitive)); + break; + + case JSON_PRIMITIVE_TYPE_NULL: + break; + } +} + +Rlist *RlistFromContainer(const JsonElement *container) +{ + Rlist *list = NULL; + + switch (JsonGetElementType(container)) + { + case JSON_ELEMENT_TYPE_PRIMITIVE: + RlistAppendContainerPrimitive(&list, container); + break; + + case JSON_ELEMENT_TYPE_CONTAINER: + { + JsonIterator iter = JsonIteratorInit(container); + const JsonElement *child; + + while (NULL != (child = JsonIteratorNextValue(&iter))) + { + if (JsonGetElementType(child) == JSON_ELEMENT_TYPE_PRIMITIVE) + { + RlistAppendContainerPrimitive(&list, child); + } + } + } + break; + } + + return list; +} diff --git a/libpromises/rlist.h b/libpromises/rlist.h new file mode 100644 index 0000000000..a8acafc8ce --- /dev/null +++ b/libpromises/rlist.h @@ -0,0 +1,119 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_RLIST_H +#define CFENGINE_RLIST_H + +#include +#include +#include +#include + +/* NOTE: an empty Rlist is simply NULL. */ +struct Rlist_ +{ + Rval val; + Rlist *next; +}; + +RvalType DataTypeToRvalType(DataType datatype); + +bool RlistValueIsType(const Rlist *rlist, RvalType type); +char *RvalScalarValue(Rval rval); +FnCall *RvalFnCallValue(Rval rval); +Rlist *RvalRlistValue(Rval rval); +JsonElement *RvalContainerValue(Rval rval); + +const char *RvalTypeToString(RvalType type); + +Rval RvalNew(const void *item, RvalType type); + +/** + * Get new secret Rval. + * + * @note RvalDestroy() not required to be called on the returned value. + */ +Rval RvalNewSecret(); + +Rval RvalNewRewriter(const void *item, RvalType type, JsonElement *map); +Rval RvalCopy(Rval rval); +Rval RvalCopyRewriter(Rval rval, JsonElement *map); +void RvalDestroy(Rval rval); +JsonElement *RvalToJson(Rval rval); +char *RvalToString(Rval rval); +char *RlistToString(const Rlist *rlist); +void RvalWrite(Writer *writer, Rval rval); +void RvalWriteQuoted(Writer *writer, Rval rval); +void RvalWriteRaw(Writer *writer, Rval rval); +unsigned RvalHash(Rval rval, unsigned seed); + +Rlist *RlistCopy(const Rlist *list); +Rlist *RlistCopyRewriter(const Rlist *list, JsonElement *map); +unsigned int RlistHash (const Rlist *list, unsigned seed); +unsigned int RlistHash_untyped(const void *list, unsigned seed); +void RlistDestroy (Rlist *list); +void RlistDestroy_untyped(void *rl); +void RlistDestroyEntry(Rlist **liststart, Rlist *entry); +char *RlistScalarValue(const Rlist *rlist); +char *RlistScalarValueSafe(const Rlist *rlist); +FnCall *RlistFnCallValue(const Rlist *rlist); +Rlist *RlistRlistValue(const Rlist *rlist); +Rlist *RlistParseShown(const char *string); +Rlist *RlistParseString(const char *string); +Rlist *RlistKeyIn(Rlist *list, const char *key); +int RlistLen(const Rlist *start); +bool RlistMatchesRegexRlist(const Rlist *list, const Rlist *search); +bool RlistMatchesRegex(const Rlist *list, const char *str); +bool RlistIsInListOfRegex(const Rlist *list, const char *str); +bool RlistIsNullList(const Rlist *list); +bool RlistContainsString(const Rlist *list, const char *string); + +Rlist *RlistAppendRval(Rlist **start, Rval rval); + +Rlist *RlistPrependScalarIdemp(Rlist **start, const char *scalar); +Rlist *RlistAppendScalarIdemp(Rlist **start, const char *scalar); +Rlist *RlistAppendScalar(Rlist **start, const char *scalar); + +Rlist *RlistPrepend(Rlist **start, const void *item, RvalType type); +Rlist *RlistAppend(Rlist **start, const void *item, RvalType type); +Rlist *RlistAppendAllTypes(Rlist **start, const void *item, RvalType type, bool all_types); +Rlist *RlistAppendString(Rlist **start, const char *string); + +Rlist *RlistFromSplitString(const char *string, char sep); +Rlist *RlistFromStringSplitLines(const char *string, bool detect_crlf); +Rlist *RlistFromSplitRegex(const char *string, const char *regex, size_t max_entries, bool allow_blanks); +Rlist *RlistFromRegexSplitNoOverflow(const char *string, const char *regex, int max); +Rlist *RlistFromContainer(const JsonElement *container); + +void RlistWrite(Writer *writer, const Rlist *list); +Rlist *RlistLast(Rlist *start); +void RlistFilter(Rlist **list, bool (*KeepPredicate)(void *item, void *predicate_data), void *predicate_user_data, void (*DestroyItem)(void *item)); +void RlistReverse(Rlist **list); +void ScalarWrite(Writer *w, const char *s, bool quote, bool raw); +void RlistFlatten(EvalContext *ctx, Rlist **list); +bool RlistEqual (const Rlist *list1, const Rlist *list2); +bool RlistEqual_untyped(const void *list1, const void *list2); + + +#endif diff --git a/libpromises/scope.c b/libpromises/scope.c new file mode 100644 index 0000000000..265ee7e931 --- /dev/null +++ b/libpromises/scope.c @@ -0,0 +1,345 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*******************************************************************/ + +const char *SpecialScopeToString(SpecialScope scope) +{ + switch (scope) + { + case SPECIAL_SCOPE_CONST: + return "const"; + case SPECIAL_SCOPE_EDIT: + return "edit"; + case SPECIAL_SCOPE_MATCH: + return "match"; + case SPECIAL_SCOPE_MON: + return "mon"; + case SPECIAL_SCOPE_SYS: + return "sys"; + case SPECIAL_SCOPE_DEF: + return "def"; + case SPECIAL_SCOPE_THIS: + return "this"; + case SPECIAL_SCOPE_BODY: + return "body"; + default: + ProgrammingError("Unhandled special scope"); + } +} + +SpecialScope SpecialScopeFromString(const char *scope) +{ + if (scope == NULL) + { + return SPECIAL_SCOPE_NONE; + } + else if (strcmp("const", scope) == 0) + { + return SPECIAL_SCOPE_CONST; + } + else if (strcmp("edit", scope) == 0) + { + return SPECIAL_SCOPE_EDIT; + } + else if (strcmp("match", scope) == 0) + { + return SPECIAL_SCOPE_MATCH; + } + else if (strcmp("mon", scope) == 0) + { + return SPECIAL_SCOPE_MON; + } + else if (strcmp("sys", scope) == 0) + { + return SPECIAL_SCOPE_SYS; + } + else if (strcmp("def", scope) == 0) + { + return SPECIAL_SCOPE_DEF; + } + else if (strcmp("this", scope) == 0) + { + return SPECIAL_SCOPE_THIS; + } + else if (strcmp("body", scope) == 0) + { + return SPECIAL_SCOPE_BODY; + } + + /* All other scopes fall here, for example all bundle names. It means that + * scope was not special. */ + return SPECIAL_SCOPE_NONE; +} + +void ScopeAugment(EvalContext *ctx, const Bundle *bp, const Promise *pp, const Rlist *arguments) +{ + if (RlistLen(bp->args) != RlistLen(arguments)) + { + Log(LOG_LEVEL_ERR, "While constructing scope '%s'", bp->name); + fprintf(stderr, "Formal = "); + { + Writer *w = FileWriter(stderr); + RlistWrite(w, bp->args); + FileWriterDetach(w); + } + fprintf(stderr, ", Actual = "); + { + Writer *w = FileWriter(stderr); + RlistWrite(w, arguments); + FileWriterDetach(w); + } + fprintf(stderr, "\n"); + FatalError(ctx, "Augment scope, formal and actual parameter mismatch is fatal"); + } + + const Bundle *pbp = NULL; + if (pp != NULL) + { + pbp = PromiseGetBundle(pp); + } + + for (const Rlist *rpl = bp->args, *rpr = arguments; rpl != NULL; rpl = rpl->next, rpr = rpr->next) + { + const char *lval = RlistScalarValue(rpl); + + Log(LOG_LEVEL_VERBOSE, "V: + Private parameter: '%s' in scope '%s' (type: %c) in pass %d", lval, bp->name, rpr->val.type, EvalContextGetPass(ctx)); + + // CheckBundleParameters() already checked that there is no namespace collision + // By this stage all functions should have been expanded, so we only have scalars left + + if (rpr->val.type == RVAL_TYPE_SCALAR && IsNakedVar(RlistScalarValue(rpr), '@')) + { + + char naked[CF_BUFSIZE]; + + GetNaked(naked, RlistScalarValue(rpr)); + + DataType value_type; + const void *value; + if (pbp != NULL) + { + VarRef *ref = VarRefParseFromBundle(naked, pbp); + value = EvalContextVariableGet(ctx, ref, &value_type); + VarRefDestroy(ref); + } + else + { + VarRef *ref = VarRefParseFromBundle(naked, bp); + value = EvalContextVariableGet(ctx, ref, &value_type); + VarRefDestroy(ref); + } + + switch (value_type) + { + case CF_DATA_TYPE_STRING_LIST: + case CF_DATA_TYPE_INT_LIST: + case CF_DATA_TYPE_REAL_LIST: + { + VarRef *ref = VarRefParseFromBundle(lval, bp); + EvalContextVariablePut(ctx, ref, value, CF_DATA_TYPE_STRING_LIST, "source=promise"); + VarRefDestroy(ref); + } + break; + case CF_DATA_TYPE_CONTAINER: + { + VarRef *ref = VarRefParseFromBundle(lval, bp); + EvalContextVariablePut(ctx, ref, value, CF_DATA_TYPE_CONTAINER, "source=promise"); + VarRefDestroy(ref); + } + break; + default: + { + Log(LOG_LEVEL_ERR, "List or container parameter '%s' not found while constructing scope '%s' - use @(scope.variable) in calling reference", naked, bp->name); + VarRef *ref = VarRefParseFromBundle(lval, bp); + EvalContextVariablePut(ctx, ref, RlistScalarValue(rpr), CF_DATA_TYPE_STRING, "source=promise"); + VarRefDestroy(ref); + } + break; + } + } + else + { + switch(rpr->val.type) + { + case RVAL_TYPE_SCALAR: + { + VarRef *ref = VarRefParseFromBundle(lval, bp); + EvalContextVariablePut(ctx, ref, RvalScalarValue(rpr->val), CF_DATA_TYPE_STRING, "source=promise"); + VarRefDestroy(ref); + } + break; + + case RVAL_TYPE_FNCALL: + { + FnCall *subfp = RlistFnCallValue(rpr); + Rval rval = FnCallEvaluate(ctx, PromiseGetPolicy(pp), subfp, pp).rval; + if (rval.type == RVAL_TYPE_SCALAR) + { + VarRef *ref = VarRefParseFromBundle(lval, bp); + EvalContextVariablePut(ctx, ref, RvalScalarValue(rval), CF_DATA_TYPE_STRING, "source=promise"); + VarRefDestroy(ref); + } + else + { + Log(LOG_LEVEL_ERR, "Only functions returning scalars can be used as arguments"); + } + RvalDestroy(rval); + } + break; + default: + ProgrammingError("An argument neither a scalar nor a list seemed to appear. Impossible"); + } + } + } + +/* Check that there are no danglers left to evaluate in the hash table itself */ + + return; +} + + +void ScopeMapBodyArgs(EvalContext *ctx, const Body *body, const Rlist *args) +{ + const Rlist *arg = NULL; + const Rlist *param = NULL; + + for (arg = args, param = body->args; arg != NULL && param != NULL; arg = arg->next, param = param->next) + { + DataType arg_type = CF_DATA_TYPE_NONE; + switch (arg->val.type) + { + case RVAL_TYPE_SCALAR: + arg_type = StringDataType(ctx, RlistScalarValue(arg)); + break; + + case RVAL_TYPE_FNCALL: + { + const FnCallType *fn = FnCallTypeGet(RlistFnCallValue(arg)->name); + if (!fn) + { + FatalError(ctx, "Argument '%s' given to body '%s' is not a valid function", + RlistFnCallValue(arg)->name, body->name); + } + arg_type = fn->dtype; + } + break; + + default: + FatalError(ctx, "Cannot derive data type from Rval type %c", arg->val.type); + } + + switch (arg->val.type) + { + case RVAL_TYPE_SCALAR: + { + const char *lval = RlistScalarValue(param); + VarRef *ref = VarRefParseFromNamespaceAndScope(lval, NULL, "body", CF_NS, '.'); + EvalContextVariablePut(ctx, ref, RvalScalarValue(arg->val), arg_type, "source=body"); + VarRefDestroy(ref); + } + break; + + case RVAL_TYPE_LIST: + { + const char *lval = RlistScalarValue(param); + VarRef *ref = VarRefParseFromNamespaceAndScope(lval, NULL, "body", CF_NS, '.'); + EvalContextVariablePut(ctx, ref, RvalRlistValue(arg->val), arg_type, "source=body"); + VarRefDestroy(ref); + } + break; + + case RVAL_TYPE_FNCALL: + { + FnCall *fp = RlistFnCallValue(arg); + arg_type = CF_DATA_TYPE_NONE; + { + const FnCallType *fncall_type = FnCallTypeGet(fp->name); + if (fncall_type) + { + arg_type = fncall_type->dtype; + } + } + + FnCallResult res = FnCallEvaluate(ctx, body->parent_policy, fp, NULL); + + if (res.status == FNCALL_FAILURE && THIS_AGENT_TYPE != AGENT_TYPE_COMMON) + { + Log(LOG_LEVEL_VERBOSE, "Embedded function argument does not resolve to a name - probably too many evaluation levels for '%s'", + fp->name); + } + else + { + const char *lval = RlistScalarValue(param); + void *rval = res.rval.item; + + VarRef *ref = VarRefParseFromNamespaceAndScope(lval, NULL, "body", CF_NS, '.'); + EvalContextVariablePut(ctx, ref, rval, arg_type, "source=body"); + VarRefDestroy(ref); + } + + RvalDestroy(res.rval); + } + + break; + + default: + /* Nothing else should happen */ + ProgrammingError("Software error: something not a scalar/function in argument literal"); + } + } +} + +/*******************************************************************/ +/* Utility functions */ +/*******************************************************************/ + +void JoinScopeName(const char *ns, const char *bundle, char scope_out[CF_MAXVARSIZE]) +{ + assert(bundle); + + if (ns) + { + snprintf(scope_out, CF_MAXVARSIZE, "%s%c%s", ns, CF_NS, bundle); + } + else + { + snprintf(scope_out, CF_MAXVARSIZE, "%s", bundle); + } +} diff --git a/libpromises/scope.h b/libpromises/scope.h new file mode 100644 index 0000000000..4d5451320d --- /dev/null +++ b/libpromises/scope.h @@ -0,0 +1,62 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_SCOPE_H +#define CFENGINE_SCOPE_H + +#include + +#include + +typedef enum +{ + SPECIAL_SCOPE_CONST, + SPECIAL_SCOPE_EDIT, + SPECIAL_SCOPE_MATCH, + SPECIAL_SCOPE_MON, + SPECIAL_SCOPE_SYS, + SPECIAL_SCOPE_THIS, + SPECIAL_SCOPE_BODY, + SPECIAL_SCOPE_DEF, + + SPECIAL_SCOPE_NONE +} SpecialScope; + +const char *SpecialScopeToString(SpecialScope scope); +SpecialScope SpecialScopeFromString(const char *scope); + +/** + * @brief augments a scope, expecting corresponding lists of lvals and rvals (implying same length). + * in addition to copying them in, also attempts to do one-pass resolution of variables, + * and evaluates function calls, and attempts expansion on senior scope members. + */ +void ScopeAugment(EvalContext *ctx, const Bundle *bp, const Promise *pp, const Rlist *arguments); + +void ScopeMapBodyArgs(EvalContext *ctx, const Body *body, const Rlist *args); + +// TODO: namespacing utility functions. there are probably a lot of these floating around, but probably best +// leave them until we get a proper symbol table +void JoinScopeName(const char *ns, const char *bundle, char scope_out[CF_MAXVARSIZE]); + +#endif diff --git a/libpromises/shared_lib.c b/libpromises/shared_lib.c new file mode 100644 index 0000000000..865e546e42 --- /dev/null +++ b/libpromises/shared_lib.c @@ -0,0 +1,67 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef __MINGW32__ + +#include +#include +#include +#include +#include +#include + +#include + +void *shlib_open(const char *lib_name) +{ + struct stat statbuf; + if (stat(lib_name, &statbuf) == -1) + { + Log(LOG_LEVEL_DEBUG, "Could not open shared library: %s\n", GetErrorStr()); + return NULL; + } + + void * ret = dlopen(lib_name, RTLD_NOW); + if (!ret) + { + Log(LOG_LEVEL_ERR, "Could not open shared library: %s\n", dlerror()); + } + return ret; +} + +void *shlib_load(void *handle, const char *symbol_name) +{ + static pthread_mutex_t dlsym_mutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; + ThreadLock(&dlsym_mutex); + void *ret = dlsym(handle, symbol_name); + ThreadUnlock(&dlsym_mutex); + return ret; +} + +void shlib_close(void *handle) +{ + dlclose(handle); +} + +#endif // !__MINGW32__ diff --git a/libpromises/shared_lib.h b/libpromises/shared_lib.h new file mode 100644 index 0000000000..25ad12d4ff --- /dev/null +++ b/libpromises/shared_lib.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef SHARED_LIB_H +#define SHARED_LIB_H + +void *shlib_open(const char *lib_name); +void *shlib_load(void *handle, const char *symbol_name); +void shlib_close(void *handle); + +#endif // SHARED_LIB_H diff --git a/libpromises/signals.c b/libpromises/signals.c new file mode 100644 index 0000000000..21d150cd65 --- /dev/null +++ b/libpromises/signals.c @@ -0,0 +1,252 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include /* GetStateDir() */ +#include /* FILE_SEPARATOR */ +#include /* TerminateCustomPromises */ + +static bool PENDING_TERMINATION = false; /* GLOBAL_X */ + +static bool RELOAD_CONFIG = false; /* GLOBAL_X */ +/********************************************************************/ + +bool IsPendingTermination(void) +{ + return PENDING_TERMINATION; +} + +bool ReloadConfigRequested(void) +{ + return RELOAD_CONFIG; +} + +void ClearRequestReloadConfig() +{ + RELOAD_CONFIG = false; +} + +void RequestReloadConfig() +{ + RELOAD_CONFIG = true; +} + +/********************************************************************/ + +static int SIGNAL_PIPE[2] = { -1, -1 }; /* GLOBAL_C */ + +static void CloseSignalPipe(void) +{ + int c = 2; + while (c > 0) + { + c--; + if (SIGNAL_PIPE[c] >= 0) + { + close(SIGNAL_PIPE[c]); + SIGNAL_PIPE[c] = -1; + } + } +} + +/** + * Make a pipe that can be used to flag that a signal has arrived. + * Using a pipe avoids race conditions, since it saves its values until emptied. + * Use GetSignalPipe() to get the pipe. + * Note that we use a real socket as the pipe, because Windows only supports + * using select() with real sockets. This means also using send() and recv() + * instead of write() and read(). + */ +void MakeSignalPipe(void) +{ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, SIGNAL_PIPE) != 0) + { + Log(LOG_LEVEL_CRIT, "Could not create internal communication pipe. Cannot continue. (socketpair: '%s')", + GetErrorStr()); + DoCleanupAndExit(EXIT_FAILURE); + } + + RegisterCleanupFunction(&CloseSignalPipe); + + for (int c = 0; c < 2; c++) + { +#ifdef __MINGW32__ + u_long enable = 1; + int ret = ioctlsocket(SIGNAL_PIPE[c], FIONBIO, &enable); +#define CNTLNAME "ioctlsocket" +#else /* Unix: */ + int ret = fcntl(SIGNAL_PIPE[c], F_SETFL, O_NONBLOCK); +#define CNTLNAME "fcntl" +#endif /* __MINGW32__ */ + + if (ret != 0) + { + Log(LOG_LEVEL_CRIT, + "Could not unblock internal communication pipe. " + "Cannot continue. (" CNTLNAME ": '%s')", + GetErrorStr()); + DoCleanupAndExit(EXIT_FAILURE); + } +#undef CNTLNAME + } +} + +/** + * Gets the signal pipe, which is non-blocking. + * Each byte read corresponds to one arrived signal. + * Note: Use recv() to read from the pipe, not read(). + */ +int GetSignalPipe(void) +{ + return SIGNAL_PIPE[0]; +} + +static void SignalNotify(int signum) +{ + unsigned char sig = (unsigned char)signum; + if (SIGNAL_PIPE[1] >= 0) + { + // send() is async-safe, according to POSIX. + if (send(SIGNAL_PIPE[1], &sig, 1, 0) < 0) + { + // These signal contention. Everything else is an error. + if (errno != EAGAIN +#ifndef __MINGW32__ + && errno != EWOULDBLOCK +#endif + ) + { + // This is not async safe, but if we get in here there's something really weird + // going on. + Log(LOG_LEVEL_CRIT, "Could not write to signal pipe. Unsafe to continue. (write: '%s')", + GetErrorStr()); + _exit(EXIT_FAILURE); + } + } + } +} + +void HandleSignalsForAgent(int signum) +{ + switch (signum) + { + case SIGTERM: + case SIGINT: + /* TODO don't exit from the signal handler, just set a flag. Reason is + * that all the cleanup() hooks we register are not reentrant. */ + TerminateCustomPromises(); + DoCleanupAndExit(0); + case SIGBUS: + /* SIGBUS almost certainly means a violation of mmap() area boundaries + * or some mis-aligned memory access. IOW, an LMDB corruption. */ + { + char filename[PATH_MAX] = { 0 }; /* trying to avoid memory allocation */ + xsnprintf(filename, PATH_MAX, "%s%c%s", + GetStateDir(), FILE_SEPARATOR, CF_DB_REPAIR_TRIGGER); + int fd = open(filename, O_CREAT|O_RDWR, CF_PERMS_DEFAULT); + if (fd != -1) + { + close(fd); + } + + /* avoid calling complex logging functions */ + fprintf(stdout, "process killed by SIGBUS\n"); + + /* else: we tried, nothing more to do in the limited environment of a + * signal handler */ + _exit(1); + } + break; + case SIGUSR1: + LogSetGlobalLevel(LOG_LEVEL_DEBUG); + break; + case SIGUSR2: + LogSetGlobalLevel(LOG_LEVEL_NOTICE); + break; + default: + /* No action */ + break; + } + + SignalNotify(signum); + +/* Reset the signal handler */ + signal(signum, HandleSignalsForAgent); +} + +/********************************************************************/ + +void HandleSignalsForDaemon(int signum) +{ + switch (signum) + { + case SIGTERM: + case SIGINT: + case SIGSEGV: + case SIGKILL: + PENDING_TERMINATION = true; + break; + case SIGBUS: + /* SIGBUS almost certainly means a violation of mmap() area boundaries + * or some mis-aligned memory access. IOW, an LMDB corruption. */ + { + char filename[PATH_MAX] = { 0 }; /* trying to avoid memory allocation */ + xsnprintf(filename, PATH_MAX, "%s%c%s", + GetStateDir(), FILE_SEPARATOR, CF_DB_REPAIR_TRIGGER); + int fd = open(filename, O_CREAT|O_RDWR, CF_PERMS_DEFAULT); + if (fd != -1) + { + close(fd); + } + + /* avoid calling complex logging functions */ + fprintf(stdout, "process killed by SIGBUS\n"); + + /* else: we tried, nothing more to do in the limited environment of a + * signal handler */ + _exit(1); + } + break; + case SIGUSR1: + LogSetGlobalLevel(LOG_LEVEL_DEBUG); + break; + case SIGUSR2: + LogSetGlobalLevel(LOG_LEVEL_NOTICE); + break; + case SIGHUP: + RELOAD_CONFIG = true; + break; + case SIGPIPE: + default: + /* No action */ + break; + } + + /* Notify processes that use the signal pipe (cf-serverd). */ + SignalNotify(signum); + + /* Reset the signal handler. */ + signal(signum, HandleSignalsForDaemon); +} diff --git a/libpromises/signals.h b/libpromises/signals.h new file mode 100644 index 0000000000..4ddbaf6026 --- /dev/null +++ b/libpromises/signals.h @@ -0,0 +1,42 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_SIGNALS_H +#define CFENGINE_SIGNALS_H + +#include + +// check whether the running daemon should terminate after having received a signal. +bool IsPendingTermination(void); + +bool ReloadConfigRequested(void); +void ClearRequestReloadConfig(); +void RequestReloadConfig(void); + +void MakeSignalPipe(void); +int GetSignalPipe(void); +void HandleSignalsForDaemon(int signum); +void HandleSignalsForAgent(int signum); + +#endif diff --git a/libpromises/sort.c b/libpromises/sort.c new file mode 100644 index 0000000000..6c5d142995 --- /dev/null +++ b/libpromises/sort.c @@ -0,0 +1,455 @@ +/*******************************************************************/ + +/* The following sort functions are trivial rewrites of merge-sort + * implementation by Simon Tatham + * copyright 2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +#include +#include +#include + +typedef bool (*LessFn)(void *lhs, void *rhs, void *ctx); +typedef void * (*GetNextElementFn)(void *element); +typedef void (*PutNextElementFn)(void *element, void *next); + +static void *Sort(void *list, LessFn less, GetNextElementFn next, PutNextElementFn putnext, void *ctx) +{ + void *p, *q, *e, *tail; + int insize, nmerges, psize, qsize, i; + + if (list == NULL) + { + return NULL; + } + + insize = 1; + + while (true) + { + p = list; + list = NULL; + tail = NULL; + + nmerges = 0; /* count number of merges we do in this pass */ + + while (p) + { + nmerges++; /* there exists a merge to be done */ + /* step `insize' places along from p */ + q = p; + psize = 0; + + for (i = 0; i < insize; i++) + { + psize++; + + q = next(q); + + if (!q) + { + break; + } + } + + /* if q hasn't fallen off end, we have two lists to merge */ + qsize = insize; + + /* now we have two lists; merge them */ + while ((psize > 0) || ((qsize > 0) && q)) + { + /* decide whether next element of merge comes from p or q */ + if (psize == 0) + { + /* p is empty; e must come from q. */ + e = q; + q = next(q); + qsize--; + } + else if ((qsize == 0) || (!q)) + { + /* q is empty; e must come from p. */ + e = p; + p = next(p); + psize--; + } + else if (less(p, q, ctx)) + { + /* First element of p is lower (or same); + * e must come from p. */ + e = p; + p = next(p); + psize--; + } + else + { + /* First element of q is lower; e must come from q. */ + e = q; + q = next(q); + qsize--; + } + + /* add the next element to the merged list */ + if (tail) + { + putnext(tail, e); + } + else + { + list = e; + } + + tail = e; + } + + /* now p has stepped `insize' places along, and q has too */ + p = q; + } + + putnext(tail, NULL); + + /* If we have done only one merge, we're finished. */ + + if (nmerges <= 1) /* allow for nmerges==0, the empty list case */ + { + return list; + } + + /* Otherwise repeat, merging lists twice the size */ + insize *= 2; + } +} + +/* Item* callbacks */ + +static bool ItemNameLess(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return strcmp(((Item*)lhs)->name, ((Item*)rhs)->name) < 0; +} + +static bool ItemClassesLess(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return strcmp(((Item*)lhs)->classes, ((Item*)rhs)->classes) < 0; +} + +static bool ItemCounterMore(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return ((Item*)lhs)->counter > ((Item*)rhs)->counter; +} + +static bool ItemTimeMore(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return ((Item*)lhs)->time > ((Item*)rhs)->time; +} + +static void *ItemGetNext(void *element) +{ + return ((Item*)element)->next; +} + +static void ItemPutNext(void *element, void *next) +{ + ((Item*)element)->next = (Item *)next; +} + +/* Item* sorting */ + +Item *SortItemListNames(Item *list) +{ + return Sort(list, &ItemNameLess, &ItemGetNext, &ItemPutNext, NULL); +} + +Item *SortItemListClasses(Item *list) +{ + return Sort(list, &ItemClassesLess, &ItemGetNext, &ItemPutNext, NULL); +} + +Item *SortItemListCounters(Item *list) +{ + return Sort(list, &ItemCounterMore, &ItemGetNext, &ItemPutNext, NULL); +} + +Item *SortItemListTimes(Item *list) +{ + return Sort(list, &ItemTimeMore, &ItemGetNext, &ItemPutNext, NULL); +} + +/* Rlist* and String* callbacks */ + +static bool RlistCustomItemLess(void *lhs_, void *rhs_, void *ctx) +{ + Rlist *lhs = lhs_; + Rlist *rhs = rhs_; + bool (*cmp)(void *a, void *b) = ctx; + + return (*cmp)(lhs->val.item, rhs->val.item); +} + +static bool RlistItemLess(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return strcmp(((Rlist*)lhs)->val.item, ((Rlist*)rhs)->val.item) < 0; +} + +static bool StringItemLess(const char *lhs, const char *rhs, ARG_UNUSED void *ctx) +{ + return strcmp(lhs, rhs) < 0; +} + +static bool StringItemNumberLess(const char *lhs, const char *rhs, ARG_UNUSED void *ctx, bool int_mode) +{ + char remainder[4096]; + double left; + double right; + + bool matched_left = sscanf(lhs, "%lf", &left) > 0; + bool matched_right = sscanf(rhs, "%lf", &right) > 0; + + if (!matched_left) + { + matched_left = sscanf(lhs, "%lf%4095s", &left, remainder) > 0; + } + + if (!matched_right) + { + matched_right = sscanf(rhs, "%lf%4095s", &right, remainder) > 0; + } + + if (matched_left && matched_right) + { + if (int_mode) + { + return ((long int)left) - ((long int)right) < 0; + } + else + { + return left - right < 0; + } + } + + if (matched_left) + { + return false; + } + + if (matched_right) + { + return true; + } + + // neither item matched + return StringItemLess(lhs, rhs, ctx); +} + +static bool RlistItemNumberLess(void *lhs, void *rhs, ARG_UNUSED void *ctx, bool int_mode) +{ + return StringItemNumberLess(RlistScalarValue((Rlist*)lhs), RlistScalarValue((Rlist*)rhs), ctx, int_mode); +} + +static bool RlistItemIntLess(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return RlistItemNumberLess(lhs, rhs, ctx, true); +} + +static bool RlistItemRealLess(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return RlistItemNumberLess(lhs, rhs, ctx, false); +} + +static bool StringItemIPLess(const char *left_item, const char *right_item, ARG_UNUSED void *ctx) +{ + Buffer *left_buffer = BufferNewFrom(left_item, strlen(left_item)); + Buffer *right_buffer = BufferNewFrom(right_item, strlen(right_item)); + + IPAddress *left = IPAddressNew(left_buffer); + IPAddress *right = IPAddressNew(right_buffer); + + bool matched_left = (left != NULL); + bool matched_right = (right != NULL); + + BufferDestroy(left_buffer); + BufferDestroy(right_buffer); + + if (matched_left && matched_right) + { + bool less = IPAddressCompareLess(left, right); + IPAddressDestroy(&left); + IPAddressDestroy(&right); + return less; + } + + IPAddressDestroy(&left); + IPAddressDestroy(&right); + + if (matched_left) + { + return false; + } + + if (matched_right) + { + return true; + } + + // neither item matched + return StringItemLess(left_item, right_item, ctx); +} + +static bool RlistItemIPLess(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return StringItemIPLess(RlistScalarValue((Rlist*)lhs), RlistScalarValue((Rlist*)rhs), ctx); +} + +static long ParseEtherAddress(const char* input, unsigned char *addr) +{ + if (strlen(input) > 12) + { + return sscanf(input, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5]); + } + + return sscanf(input, "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", + &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5]); +} + +static bool StringItemMACLess(const char *lhs, const char *rhs, ARG_UNUSED void *ctx) +{ + int bytes = 6; + unsigned char left[bytes], right[bytes]; + int matched_left = (ParseEtherAddress(lhs, left) == 6); + int matched_right = (ParseEtherAddress(rhs, right) == 6);; + + if (matched_left && matched_right) + { + int difference = memcmp(left, right, bytes); + if (difference != 0) return difference < 0; + } + + if (matched_left) + { + return false; + } + + if (matched_right) + { + return true; + } + + // neither item matched + return StringItemLess(lhs, rhs, ctx); +} + +static bool RlistItemMACLess(void *lhs, void *rhs, ARG_UNUSED void *ctx) +{ + return StringItemMACLess(RlistScalarValue((Rlist*)lhs), RlistScalarValue((Rlist*)rhs), ctx); +} + +static void *RlistGetNext(void *element) +{ + return ((Rlist*)element)->next; +} + +static void RlistPutNext(void *element, void *next) +{ + ((Rlist*)element)->next = (Rlist *)next; +} + +/* Rlist* sorting */ + +Rlist *SortRlist(Rlist *list, bool (*CompareItems) ()) +{ + return Sort(list, &RlistCustomItemLess, &RlistGetNext, &RlistPutNext, CompareItems); +} + +Rlist *AlphaSortRListNames(Rlist *list) +{ + return Sort(list, &RlistItemLess, &RlistGetNext, &RlistPutNext, NULL); +} + +Rlist *IntSortRListNames(Rlist *list) +{ + return Sort(list, &RlistItemIntLess, &RlistGetNext, &RlistPutNext, NULL); +} + +Rlist *RealSortRListNames(Rlist *list) +{ + return Sort(list, &RlistItemRealLess, &RlistGetNext, &RlistPutNext, NULL); +} + +Rlist *IPSortRListNames(Rlist *list) +{ + return Sort(list, &RlistItemIPLess, &RlistGetNext, &RlistPutNext, NULL); +} + +Rlist *MACSortRListNames(Rlist *list) +{ + return Sort(list, &RlistItemMACLess, &RlistGetNext, &RlistPutNext, NULL); +} + +bool GenericItemLess(const char *sort_type, void *lhs, void *rhs) +{ + if (strcmp(sort_type, "int") == 0) + { + return RlistItemNumberLess(lhs, rhs, NULL, true); + } + else if (strcmp(sort_type, "real") == 0) + { + return RlistItemNumberLess(lhs, rhs, NULL, false); + } + else if (strcasecmp(sort_type, "IP") == 0) + { + return RlistItemIPLess(lhs, rhs, NULL); + } + else if (strcasecmp(sort_type, "MAC") == 0) + { + return RlistItemMACLess(lhs, rhs, NULL); + } + + // "lex" + return RlistItemLess(lhs, rhs, NULL); +} + +bool GenericStringItemLess(const char *sort_type, const char *lhs, const char *rhs) +{ + if (strcmp(sort_type, "int") == 0) + { + return StringItemNumberLess(lhs, rhs, NULL, true); + } + else if (strcmp(sort_type, "real") == 0) + { + return StringItemNumberLess(lhs, rhs, NULL, false); + } + else if (strcasecmp(sort_type, "IP") == 0) + { + return StringItemIPLess(lhs, rhs, NULL); + } + else if (strcasecmp(sort_type, "MAC") == 0) + { + return StringItemMACLess(lhs, rhs, NULL); + } + + // "lex" + return StringItemLess(lhs, rhs, NULL); +} diff --git a/libpromises/sort.h b/libpromises/sort.h new file mode 100644 index 0000000000..53abf6f409 --- /dev/null +++ b/libpromises/sort.h @@ -0,0 +1,48 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_SORT_H +#define CFENGINE_SORT_H + +/* + * Sorting is destructive, use the returned pointer to refer to sorted list. + */ + +Item *SortItemListNames(Item *list); /* Alphabetical */ +Item *SortItemListClasses(Item *list); /* Alphabetical */ +Item *SortItemListCounters(Item *list); /* Reverse sort */ +Item *SortItemListTimes(Item *list); /* Reverse sort */ + +Rlist *SortRlist(Rlist *list, bool (*CompareItems) ()); +Rlist *AlphaSortRListNames(Rlist *list); +Rlist *IntSortRListNames(Rlist *list); +Rlist *RealSortRListNames(Rlist *list); +Rlist *IPSortRListNames(Rlist *list); +Rlist *MACSortRListNames(Rlist *list); + +bool GenericItemLess(const char *mode, void *lhs, void *rhs); + +bool GenericStringItemLess(const char *sort_type, const char *lhs, const char *rhs); + +#endif diff --git a/libpromises/storage_tools.c b/libpromises/storage_tools.c new file mode 100644 index 0000000000..db25598900 --- /dev/null +++ b/libpromises/storage_tools.c @@ -0,0 +1,118 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#ifdef HAVE_SYS_STATFS_H +# include +#endif +#ifdef HAVE_SYS_VFS_H +# include +#endif + +#ifdef HAVE_SYS_STATVFS_H +# include +#endif + +/************************************************************************/ + +#ifndef __MINGW32__ + +off_t GetDiskUsage(char *file, CfSize type) +{ +# if defined __sun || defined sco || defined __OpenBSD__ || (defined(__NetBSD__) && __NetBSD_Version__ >= 200040000) + struct statvfs buf; +# else + struct statfs buf; +# endif + int64_t used = 0, avail = 0; + int capacity = 0; + + memset(&buf, 0, sizeof(buf)); + +# if defined __sun || defined sco || defined __OpenBSD__ || (defined(__NetBSD__) && __NetBSD_Version__ >= 200040000) + if (statvfs(file, &buf) != 0) + { + Log(LOG_LEVEL_ERR, "Couldn't get filesystem info for %s (statvfs)", file); + return CF_INFINITY; + } +# elif defined __SCO_DS || defined _CRAY || (defined(__NetBSD__) && __NetBSD_Version__ >= 200040000) + if (statfs(file, &buf, sizeof(struct statfs), 0) != 0) + { + Log(LOG_LEVEL_ERR, "Couldn't get filesystem info for %s (statfs)", file); + return CF_INFINITY; + } +# else + if (statfs(file, &buf) != 0) + { + Log(LOG_LEVEL_ERR, "Couldn't get filesystem info for '%s'. (statfs: %s)", file, GetErrorStr()); + return CF_INFINITY; + } +# endif + +# if defined __sun + used = (buf.f_blocks - buf.f_bfree) * (int64_t)buf.f_frsize; + avail = buf.f_bavail * (int64_t)buf.f_frsize; +# endif + +# if defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __hpux || defined __APPLE__ + used = (buf.f_blocks - buf.f_bfree) * (int64_t)buf.f_bsize; + avail = buf.f_bavail * (int64_t)buf.f_bsize; +# endif + +# if defined _AIX || defined __SCO_DS || defined _CRAY + used = (buf.f_blocks - buf.f_bfree) * (int64_t)buf.f_bsize; + avail = buf.f_bfree * (int64_t)buf.f_bsize; +# endif + +# if defined __linux__ + used = (buf.f_blocks - buf.f_bfree) * (int64_t)buf.f_bsize; + avail = buf.f_bavail * (int64_t)buf.f_bsize; +# endif + + capacity = (double) (avail) / (double) (avail + used) * 100; + + Log(LOG_LEVEL_DEBUG, "GetDiskUsage(%s) = %jd/%jd", file, (intmax_t) avail, (intmax_t) capacity); + + if (type == CF_SIZE_ABS) + { + // TODO: This should be handled better by actually returning a bigger + // data type, but for now just protect against overflow by hitting the + // ceiling. + if (sizeof(off_t) < sizeof(int64_t) && avail > 0x7fffffffLL) + { + return 0x7fffffff; + } + else + { + return avail; + } + } + else + { + return capacity; + } +} + +#endif /* __MINGW32__ */ diff --git a/libpromises/string_expressions.c b/libpromises/string_expressions.c new file mode 100644 index 0000000000..7b510a149e --- /dev/null +++ b/libpromises/string_expressions.c @@ -0,0 +1,338 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include + +#include +#include +#include + +/* */ + +static StringParseResult ParseQname(const char *expr, int start, int end) +{ + StringParseResult lhs, rhs; + StringExpression *ret, *subret, *dot; + + lhs = ParseStringExpression(expr, start, end); + + if (!lhs.result) + { + return lhs; + } + + if (lhs.position == end || expr[lhs.position] != '.') + { + return lhs; + } + + rhs = ParseStringExpression(expr, lhs.position + 1, end); + + if (!rhs.result) + { + FreeStringExpression(lhs.result); + return rhs; + } + + dot = xcalloc(1, sizeof(StringExpression)); + dot->op = LITERAL; + dot->val.literal.literal = xstrdup("."); + + subret = xcalloc(1, sizeof(StringExpression)); + subret->op = CONCAT; + subret->val.concat.lhs = dot; + subret->val.concat.rhs = rhs.result; + + ret = xcalloc(1, sizeof(StringExpression)); + ret->op = CONCAT; + ret->val.concat.lhs = lhs.result; + ret->val.concat.rhs = subret; + + return (StringParseResult) {ret, rhs.position}; +} + +/* */ + +static StringParseResult ParseVarRef(const char *expr, int start, int end) +{ + if (start + 1 < end && (expr[start] == '$' || expr[start] == '@')) + { + if (expr[start + 1] == '(' || expr[start + 1] == '{') + { + char closing_bracket = expr[start + 1] == '(' ? ')' : '}'; + StringParseResult res = ParseQname(expr, start + 2, end); + + if (res.result) + { + if (res.position < end && expr[res.position] == closing_bracket) + { + StringExpression *ret = xcalloc(1, sizeof(StringExpression)); + + ret->op = VARREF; + ret->val.varref.name = res.result; + + if (expr[start] == '$') + { + ret->val.varref.type = VAR_REF_TYPE_SCALAR; + } + else if (expr[start] == '@') + { + ret->val.varref.type = VAR_REF_TYPE_LIST; + } + else + { + ProgrammingError("Unrecognized var ref type"); + } + + return (StringParseResult) {ret, res.position + 1}; + } + else + { + FreeStringExpression(res.result); + return (StringParseResult) {NULL, res.position}; + } + } + else + { + return res; + } + } + else + { + return (StringParseResult) {NULL, start + 1}; + } + } + else + { + return (StringParseResult) {NULL, start}; + } +} + +/* */ + +static inline bool ValidTokenCharacter(char c, bool *inside_index) +{ + assert(inside_index != NULL); + + if (c >= 'a' && c <= 'z') + { + return true; + } + + if (c >= 'A' && c <= 'Z') + { + return true; + } + + if (c >= '0' && c <= '9') + { + return true; + } + + if (c == '_' || c == ':') + { + return true; + } + + if (c == '[') + { + *inside_index = true; + return true; + } + + if (c == ']') + { + if (*inside_index) + { + *inside_index = false; + } + return true; + } + + if ((c == ' ') && *inside_index) + { + return true; + } + + return false; +} + +static StringParseResult ParseToken(const char *expr, int start, int end) +{ + int endlit = start; + + bool inside_index = false; + while (endlit < end && ValidTokenCharacter(expr[endlit], &inside_index)) + { + endlit++; + } + + if (endlit > start) + { + StringExpression *ret = xcalloc(1, sizeof(StringExpression)); + + ret->op = LITERAL; + ret->val.literal.literal = xstrndup(expr + start, endlit - start); + + return (StringParseResult) {ret, endlit}; + } + else + { + return (StringParseResult) {NULL, endlit}; + } +} + +/* */ + +static StringParseResult ParseTerm(const char *expr, int start, int end) +{ + StringParseResult res = ParseToken(expr, start, end); + + if (res.result) + { + return res; + } + else + { + return ParseVarRef(expr, start, end); + } +} + +/* */ + +StringParseResult ParseStringExpression(const char *expr, int start, int end) +{ + StringParseResult lhs = ParseTerm(expr, start, end); + + if (lhs.result) + { + StringParseResult rhs = ParseStringExpression(expr, lhs.position, end); + + if (rhs.result) + { + StringExpression *ret = xcalloc(1, sizeof(StringExpression)); + + ret->op = CONCAT; + ret->val.concat.lhs = lhs.result; + ret->val.concat.rhs = rhs.result; + + return (StringParseResult) {ret, rhs.position}; + } + else + { + return lhs; + } + } + else + { + return lhs; + } +} + +/* Evaluation */ + +static char *EvalConcat(const StringExpression *expr, VarRefEvaluator evalfn, void *param) +{ + char *lhs, *rhs, *res; + + lhs = EvalStringExpression(expr->val.concat.lhs, evalfn, param); + if (!lhs) + { + return NULL; + } + + rhs = EvalStringExpression(expr->val.concat.rhs, evalfn, param); + if (!rhs) + { + free(lhs); + return NULL; + } + + xasprintf(&res, "%s%s", lhs, rhs); + free(lhs); + free(rhs); + return res; +} + +static char *EvalVarRef(const StringExpression *expr, VarRefEvaluator evalfn, void *param) +{ + char *name, *eval; + + name = EvalStringExpression(expr->val.varref.name, evalfn, param); + if (!name) + { + return NULL; + } + + eval = (*evalfn) (name, expr->val.varref.type, param); + free(name); + return eval; +} + +char *EvalStringExpression(const StringExpression *expr, VarRefEvaluator evalfn, void *param) +{ + switch (expr->op) + { + case CONCAT: + return EvalConcat(expr, evalfn, param); + case LITERAL: + return xstrdup(expr->val.literal.literal); + case VARREF: + return EvalVarRef(expr, evalfn, param); + default: + ProgrammingError("Unknown type of string expression" "encountered during evaluation: %d", expr->op); + } +} + +/* Freeing results */ + +void FreeStringExpression(StringExpression *expr) +{ + if (!expr) + { + return; + } + + switch (expr->op) + { + case CONCAT: + FreeStringExpression(expr->val.concat.lhs); + FreeStringExpression(expr->val.concat.rhs); + break; + case LITERAL: + free(expr->val.literal.literal); + break; + case VARREF: + FreeStringExpression(expr->val.varref.name); + break; + default: + ProgrammingError("Unknown type of string expression encountered: %d", expr->op); + } + + free(expr); +} diff --git a/libpromises/string_expressions.h b/libpromises/string_expressions.h new file mode 100644 index 0000000000..6fcf0b9702 --- /dev/null +++ b/libpromises/string_expressions.h @@ -0,0 +1,124 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_STRING_EXPRESSIONS_H +#define CFENGINE_STRING_EXPRESSIONS_H + +/* + String expressions grammar: + + ::= + + + ::= + + + ::= [a-zA-Z0-9_:]+ + + ::= $( ) + ${ } + + ::= + . + + Subsequent s are concatenated during evaluation. +*/ + +typedef enum +{ + CONCAT, + LITERAL, + VARREF +} StringOp; + +typedef enum +{ + VAR_REF_TYPE_SCALAR, + VAR_REF_TYPE_LIST +} VarRefType; + +typedef struct StringExpression_ StringExpression; + +struct StringExpression_ +{ + StringOp op; + union StringExpressionValue + { + struct + { + StringExpression *lhs; + StringExpression *rhs; + } concat; + + struct + { + char *literal; + } literal; + + struct + { + StringExpression *name; + VarRefType type; + } varref; + } val; +}; + +/* Parsing and evaluation */ + +/* + * Result of parsing. + * + * if succeeded, then result is the result of parsing and position is last + * character consumed. + * + * if not succeeded, then result is NULL and position is last character consumed + * before the error. + */ +typedef struct +{ + StringExpression *result; + int position; +} StringParseResult; + +StringParseResult ParseStringExpression(const char *expr, int start, int end); + +/* + * Evaluator should return either heap-allocated string or NULL. In later case + * evaluation will be aborted and NULL will be returned from + * EvalStringExpression. + */ +typedef char *(*VarRefEvaluator) (const char *varname, VarRefType type, void *param); + +/* + * Result is heap-allocated. In case evalfn() returns NULL whole + * EvalStringExpression returns NULL as well. + */ +char *EvalStringExpression(const StringExpression *expr, VarRefEvaluator evalfn, void *param); + +/* + * Frees StringExpression produced by ParseStringExpression. NULL-safe. + */ +void FreeStringExpression(StringExpression *expr); + +#endif diff --git a/libpromises/syntax.c b/libpromises/syntax.c new file mode 100644 index 0000000000..ea73205e63 --- /dev/null +++ b/libpromises/syntax.c @@ -0,0 +1,1332 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* IsStrIn */ +#include /* StringMatchFull */ +#include +#include +#include +#include + + +static SyntaxTypeMatch CheckParseString(const char *lv, const char *s, const char *range); +static SyntaxTypeMatch CheckParseInt(const char *lv, const char *s, const char *range); +static SyntaxTypeMatch CheckParseReal(const char *lv, const char *s, const char *range); +static SyntaxTypeMatch CheckParseRealRange(const char *lval, const char *s, const char *range); +static SyntaxTypeMatch CheckParseIntRange(const char *lval, const char *s, const char *range); +static SyntaxTypeMatch CheckParseOpts(const char *s, const char *range); +static SyntaxTypeMatch CheckFnCallType(const char *s, DataType dtype); + +/*********************************************************/ + +static const PromiseTypeSyntax *PromiseTypeSyntaxGetStrict(const char *bundle_type, const char *promise_type) +{ + assert(bundle_type != NULL); + assert(promise_type != NULL); + + for (int module_index = 0; module_index < CF3_MODULES; module_index++) + { + for (int promise_type_index = 0; CF_ALL_PROMISE_TYPES[module_index][promise_type_index].promise_type; promise_type_index++) + { + const PromiseTypeSyntax *promise_type_syntax = &CF_ALL_PROMISE_TYPES[module_index][promise_type_index]; + + if (strcmp(bundle_type, promise_type_syntax->bundle_type) == 0 + && strcmp(promise_type, promise_type_syntax->promise_type) == 0) + { + return promise_type_syntax; + } + } + } + return NULL; +} + +bool IsBuiltInPromiseType(const char *const promise_type) +{ + // Any built in promise type, regardless of bundle (agent) type + assert(promise_type != NULL); + + for (int module_index = 0; module_index < CF3_MODULES; module_index++) + { + const PromiseTypeSyntax *const module = CF_ALL_PROMISE_TYPES[module_index]; + for (int promise_type_index = 0; module[promise_type_index].promise_type; promise_type_index++) + { + const PromiseTypeSyntax *promise_type_syntax = &(module[promise_type_index]); + if (StringEqual(promise_type, promise_type_syntax->promise_type)) + { + return true; + } + } + } + return false; +} + +const PromiseTypeSyntax *PromiseTypeSyntaxGet(const char *bundle_type, const char *promise_type) +{ + const PromiseTypeSyntax *pts = PromiseTypeSyntaxGetStrict(bundle_type, promise_type); + if (!pts) + { + pts = PromiseTypeSyntaxGetStrict("*", promise_type); + } + return pts; +} + +static const ConstraintSyntax *GetCommonConstraint(const char *lval) +{ + for (int i = 0; CF_COMMON_PROMISE_TYPES[i].promise_type; i++) + { + const ConstraintSyntax *constraints = CF_COMMON_PROMISE_TYPES[i].constraints; + for (int j = 0; constraints[j].lval != NULL; j++) + { + if (StringEqual(constraints[j].lval, lval)) + { + return &(constraints[j]); + } + } + } + + return NULL; +} + +const ConstraintSyntax *BodySyntaxGetConstraintSyntax(const ConstraintSyntax *body_syntax, const char *lval) +{ + if (body_syntax == NULL) + { + return NULL; + } + for (int j = 0; body_syntax[j].lval; j++) + { + if (strcmp(body_syntax[j].lval, lval) == 0) + { + return &body_syntax[j]; + } + } + return NULL; +} + +const ConstraintSyntax *PromiseTypeSyntaxGetConstraintSyntax(const PromiseTypeSyntax *promise_type_syntax, const char *lval) +{ + assert(promise_type_syntax != NULL); + assert(lval != NULL); + + for (int i = 0; promise_type_syntax->constraints[i].lval; i++) + { + if (strcmp(promise_type_syntax->constraints[i].lval, lval) == 0) + { + return &promise_type_syntax->constraints[i]; + } + } + + const ConstraintSyntax *constraint_syntax = NULL; + if (strcmp("edit_line", promise_type_syntax->bundle_type) == 0) + { + constraint_syntax = BodySyntaxGetConstraintSyntax(CF_COMMON_EDITBODIES, lval); + if (constraint_syntax) + { + return constraint_syntax; + } + } + else if (strcmp("edit_xml", promise_type_syntax->bundle_type) == 0) + { + constraint_syntax = BodySyntaxGetConstraintSyntax(CF_COMMON_XMLBODIES, lval); + if (constraint_syntax) + { + return constraint_syntax; + } + } + + return GetCommonConstraint(lval); +} + +const BodySyntax *BodySyntaxGet(ARG_UNUSED ParserBlock block, const char *body_type) +{ + if (block == PARSER_BLOCK_PROMISE) + { + // Required: promise agent + if (!StringEqual(body_type, "agent")) + { + return NULL; + } + return &CUSTOM_PROMISE_BLOCK_SYNTAX; + } + + assert(block == PARSER_BLOCK_BODY); + for (int i = 0; i < CF3_MODULES; i++) + { + const PromiseTypeSyntax *promise_type_syntax = CF_ALL_PROMISE_TYPES[i]; + + for (int k = 0; promise_type_syntax[k].bundle_type != NULL; k++) + { + for (int z = 0; promise_type_syntax[k].constraints[z].lval != NULL; z++) + { + const ConstraintSyntax constraint_syntax = promise_type_syntax[k].constraints[z]; + + if (constraint_syntax.dtype == CF_DATA_TYPE_BODY && strcmp(body_type, constraint_syntax.lval) == 0) + { + return constraint_syntax.range.body_type_syntax; + } + } + } + } + + for (int i = 0; CONTROL_BODIES[i].body_type != NULL; i++) + { + const BodySyntax body_syntax = CONTROL_BODIES[i]; + + if (strcmp(body_type, body_syntax.body_type) == 0) + { + return &CONTROL_BODIES[i]; + } + } + + // Not a built in body type, assume it's a custom body type + return &CUSTOM_BODY_BLOCK_SYNTAX; +} + +const char *SyntaxStatusToString(SyntaxStatus status) +{ + assert( status == SYNTAX_STATUS_DEPRECATED || + status == SYNTAX_STATUS_NORMAL || + status == SYNTAX_STATUS_REMOVED ); + switch (status) + { + case SYNTAX_STATUS_DEPRECATED: + return "deprecated"; + case SYNTAX_STATUS_NORMAL: + return "normal"; + case SYNTAX_STATUS_REMOVED: + return "removed"; + case SYNTAX_STATUS_CUSTOM: + return "custom"; + default: + break; + } + return NULL; +} + +/****************************************************************************/ + +DataType ExpectedDataType(const char *lvalname) +{ + int i, j, k, l; + const ConstraintSyntax *bs, *bs2; + const PromiseTypeSyntax *ss; + + for (i = 0; i < CF3_MODULES; i++) + { + if ((ss = CF_ALL_PROMISE_TYPES[i]) == NULL) + { + continue; + } + + for (j = 0; ss[j].promise_type != NULL; j++) + { + if ((bs = ss[j].constraints) == NULL) + { + continue; + } + + for (k = 0; bs[k].lval != NULL; k++) + { + if (strcmp(lvalname, bs[k].lval) == 0) + { + return bs[k].dtype; + } + } + + for (k = 0; bs[k].lval != NULL; k++) + { + if (bs[k].dtype == CF_DATA_TYPE_BODY) + { + bs2 = bs[k].range.body_type_syntax->constraints; + + if (bs2 == NULL || bs2 == (void *) CF_BUNDLE) + { + continue; + } + + for (l = 0; bs2[l].dtype != CF_DATA_TYPE_NONE; l++) + { + if (strcmp(lvalname, bs2[l].lval) == 0) + { + return bs2[l].dtype; + } + } + } + } + + } + } + + return CF_DATA_TYPE_NONE; +} + +/****************************************************************************/ +/* Level 1 */ +/****************************************************************************/ + +const char *SyntaxTypeMatchToString(SyntaxTypeMatch result) +{ + assert(result < SYNTAX_TYPE_MATCH_MAX); + + static const char *const msgs[SYNTAX_TYPE_MATCH_MAX] = + { + [SYNTAX_TYPE_MATCH_OK] = "OK", + + [SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED] = "Cannot check unexpanded value", + [SYNTAX_TYPE_MATCH_ERROR_RANGE_BRACKETED] = "Real range specification should not be enclosed in brackets - just 'a,b'", + [SYNTAX_TYPE_MATCH_ERROR_RANGE_MULTIPLE_ITEMS] = "Range format specifier should be of form 'a,b'' but got multiple items", + [SYNTAX_TYPE_MATCH_ERROR_GOT_SCALAR] = "Attempted to give a scalar to a non-scalar type", + [SYNTAX_TYPE_MATCH_ERROR_GOT_LIST] = "Attempted to give a list to a non-list type", + [SYNTAX_TYPE_MATCH_ERROR_GOT_NULL] = "Attempted to give a value of type null", + + [SYNTAX_TYPE_MATCH_ERROR_STRING_UNIX_PERMISSION] = "Error parsing Unix permission string", + + [SYNTAX_TYPE_MATCH_ERROR_SCALAR_OUT_OF_RANGE] = "Scalar value is out of range", + [SYNTAX_TYPE_MATCH_ERROR_EMPTY_SCALAR_OUT_OF_RANGE] = "Empty scalar value is out of range", + + [SYNTAX_TYPE_MATCH_ERROR_INT_PARSE] = "Cannot parse value as integer", + [SYNTAX_TYPE_MATCH_ERROR_INT_OUT_OF_RANGE] = "Integer is out of range", + + [SYNTAX_TYPE_MATCH_ERROR_REAL_INF] = "Keyword 'inf' has an integer value, cannot be used as real", + [SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE] = "Real value is out of range", + + [SYNTAX_TYPE_MATCH_ERROR_OPTS_OUT_OF_RANGE] = "Selection is out of bounds", + + [SYNTAX_TYPE_MATCH_ERROR_FNCALL_RETURN_TYPE] = "Function does not return the required type", + [SYNTAX_TYPE_MATCH_ERROR_FNCALL_UNKNOWN] = "Unknown function", + + [SYNTAX_TYPE_MATCH_ERROR_CONTEXT_OUT_OF_RANGE] = "Context string is invalid/out of range", + [SYNTAX_TYPE_MATCH_ERROR_ABSOLUTE_PATH] = "Filename is not an absolute path", + }; + + return msgs[result]; +} + +SyntaxTypeMatch CheckConstraintTypeMatch(const char *lval, Rval rval, DataType dt, const char *range, int level) +{ + Rlist *rp; + Item *checklist; + +/* Get type of lval */ + + switch (rval.type) + { + case RVAL_TYPE_SCALAR: + switch (dt) + { + case CF_DATA_TYPE_STRING_LIST: + case CF_DATA_TYPE_INT_LIST: + case CF_DATA_TYPE_REAL_LIST: + case CF_DATA_TYPE_CONTEXT_LIST: + case CF_DATA_TYPE_OPTION_LIST: + if (level == 0) + { + return SYNTAX_TYPE_MATCH_ERROR_GOT_SCALAR; + } + break; + default: + /* Only lists are incompatible with scalars */ + break; + } + break; + + case RVAL_TYPE_LIST: + + switch (dt) + { + case CF_DATA_TYPE_STRING_LIST: + case CF_DATA_TYPE_INT_LIST: + case CF_DATA_TYPE_REAL_LIST: + case CF_DATA_TYPE_CONTEXT_LIST: + case CF_DATA_TYPE_OPTION_LIST: + break; + default: + return SYNTAX_TYPE_MATCH_ERROR_GOT_LIST; + } + + for (rp = (Rlist *) rval.item; rp != NULL; rp = rp->next) + { + SyntaxTypeMatch err = CheckConstraintTypeMatch(lval, rp->val, dt, range, 1); + switch (err) + { + case SYNTAX_TYPE_MATCH_OK: + case SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED: + break; + + default: + return err; + } + } + + return SYNTAX_TYPE_MATCH_OK; + + case RVAL_TYPE_FNCALL: + + /* Fn-like objects are assumed to be parameterized bundles in these... */ + + checklist = SplitString("bundlesequence,edit_line,edit_xml,usebundle,service_bundle,home_bundle", ','); + + if (!IsItemIn(checklist, lval)) + { + SyntaxTypeMatch err = CheckFnCallType(RvalFnCallValue(rval)->name, dt); + DeleteItemList(checklist); + return err; + } + + DeleteItemList(checklist); + return SYNTAX_TYPE_MATCH_OK; + + case RVAL_TYPE_CONTAINER: + break; + + case RVAL_TYPE_NOPROMISEE: + return SYNTAX_TYPE_MATCH_ERROR_GOT_NULL; + } + +/* If we get here, we have a literal scalar type */ + + switch (dt) + { + case CF_DATA_TYPE_STRING: + case CF_DATA_TYPE_STRING_LIST: + return CheckParseString(lval, (const char *) rval.item, range); + + case CF_DATA_TYPE_INT: + case CF_DATA_TYPE_INT_LIST: + return CheckParseInt(lval, (const char *) rval.item, range); + + case CF_DATA_TYPE_REAL: + case CF_DATA_TYPE_REAL_LIST: + return CheckParseReal(lval, (const char *) rval.item, range); + + case CF_DATA_TYPE_BODY: + case CF_DATA_TYPE_BUNDLE: + case CF_DATA_TYPE_CONTAINER: + break; + + case CF_DATA_TYPE_OPTION: + case CF_DATA_TYPE_OPTION_LIST: + return CheckParseOpts(RvalScalarValue(rval), range); + + case CF_DATA_TYPE_CONTEXT: + case CF_DATA_TYPE_CONTEXT_LIST: + return CheckParseContext((const char *) rval.item, range); + + case CF_DATA_TYPE_INT_RANGE: + return CheckParseIntRange(lval, (const char *) rval.item, range); + + case CF_DATA_TYPE_REAL_RANGE: + return CheckParseRealRange(lval, (char *) rval.item, range); + + default: + ProgrammingError("Unknown (unhandled) datatype for lval = %s (CheckConstraintTypeMatch)", lval); + break; + } + + return SYNTAX_TYPE_MATCH_OK; +} + +/****************************************************************************/ + +DataType StringDataType(EvalContext *ctx, const char *string) +{ + int islist = false; /* TODO something is wrong here */ + +/*------------------------------------------------------- +What happens if we embed vars in a literal string + "$(list)withending" - a list? + "$(list1)$(list2)" - not a simple list +Disallow these manual concatenations as ambiguous. +Demand this syntax to work around + +vars: + + "listvar" slist => EmbellishList("prefix$(list)suffix"); +---------------------------------------------------------*/ + + size_t len = strlen(string); + + if (*string == '$') + { + Buffer *inner_value = BufferNew(); + if (ExtractScalarReference(inner_value, string, len, true)) + { + DataType dtype; + if (!IsExpandable(BufferData(inner_value))) + { + VarRef *ref = VarRefParse(BufferData(inner_value)); + EvalContextVariableGet(ctx, ref, &dtype); + VarRefDestroy(ref); + + if (DataTypeToRvalType(dtype) == RVAL_TYPE_LIST) + { + if (!islist) + { + islist = true; + } + else + { + islist = false; + } + } + } + + if (BufferSize(inner_value) == strlen(string)) + { + BufferDestroy(inner_value); + return dtype; + } + else + { + BufferDestroy(inner_value); + return CF_DATA_TYPE_STRING; + } + } + + BufferDestroy(inner_value); + } + + return CF_DATA_TYPE_STRING; +} + +/****************************************************************************/ +/* Level 1 */ +/****************************************************************************/ + +static SyntaxTypeMatch CheckParseString(const char *lval, const char *s, const char *range) +{ + if (s == NULL) + { + return SYNTAX_TYPE_MATCH_OK; + } + + if (strlen(range) == 0) + { + return SYNTAX_TYPE_MATCH_OK; + } + + if (IsNakedVar(s, '@') || IsNakedVar(s, '$')) + { + return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; + } + +/* Deal with complex strings as special cases */ + + if (strcmp(lval, "mode") == 0 || strcmp(lval, "search_mode") == 0) + { + mode_t plus, minus; + + if (!ParseModeString(s, &plus, &minus)) + { + return SYNTAX_TYPE_MATCH_ERROR_STRING_UNIX_PERMISSION; + } + } + + /* FIXME: review this strcmp. Moved out from StringMatch */ + if (!strcmp(range, s) || StringMatchFull(range, s)) + { + return SYNTAX_TYPE_MATCH_OK; + } + + if (IsCf3VarString(s)) + { + return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; + } + else if ('\0' == s[0]) + { + return SYNTAX_TYPE_MATCH_ERROR_EMPTY_SCALAR_OUT_OF_RANGE; + } + else if (!strcmp(range, CF_ABSPATHRANGE)) + { + return SYNTAX_TYPE_MATCH_ERROR_ABSOLUTE_PATH; + } + else + { + return SYNTAX_TYPE_MATCH_ERROR_SCALAR_OUT_OF_RANGE; + } + + return SYNTAX_TYPE_MATCH_OK; +} + +/****************************************************************************/ + +SyntaxTypeMatch CheckParseContext(const char *context, const char *range) +{ + if (strlen(range) == 0) + { + return SYNTAX_TYPE_MATCH_OK; + } + + /* FIXME: review this strcmp. Moved out from StringMatch */ + if (!strcmp(range, context) || StringMatchFull(range, context)) + { + return SYNTAX_TYPE_MATCH_OK; + } + + return SYNTAX_TYPE_MATCH_ERROR_CONTEXT_OUT_OF_RANGE; +} + +/****************************************************************************/ + +static SyntaxTypeMatch CheckParseInt(const char *lval, const char *s, const char *range) +{ + Item *split; + int n; + long long max = CF_LOWINIT, min = CF_HIGHINIT; + + // Numeric types are registered by range separated by comma str "min,max" + split = SplitString(range, ','); + + if ((n = ListLen(split)) != 2) + { + ProgrammingError("INTERN: format specifier for int rvalues is not ok for lval %s - got %d items", lval, n); + } + + sscanf(split->name, "%lld", &min); + + if (strcmp(split->next->name, "inf") == 0) + { + max = CF_INFINITY; + } + else + { + sscanf(split->next->name, "%lld", &max); + } + + DeleteItemList(split); + + if (min == CF_HIGHINIT || max == CF_LOWINIT) + { + ProgrammingError("INTERN: could not parse format specifier for int rvalues for lval %s", lval); + } + + if (IsCf3VarString(s)) + { + return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; + } + + long val = IntFromString(s); + + if (val == CF_NOINT) + { + return SYNTAX_TYPE_MATCH_ERROR_INT_PARSE; + } + + if (val > max || val < min) + { + return SYNTAX_TYPE_MATCH_ERROR_INT_OUT_OF_RANGE; + } + + return SYNTAX_TYPE_MATCH_OK; +} + +/****************************************************************************/ + +static SyntaxTypeMatch CheckParseIntRange(const char *lval, const char *s, const char *range) +{ + Item *split, *ip, *rangep; + int n; + long long max = CF_LOWINIT, min = CF_HIGHINIT; + + // Numeric types are registered by range separated by comma str "min,max" + if (*s == '[' || *s == '(') + { + return SYNTAX_TYPE_MATCH_ERROR_RANGE_BRACKETED; + } + + split = SplitString(range, ','); + + if ((n = ListLen(split)) != 2) + { + ProgrammingError("Format specifier %s for irange rvalues is not ok for lval %s - got %d items", range, lval, n); + } + + sscanf(split->name, "%lld", &min); + + if (strcmp(split->next->name, "inf") == 0) + { + max = CF_INFINITY; + } + else + { + sscanf(split->next->name, "%lld", &max); + } + + DeleteItemList(split); + + if (min == CF_HIGHINIT || max == CF_LOWINIT) + { + ProgrammingError("Could not parse irange format specifier for int rvalues for lval %s", lval); + } + + if (IsCf3VarString(s)) + { + return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; + } + + rangep = SplitString(s, ','); + + if ((n = ListLen(rangep)) != 2) + { + return SYNTAX_TYPE_MATCH_ERROR_RANGE_MULTIPLE_ITEMS; + } + + for (ip = rangep; ip != NULL; ip = ip->next) + { + long val = IntFromString(ip->name); + + if (val > max || val < min) + { + return SYNTAX_TYPE_MATCH_ERROR_INT_OUT_OF_RANGE; + } + } + + DeleteItemList(rangep); + + return SYNTAX_TYPE_MATCH_OK; +} + +/****************************************************************************/ + +static SyntaxTypeMatch CheckParseReal(const char *lval, const char *s, const char *range) +{ + Item *split; + double max = (double) CF_LOWINIT, min = (double) CF_HIGHINIT, val; + int n; + + if (strcmp(s, "inf") == 0) + { + return SYNTAX_TYPE_MATCH_ERROR_REAL_INF; + } + + if (IsCf3VarString(s)) + { + return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; + } + +/* Numeric types are registered by range separated by comma str "min,max" */ + + split = SplitString(range, ','); + + if ((n = ListLen(split)) != 2) + { + ProgrammingError("Format specifier for real rvalues is not ok for lval %s - %d items", lval, n); + } + + sscanf(split->name, "%lf", &min); + sscanf(split->next->name, "%lf", &max); + DeleteItemList(split); + + if (min == CF_HIGHINIT || max == CF_LOWINIT) + { + ProgrammingError("Could not parse format specifier for int rvalues for lval %s", lval); + } + + if (!DoubleFromString(s, &val)) + { + return SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE; + } + + if (val > max || val < min) + { + return SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE; + } + + return SYNTAX_TYPE_MATCH_OK; +} + +/****************************************************************************/ + +static SyntaxTypeMatch CheckParseRealRange(const char *lval, const char *s, const char *range) +{ + Item *split, *rangep, *ip; + double max = (double) CF_LOWINIT, min = (double) CF_HIGHINIT, val; + int n; + + if (*s == '[' || *s == '(') + { + return SYNTAX_TYPE_MATCH_ERROR_RANGE_BRACKETED; + } + + if (strcmp(s, "inf") == 0) + { + return SYNTAX_TYPE_MATCH_ERROR_REAL_INF; + } + + if (IsCf3VarString(s)) + { + return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; + } + +/* Numeric types are registered by range separated by comma str "min,max" */ + + split = SplitString(range, ','); + + if ((n = ListLen(split)) != 2) + { + ProgrammingError("Format specifier for real rvalues is not ok for lval %s - %d items", lval, n); + } + + sscanf(split->name, "%lf", &min); + sscanf(split->next->name, "%lf", &max); + DeleteItemList(split); + + if (min == CF_HIGHINIT || max == CF_LOWINIT) + { + ProgrammingError("Could not parse format specifier for int rvalues for lval %s", lval); + } + + rangep = SplitString(s, ','); + + if ((n = ListLen(rangep)) != 2) + { + return SYNTAX_TYPE_MATCH_ERROR_RANGE_MULTIPLE_ITEMS; + } + + for (ip = rangep; ip != NULL; ip = ip->next) + { + if (!DoubleFromString(ip->name, &val)) + { + return SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE; + } + + if (val > max || val < min) + { + return SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE; + } + } + + DeleteItemList(rangep); + + return SYNTAX_TYPE_MATCH_OK; +} + +/****************************************************************************/ + +static SyntaxTypeMatch CheckParseOpts(const char *s, const char *range) +{ + Item *split; + +/* List/menu types are separated by comma str "a,b,c,..." */ + + if (IsNakedVar(s, '@') || IsNakedVar(s, '$')) + { + return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; + } + + split = SplitString(range, ','); + + if (!IsItemIn(split, s)) + { + DeleteItemList(split); + return SYNTAX_TYPE_MATCH_ERROR_OPTS_OUT_OF_RANGE; + } + + DeleteItemList(split); + + return SYNTAX_TYPE_MATCH_OK; +} + +/****************************************************************************/ + +bool CheckParseVariableName(const char *const name) +{ + assert(name != NULL); + + const char *const reserved[] = { + "promiser", + "handle", + "promise_filename", + "promise_dirname", + "promise_linenumber", + "this", + NULL + }; + + if (IsStrIn(name, reserved)) + { + return false; + } + + int count = 0, level = 0; + + const char *const first_dot = strchr(name, '.'); + + if (first_dot != NULL) + { + for (const char *sp = name; *sp != '\0'; sp++) + { + switch (*sp) + { + case '.': + count++; + if (count > 1 && level != 1) + { + // Adding a second dot is not allowed, + // except inside 1 level of square brackets + return false; + } + break; + + case '[': + level++; + break; + + case ']': + level--; + break; + + default: + break; + } + + if (level > 1) + { + yyerror("Too many levels of [] reserved for array use"); + return false; + } + } + + if (count == 1) + { + // Check that there is something before and after first dot: + if (name[0] == '.' || first_dot[1] == '\0') + { + return false; + } + } + } + + return true; +} + +/****************************************************************************/ + +static SyntaxTypeMatch CheckFnCallType(const char *s, DataType desired_type) +{ + const FnCallType *fn = FnCallTypeGet(s); + if (fn != NULL) + { + DataType fn_ret_type = fn->dtype; + if (desired_type != fn_ret_type) + { + /* All scalar values can be used where string is expected (they are, + * in fact, strings internally, see RvalType). */ + if ((desired_type == CF_DATA_TYPE_STRING) && + ((fn_ret_type == CF_DATA_TYPE_INT) || + (fn_ret_type == CF_DATA_TYPE_REAL) || + (fn_ret_type == CF_DATA_TYPE_OPTION) || + (fn_ret_type == CF_DATA_TYPE_CONTEXT))) + { + return SYNTAX_TYPE_MATCH_OK; + } + + + /* Ok to allow fn calls of correct element-type in lists */ + + if (fn_ret_type == CF_DATA_TYPE_STRING && desired_type == CF_DATA_TYPE_STRING_LIST) + { + return SYNTAX_TYPE_MATCH_OK; + } + + if (fn_ret_type == CF_DATA_TYPE_STRING && desired_type == CF_DATA_TYPE_CONTEXT) + { + return SYNTAX_TYPE_MATCH_OK; + } + + if (fn_ret_type == CF_DATA_TYPE_INT && desired_type == CF_DATA_TYPE_INT_LIST) + { + return SYNTAX_TYPE_MATCH_OK; + } + + if (fn_ret_type == CF_DATA_TYPE_REAL && desired_type == CF_DATA_TYPE_REAL_LIST) + { + return SYNTAX_TYPE_MATCH_OK; + } + + if (fn_ret_type == CF_DATA_TYPE_OPTION && desired_type == CF_DATA_TYPE_OPTION_LIST) + { + return SYNTAX_TYPE_MATCH_OK; + } + + if (fn_ret_type == CF_DATA_TYPE_CONTEXT && desired_type == CF_DATA_TYPE_CONTEXT_LIST) + { + return SYNTAX_TYPE_MATCH_OK; + } + + return SYNTAX_TYPE_MATCH_ERROR_FNCALL_RETURN_TYPE; + } + else + { + return SYNTAX_TYPE_MATCH_OK; + } + } + else + { + return SYNTAX_TYPE_MATCH_ERROR_FNCALL_UNKNOWN; + } +} + + +/****************************************************************************/ + +static JsonElement *ConstraintSyntaxToJson(const ConstraintSyntax *constraint_syntax) +{ + JsonElement *json_constraint = JsonObjectCreate(5); + + JsonObjectAppendString(json_constraint, "attribute", constraint_syntax->lval); + JsonObjectAppendString(json_constraint, "status", SyntaxStatusToString(constraint_syntax->status)); + JsonObjectAppendString(json_constraint, "type", DataTypeToString(constraint_syntax->dtype)); + + if (constraint_syntax->dtype != CF_DATA_TYPE_BODY && constraint_syntax->dtype != CF_DATA_TYPE_BUNDLE) + { + JsonObjectAppendString(json_constraint, "range", constraint_syntax->range.validation_string); + } + + return json_constraint; +} + +static JsonElement *BodySyntaxToJson(const BodySyntax *body_syntax) +{ + JsonElement *json_body = JsonObjectCreate(2); + + JsonObjectAppendString(json_body, "status", SyntaxStatusToString(body_syntax->status)); + { + JsonElement *attributes = JsonObjectCreate(50); + + for (int i = 0; body_syntax->constraints[i].lval; i++) + { + const ConstraintSyntax *constraint_syntax = &body_syntax->constraints[i]; + if (constraint_syntax->status != SYNTAX_STATUS_REMOVED) + { + JsonElement *json_constraint = ConstraintSyntaxToJson(constraint_syntax); + JsonObjectAppendString(json_constraint, "visibility", "body"); + JsonObjectAppendObject(attributes, constraint_syntax->lval, json_constraint); + } + } + + JsonObjectAppendObject(json_body, "attributes", attributes); + } + + return json_body; +} + +static JsonElement *JsonBundleTypeNew(void) +{ + JsonElement *json_bundle_type = JsonObjectCreate(2); + + JsonObjectAppendString(json_bundle_type, "status", SyntaxStatusToString(SYNTAX_STATUS_NORMAL)); + JsonObjectAppendArray(json_bundle_type, "promiseTypes", JsonArrayCreate(50)); + + return json_bundle_type; +} + +static JsonElement *BundleTypesToJson(void) +{ + JsonElement *bundle_types = JsonObjectCreate(50); + + Seq *common_promise_types = SeqNew(50, free); + + for (int module_index = 0; module_index < CF3_MODULES; module_index++) + { + for (int promise_type_index = 0; CF_ALL_PROMISE_TYPES[module_index][promise_type_index].promise_type; promise_type_index++) + { + const PromiseTypeSyntax *promise_type_syntax = &CF_ALL_PROMISE_TYPES[module_index][promise_type_index]; + + // skip global constraints + if (strcmp("*", promise_type_syntax->promise_type) == 0) + { + continue; + } + + // collect common promise types to be appended at the end + if (strcmp("*", promise_type_syntax->bundle_type) == 0) + { + SeqAppend(common_promise_types, xstrdup(promise_type_syntax->promise_type)); + continue; + } + + if (promise_type_syntax->status == SYNTAX_STATUS_REMOVED) + { + continue; + } + + JsonElement *bundle_type = JsonObjectGet(bundle_types, promise_type_syntax->bundle_type); + if (!bundle_type) + { + bundle_type = JsonBundleTypeNew(); + JsonObjectAppendObject(bundle_types, promise_type_syntax->bundle_type, bundle_type); + } + assert(bundle_type); + + JsonElement *promise_types = JsonObjectGet(bundle_type, "promiseTypes"); + assert(promise_types); + + JsonArrayAppendString(promise_types, promise_type_syntax->promise_type); + } + } + + // Append the common bundle, which has only common promise types, but is not declared in syntax + { + JsonElement *bundle_type = JsonBundleTypeNew(); + JsonObjectAppendObject(bundle_types, "common", bundle_type); + } + + JsonIterator it = JsonIteratorInit(bundle_types); + const char *bundle_type = NULL; + while ((bundle_type = JsonIteratorNextKey(&it))) + { + JsonElement *promise_types = JsonObjectGetAsArray(JsonObjectGetAsObject(bundle_types, bundle_type), "promiseTypes"); + for (size_t i = 0; i < SeqLength(common_promise_types); i++) + { + const char *common_promise_type = SeqAt(common_promise_types, i); + JsonArrayAppendString(promise_types, common_promise_type); + } + } + + SeqDestroy(common_promise_types); + return bundle_types; +} + +static JsonElement *JsonPromiseTypeNew(SyntaxStatus status) +{ + JsonElement *promise_type = JsonObjectCreate(2); + + JsonObjectAppendString(promise_type, "status", SyntaxStatusToString(status)); + JsonObjectAppendObject(promise_type, "attributes", JsonObjectCreate(50)); + + return promise_type; +} + +static JsonElement *PromiseTypesToJson(void) +{ + JsonElement *promise_types = JsonObjectCreate(50); + + const PromiseTypeSyntax *global_promise_type = PromiseTypeSyntaxGet("*", "*"); + + for (int module_index = 0; module_index < CF3_MODULES; module_index++) + { + for (int promise_type_index = 0; CF_ALL_PROMISE_TYPES[module_index][promise_type_index].promise_type; promise_type_index++) + { + const PromiseTypeSyntax *promise_type_syntax = &CF_ALL_PROMISE_TYPES[module_index][promise_type_index]; + + // skip global and bundle-local common constraints + if (strcmp("*", promise_type_syntax->promise_type) == 0) + { + continue; + } + + if (promise_type_syntax->status == SYNTAX_STATUS_REMOVED) + { + continue; + } + + JsonElement *promise_type = JsonObjectGet(promise_types, promise_type_syntax->promise_type); + if (!promise_type) + { + promise_type = JsonPromiseTypeNew(promise_type_syntax->status); + JsonObjectAppendObject(promise_types, promise_type_syntax->promise_type, promise_type); + } + assert(promise_type); + + JsonElement *attributes = JsonObjectGet(promise_type, "attributes"); + assert(attributes); + + for (int i = 0; promise_type_syntax->constraints[i].lval; i++) + { + const ConstraintSyntax *constraint_syntax = &promise_type_syntax->constraints[i]; + JsonElement *json_constraint = ConstraintSyntaxToJson(constraint_syntax); + JsonObjectAppendString(json_constraint, "visibility", "promiseType"); + JsonObjectAppendObject(attributes, constraint_syntax->lval, json_constraint); + } + + // append bundle common constraints + const PromiseTypeSyntax *bundle_promise_type = PromiseTypeSyntaxGet(promise_type_syntax->bundle_type, "*"); + if (strcmp("*", bundle_promise_type->bundle_type) != 0) + { + for (int i = 0; bundle_promise_type->constraints[i].lval; i++) + { + const ConstraintSyntax *constraint_syntax = &bundle_promise_type->constraints[i]; + JsonElement *json_constraint = ConstraintSyntaxToJson(constraint_syntax); + JsonObjectAppendString(json_constraint, "visibility", "bundle"); + JsonObjectAppendObject(attributes, constraint_syntax->lval, json_constraint); + } + } + + // append global common constraints + for (int i = 0; global_promise_type->constraints[i].lval; i++) + { + const ConstraintSyntax *constraint_syntax = &global_promise_type->constraints[i]; + JsonElement *json_constraint = ConstraintSyntaxToJson(constraint_syntax); + JsonObjectAppendString(json_constraint, "visibility", "global"); + JsonObjectAppendObject(attributes, constraint_syntax->lval, json_constraint); + } + } + } + + return promise_types; +} + +static JsonElement *BodyTypesToJson(void) +{ + JsonElement *body_types = JsonObjectCreate(50); + + for (int module_index = 0; module_index < CF3_MODULES; module_index++) + { + for (int promise_type_index = 0; CF_ALL_PROMISE_TYPES[module_index][promise_type_index].promise_type; promise_type_index++) + { + const PromiseTypeSyntax *promise_type_syntax = &CF_ALL_PROMISE_TYPES[module_index][promise_type_index]; + + for (int constraint_index = 0; promise_type_syntax->constraints[constraint_index].lval; constraint_index++) + { + const ConstraintSyntax *constraint_syntax = &promise_type_syntax->constraints[constraint_index]; + if (constraint_syntax->dtype != CF_DATA_TYPE_BODY) + { + continue; + } + + if (constraint_syntax->status == SYNTAX_STATUS_REMOVED) + { + continue; + } + + const BodySyntax *body_syntax = constraint_syntax->range.body_type_syntax; + JsonElement *body_type = JsonObjectGet(body_types, body_syntax->body_type); + if (!body_type) + { + JsonElement *body_type = BodySyntaxToJson(body_syntax); + JsonObjectAppendObject(body_types, body_syntax->body_type, body_type); + } + } + } + } + + for (int i = 0; CONTROL_BODIES[i].body_type; i++) + { + const BodySyntax *body_syntax = &CONTROL_BODIES[i]; + + if (body_syntax->status == SYNTAX_STATUS_REMOVED) + { + continue; + } + + JsonElement *body_type = JsonObjectGet(body_types, body_syntax->body_type); + if (!body_type) + { + JsonElement *body_type = BodySyntaxToJson(body_syntax); + JsonObjectAppendObject(body_types, body_syntax->body_type, body_type); + } + } + + return body_types; +} + +static const char *FnCallCategoryToString(FnCallCategory category) +{ + static const char *const category_str[] = + { + [FNCALL_CATEGORY_COMM] = "communication", + [FNCALL_CATEGORY_DATA] = "data", + [FNCALL_CATEGORY_FILES] = "files", + [FNCALL_CATEGORY_IO] = "io", + [FNCALL_CATEGORY_SYSTEM] = "system", + [FNCALL_CATEGORY_UTILS] = "utils", + [FNCALL_CATEGORY_INTERNAL] = "internal" + }; + + return category_str[category]; +} + +static JsonElement *FnCallTypeToJson(const FnCallType *fn_syntax) +{ + JsonElement *json_fn = JsonObjectCreate(10); + + JsonObjectAppendString(json_fn, "status", SyntaxStatusToString(fn_syntax->status)); + JsonObjectAppendString(json_fn, "returnType", DataTypeToString(fn_syntax->dtype)); + + { + JsonElement *params = JsonArrayCreate(10); + for (int i = 0; fn_syntax->args[i].pattern; i++) + { + const FnCallArg *param = &fn_syntax->args[i]; + + JsonElement *json_param = JsonObjectCreate(2); + JsonObjectAppendString(json_param, "type", DataTypeToString(param->dtype)); + JsonObjectAppendString(json_param, "range", param->pattern); + JsonObjectAppendString(json_param, "description", param->description); + JsonArrayAppendObject(params, json_param); + } + JsonObjectAppendArray(json_fn, "parameters", params); + } + + JsonObjectAppendBool(json_fn, "variadic", fn_syntax->options & FNCALL_OPTION_VARARG); + JsonObjectAppendBool(json_fn, "cached", fn_syntax->options & FNCALL_OPTION_CACHED); + JsonObjectAppendBool(json_fn, "collecting", fn_syntax->options & FNCALL_OPTION_COLLECTING); + JsonObjectAppendString(json_fn, "category", FnCallCategoryToString(fn_syntax->category)); + + return json_fn; +} + +static JsonElement *FunctionsToJson(void) +{ + JsonElement *functions = JsonObjectCreate(500); + + for (int i = 0; CF_FNCALL_TYPES[i].name; i++) + { + const FnCallType *fn_syntax = &CF_FNCALL_TYPES[i]; + + if (fn_syntax->status == SYNTAX_STATUS_REMOVED) + { + continue; + } + + JsonObjectAppendObject(functions, fn_syntax->name, FnCallTypeToJson(fn_syntax)); + } + + return functions; +} + +JsonElement *SyntaxToJson(void) +{ + JsonElement *syntax_tree = JsonObjectCreate(3); + + JsonObjectAppendObject(syntax_tree, "bundleTypes", BundleTypesToJson()); + JsonObjectAppendObject(syntax_tree, "promiseTypes", PromiseTypesToJson()); + JsonObjectAppendObject(syntax_tree, "bodyTypes", BodyTypesToJson()); + JsonObjectAppendObject(syntax_tree, "functions", FunctionsToJson()); + + return syntax_tree; +} diff --git a/libpromises/syntax.h b/libpromises/syntax.h new file mode 100644 index 0000000000..7e3a1d7b62 --- /dev/null +++ b/libpromises/syntax.h @@ -0,0 +1,130 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_SYNTAX_H +#define CFENGINE_SYNTAX_H + +#include + +#include +#include +#include + +#include + +/* + * WARNING: This file is in need of serious cleanup. + */ + + +typedef enum +{ + SYNTAX_TYPE_MATCH_OK, + + SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED, + SYNTAX_TYPE_MATCH_ERROR_RANGE_BRACKETED, + SYNTAX_TYPE_MATCH_ERROR_RANGE_MULTIPLE_ITEMS, + SYNTAX_TYPE_MATCH_ERROR_GOT_SCALAR, + SYNTAX_TYPE_MATCH_ERROR_GOT_LIST, + SYNTAX_TYPE_MATCH_ERROR_GOT_NULL, + + SYNTAX_TYPE_MATCH_ERROR_SCALAR_OUT_OF_RANGE, + SYNTAX_TYPE_MATCH_ERROR_EMPTY_SCALAR_OUT_OF_RANGE, + + SYNTAX_TYPE_MATCH_ERROR_STRING_UNIX_PERMISSION, + + SYNTAX_TYPE_MATCH_ERROR_INT_PARSE, + SYNTAX_TYPE_MATCH_ERROR_INT_OUT_OF_RANGE, + + SYNTAX_TYPE_MATCH_ERROR_REAL_INF, + SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE, + + SYNTAX_TYPE_MATCH_ERROR_OPTS_OUT_OF_RANGE, + + SYNTAX_TYPE_MATCH_ERROR_FNCALL_RETURN_TYPE, + SYNTAX_TYPE_MATCH_ERROR_FNCALL_UNKNOWN, + + SYNTAX_TYPE_MATCH_ERROR_CONTEXT_OUT_OF_RANGE, + + SYNTAX_TYPE_MATCH_ERROR_ABSOLUTE_PATH, + + SYNTAX_TYPE_MATCH_MAX +} SyntaxTypeMatch; + +const char *SyntaxTypeMatchToString(SyntaxTypeMatch result); + +bool CheckParseVariableName(const char *name); +SyntaxTypeMatch CheckConstraintTypeMatch(const char *lval, Rval rval, DataType dt, const char *range, int level); +SyntaxTypeMatch CheckParseContext(const char *context, const char *range); +DataType StringDataType(EvalContext *ctx, const char *string); +DataType ExpectedDataType(const char *lvalname); + +bool IsBuiltInPromiseType(const char *const promise_type); +const PromiseTypeSyntax *PromiseTypeSyntaxGet(const char *bundle_type, const char *promise_type); +const ConstraintSyntax *PromiseTypeSyntaxGetConstraintSyntax(const PromiseTypeSyntax *promise_type_syntax, const char *lval); + +const BodySyntax *BodySyntaxGet(ParserBlock block, const char *body_type); +const ConstraintSyntax *BodySyntaxGetConstraintSyntax(const ConstraintSyntax *body_syntax, const char *lval); + +const char *SyntaxStatusToString(SyntaxStatus status); + +JsonElement *SyntaxToJson(void); + +#define ConstraintSyntaxNewNull() { NULL, CF_DATA_TYPE_NONE, .range.validation_string = NULL, .status = SYNTAX_STATUS_NORMAL } +#define ConstraintSyntaxNewBool(lval, description, status) { lval, CF_DATA_TYPE_OPTION, .range.validation_string = CF_BOOL, description, status } + +#define ConstraintSyntaxNewOption(lval, options, description, status) { lval, CF_DATA_TYPE_OPTION, .range.validation_string = options, description, status } +#define ConstraintSyntaxNewOptionList(lval, item_range, description, status) { lval, CF_DATA_TYPE_OPTION_LIST, .range.validation_string = item_range, description, status } + +#define ConstraintSyntaxNewString(lval, regex, description, status) { lval, CF_DATA_TYPE_STRING, .range.validation_string = regex, description, status } +#define ConstraintSyntaxNewStringList(lval, item_range, description, status) { lval, CF_DATA_TYPE_STRING_LIST, .range.validation_string = item_range, description, status } + +#define ConstraintSyntaxNewInt(lval, int_range, description, status) { lval, CF_DATA_TYPE_INT, .range.validation_string = int_range, description, status } +#define ConstraintSyntaxNewIntRange(lval, int_range, description, status ) { lval , CF_DATA_TYPE_INT_RANGE, .range.validation_string = int_range, description, status } +#define ConstraintSyntaxNewIntList(lval, description, status) { lval, CF_DATA_TYPE_INT_LIST, .range.validation_string = CF_INTRANGE, description, status } + +#define ConstraintSyntaxNewReal(lval, real_range, description, status) { lval, CF_DATA_TYPE_REAL, .range.validation_string = real_range, description, status } +#define ConstraintSyntaxNewRealList(lval, description, status) { lval, CF_DATA_TYPE_REAL_LIST, .range.validation_string = CF_REALRANGE, description, status } + +#define ConstraintSyntaxNewContext(lval, description, status) { lval, CF_DATA_TYPE_CONTEXT, .range.validation_string = CF_CLASSRANGE, description, status } +#define ConstraintSyntaxNewContextList(lval, description, status) { lval, CF_DATA_TYPE_CONTEXT_LIST, .range.validation_string = CF_CLASSRANGE, description, status } + +#define ConstraintSyntaxNewContainer(lval, description, status) { lval, CF_DATA_TYPE_CONTAINER, .range.validation_string = "", description, status } + +#define ConstraintSyntaxNewBody(lval, body_syntax, description, status) { lval, CF_DATA_TYPE_BODY, .range.body_type_syntax = body_syntax, description, status } +#define ConstraintSyntaxNewBundle(lval, description, status) { lval, CF_DATA_TYPE_BUNDLE, .range.validation_string = CF_BUNDLE, description, status } + +#define BodySyntaxNew(body_type, constraints, check_fn, status) { body_type, constraints, check_fn, status } +#define BodySyntaxNewNull() { NULL, NULL, NULL, SYNTAX_STATUS_NORMAL } + +#define PromiseTypeSyntaxNew(agent_type, promise_type, constraints, check_fn, status) { agent_type, promise_type, constraints, check_fn, status } +#define PromiseTypeSyntaxNewNull() PromiseTypeSyntaxNew(NULL, NULL, NULL, NULL, SYNTAX_STATUS_NORMAL) + +#define FnCallTypeNew(name, return_type, arguments, implementation, description, opts, category, status) { name, return_type, arguments, implementation, description, .options = opts, category, status } +#define FnCallTypeNewNull() FnCallTypeNew(NULL, CF_DATA_TYPE_NONE, NULL, NULL, NULL, false, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL) + +#define CONSTRAINT_SYNTAX_GLOBAL { "meta", CF_DATA_TYPE_STRING_LIST, .range.validation_string = "", "Tags describing the body", SYNTAX_STATUS_NORMAL }, \ + { "inherit_from", CF_DATA_TYPE_BODY, .range.validation_string = "", "Body from which attributes will be inherited", SYNTAX_STATUS_NORMAL } + +#endif diff --git a/libpromises/syslog_client.c b/libpromises/syslog_client.c new file mode 100644 index 0000000000..0b68048c56 --- /dev/null +++ b/libpromises/syslog_client.c @@ -0,0 +1,146 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +#include + +/* + * Set by cf-agent/cf-serverd from body agent/server control. + */ +static char SYSLOG_HOST[MAXHOSTNAMELEN] = "localhost"; +/* + * Set by cf-agent/cf-serverd from body agent/server control. + */ +static uint16_t SYSLOG_PORT = 514; +/* + * Set by cf-agent/cf-serverd/cf-execd from body agent/exec/server control. + */ +static int SYSLOG_FACILITY = LOG_USER; + + +void SetSyslogFacility(int facility) +{ + SYSLOG_FACILITY = facility; +} + +int GetSyslogFacility() +{ + return SYSLOG_FACILITY; +} + +bool SetSyslogHost(const char *host) +{ + if (strlen(host) < sizeof(SYSLOG_HOST)) + { + strcpy(SYSLOG_HOST, host); + return true; + } + else + { + return false; + } +} + +void SetSyslogPort(uint16_t port) +{ + SYSLOG_PORT = port; +} + +void RemoteSysLog(int log_priority, const char *log_string) +{ + time_t now = time(NULL); + + struct addrinfo query = { 0 }, *response = NULL; + char strport[PRINTSIZE(unsigned)]; + xsnprintf(strport, sizeof(strport), "%u", (unsigned) SYSLOG_PORT); + + query.ai_family = AF_UNSPEC; + query.ai_socktype = SOCK_DGRAM; + + int err = getaddrinfo(SYSLOG_HOST, strport, &query, &response); + if (err != 0) + { + Log(LOG_LEVEL_INFO, + "Unable to find syslog_host or service: (%s/%s) %s", + SYSLOG_HOST, strport, gai_strerror(err)); + if (response != NULL) + { + freeaddrinfo(response); + } + return; + } + + for (const struct addrinfo *ap = response; ap != NULL; ap = ap->ai_next) + { + /* No DNS lookup, just convert IP address to string. */ + char txtaddr[CF_MAX_IP_LEN] = ""; + getnameinfo(ap->ai_addr, ap->ai_addrlen, + txtaddr, sizeof(txtaddr), + NULL, 0, NI_NUMERICHOST); + Log(LOG_LEVEL_VERBOSE, + "Connect to syslog '%s' = '%s' on port '%s'", + SYSLOG_HOST, txtaddr, strport); + + int sd = socket(ap->ai_family, ap->ai_socktype, IPPROTO_UDP); + if (sd == -1) + { + Log(LOG_LEVEL_INFO, "Couldn't open a socket. (socket: %s)", GetErrorStr()); + continue; + } + else + { + const size_t rfc3164_len = 1024; + char message[rfc3164_len]; + char timebuffer[26]; + pid_t pid = getpid(); + + snprintf( + message, + sizeof(message), + "<%i>%.15s %.256s %.256s[%ld]: %s", + (log_priority | SYSLOG_FACILITY), + (cf_strtimestamp_local(now, timebuffer) + 4), // Skips day str + VFQNAME, + VPREFIX, + (long) pid, + log_string); + err = sendto(sd, message, strlen(message), + 0, ap->ai_addr, ap->ai_addrlen); + if (err == -1) + { + Log(LOG_LEVEL_VERBOSE, "Couldn't send '%s' to syslog server '%s'. (sendto: %s)", + message, SYSLOG_HOST, GetErrorStr()); + } + else + { + Log(LOG_LEVEL_VERBOSE, "Syslog message: '%s' to server '%s'", message, SYSLOG_HOST); + } + close(sd); + } + } + + freeaddrinfo(response); +} diff --git a/libpromises/syslog_client.h b/libpromises/syslog_client.h new file mode 100644 index 0000000000..8ce038c751 --- /dev/null +++ b/libpromises/syslog_client.h @@ -0,0 +1,41 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_SYSLOG_CLIENT_H +#define CFENGINE_SYSLOG_CLIENT_H + +#include + +/* + * This module provides implementation of UDP syslog protocol + */ + +bool SetSyslogHost(const char *host); +void SetSyslogPort(uint16_t port); +void SetSyslogFacility(int facility); +int GetSyslogFacility(); + +void RemoteSysLog(int log_priority, const char *log_string); + +#endif diff --git a/libpromises/systype.c b/libpromises/systype.c new file mode 100644 index 0000000000..eb57d28073 --- /dev/null +++ b/libpromises/systype.c @@ -0,0 +1,149 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#if defined __FreeBSD__ +#include +#endif + +/* Set in libenv/sysinfo.c::DetectEnvironment (called every time environment + reload is performed). + + Utilized all over the place, usually to look up OS-specific command/option to + call external utility +*/ +PlatformContext VSYSTEMHARDCLASS; /* GLOBAL_E?, initialized_later */ +PlatformContext VPSHARDCLASS; /* used to define which ps command to use*/ + + +/* Configure system name and system-specific details. */ + +const char *const CLASSTEXT[] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = "", + [PLATFORM_CONTEXT_OPENVZ] = "virt_host_vz_vzps", + [PLATFORM_CONTEXT_HP] = "hpux", + [PLATFORM_CONTEXT_AIX] = "aix", + [PLATFORM_CONTEXT_LINUX] = "linux", + [PLATFORM_CONTEXT_BUSYBOX] = "linux", + [PLATFORM_CONTEXT_SOLARIS] = "solaris", + [PLATFORM_CONTEXT_SUN_SOLARIS] = "solaris", + [PLATFORM_CONTEXT_FREEBSD] = "freebsd", + [PLATFORM_CONTEXT_NETBSD] = "netbsd", + [PLATFORM_CONTEXT_CRAYOS] = "cray", + [PLATFORM_CONTEXT_WINDOWS_NT] = "windows", + [PLATFORM_CONTEXT_SYSTEMV] = "unix_sv", + [PLATFORM_CONTEXT_OPENBSD] = "openbsd", + [PLATFORM_CONTEXT_CFSCO] = "sco", + [PLATFORM_CONTEXT_DARWIN] = "darwin", + [PLATFORM_CONTEXT_QNX] = "qnx", + [PLATFORM_CONTEXT_DRAGONFLY] = "dragonfly", + [PLATFORM_CONTEXT_MINGW] = "windows", + [PLATFORM_CONTEXT_VMWARE] = "vmware", + [PLATFORM_CONTEXT_ANDROID] = "android", +}; + +const char *const VPSCOMM[] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = "", + [PLATFORM_CONTEXT_OPENVZ] = "/bin/vzps", /* virt_host_vz_vzps */ + [PLATFORM_CONTEXT_HP] = "/bin/ps", /* hpux */ + [PLATFORM_CONTEXT_AIX] = "/bin/ps", /* aix */ + [PLATFORM_CONTEXT_LINUX] = "/bin/ps", /* linux */ + [PLATFORM_CONTEXT_BUSYBOX] = "/bin/ps", /* linux */ + [PLATFORM_CONTEXT_SOLARIS] = "/bin/ps", /* solaris >= 11 */ + [PLATFORM_CONTEXT_SUN_SOLARIS] = "/bin/ps", /* solaris < 11 */ + [PLATFORM_CONTEXT_FREEBSD] = "/bin/ps", /* freebsd */ + [PLATFORM_CONTEXT_NETBSD] = "/bin/ps", /* netbsd */ + [PLATFORM_CONTEXT_CRAYOS] = "/bin/ps", /* cray */ + [PLATFORM_CONTEXT_WINDOWS_NT] = "/bin/ps", /* NT - cygnus */ + [PLATFORM_CONTEXT_SYSTEMV] = "/bin/ps", /* unixware */ + [PLATFORM_CONTEXT_OPENBSD] = "/bin/ps", /* openbsd */ + [PLATFORM_CONTEXT_CFSCO] = "/bin/ps", /* sco */ + [PLATFORM_CONTEXT_DARWIN] = "/bin/ps", /* darwin */ + [PLATFORM_CONTEXT_QNX] = "/bin/ps", /* qnx */ + [PLATFORM_CONTEXT_DRAGONFLY] = "/bin/ps", /* dragonfly */ + [PLATFORM_CONTEXT_MINGW] = "mingw-invalid", /* mingw */ + [PLATFORM_CONTEXT_VMWARE] = "/bin/ps", /* vmware */ + [PLATFORM_CONTEXT_ANDROID] = "/system/xbin/busybox ps", /* android */ +}; + +// linux after rhel 3: ps -eo user,pid,ppid,pgid,%cpu,%mem,vsize,ni,rss,stat,nlwp,stime,time,args +// solaris: ps -eo user,pid,ppid,pgid,pcpu,pmem,vsz,pri,rss,nlwp,stime,time,args + +const char *const VPSOPTS[] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = "", + [PLATFORM_CONTEXT_OPENVZ] = "-E 0 -o user,pid,ppid,pgid,pcpu,pmem,vsz,ni,rss,thcount,stime,time,args", /* virt_host_vz_vzps (with vzps, the -E 0 replace the -e) */ + [PLATFORM_CONTEXT_HP] = "-ef", /* hpux */ + [PLATFORM_CONTEXT_AIX] = "-N -eo user,pid,ppid,pgid,pcpu,pmem,vsz,ni,stat,st=STIME,time,args", /* aix */ + /* Note: keep in sync with GetProcessOptions()'s hack for Linux 2.4 */ + /* For header text "TTY" specify "tname" (not "tty" which gives header "TT") */ + [PLATFORM_CONTEXT_LINUX] = "-eo user:30,pid,ppid,pgid,pcpu,pmem,vsz,ni,rss:9,tname,nlwp,stime,etime,time,args",/* linux */ + [PLATFORM_CONTEXT_BUSYBOX] = "", /* linux / busybox */ + [PLATFORM_CONTEXT_SOLARIS] = "-eo user,pid,pcpu,pmem,osz,rss,tty,s,stime,time,args", /* solaris >= 11 */ + [PLATFORM_CONTEXT_SUN_SOLARIS] = "-eo user,pid,pcpu,pmem,osz,rss,tty,s,stime,time,args", /* solaris < 11 */ +#if __FreeBSD_version >= 903000 + [PLATFORM_CONTEXT_FREEBSD] = "auxw -J 0", /* freebsd 9.3 and newer */ +#else + [PLATFORM_CONTEXT_FREEBSD] = "auxw", /* freebsd 9.2 and older*/ +#endif + [PLATFORM_CONTEXT_NETBSD] = "-axwwo user,pid,ppid,pgid,pcpu,pmem,vsz,ni,rss,nlwp,start,time,args", /* netbsd */ + [PLATFORM_CONTEXT_CRAYOS] = "-elyf", /* cray */ + [PLATFORM_CONTEXT_WINDOWS_NT] = "-aW", /* NT */ + [PLATFORM_CONTEXT_SYSTEMV] = "-ef", /* Unixware */ + [PLATFORM_CONTEXT_OPENBSD] = "-axwwo user,pid,ppid,pgid,pcpu,pmem,vsz,ni,rss,start,time,args", /* openbsd */ + [PLATFORM_CONTEXT_CFSCO] = "-ef", /* sco */ + [PLATFORM_CONTEXT_DARWIN] = "auxw", /* darwin */ + [PLATFORM_CONTEXT_QNX] = "-elyf", /* qnx */ + [PLATFORM_CONTEXT_DRAGONFLY] = "auxw", /* dragonfly */ + [PLATFORM_CONTEXT_MINGW] = "mingw-invalid", /* mingw */ + [PLATFORM_CONTEXT_VMWARE] = "?", /* vmware */ + [PLATFORM_CONTEXT_ANDROID] = "", /* android */ +}; + +const char *const VFSTAB[] = +{ + [PLATFORM_CONTEXT_UNKNOWN] = "-", + [PLATFORM_CONTEXT_OPENVZ] = "/etc/fstab", /* virt_host_vz_vzps */ + [PLATFORM_CONTEXT_HP] = "/etc/fstab", /* hpux */ + [PLATFORM_CONTEXT_AIX] = "/etc/filesystems", /* aix */ + [PLATFORM_CONTEXT_LINUX] = "/etc/fstab", /* linux */ + [PLATFORM_CONTEXT_BUSYBOX] = "/etc/fstab", /* linux */ + [PLATFORM_CONTEXT_SOLARIS] = "/etc/vfstab", /* solaris */ + [PLATFORM_CONTEXT_SUN_SOLARIS] = "/etc/vfstab", /* solaris */ + [PLATFORM_CONTEXT_FREEBSD] = "/etc/fstab", /* freebsd */ + [PLATFORM_CONTEXT_NETBSD] = "/etc/fstab", /* netbsd */ + [PLATFORM_CONTEXT_CRAYOS] = "/etc/fstab", /* cray */ + [PLATFORM_CONTEXT_WINDOWS_NT] = "/etc/fstab", /* NT */ + [PLATFORM_CONTEXT_SYSTEMV] = "/etc/vfstab", /* Unixware */ + [PLATFORM_CONTEXT_OPENBSD] = "/etc/fstab", /* openbsd */ + [PLATFORM_CONTEXT_CFSCO] = "/etc/default/filesys",/* sco */ + [PLATFORM_CONTEXT_DARWIN] = "/etc/fstab", /* darwin */ + [PLATFORM_CONTEXT_QNX] = "/etc/fstab", /* qnx */ + [PLATFORM_CONTEXT_DRAGONFLY] = "/etc/fstab", /* dragonfly */ + [PLATFORM_CONTEXT_MINGW] = "", /* mingw */ + [PLATFORM_CONTEXT_VMWARE] = "/etc/fstab", /* vmware */ + [PLATFORM_CONTEXT_ANDROID] = "", /* android */ +}; diff --git a/libpromises/systype.h b/libpromises/systype.h new file mode 100644 index 0000000000..1981118205 --- /dev/null +++ b/libpromises/systype.h @@ -0,0 +1,68 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_SYSTYPE_H +#define CFENGINE_SYSTYPE_H + +/*******************************************************************/ + +typedef enum +{ + PLATFORM_CONTEXT_UNKNOWN, + PLATFORM_CONTEXT_OPENVZ, /* VZ Host with vzps installed */ + PLATFORM_CONTEXT_HP, + PLATFORM_CONTEXT_AIX, + PLATFORM_CONTEXT_LINUX, + PLATFORM_CONTEXT_BUSYBOX, /* Linux-based Busybox toolset */ + PLATFORM_CONTEXT_SOLARIS, /* > 5.10, BSD-compatible system tools */ + PLATFORM_CONTEXT_SUN_SOLARIS, /* < 5.11, BSD tools in /usr/ucb */ + PLATFORM_CONTEXT_FREEBSD, + PLATFORM_CONTEXT_NETBSD, + PLATFORM_CONTEXT_CRAYOS, + PLATFORM_CONTEXT_WINDOWS_NT, /* MS-Win CygWin */ + PLATFORM_CONTEXT_SYSTEMV, + PLATFORM_CONTEXT_OPENBSD, + PLATFORM_CONTEXT_CFSCO, + PLATFORM_CONTEXT_DARWIN, /* MacOS X */ + PLATFORM_CONTEXT_QNX, + PLATFORM_CONTEXT_DRAGONFLY, + PLATFORM_CONTEXT_MINGW, /* MS-Win native */ + PLATFORM_CONTEXT_VMWARE, + PLATFORM_CONTEXT_ANDROID, + + PLATFORM_CONTEXT_MAX /* Not an actual platform: must be last */ +} PlatformContext; + +/*******************************************************************/ + +extern PlatformContext VSYSTEMHARDCLASS; +extern PlatformContext VPSHARDCLASS; /* used to define which ps command to use*/ +extern const char *const CLASSTEXT[PLATFORM_CONTEXT_MAX]; +extern const char *const VPSCOMM[PLATFORM_CONTEXT_MAX]; +extern const char *const VPSOPTS[PLATFORM_CONTEXT_MAX]; +extern const char *const VFSTAB[PLATFORM_CONTEXT_MAX]; + +/*******************************************************************/ + +#endif /* CFENGINE_SYSTYPE_H */ diff --git a/libpromises/text2cstring.pl b/libpromises/text2cstring.pl new file mode 100755 index 0000000000..a1e9781501 --- /dev/null +++ b/libpromises/text2cstring.pl @@ -0,0 +1,42 @@ +#!/usr/bin/perl +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +use warnings; +use strict; +use Data::Dumper; + +my $source = shift @ARGV; + +die "Syntax: $0 SOURCE" unless defined $source; + +open my $sf, '<', $source or die "Can't read from file '$source': $!"; +my @lines = <$sf>; + +chomp @lines; +s/\\/\\\\/g foreach @lines; +s/"/\\"/g foreach @lines; +@lines = map { " \"$_\\n\"\n" } @lines; + +print @lines; diff --git a/libpromises/timeout.c b/libpromises/timeout.c new file mode 100644 index 0000000000..5ad63baf85 --- /dev/null +++ b/libpromises/timeout.c @@ -0,0 +1,68 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include + +void SetTimeOut(int timeout) +{ + ALARM_PID = -1; + signal(SIGALRM, (void *) TimeOut); + alarm(timeout); +} + +/*************************************************************************/ + +void TimeOut() +{ + alarm(0); + + if (ALARM_PID != -1) + { + Log(LOG_LEVEL_VERBOSE, "Time out of process %jd", (intmax_t)ALARM_PID); + GracefulTerminate(ALARM_PID, PROCESS_START_TIME_UNKNOWN); + } + else + { + Log(LOG_LEVEL_VERBOSE, "%s> Time out", VPREFIX); + } +} + +/*************************************************************************/ + +time_t SetReferenceTime(void) +{ + time_t tloc; + + if ((tloc = time((time_t *) NULL)) == -1) + { + Log(LOG_LEVEL_ERR, "Couldn't read system clock. (time: %s)", GetErrorStr()); + } + + CFSTARTTIME = tloc; + Log(LOG_LEVEL_VERBOSE, "Reference time set to '%s'", ctime(&tloc)); + + return tloc; +} diff --git a/libpromises/timeout.h b/libpromises/timeout.h new file mode 100644 index 0000000000..33af0f7f05 --- /dev/null +++ b/libpromises/timeout.h @@ -0,0 +1,32 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_TIMEOUT_H +#define CFENGINE_TIMEOUT_H + +void SetTimeOut(int timeout); +void TimeOut(void); +time_t SetReferenceTime(void); + +#endif diff --git a/libpromises/unix.c b/libpromises/unix.c new file mode 100644 index 0000000000..bef9389df8 --- /dev/null +++ b/libpromises/unix.c @@ -0,0 +1,530 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include + +#ifdef HAVE_SYS_UIO_H +# include +#endif + +#ifndef __MINGW32__ + +/* Max size of the 'passwd' string in the getpwuid_r() function, + * man:getpwuid_r(3) says that this value "Should be more than enough". */ +#define GETPW_R_SIZE_MAX 16384 +#define GETGR_R_SIZE_MAX 16384 /* same for group name */ + +static bool IsProcessRunning(pid_t pid); + +void ProcessSignalTerminate(pid_t pid) +{ + if(!IsProcessRunning(pid)) + { + return; + } + + + if(kill(pid, SIGINT) == -1) + { + Log(LOG_LEVEL_ERR, "Could not send SIGINT to pid '%jd'. (kill: %s)", + (intmax_t)pid, GetErrorStr()); + } + + sleep(1); + if (kill(pid, 0) != 0) + { + /* can no longer send signals to the process => it's dead now */ + return; + } + + if(kill(pid, SIGTERM) == -1) + { + Log(LOG_LEVEL_ERR, "Could not send SIGTERM to pid '%jd'. (kill: %s)", + (intmax_t)pid, GetErrorStr()); + } + + sleep(5); + if (kill(pid, 0) != 0) + { + /* can no longer send signals to the process => it's dead now */ + return; + } + + if(kill(pid, SIGKILL) == -1) + { + Log(LOG_LEVEL_ERR, "Could not send SIGKILL to pid '%jd'. (kill: %s)", + (intmax_t)pid, GetErrorStr()); + } + + sleep(1); +} + +/*************************************************************/ + +static bool IsProcessRunning(pid_t pid) +{ + int res = kill(pid, 0); + + if(res == 0) + { + return true; + } + + if(res == -1 && errno == ESRCH) + { + return false; + } + + Log(LOG_LEVEL_ERR, "Failed checking for process existence. (kill: %s)", GetErrorStr()); + + return false; +} + +/*************************************************************/ + +bool IsExecutable(const char *file) +{ + struct stat sb; + gid_t grps[NGROUPS]; + int n; + + if (stat(file, &sb) == -1) + { + Log(LOG_LEVEL_ERR, "Proposed executable file '%s' doesn't exist", file); + return false; + } + + if (sb.st_mode & 02) + { + Log(LOG_LEVEL_ERR, "SECURITY ALERT: promised executable '%s' is world writable! ", file); + Log(LOG_LEVEL_ERR, "SECURITY ALERT: CFEngine will not execute this - requires human inspection"); + return false; + } + + if ((getuid() == sb.st_uid) || (getuid() == 0)) + { + if (sb.st_mode & 0100) + { + return true; + } + } + else if (getgid() == sb.st_gid) + { + if (sb.st_mode & 0010) + { + return true; + } + } + else + { + if (sb.st_mode & 0001) + { + return true; + } + + if ((n = getgroups(NGROUPS, grps)) > 0) + { + int i; + + for (i = 0; i < n; i++) + { + if (grps[i] == sb.st_gid) + { + if (sb.st_mode & 0010) + { + return true; + } + } + } + } + } + + return false; +} + +bool ShellCommandReturnsZero(const char *command, ShellType shell) +{ + int status; + pid_t pid; + + if (shell == SHELL_TYPE_POWERSHELL) + { + Log(LOG_LEVEL_ERR, "Powershell is only supported on Windows"); + return false; + } + + if ((pid = fork()) < 0) + { + Log(LOG_LEVEL_ERR, "Failed to fork new process: %s", command); + return false; + } + else if (pid == 0) /* child */ + { + ALARM_PID = -1; + + if (shell == SHELL_TYPE_USE) + { + if (execl(SHELL_PATH, "sh", "-c", command, NULL) == -1) + { + Log(LOG_LEVEL_ERR, "Command '%s' failed. (execl: %s)", command, GetErrorStr()); + exit(EXIT_FAILURE); /* OK since this is a forked proc and no registered cleanup functions */ + } + } + else + { + char **argv = ArgSplitCommand(command, NULL); + int devnull; + + if (LogGetGlobalLevel() < LOG_LEVEL_INFO) + { + if ((devnull = open("/dev/null", O_WRONLY)) == -1) + { + Log(LOG_LEVEL_ERR, "Command '%s' failed. (open: %s)", command, GetErrorStr()); + exit(EXIT_FAILURE); /* OK since this is a forked proc and no registered cleanup functions */ + } + + if (dup2(devnull, STDOUT_FILENO) == -1 || dup2(devnull, STDERR_FILENO) == -1) + { + Log(LOG_LEVEL_ERR, "Command '%s' failed. (dup2: %s)", command, GetErrorStr()); + exit(EXIT_FAILURE); /* OK since this is a forked proc and no registered cleanup functions */ + } + + close(devnull); + } + + if (execv(argv[0], argv) == -1) + { + Log(LOG_LEVEL_ERR, "Command '%s' failed. (execv: %s)", argv[0], GetErrorStr()); + exit(EXIT_FAILURE); /* OK since this is a forked proc and no registered cleanup functions */ + } + } + } + else /* parent */ + { + ALARM_PID = pid; + + while (waitpid(pid, &status, 0) < 0) + { + if (errno != EINTR) + { + return false; + } + } + + return (WEXITSTATUS(status) == 0); + } + + return false; +} + +/*************************************************************/ + +static bool GetUserGroupInfoFromGetent(const char *type, const char *query, + char *name, size_t name_size, uintmax_t *id, + LogLevel error_log_level) +{ + struct stat sb; + char* getent_bin; + if (stat("/bin/getent", &sb) == 0) + { + getent_bin = "/bin/getent"; + } + else + { + getent_bin = "/usr/bin/getent"; + } + + char buf[CF_BUFSIZE]; + NDEBUG_UNUSED int print_ret = snprintf(buf, sizeof(buf), "%s %s %s", getent_bin, type, query); + assert(print_ret < sizeof(buf)); + + FILE *out = cf_popen(buf, "r", OUTPUT_SELECT_STDOUT); + size_t offset = 0; + size_t n_read; + while ((n_read = fread(buf + offset, 1, sizeof(buf) - offset, out)) > 0) + { + offset += n_read; + } + buf[offset] = '\0'; + if (!feof(out)) + { + Log(error_log_level, "Failed to read output from 'getent %s %s'", type, query); + cf_pclose(out); + return false; + } + int ec = cf_pclose(out); + if (ec == 2) + { + /* not found */ + return false; + } + else if (ec != 0) + { + Log(error_log_level, "Failed to get information about '%s %s' using getent", type, query); + return false; + } + + char *nl = strchr(buf, '\n'); + if ((nl != NULL) && (nl < buf + offset) && (strchr(nl + 1, '\n') != NULL)) + { + Log(error_log_level, "Multiple results from 'getent %s %s'", type, query); + return false; + } + + /* The format is: + * name:password:id:... + * (just like /etc/passwd and /etc/group) */ + char *next_colon = strchr(buf, ':'); + if (next_colon == NULL) + { + Log(error_log_level, "Invalid data from 'getent %s %s': %s", type, query, buf); + return false; + } + *next_colon = '\0'; + + if (name != NULL) + { + size_t ret = strlcpy(name, buf, name_size); + assert(ret < name_size); + if (ret >= name_size) + { + /* This should never happen, but if it does, it's always an error */ + Log(LOG_LEVEL_ERR, "Failed to extract info from 'getent %s %s', buffer too small", + type, query); + return false; + } + } + + if (id == NULL) + { + /* We are done. */ + return true; + } + + /* just skip the next field (password) */ + next_colon = strchr(next_colon + 1, ':'); + if (next_colon == NULL) + { + Log(error_log_level, "Invalid data from 'getent %s %s': %s", type, query, buf); + return false; + } + *next_colon = '\0'; + + char *id_start = next_colon + 1; + next_colon = strchr(id_start, ':'); + if (next_colon == NULL) + { + Log(error_log_level, "Invalid data from 'getent %s %s': %s", type, query, buf); + return false; + } + *next_colon = '\0'; + + int n_scanned = sscanf(id_start, "%ju", id); + if (n_scanned != 1) + { + Log(error_log_level, "Failed to extract info from 'getent %s %s': unexpected ID data '%s'", + type, query, buf); + return false; + } + + return true; +} + +bool GetUserName(uid_t uid, char *user_name_buf, size_t buf_size, LogLevel error_log_level) +{ + char buf[GETPW_R_SIZE_MAX] = {0}; + struct passwd pwd; + struct passwd *result; + + int ret = getpwuid_r(uid, &pwd, buf, GETPW_R_SIZE_MAX, &result); + if (result == NULL) + { + char uid_str[32]; /* len("%d" % (2**64 - 1)) == 20 */ + NDEBUG_UNUSED int print_ret = snprintf(uid_str, sizeof(uid_str), "%ju", (uintmax_t) uid); + assert(print_ret < sizeof(uid_str)); + + if (GetUserGroupInfoFromGetent("passwd", uid_str, + user_name_buf, buf_size, NULL, + error_log_level)) + { + /* Found by getent. */ + return true; + } + else + { + Log(error_log_level, "Could not get user name for uid %ju, (getpwuid: %s)", + (uintmax_t) uid, (ret == 0) ? "not found" : GetErrorStrFromCode(ret)); + return false; + } + } + + if ((user_name_buf != NULL) && (buf_size > 0)) + { + ret = strlcpy(user_name_buf, result->pw_name, buf_size); + assert(ret < buf_size); + if (ret >= buf_size) + { + /* Should never happen, but if it does, it's definitely an error. */ + Log(LOG_LEVEL_ERR, "Failed to get user name for uid %ju (buffer too small)", + (uintmax_t) uid); + return false; + } + } + + return true; +} + +bool GetCurrentUserName(char *userName, int userNameLen) +{ + memset(userName, 0, userNameLen); + bool success = GetUserName(getuid(), userName, userNameLen, LOG_LEVEL_ERR); + if (!success) + { + strlcpy(userName, "UNKNOWN", userNameLen); + } + + return success; +} + +bool GetGroupName(gid_t gid, char *group_name_buf, size_t buf_size, LogLevel error_log_level) +{ + char buf[GETGR_R_SIZE_MAX] = {0}; + struct group grp; + struct group *result; + + int ret = getgrgid_r(gid, &grp, buf, GETGR_R_SIZE_MAX, &result); + if (result == NULL) + { + char gid_str[32]; /* len("%d" % (2**64 - 1)) == 20 */ + NDEBUG_UNUSED int print_ret = snprintf(gid_str, sizeof(gid_str), "%ju", (uintmax_t) gid); + assert(print_ret < sizeof(gid_str)); + + if (GetUserGroupInfoFromGetent("group", gid_str, + group_name_buf, buf_size, NULL, + error_log_level)) + { + /* Found by getent. */ + return true; + } + else + { + Log(error_log_level, "Could not get group name for gid %ju, (getgrgid: %s)", + (uintmax_t) gid, (ret == 0) ? "not found" : GetErrorStrFromCode(ret)); + return false; + } + } + + if ((group_name_buf != NULL) && (buf_size > 0)) + { + ret = strlcpy(group_name_buf, result->gr_name, buf_size); + assert(ret < buf_size); + if (ret >= buf_size) + { + /* Should never happen, but if it does, it's definitely an error. */ + Log(LOG_LEVEL_ERR, "Failed to get group name for gid %ju (buffer too small)", + (uintmax_t) gid); + return false; + } + } + + return true; +} + +bool GetUserID(const char *user_name, uid_t *uid, LogLevel error_log_level) +{ + char buf[GETPW_R_SIZE_MAX] = {0}; + struct passwd pwd; + struct passwd *result; + + int ret = getpwnam_r(user_name, &pwd, buf, GETPW_R_SIZE_MAX, &result); + if (result == NULL) + { + uintmax_t tmp; + if (GetUserGroupInfoFromGetent("passwd", user_name, + NULL, 0, &tmp, + error_log_level)) + { + /* Found by getent. */ + if (uid != NULL) + { + *uid = (uid_t) tmp; + } + return true; + } + else + { + Log(error_log_level, "Could not get UID for user '%s', (getpwnam: %s)", + user_name, (ret == 0) ? "not found" : GetErrorStrFromCode(ret)); + return false; + } + } + + if (uid != NULL) + { + *uid = result->pw_uid; + } + + return true; +} + +bool GetGroupID(const char *group_name, gid_t *gid, LogLevel error_log_level) +{ + char buf[GETGR_R_SIZE_MAX] = {0}; + struct group grp; + struct group *result; + + int ret = getgrnam_r(group_name, &grp, buf, GETGR_R_SIZE_MAX, &result); + if (result == NULL) + { + uintmax_t tmp; + if (GetUserGroupInfoFromGetent("group", group_name, + NULL, 0, &tmp, + error_log_level)) + { + /* Found by getent. */ + if (gid != NULL) + { + *gid = (gid_t) tmp; + } + return true; + } + else + { + Log(error_log_level, "Could not get GID for group '%s', (getgrnam: %s)", + group_name, (ret == 0) ? "not found" : GetErrorStrFromCode(ret)); + return false; + } + } + + if (gid != NULL) + { + *gid = result->gr_gid; + } + + return true; +} + +#endif /* !__MINGW32__ */ diff --git a/libpromises/unix.h b/libpromises/unix.h new file mode 100644 index 0000000000..fbad097d14 --- /dev/null +++ b/libpromises/unix.h @@ -0,0 +1,62 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. + +*/ + +#ifndef CFENGINE_UNIX_H +#define CFENGINE_UNIX_H + +#include +#include + +void ProcessSignalTerminate(pid_t pid); +bool GetCurrentUserName(char *userName, int userNameLen); + +#ifndef __MINGW32__ +/** + * Get user name for the user with UID #uid + * + * @param uid UID of the user + * @param user_name_buf buffer to store the user name (if found) + * (%NULL if only checking if a user with #uid exists) + * @param buf_size size of the #user_name_buf buffer + * @param error_log_level log level to store errors with (not found or an actual error) + * @return whether the lookup was successful or not + */ +bool GetUserName(uid_t uid, char *user_name_buf, size_t buf_size, LogLevel error_log_level); +bool GetGroupName(gid_t gid, char *group_name_buf, size_t buf_size, LogLevel error_log_level); + +/** + * Get UID for the user with user name #user_name + * + * @param user_name user name of the user + * @param[out] uid place to store the UID of user #user_name (if found) + * (%NULL if only checking if a user with #user_name exists) + * @param error_log_level log level to store errors with (not found or an actual error) + * @return whether the lookup was successful or not + */ +bool GetUserID(const char *user_name, uid_t *uid, LogLevel error_log_level); +bool GetGroupID(const char *group_name, gid_t *gid, LogLevel error_log_level); +#endif + +#endif diff --git a/libpromises/var_expressions.c b/libpromises/var_expressions.c new file mode 100644 index 0000000000..e7ca316b50 --- /dev/null +++ b/libpromises/var_expressions.c @@ -0,0 +1,601 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include + +// This is not allowed to be the part of VarRef.indices so looks safe +// to be used as multi array indices separator while hashing. +#define ARRAY_SEPARATOR_HASH ']' + +static unsigned VarRefHash(const VarRef *ref) +{ + unsigned int h = 0; + + if (VarRefIsQualified(ref)) + { + if (ref->ns) + { + for (int i = 0; ref->ns[i] != '\0'; i++) + { + h += ref->ns[i]; + h += (h << 10); + h ^= (h >> 6); + } + } + else + { + h = 1195645448; // hash of "default" + } + + int len = strlen(ref->scope); + for (int i = 0; i < len; i++) + { + h += ref->scope[i]; + h += (h << 10); + h ^= (h >> 6); + } + } + + for (int i = 0; ref->lval[i] != '\0'; i++) + { + h += ref->lval[i]; + h += (h << 10); + h ^= (h >> 6); + } + + for (size_t k = 0; k < ref->num_indices; k++) + { + // Fixing multi index arrays hashing collisions - Redmine 6674 + // Multi index arrays with indexes expanded to the same string + // (e.g. v[te][st], v[t][e][s][t]) will not be hashed to the same value. + h += ARRAY_SEPARATOR_HASH; + h += (h << 10); + h ^= (h >> 6); + + for (int i = 0; ref->indices[k][i] != '\0'; i++) + { + h += ref->indices[k][i]; + h += (h << 10); + h ^= (h >> 6); + } + } + + h += (h << 3); + h ^= (h >> 11); + h += (h << 15); + + return h; +} + +unsigned int VarRefHash_untyped(const void *ref, + unsigned int seed ARG_UNUSED) +{ + return VarRefHash(ref); +} + +VarRef VarRefConst(const char *ns, const char *scope, const char *lval) +{ + VarRef ref; + + ref.ns = (char *)ns; + ref.scope = (char *)scope; + ref.lval = (char *)lval; + ref.num_indices = 0; + ref.indices = NULL; + + return ref; +} + +VarRef *VarRefCopy(const VarRef *ref) +{ + VarRef *copy = xmalloc(sizeof(VarRef)); + + copy->ns = ref->ns ? xstrdup(ref->ns) : NULL; + copy->scope = ref->scope ? xstrdup(ref->scope) : NULL; + copy->lval = ref->lval ? xstrdup(ref->lval) : NULL; + + copy->num_indices = ref->num_indices; + if (ref->num_indices > 0) + { + copy->indices = xmalloc(ref->num_indices * sizeof(char*)); + for (size_t i = 0; i < ref->num_indices; i++) + { + copy->indices[i] = xstrdup(ref->indices[i]); + } + } + else + { + copy->indices = NULL; + } + + return copy; +} + +VarRef *VarRefCopyLocalized(const VarRef *ref) +{ + VarRef *copy = xmalloc(sizeof(VarRef)); + + copy->ns = NULL; + copy->scope = xstrdup("this"); + copy->lval = ref->lval ? xstrdup(ref->lval) : NULL; + + copy->num_indices = ref->num_indices; + if (ref->num_indices > 0) + { + copy->indices = xmalloc(ref->num_indices * sizeof(char*)); + for (size_t i = 0; i < ref->num_indices; i++) + { + copy->indices[i] = xstrdup(ref->indices[i]); + } + } + else + { + copy->indices = NULL; + } + + return copy; +} + +VarRef *VarRefCopyIndexless(const VarRef *ref) +{ + VarRef *copy = xmalloc(sizeof(VarRef)); + + copy->ns = ref->ns ? xstrdup(ref->ns) : NULL; + copy->scope = ref->scope ? xstrdup(ref->scope) : NULL; + copy->lval = ref->lval ? xstrdup(ref->lval) : NULL; + copy->num_indices = 0; + copy->indices = NULL; + + return copy; +} + + + +static bool IndexBracketsBalance(const char *var_string) +{ + int count = 0; + for (const char *c = var_string; *c != '\0'; c++) + { + if (*c == '[') + { + count++; + } + if (*c == ']') + { + count--; + } + } + + return count == 0; +} + + +static size_t IndexCount(const char *var_string) +{ + size_t count = 0; + size_t level = 0; + + for (const char *c = var_string; *c != '\0'; c++) + { + if (*c == '[') + { + if (level == 0) + { + count++; + } + level++; + } + if (*c == ']') + { + level--; + } + } + + return count; +} + +VarRef *VarRefParseFromNamespaceAndScope(const char *qualified_name, + const char *_ns, const char *_scope, + char ns_separator, char scope_separator) +{ + assert(qualified_name); + char *ns = NULL; + + const char *indices_start = strchr(qualified_name, '['); + + const char *scope_start = strchr(qualified_name, ns_separator); + if (scope_start && (!indices_start || scope_start < indices_start)) + { + ns = xstrndup(qualified_name, scope_start - qualified_name); + scope_start++; + } + else + { + scope_start = qualified_name; + } + + char *scope = NULL; + + const char *lval_start = strchr(scope_start, scope_separator); + + if (lval_start && (!indices_start || lval_start < indices_start)) + { + lval_start++; + scope = xstrndup(scope_start, lval_start - scope_start - 1); + } + else + { + lval_start = scope_start; + } + + char *lval = NULL; + char **indices = NULL; + size_t num_indices = 0; + + if (indices_start) + { + indices_start++; + lval = xstrndup(lval_start, indices_start - lval_start - 1); + + if (!IndexBracketsBalance(indices_start - 1)) + { + Log(LOG_LEVEL_ERR, "Broken variable expression, index brackets do not balance, in '%s'", qualified_name); + } + else + { + num_indices = IndexCount(indices_start - 1); + indices = xmalloc(num_indices * sizeof(char *)); + + Buffer *buf = BufferNew(); + size_t cur_index = 0; + size_t open_count = 1; + + for (const char *c = indices_start; *c != '\0'; c++) + { + if (*c == '[') + { + if (open_count++ == 0) + { + cur_index++; + continue; + } + } + else if (*c == ']') + { + if (open_count-- == 1) + { + indices[cur_index] = xstrdup(BufferData(buf)); + BufferClear(buf); + continue; + } + } + + BufferAppend(buf, c, sizeof(char)); + } + BufferDestroy(buf); + } + } + else + { + lval = xstrdup(lval_start); + } + + assert(lval); + + if (scope) + { + if (SpecialScopeFromString(scope) != SPECIAL_SCOPE_NONE) + { + _ns = NULL; + } + + /* + * Force considering non-special "this." variables as unqualified. + * This allows qualifying bundle parameters passed as reference with a "this" scope + * in the calling bundle. + */ + if (is_this_not_special(scope, lval)) { + free(scope); + scope = NULL; + } + } + + VarRef *ref = xmalloc(sizeof(VarRef)); + + ref->ns = ns ? ns : (_ns ? xstrdup(_ns) : NULL); + ref->scope = scope ? scope : (_scope ? xstrdup(_scope) : NULL); + ref->lval = lval; + ref->indices = indices; + ref->num_indices = num_indices; + + return ref; +} + +/* + * This function will return true if the given variable is + * a this.something variable that is an alias to a non-special local variable. + */ +bool is_this_not_special(const char *scope, const char *lval) { + // TODO: better way to get this list? + const char *special_this_variables[] = {"v","k","this","service_policy","promiser","promiser_uid","promiser_gid","promiser_pid","promiser_ppid","bundle","handle","namespace","promise_filename","promise_dirname","promise_linenumber", NULL}; + + if (!scope) { + return false; + } + + if (SpecialScopeFromString(scope) != SPECIAL_SCOPE_THIS) { + return false; + } + + if (IsStrIn(lval, special_this_variables)) { + return false; + } + + return true; +} + +VarRef *VarRefParse(const char *var_ref_string) +{ + return VarRefParseFromNamespaceAndScope(var_ref_string, NULL, NULL, CF_NS, '.'); +} + +VarRef *VarRefParseFromScope(const char *var_ref_string, const char *scope) +{ + if (!scope) + { + return VarRefParseFromNamespaceAndScope(var_ref_string, NULL, NULL, CF_NS, '.'); + } + + const char *scope_start = strchr(scope, CF_NS); + if (scope_start) + { + char *ns = xstrndup(scope, scope_start - scope); + VarRef *ref = VarRefParseFromNamespaceAndScope(var_ref_string, ns, scope_start + 1, CF_NS, '.'); + free(ns); + return ref; + } + else + { + return VarRefParseFromNamespaceAndScope(var_ref_string, NULL, scope, CF_NS, '.'); + } +} + +/** + * @brief Parse the variable reference in the context of a bundle. This means + * that the VarRef will inherit scope and namespace of the bundle if + * these are not specified explicitly in the string. + */ +VarRef *VarRefParseFromBundle(const char *var_ref_string, const Bundle *bundle) +{ + if (bundle) + { + return VarRefParseFromNamespaceAndScope(var_ref_string, + bundle->ns, bundle->name, + CF_NS, '.'); + } + else + { + return VarRefParse(var_ref_string); + } +} + +void VarRefDestroy(VarRef *ref) +{ + if (ref) + { + free(ref->ns); + free(ref->scope); + free(ref->lval); + if (ref->num_indices > 0) + { + for (size_t i = 0; i < ref->num_indices; ++i) + { + free(ref->indices[i]); + } + free(ref->indices); + } + + free(ref); + } + +} + +void VarRefDestroy_untyped(void *ref) +{ + VarRefDestroy(ref); +} + +char *VarRefToString(const VarRef *ref, bool qualified) +{ + assert(ref->lval); + + Buffer *buf = BufferNew(); + if (qualified && VarRefIsQualified(ref)) + { + const char *ns = ref->ns ? ref->ns : "default"; + + BufferAppend(buf, ns, strlen(ns)); + BufferAppend(buf, ":", sizeof(char)); + BufferAppend(buf, ref->scope, strlen(ref->scope)); + BufferAppend(buf, ".", sizeof(char)); + } + + BufferAppend(buf, ref->lval, strlen(ref->lval)); + + for (size_t i = 0; i < ref->num_indices; i++) + { + BufferAppend(buf, "[", sizeof(char)); + BufferAppend(buf, ref->indices[i], strlen(ref->indices[i])); + BufferAppend(buf, "]", sizeof(char)); + } + + return BufferClose(buf); +} + +char *VarRefMangle(const VarRef *ref) +{ + char *suffix = VarRefToString(ref, false); + + if (!ref->scope) + { + return suffix; + } + else + { + if (ref->ns) + { + char *mangled = StringFormat("%s*%s#%s", ref->ns, ref->scope, suffix); + free(suffix); + return mangled; + } + else + { + char *mangled = StringFormat("%s#%s", ref->scope, suffix); + free(suffix); + return mangled; + } + } +} + +VarRef *VarRefDeMangle(const char *mangled_var_ref) +{ + return VarRefParseFromNamespaceAndScope(mangled_var_ref, NULL, NULL, + CF_MANGLED_NS, CF_MANGLED_SCOPE); +} + +static bool VarRefIsMeta(VarRef *ref) +{ + return StringEndsWith(ref->scope, "_meta"); +} + +void VarRefSetMeta(VarRef *ref, bool enabled) +{ + if (enabled) + { + if (!VarRefIsMeta(ref)) + { + char *tmp = StringConcatenate(2, ref->scope, "_meta"); + free(ref->scope); + ref->scope = tmp; + } + } + else + { + if (VarRefIsMeta(ref)) + { + char *tmp = ref->scope; + size_t len = strlen(ref->scope); + memcpy(ref->scope, StringSubstring(ref->scope, len, 0, len - strlen("_meta")), len - strlen("_meta")); + free(tmp); + } + } +} + +bool VarRefIsQualified(const VarRef *ref) +{ + return ref->scope != NULL; +} + +void VarRefQualify(VarRef *ref, const char *ns, const char *scope) +{ + assert(scope); + + free(ref->ns); + ref->ns = NULL; + + free(ref->scope); + ref->scope = NULL; + + ref->ns = ns ? xstrdup(ns) : NULL; + ref->scope = xstrdup(scope); +} + +void VarRefAddIndex(VarRef *ref, const char *index) +{ + if (ref->indices) + { + assert(ref->num_indices > 0); + ref->indices = xrealloc(ref->indices, sizeof(char *) * (ref->num_indices + 1)); + } + else + { + assert(ref->num_indices == 0); + ref->indices = xmalloc(sizeof(char *)); + } + + ref->indices[ref->num_indices] = xstrdup(index); + ref->num_indices++; +} + +int VarRefCompare(const VarRef *a, const VarRef *b) +{ + int ret = strcmp(a->lval, b->lval); + if (ret != 0) + { + return ret; + } + + ret = strcmp(NULLStringToEmpty(a->scope), NULLStringToEmpty(b->scope)); + if (ret != 0) + { + return ret; + } + + const char *a_ns = a->ns ? a->ns : "default"; + const char *b_ns = b->ns ? b->ns : "default"; + + ret = strcmp(a_ns, b_ns); + if (ret != 0) + { + return ret; + } + + ret = a->num_indices - b->num_indices; + if (ret != 0) + { + return ret; + } + + for (size_t i = 0; i < a->num_indices; i++) + { + ret = strcmp(a->indices[i], b->indices[i]); + if (ret != 0) + { + return ret; + } + } + + return 0; +} + +bool VarRefEqual_untyped(const void *a, const void *b) +{ + return (VarRefCompare(a, b) == 0); +} diff --git a/libpromises/var_expressions.h b/libpromises/var_expressions.h new file mode 100644 index 0000000000..cd58961411 --- /dev/null +++ b/libpromises/var_expressions.h @@ -0,0 +1,110 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_VAR_EXPRESSIONS_H +#define CFENGINE_VAR_EXPRESSIONS_H + +#include + +#include +#include +#include /* StringContains() */ + +/** + VarRef is immutable, which means that after allocated the members never + change, until all of it is freed. + + @TODO constify all pointers returned from VarRef initializers (VarRefCopy, + VarRefParse, etc) +*/ +typedef struct +{ + char *ns; + char *scope; + char *lval; + char **indices; + size_t num_indices; + + /* TODO performance: when using VarRefCopy() we just need to allocate one + * big chunk with malloc() and all pointers can point in there. This + * whole struct can be a single malloc'ed chunk with array[] space in + * the end, if our use allows it (no changes after creation). */ + /* bool single_alloc; */ + /* char space[]; */ +} VarRef; + +VarRef *VarRefCopy(const VarRef *ref); +VarRef *VarRefCopyLocalized(const VarRef *ref); +VarRef *VarRefCopyIndexless(const VarRef *ref); + +bool is_this_not_special(const char *scope, const char *lval); + +VarRef *VarRefParse(const char *var_ref_string); + +VarRef *VarRefParseFromBundle(const char *var_ref_string, const Bundle *bundle); +VarRef *VarRefParseFromScope(const char *var_ref_string, const char *scope); +VarRef *VarRefParseFromNamespaceAndScope(const char *qualified_name, + const char *_ns, const char *_scope, + char ns_separator, char scope_separator); +VarRef VarRefConst(const char *ns, const char *scope, const char *lval); + +void VarRefDestroy (VarRef *ref); +void VarRefDestroy_untyped(void *ref); + +char *VarRefToString(const VarRef *ref, bool qualified); + +void VarRefSetMeta(VarRef *ref, bool enabled); + +bool VarRefIsQualified(const VarRef *ref); +void VarRefQualify(VarRef *ref, const char *ns, const char *scope); +void VarRefAddIndex(VarRef *ref, const char *index); + +int VarRefCompare(const VarRef *a, const VarRef *b); + +bool VarRefEqual_untyped(const void *a, const void *b); + +unsigned int VarRefHash_untyped(const void *ref, + unsigned int seed); + +static inline bool StringContainsUnresolved(const char *str) +{ + // clang-format off + return (StringContains(str, "$(") + || StringContains(str, "${") + || StringContains(str, "@{") + || StringContains(str, "@(")); + // clang-format on +} + +/** Whether #str is of form "@{something}"/"@(something)" or not. */ +static inline bool StringIsBareNonScalarRef(const char *str) +{ + assert(str != NULL); + const size_t str_len = strlen(str); + return ((str[0] == '@') && + (((str[1] == '(') && (str[str_len - 1] == ')')) || + ((str[1] == '{') && (str[str_len - 1] == '}')))); +} + +#endif diff --git a/libpromises/variable.c b/libpromises/variable.c new file mode 100644 index 0000000000..213c342531 --- /dev/null +++ b/libpromises/variable.c @@ -0,0 +1,407 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#include + +#include +#include +#include +#include /* DataTypeToString */ + +#define VARIABLE_TAG_SECRET "secret" + +struct Variable_ +{ + VarRef *ref; + Rval rval; + DataType type; + StringSet *tags; + char *comment; + const Promise *promise; // The promise that set the present value +}; + +static Variable *VariableNew(VarRef *ref, Rval rval, DataType type, + StringSet *tags, char *comment, + const Promise *promise) +{ + Variable *var = xmalloc(sizeof(Variable)); + + var->ref = ref; + var->rval = rval; + var->type = type; + var->tags = tags; + var->comment = comment; + var->promise = promise; + + return var; +} + +/* DO NOT EXPORT, this is for internal (hash table) use only, and it doesn't + * free everything in Variable, in particular it leaves var->ref to be handled + * by the Map implementation calling the key-destroy function. */ +static void VariableDestroy(Variable *var) +{ + if (var != NULL) + { + RvalDestroy(var->rval); + StringSetDestroy(var->tags); + free(var->comment); + // Nothing to do for ->promise + + free(var); + } +} + +static void VariableDestroy_untyped(void *var) +{ + VariableDestroy(var); +} + +const VarRef *VariableGetRef(const Variable *var) +{ + assert(var != NULL); + return var->ref; +} + +DataType VariableGetType(const Variable *var) +{ + assert(var != NULL); + return var->type; +} + +RvalType VariableGetRvalType(const Variable *var) +{ + assert(var != NULL); + return var->rval.type; +} + +StringSet *VariableGetTags(const Variable *var) +{ + assert(var != NULL); + return var->tags; +} + +const char *VariableGetComment(const Variable *var) +{ + assert(var != NULL); + return var->comment; +} + +const Promise *VariableGetPromise(const Variable *var) +{ + assert(var != NULL); + return var->promise; +} + +bool VariableIsSecret(const Variable *var) +{ + assert(var != NULL); + return ((var->tags != NULL) && StringSetContains(var->tags, VARIABLE_TAG_SECRET)); +} + +Rval VariableGetRval(const Variable *var, bool get_secret) +{ + assert(var != NULL); + if (!get_secret && VariableIsSecret(var)) + { + return RvalNewSecret(); + } + return var->rval; +} + +void VariableSetRval(Variable *var, Rval new_rval) +{ + assert(var != NULL); + RvalDestroy(var->rval); + var->rval = new_rval; +} + +/** + Define "VarMap" hash table. + Key: VarRef + Value: Variable +*/ +TYPED_MAP_DECLARE(Var, VarRef *, Variable *) +TYPED_MAP_DEFINE(Var, VarRef *, Variable *, + VarRefHash_untyped, + VarRefEqual_untyped, + VarRefDestroy_untyped, + VariableDestroy_untyped) + + +struct VariableTable_ +{ + VarMap *vars; +}; + +struct VariableTableIterator_ +{ + VarRef *ref; + MapIterator iter; +}; + +VariableTable *VariableTableNew(void) +{ + VariableTable *table = xmalloc(sizeof(VariableTable)); + + table->vars = VarMapNew(); + + return table; +} + +void VariableTableDestroy(VariableTable *table) +{ + if (table) + { + VarMapDestroy(table->vars); + free(table); + } +} + +/* NULL return value means variable not found. */ +Variable *VariableTableGet(const VariableTable *table, const VarRef *ref) +{ + Variable *v = VarMapGet(table->vars, ref); + + char *ref_s = VarRefToString(ref, true); /* TODO optimise */ + + if (v != NULL) + { + CF_ASSERT(v->rval.item != NULL || DataTypeIsIterable(v->type), + "VariableTableGet(%s): " + "Only iterables (Rlists) are allowed to be NULL", + ref_s); + } + + if (LogModuleEnabled(LOG_MOD_VARTABLE)) + { + Buffer *buf = BufferNew(); + BufferPrintf(buf, "VariableTableGet(%s): %s", ref_s, + v ? DataTypeToString(v->type) : "NOT FOUND"); + if (v != NULL) + { + char *value_s; + BufferAppendString(buf, " => "); + if (DataTypeIsIterable(v->type) && + v->rval.item == NULL) + { + value_s = xstrdup("EMPTY"); + } + else + { + value_s = RvalToString(v->rval); + } + + BufferAppendString(buf, value_s); + free(value_s); + } + + LogDebug(LOG_MOD_VARTABLE, "%s", BufferGet(buf)); + + BufferDestroy(buf); + } + + free(ref_s); + return v; +} + +bool VariableTableRemove(VariableTable *table, const VarRef *ref) +{ + return VarMapRemove(table->vars, ref); +} + +bool VariableTablePut(VariableTable *table, const VarRef *ref, + const Rval *rval, DataType type, + StringSet *tags, char *comment, + const Promise *promise) +{ + assert(VarRefIsQualified(ref)); + + /* TODO assert there are no CF_NS or '.' in the variable name? */ + + if (LogModuleEnabled(LOG_MOD_VARTABLE)) + { + char *value_s = RvalToString(*rval); + LogDebug(LOG_MOD_VARTABLE, "VariableTablePut(%s): %s => %s", + ref->lval, DataTypeToString(type), + rval->item ? value_s : "EMPTY"); + free(value_s); + } + + CF_ASSERT(rval != NULL || DataTypeIsIterable(type), + "VariableTablePut(): " + "Only iterables (Rlists) are allowed to be NULL"); + + Variable *var = VariableNew(VarRefCopy(ref), RvalCopy(*rval), type, + tags, comment, promise); + return VarMapInsert(table->vars, var->ref, var); +} + +bool VariableTableClear(VariableTable *table, const char *ns, const char *scope, const char *lval) +{ + const size_t vars_num = VarMapSize(table->vars); + + if (!ns && !scope && !lval) + { + VarMapClear(table->vars); + bool has_vars = (vars_num > 0); + return has_vars; + } + + /* We can't remove elements from the hash table while we are iterating + * over it. So we first store the VarRef pointers on a list. */ + + VarRef **to_remove = xmalloc(vars_num * sizeof(*to_remove)); + size_t remove_count = 0; + + { + VariableTableIterator *iter = VariableTableIteratorNew(table, ns, scope, lval); + + for (Variable *v = VariableTableIteratorNext(iter); + v != NULL; + v = VariableTableIteratorNext(iter)) + { + to_remove[remove_count] = v->ref; + remove_count++; + } + VariableTableIteratorDestroy(iter); + } + + if (remove_count == 0) + { + free(to_remove); + return false; + } + + size_t removed = 0; + for(size_t i = 0; i < remove_count; i++) + { + if (VariableTableRemove(table, to_remove[i])) + { + removed++; + } + } + + free(to_remove); + assert(removed == remove_count); + return true; +} + +size_t VariableTableCount(const VariableTable *table, const char *ns, const char *scope, const char *lval) +{ + if (!ns && !scope && !lval) + { + return VarMapSize(table->vars); + } + + VariableTableIterator *iter = VariableTableIteratorNew(table, ns, scope, lval); + size_t count = 0; + while (VariableTableIteratorNext(iter)) + { + count++; + } + VariableTableIteratorDestroy(iter); + return count; +} + +VariableTableIterator *VariableTableIteratorNewFromVarRef(const VariableTable *table, const VarRef *ref) +{ + VariableTableIterator *iter = xmalloc(sizeof(VariableTableIterator)); + + iter->ref = VarRefCopy(ref); + iter->iter = MapIteratorInit(table->vars->impl); + + return iter; +} + +VariableTableIterator *VariableTableIteratorNew(const VariableTable *table, const char *ns, const char *scope, const char *lval) +{ + VarRef ref = { 0 }; + ref.ns = (char *)ns; + ref.scope = (char *)scope; + ref.lval = (char *)lval; + + return VariableTableIteratorNewFromVarRef(table, &ref); +} + +Variable *VariableTableIteratorNext(VariableTableIterator *iter) +{ + MapKeyValue *keyvalue; + + while ((keyvalue = MapIteratorNext(&iter->iter)) != NULL) + { + Variable *var = keyvalue->value; + const char *key_ns = var->ref->ns ? var->ref->ns : "default"; + + if (iter->ref->ns && strcmp(key_ns, iter->ref->ns) != 0) + { + continue; + } + + if (iter->ref->scope && strcmp(var->ref->scope, iter->ref->scope) != 0) + { + continue; + } + + if (iter->ref->lval && strcmp(var->ref->lval, iter->ref->lval) != 0) + { + continue; + } + + if (iter->ref->num_indices > 0) + { + if (iter->ref->num_indices > var->ref->num_indices) + { + continue; + } + + bool match = true; + for (size_t i = 0; i < iter->ref->num_indices; i++) + { + if (strcmp(var->ref->indices[i], iter->ref->indices[i]) != 0) + { + match = false; + break; + } + } + + if (!match) + { + continue; + } + } + + return var; + } + + return NULL; +} + +void VariableTableIteratorDestroy(VariableTableIterator *iter) +{ + if (iter) + { + VarRefDestroy(iter->ref); + free(iter); + } +} diff --git a/libpromises/variable.h b/libpromises/variable.h new file mode 100644 index 0000000000..1c653a85d4 --- /dev/null +++ b/libpromises/variable.h @@ -0,0 +1,83 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ +#ifndef CFENGINE_VARIABLE_H +#define CFENGINE_VARIABLE_H + +#include + +#include + +typedef struct Variable_ Variable; +typedef struct VariableTable_ VariableTable; +typedef struct VariableTableIterator_ VariableTableIterator; + +const VarRef *VariableGetRef(const Variable *var); +DataType VariableGetType(const Variable *var); +RvalType VariableGetRvalType(const Variable *var); +StringSet *VariableGetTags(const Variable *var); +const char *VariableGetComment(const Variable *var); +const Promise *VariableGetPromise(const Variable *var); + +/** + * Get the variable's value. + * + * @param get_secret Whether to get the secret value in case the variable is tagged as secret + * + * @note #get_secret should only be %true if the potentially secret value is + * really required, i.e. being actually used not logged, reported,... + */ +Rval VariableGetRval(const Variable *var, bool get_secret); + +/** + * Set new value of variable. + * + * @note Destroys the old variable's value. + */ +void VariableSetRval(Variable *var, Rval new_rval); + +/** + * Whether the variable is marked as secret or not. + */ +bool VariableIsSecret(const Variable *var); + +VariableTable *VariableTableNew(void); +void VariableTableDestroy(VariableTable *table); + +bool VariableTablePut(VariableTable *table, const VarRef *ref, + const Rval *rval, DataType type, + StringSet *tags, char *comment, const Promise *promise); +Variable *VariableTableGet(const VariableTable *table, const VarRef *ref); +bool VariableTableRemove(VariableTable *table, const VarRef *ref); + +size_t VariableTableCount(const VariableTable *table, const char *ns, const char *scope, const char *lval); +bool VariableTableClear(VariableTable *table, const char *ns, const char *scope, const char *lval); + +VariableTableIterator *VariableTableIteratorNew(const VariableTable *table, const char *ns, const char *scope, const char *lval); +VariableTableIterator *VariableTableIteratorNewFromVarRef(const VariableTable *table, const VarRef *ref); +Variable *VariableTableIteratorNext(VariableTableIterator *iter); +void VariableTableIteratorDestroy(VariableTableIterator *iter); + +VariableTable *VariableTableCopyLocalized(const VariableTable *table, const char *ns, const char *scope); + +#endif diff --git a/libpromises/vars.c b/libpromises/vars.c new file mode 100644 index 0000000000..ad276c120a --- /dev/null +++ b/libpromises/vars.c @@ -0,0 +1,453 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool IsCf3Scalar(char *str); + +/*******************************************************************/ + +bool RlistIsUnresolved(const Rlist *list) +{ + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + // JSON data container values are never expanded, except with the + // data_expand() function which see. + if (rp->val.type == RVAL_TYPE_CONTAINER) + { + continue; + } + + if (rp->val.type != RVAL_TYPE_SCALAR) + { + return true; + } + + if (IsCf3Scalar(RlistScalarValue(rp))) + { + if (strstr(RlistScalarValue(rp), "$(this)") || strstr(RlistScalarValue(rp), "${this}") || + strstr(RlistScalarValue(rp), "$(this.k)") || strstr(RlistScalarValue(rp), "${this.k}") || + strstr(RlistScalarValue(rp), "$(this.k[1])") || strstr(RlistScalarValue(rp), "${this.k[1]}") || + strstr(RlistScalarValue(rp), "$(this.v)") || strstr(RlistScalarValue(rp), "${this.v}")) + { + // We should allow this in function args for substitution in maplist() etc + // We should allow this.k and this.k[1] and this.v in function args for substitution in maparray() etc + } + else + { + return true; + } + } + } + + return false; +} + +/******************************************************************/ + +bool StringContainsVar(const char *s, const char *v) +{ + int vlen = strlen(v); + + if (s == NULL) + { + return false; + } + +/* Look for ${v}, $(v), @{v}, $(v) */ + + for (;;) + { + /* Look for next $ or @ */ + s = strpbrk(s, "$@"); + if (s == NULL) + { + return false; + } + /* If next symbol */ + if (*++s == '\0') + { + return false; + } + /* is { or ( */ + if (*s != '(' && *s != '{') + { + continue; + } + /* Then match the variable starting from next symbol */ + if (strncmp(s + 1, v, vlen) != 0) + { + continue; + } + /* And if it matched, match the closing bracket */ + if ((s[0] == '(' && s[vlen + 1] == ')') || (s[0] == '{' && s[vlen + 1] == '}')) + { + return true; + } + } +} + +/*********************************************************************/ + +bool IsCf3VarString(const char *str) +{ + char left = 'x', right = 'x'; + int dollar = false; + int bracks = 0, vars = 0; + + if (str == NULL) + { + return false; + } + + for (const char *sp = str; *sp != '\0'; sp++) /* check for varitems */ + { + switch (*sp) + { + case '$': + case '@': + if (*(sp + 1) == '{' || *(sp + 1) == '(') + { + dollar = true; + } + break; + case '(': + case '{': + if (dollar) + { + left = *sp; + bracks++; + } + break; + case ')': + case '}': + if (dollar) + { + bracks--; + right = *sp; + } + break; + } + + /* Some chars cannot be in variable ids, e.g. + $(/bin/cat file) is legal in bash */ + + if ((bracks > 0) && (*sp == '/')) + { + return false; + } + + if (left == '(' && right == ')' && dollar && (bracks == 0)) + { + vars++; + dollar = false; + } + + if (left == '{' && right == '}' && dollar && (bracks == 0)) + { + vars++; + dollar = false; + } + } + + if (dollar && (bracks != 0)) + { + char output[CF_BUFSIZE]; + + snprintf(output, CF_BUFSIZE, "Broken variable syntax or bracket mismatch in string (%s)", str); + yyerror(output); + return false; + } + + return (vars != 0); +} + +/*********************************************************************/ + +static bool IsCf3Scalar(char *str) +{ + char *sp; + char left = 'x', right = 'x'; + int dollar = false; + int bracks = 0, vars = 0; + + if (str == NULL) + { + return false; + } + + for (sp = str; *sp != '\0'; sp++) /* check for varitems */ + { + switch (*sp) + { + case '$': + if (*(sp + 1) == '{' || *(sp + 1) == '(') + { + dollar = true; + } + break; + case '(': + case '{': + if (dollar) + { + left = *sp; + bracks++; + } + break; + case ')': + case '}': + if (dollar) + { + bracks--; + right = *sp; + } + break; + } + + /* Some chars cannot be in variable ids, e.g. + $(/bin/cat file) is legal in bash */ + + if ((bracks > 0) && (*sp == '/')) + { + return false; + } + + if (left == '(' && right == ')' && dollar && (bracks == 0)) + { + vars++; + dollar = false; + } + + if (left == '{' && right == '}' && dollar && (bracks == 0)) + { + vars++; + dollar = false; + } + } + + if (dollar && (bracks != 0)) + { + char output[CF_BUFSIZE]; + + snprintf(output, CF_BUFSIZE, "Broken scalar variable syntax or bracket mismatch in '%s'", str); + yyerror(output); + return false; + } + + return (vars != 0); +} + +/* Extract everything up to the dollar sign. */ +size_t ExtractScalarPrefix(Buffer *out, const char *str, size_t len) +{ + assert(str); + if (len == 0) + { + return 0; + } + + const char *dollar_point = NULL; + for (size_t i = 0; i < (len - 1); i++) + { + if (str[i] == '$') + { + if (str[i + 1] == '(' || str[i + 1] == '{') + { + dollar_point = str + i; + break; + } + } + } + + if (!dollar_point) + { + BufferAppend(out, str, len); + return len; + } + else if (dollar_point > str) + { + size_t prefix_len = dollar_point - str; + if (prefix_len > 0) + { + BufferAppend(out, str, prefix_len); + } + return prefix_len; + } + return 0; +} + +static const char *ReferenceEnd(const char *str, size_t len) +{ + assert(len > 1); + assert(str[0] == '$'); + assert(str[1] == '{' || str[1] == '('); + +#define MAX_VARIABLE_REFERENCE_LEVELS 10 + char stack[MAX_VARIABLE_REFERENCE_LEVELS] = { 0, str[1], 0 }; + int level = 1; + + for (size_t i = 2; i < len; i++) + { + switch (str[i]) + { + case '{': + case '(': + if (level < MAX_VARIABLE_REFERENCE_LEVELS - 1) + { + level++; + stack[level] = str[i]; + } + else + { + Log(LOG_LEVEL_ERR, "Stack overflow in variable reference parsing. More than %d levels", MAX_VARIABLE_REFERENCE_LEVELS); + return NULL; + } + break; + + case '}': + if (stack[level] != '{') + { + Log(LOG_LEVEL_ERR, "Variable reference bracket mismatch '%.*s'", + (int) len, str); + return NULL; + } + level--; + break; + case ')': + if (stack[level] != '(') + { + Log(LOG_LEVEL_ERR, "Variable reference bracket mismatch '%.*s'", + (int) len, str); + return NULL; + } + level--; + break; + } + + if (level == 0) + { + return str + i; + } + } + + return NULL; +} + +/** + * Extract variable inside dollar-paren. + * @param extract_inner ignore opening dollar-paren and closing paren. + */ +bool ExtractScalarReference(Buffer *out, const char *str, size_t len, bool extract_inner) +{ + if (len <= 1) + { + return false; + } + + const char *dollar_point = memchr(str, '$', len); + if (!dollar_point || (size_t) (dollar_point - str) == len) + { + return false; + } + else + { + const char *close_point = NULL; + { + size_t remaining = len - (dollar_point - str); + if (*(dollar_point + 1) == '{' || *(dollar_point + 1) == '(') + { + close_point = ReferenceEnd(dollar_point, remaining); + } + else + { + return ExtractScalarReference(out, dollar_point + 1, + remaining - 1, extract_inner); + } + } + + if (!close_point) + { + Log(LOG_LEVEL_ERR, "Variable reference close mismatch '%.*s'", + (int) len, str); + return false; + } + + size_t outer_len = close_point - dollar_point + 1; + if (outer_len <= 3) + { + Log(LOG_LEVEL_ERR, "Empty variable reference close mismatch '%.*s'", + (int) len, str); + return false; + } + + if (extract_inner) + { + BufferAppend(out, dollar_point + 2, outer_len - 3); + } + else + { + BufferAppend(out, dollar_point, outer_len); + } + return true; + } +} + +/*********************************************************************/ + +bool IsQualifiedVariable(const char *var) +{ + int isarraykey = false; + + for (const char *sp = var; *sp != '\0'; sp++) + { + if (*sp == '[') + { + isarraykey = true; + } + + if (isarraykey) + { + return false; + } + else + { + if (*sp == '.') + { + return true; + } + } + } + + return false; +} diff --git a/libpromises/vars.h b/libpromises/vars.h new file mode 100644 index 0000000000..61ab757654 --- /dev/null +++ b/libpromises/vars.h @@ -0,0 +1,39 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_VARS_H +#define CFENGINE_VARS_H + +#include +#include + +size_t ExtractScalarPrefix(Buffer *out, const char *str, size_t len); +bool ExtractScalarReference(Buffer *out, const char *str, size_t len, bool extract_inner); +bool RlistIsUnresolved(const Rlist *args); +bool IsQualifiedVariable(const char *var); + +bool StringContainsVar(const char *s, const char *v); +bool IsCf3VarString(const char *str); + +#endif diff --git a/libpromises/verify_classes.c b/libpromises/verify_classes.c new file mode 100644 index 0000000000..7f903e2eb6 --- /dev/null +++ b/libpromises/verify_classes.c @@ -0,0 +1,403 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* StringHash */ +#include /* StringMatchFull */ + + +static bool EvalClassExpression(EvalContext *ctx, Constraint *cp, const Promise *pp); + +static bool ValidClassName(const char *str) +{ + ParseResult res = ParseExpression(str, 0, strlen(str)); + + if (res.result) + { + FreeExpression(res.result); + } + + assert(res.position >= 0); + return res.result && (size_t) res.position == strlen(str); +} + +PromiseResult VerifyClassPromise(EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param) +{ + assert(pp != NULL); + assert(param == NULL); + + Log(LOG_LEVEL_DEBUG, "Evaluating classes promise: %s", pp->promiser); + + Attributes a = GetClassContextAttributes(ctx, pp); + + if (!StringMatchFull("[a-zA-Z0-9_]+", pp->promiser)) + { + Log(LOG_LEVEL_VERBOSE, "Class identifier '%s' contains illegal characters - canonifying", pp->promiser); + CanonifyNameInPlace(pp->promiser); + } + + if (a.context.nconstraints > 1) + { + cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, "Irreconcilable constraints in classes for '%s'", pp->promiser); + return PROMISE_RESULT_FAIL; + } + + if (a.context.expression == NULL || + EvalClassExpression(ctx, a.context.expression, pp)) + { + if (a.context.expression == NULL) + { + Log(LOG_LEVEL_DEBUG, "Setting class '%s' without an expression, implying 'any'", pp->promiser); + } + + if (!ValidClassName(pp->promiser)) + { + cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, + "Attempted to name a class '%s', which is an illegal class identifier", pp->promiser); + return PROMISE_RESULT_FAIL; + } + else + { + StringSet *tags = StringSetNew(); + StringSetAdd(tags, xstrdup("source=promise")); + + for (const Rlist *rp = PromiseGetConstraintAsList(ctx, "meta", pp); rp; rp = rp->next) + { + StringSetAdd(tags, xstrdup(RlistScalarValue(rp))); + } + + const char *comment = PromiseGetConstraintAsRval(pp, "comment", RVAL_TYPE_SCALAR); + + bool inserted = false; + if (/* Persistent classes are always global: */ + a.context.persistent > 0 || + /* Namespace-scope is global: */ + a.context.scope == CONTEXT_SCOPE_NAMESPACE || + /* If there is no explicit scope, common bundles define global + * classes, other bundles define local classes: */ + (a.context.scope == CONTEXT_SCOPE_NONE && + strcmp(PromiseGetBundle(pp)->type, "common") == 0)) + { + Log(LOG_LEVEL_VERBOSE, "C: + Global class: %s", + pp->promiser); + inserted = EvalContextClassPutSoftTagsSetWithComment(ctx, pp->promiser, + CONTEXT_SCOPE_NAMESPACE, + tags, comment); + } + else + { + Log(LOG_LEVEL_VERBOSE, "C: + Private class: %s", + pp->promiser); + inserted = EvalContextClassPutSoftTagsSetWithComment(ctx, pp->promiser, + CONTEXT_SCOPE_BUNDLE, + tags, comment); + } + + if (a.context.persistent > 0) + { + Log(LOG_LEVEL_VERBOSE, + "C: + Persistent class: '%s' (%d minutes)", + pp->promiser, a.context.persistent); + Buffer *buf = StringSetToBuffer(tags, ','); + EvalContextHeapPersistentSave(ctx, pp->promiser, a.context.persistent, + CONTEXT_STATE_POLICY_RESET, BufferData(buf)); + BufferDestroy(buf); + } + if (inserted && (comment != NULL)) + { + Log(LOG_LEVEL_VERBOSE, "Added class '%s' with comment '%s'", pp->promiser, comment); + } + + + if (!inserted) + { + StringSetDestroy(tags); + } + + return PROMISE_RESULT_NOOP; + } + } + + return PROMISE_RESULT_NOOP; +} + +static bool SelectClass(EvalContext *ctx, const Rlist *list, const Promise *pp) +{ + int count = RlistLen(list); + + if (count == 0) + { + Log(LOG_LEVEL_ERR, "No classes to select on RHS"); + PromiseRef(LOG_LEVEL_ERR, pp); + return false; + } + else if (count == 1 && IsVarList(RlistScalarValue(list))) + { + Log(LOG_LEVEL_VERBOSE, + "select_class: Can not expand list '%s' for setting class.", + RlistScalarValue(list)); + PromiseRef(LOG_LEVEL_VERBOSE, pp); + return false; + } + + assert(list); + + // Max: (strlen of VFQNAME) (strlen of VIPADDRESS) (strlen of max 64 bit integer ) '++\0' + char splay[sizeof(VFQNAME) - 1 + sizeof(VIPADDRESS) - 1 + sizeof("18446744073709551615") - 1 + 2 + 1]; + snprintf(splay, sizeof(splay), "%s+%s+%ju", VFQNAME, VIPADDRESS, (uintmax_t)getuid()); + unsigned int hash = StringHash(splay, 0); + int n = hash % count; + + while (n > 0 && list->next != NULL) + { + n--; + list = list->next; + } + + /* We are not having expanded variable or list at this point, + * so we can not set select_class. */ + if (IsExpandable(RlistScalarValue(list))) + { + Log(LOG_LEVEL_VERBOSE, + "select_class: Can not use not expanded element '%s' for setting class.", + RlistScalarValue(list)); + PromiseRef(LOG_LEVEL_VERBOSE, pp); + return false; + } + + EvalContextClassPutSoft(ctx, RlistScalarValue(list), + CONTEXT_SCOPE_NAMESPACE, "source=promise"); + return true; +} + +static bool DistributeClass(EvalContext *ctx, const Rlist *dist, const Promise *pp) +{ + int total = 0; + const Rlist *rp; + + for (rp = dist; rp != NULL; rp = rp->next) + { + int result = IntFromString(RlistScalarValue(rp)); + + if (result < 0) + { + Log(LOG_LEVEL_ERR, "Negative integer in class distribution"); + PromiseRef(LOG_LEVEL_ERR, pp); + return false; + } + + total += result; + } + + if (total == 0) + { + Log(LOG_LEVEL_ERR, "An empty distribution was specified on RHS"); + PromiseRef(LOG_LEVEL_ERR, pp); + return false; + } + + double fluct = drand48() * total; + assert(0 <= fluct && fluct < total); + + for (rp = dist; rp != NULL; rp = rp->next) + { + fluct -= IntFromString(RlistScalarValue(rp)); + if (fluct < 0) + { + break; + } + } + assert(rp); + + char buffer[CF_MAXVARSIZE]; + snprintf(buffer, CF_MAXVARSIZE, "%s_%s", pp->promiser, RlistScalarValue(rp)); + + if (strcmp(PromiseGetBundle(pp)->type, "common") == 0) + { + EvalContextClassPutSoft(ctx, buffer, CONTEXT_SCOPE_NAMESPACE, + "source=promise"); + } + else + { + EvalContextClassPutSoft(ctx, buffer, CONTEXT_SCOPE_BUNDLE, + "source=promise"); + } + + return true; +} + +enum combine_t { c_or, c_and, c_xor }; // Class combinations +static bool EvalBoolCombination(EvalContext *ctx, const Rlist *list, + enum combine_t logic) +{ + bool result = (logic == c_and); + + for (const Rlist *rp = list; rp != NULL; rp = rp->next) + { + // tolerate unexpanded entries here and interpret as "class not set" + bool here = (rp->val.type == RVAL_TYPE_SCALAR && + IsDefinedClass(ctx, RlistScalarValue(rp))); + + // shortcut "and" and "or" + switch (logic) + { + case c_or: + if (here) + { + return true; + } + break; + + case c_and: + if (!here) + { + return false; + } + break; + + default: + result ^= here; + break; + } + } + + return result; +} + +static bool EvalClassExpression(EvalContext *ctx, Constraint *cp, const Promise *pp) +{ + assert(pp); + + if (cp == NULL) // ProgrammingError ? We'll crash RSN anyway ... + { + Log(LOG_LEVEL_ERR, + "EvalClassExpression internal diagnostic discovered an ill-formed condition"); + } + + if (!IsDefinedClass(ctx, pp->classes)) + { + return false; + } + + if (IsDefinedClass(ctx, pp->promiser)) + { + if (PromiseGetConstraintAsInt(ctx, "persistence", pp) == 0) + { + Log(LOG_LEVEL_VERBOSE, + " ?> Cancelling cached persistent class %s", + pp->promiser); + EvalContextHeapPersistentRemove(pp->promiser); + } + return false; + } + + switch (cp->rval.type) + { + Rval rval; + FnCall *fp; + + case RVAL_TYPE_FNCALL: + fp = RvalFnCallValue(cp->rval); + /* Special expansion of functions for control, best effort only: */ + FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp); + + FnCallDestroy(fp); + cp->rval = res.rval; + break; + + case RVAL_TYPE_LIST: + for (Rlist *rp = cp->rval.item; rp != NULL; rp = rp->next) + { + rval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL, + "this", rp->val, true, pp); + RvalDestroy(rp->val); + rp->val = rval; + } + break; + + default: + rval = ExpandPrivateRval(ctx, NULL, "this", cp->rval.item, cp->rval.type); + RvalDestroy(cp->rval); + cp->rval = rval; + break; + } + + if (strcmp(cp->lval, "expression") == 0) + { + return (cp->rval.type == RVAL_TYPE_SCALAR && + IsDefinedClass(ctx, RvalScalarValue(cp->rval))); + } + + if (strcmp(cp->lval, "not") == 0) + { + return (cp->rval.type == RVAL_TYPE_SCALAR && + !IsDefinedClass(ctx, RvalScalarValue(cp->rval))); + } + + /* If we get here, anything remaining on the RHS must be a clist */ + if (cp->rval.type != RVAL_TYPE_LIST) + { + Log(LOG_LEVEL_ERR, "RHS of promise body attribute '%s' is not a list", cp->lval); + PromiseRef(LOG_LEVEL_ERR, pp); + return true; + } + + // Class selection + if (strcmp(cp->lval, "select_class") == 0) + { + return SelectClass(ctx, cp->rval.item, pp); + } + + // Class distributions + if (strcmp(cp->lval, "dist") == 0) + { + return DistributeClass(ctx, cp->rval.item, pp); + } + + /* Combine with and/or/xor: */ + if (strcmp(cp->lval, "or") == 0) + { + return EvalBoolCombination(ctx, cp->rval.item, c_or); + } + else if (strcmp(cp->lval, "and") == 0) + { + return EvalBoolCombination(ctx, cp->rval.item, c_and); + } + else if (strcmp(cp->lval, "xor") == 0) + { + return EvalBoolCombination(ctx, cp->rval.item, c_xor); + } + + return false; +} diff --git a/libpromises/verify_classes.h b/libpromises/verify_classes.h new file mode 100644 index 0000000000..fb11ec6cb2 --- /dev/null +++ b/libpromises/verify_classes.h @@ -0,0 +1,33 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_VERIFY_CLASSES_H +#define CFENGINE_VERIFY_CLASSES_H + +#include +#include + +PromiseResult VerifyClassPromise(EvalContext *ctx, const Promise *pp, void *param); + +#endif diff --git a/libpromises/verify_reports.c b/libpromises/verify_reports.c new file mode 100644 index 0000000000..e9fe09cf88 --- /dev/null +++ b/libpromises/verify_reports.c @@ -0,0 +1,231 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool PrintFile(const char *filename, ssize_t max_lines); +static void ReportToFile(const char *logfile, const char *message); +static void ReportToLog(const char *message); + +PromiseResult VerifyReportPromise(EvalContext *ctx, const Promise *pp) +{ + assert(pp != NULL); + + /* This check needs to happen *before* the lock is acquired otherwise the + * promise would be skipped in the next evaluation pass and a report with an + * unresolved variable reference would never be shown. */ + if ((EvalContextGetPass(ctx) < (CF_DONEPASSES - 1)) && IsCf3VarString(pp->promiser)) + { + /* Unresolved variable reference in the string to be reported and there + * is still a chance it will get resolved later. */ + return PROMISE_RESULT_SKIPPED; + } + + CfLock thislock; + char unique_name[CF_EXPANDSIZE]; + + Attributes a = GetReportsAttributes(ctx, pp); + + // We let AcquireLock worry about making a unique name + snprintf(unique_name, CF_EXPANDSIZE - 1, "%s", pp->promiser); + thislock = AcquireLock(ctx, unique_name, VUQNAME, CFSTARTTIME, a.transaction.ifelapsed, a.transaction.expireafter, pp, false); + + // Handle return values before locks, as we always do this + + if (a.report.result) + { + // User-unwritable value last-result contains the useresult + if (strlen(a.report.result) > 0) + { + snprintf(unique_name, CF_BUFSIZE, "last-result[%s]", a.report.result); + } + else + { + snprintf(unique_name, CF_BUFSIZE, "last-result"); + } + + VarRef *ref = VarRefParseFromBundle(unique_name, PromiseGetBundle(pp)); + EvalContextVariablePut(ctx, ref, pp->promiser, CF_DATA_TYPE_STRING, "source=bundle"); + VarRefDestroy(ref); + + if (thislock.lock) + { + YieldCurrentLock(thislock); + } + return PROMISE_RESULT_NOOP; + } + + if (thislock.lock == NULL) + { + return PROMISE_RESULT_SKIPPED; + } + + PromiseBanner(ctx, pp); + + if (DONTDO || (a.transaction.action == cfa_warn)) + { + cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, &a, "Need to repair reports promise: %s", pp->promiser); + YieldCurrentLock(thislock); + return PROMISE_RESULT_WARN; + } + + if (a.report.to_file) + { + ReportToFile(a.report.to_file, pp->promiser); + } + else + { + ReportToLog(pp->promiser); + } + + PromiseResult result = PROMISE_RESULT_NOOP; + if (a.report.haveprintfile) + { + if (!PrintFile(a.report.filename, a.report.numlines)) + { + result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); + } + } + + YieldCurrentLock(thislock); + + ClassAuditLog(ctx, pp, &a, result); + return result; +} + +static void ReportToLog(const char *message) +{ + char *report_message; + xasprintf(&report_message, "R: %s", message); + + fputs(report_message, stdout); + fputc('\n', stdout); + LogToSystemLog(report_message, LOG_LEVEL_NOTICE); + + free(report_message); +} + +static void ReportToFile(const char *logfile, const char *message) +{ + FILE *fp = safe_fopen_create_perms(logfile, "a", CF_PERMS_DEFAULT); + if (!fp) + { + Log(LOG_LEVEL_ERR, "Could not open log file '%s', message '%s'. (fopen: %s)", logfile, message, GetErrorStr()); + } + else + { + fprintf(fp, "%s\n", message); + fclose(fp); + } +} + +static bool PrintFile(const char *filename, ssize_t max_lines) +{ + if (!filename) + { + Log(LOG_LEVEL_VERBOSE, + "Printfile promise was incomplete, with no filename."); + return false; + } + + FILE *fp = safe_fopen(filename, "r"); + if (!fp) + { + Log(LOG_LEVEL_ERR, + "Printing of file '%s' was not possible. (fopen: %s)", + filename, GetErrorStr()); + return false; + } + + size_t line_size = CF_BUFSIZE; + char *line = xmalloc(line_size); + + ssize_t skip_lines = 0; + if (max_lines < 0) + { + skip_lines = max_lines; + max_lines = ABS(max_lines); + + while (CfReadLine(&line, &line_size, fp) != -1) + { + skip_lines++; + } + if (ferror(fp)) + { + Log(LOG_LEVEL_ERR, + "Failed to read line from stream, (getline: %s)", + GetErrorStr()); + free(line); + return false; + } + rewind(fp); + } + + for (ssize_t i = 0; i < skip_lines + max_lines; i++) + { + if (CfReadLine(&line, &line_size, fp) == -1) + { + if (ferror(fp)) + { + Log(LOG_LEVEL_ERR, + "Failed to read line from stream, (getline: %s)", + GetErrorStr()); + free(line); + return false; + } + else + { + break; + } + } + if (i >= skip_lines) + { + ReportToLog(line); + } + } + + fclose(fp); + free(line); + + return true; +} diff --git a/libpromises/verify_vars.c b/libpromises/verify_vars.c new file mode 100644 index 0000000000..234aa06107 --- /dev/null +++ b/libpromises/verify_vars.c @@ -0,0 +1,748 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include +#include +#include /* CompileRegex,StringMatchFullWithPrecompiledRegex */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct +{ + bool should_converge; + bool ok_redefine; + bool drop_undefined; + Constraint *cp_save; // e.g. string => "foo" +} ConvergeVariableOptions; + + +static ConvergeVariableOptions CollectConvergeVariableOptions(EvalContext *ctx, const Promise *pp); +static bool Epimenides(EvalContext *ctx, const char *ns, const char *scope, const char *var, Rval rval, int level); +static bool CompareRval(const void *rval1_item, RvalType rval1_type, const void *rval2_item, RvalType rval2_type); + +static bool IsLegalVariableName(EvalContext *ctx, const Promise *pp) +{ + const char *var_name = pp->promiser; + + /* TODO: remove at some point (global, leaked), but for now + * this offers an attractive speedup. */ + static Regex *rx = NULL; + if (!rx) + { + /* \200-\377 is there for multibyte unicode characters */ + rx = CompileRegex("[a-zA-Z0-9_\200-\377.]+(\\[.+\\])*"); /* Known leak, see TODO. */ + } + + if (!StringMatchFullWithPrecompiledRegex(rx, var_name)) + { + return false; + } + + char *bracket = strchr(var_name, '['); + char *dot = strchr(var_name, '.'); + /* we only care about variable name prefix, not dots inside array keys */ + if ((dot != NULL) && ((bracket == NULL) || (dot < bracket))) + { + /* detect and prevent remote bundle variable injection (CFE-1915) */ + char *prefix = xstrndup(var_name, dot - var_name); + const Bundle *cur_bundle = PromiseGetBundle(pp); + + if (!StringEqual(prefix, cur_bundle->name)) + { + Log(LOG_LEVEL_VERBOSE, + "Variable '%s' may be attempted to be injected into a remote bundle", + var_name); + if (StringSetContains(EvalContextGetBundleNames(ctx), prefix)) + { + Log(LOG_LEVEL_ERR, "Remote bundle variable injection detected!"); + free(prefix); + return false; + } + /* the conflicting bundle may be defined later, we need to remember + * this promise as potentially dangerous */ + EvalContextPushRemoteVarPromise(ctx, prefix, pp->org_pp); + } + free(prefix); + } + + return true; +} + +// TODO why not printing that new definition is skipped? +// TODO what with ifdefined? + +PromiseResult VerifyVarPromise(EvalContext *ctx, const Promise *pp, + ARG_UNUSED void *param) +{ + assert(pp != NULL); + + ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp); + + Log(LOG_LEVEL_DEBUG, "Evaluating vars promise: %s", pp->promiser); + LogDebug(LOG_MOD_VARS, + "ok_redefine=%d, drop_undefined=%d, should_converge=%d", + opts.ok_redefine, opts.drop_undefined, opts.should_converge); + + if (!opts.should_converge) + { + LogDebug(LOG_MOD_VARS, + "Skipping vars promise because should_converge=false"); + return PROMISE_RESULT_NOOP; + } + +// opts.drop_undefined = true; /* always remove @{unresolved_list} */ + + Attributes a = ZeroAttributes; + // More consideration needs to be given to using these + //a.transaction = GetTransactionConstraints(pp); + + /* Warn if promise locking was used with a promise that doesn't support it + * (which applies to all of 'vars', 'meta' and 'defaults' promises handled + * by this code). + * 'ifelapsed => "0"' (e.g. with 'action => immediate') can however be used + * to make sure cached functions are called every time. [ENT-7478] + * (Only do this in the first pass in cf-promises, we don't have to repeat + * the warning over and over.) */ + if (EvalContextGetPass(ctx) == 0 && THIS_AGENT_TYPE == AGENT_TYPE_COMMON) + { + int ifelapsed = PromiseGetConstraintAsInt(ctx, "ifelapsed", pp); + if ((ifelapsed != CF_NOINT) && + ((ifelapsed != 0) || !StringEqual(PromiseGetPromiseType(pp), "vars"))) + { + Log(LOG_LEVEL_WARNING, + "ifelapsed attribute specified in action body for %s promise '%s'," + " but %s promises do not support promise locking", + PromiseGetPromiseType(pp), pp->promiser, + PromiseGetPromiseType(pp)); + } + int expireafter = PromiseGetConstraintAsInt(ctx, "expireafter", pp); + if (expireafter != CF_NOINT) + { + Log(LOG_LEVEL_WARNING, + "expireafter attribute specified in action body for %s promise '%s'," + " but %s promises do not support promise locking", + PromiseGetPromiseType(pp), pp->promiser, + PromiseGetPromiseType(pp)); + } + } + + a.classes = GetClassDefinitionConstraints(ctx, pp); + + VarRef *ref = VarRefParseFromBundle(pp->promiser, PromiseGetBundle(pp)); + if (strcmp("meta", PromiseGetPromiseType(pp)) == 0) + { + VarRefSetMeta(ref, true); + } + + DataType existing_value_type = CF_DATA_TYPE_NONE; + const void *existing_value; + if (IsExpandable(pp->promiser)) + { + existing_value = NULL; + } + else + { + existing_value = EvalContextVariableGet(ctx, ref, &existing_value_type); + } + + Rval rval = opts.cp_save->rval; + PromiseResult result; + + if (rval.item != NULL || rval.type == RVAL_TYPE_LIST) + { + DataType data_type = DataTypeFromString(opts.cp_save->lval); + + if (opts.cp_save->rval.type == RVAL_TYPE_FNCALL) + { + FnCall *fp = RvalFnCallValue(rval); + const FnCallType *fn = FnCallTypeGet(fp->name); + if (!fn) + { + assert(false && "Canary: should have been caught before this point"); + FatalError(ctx, "While setting variable '%s' in bundle '%s', unknown function '%s'", + pp->promiser, PromiseGetBundle(pp)->name, fp->name); + } + + if (fn->dtype != DataTypeFromString(opts.cp_save->lval)) + { + FatalError(ctx, "While setting variable '%s' in bundle '%s', variable declared type '%s' but function '%s' returns type '%s'", + pp->promiser, PromiseGetBundle(pp)->name, opts.cp_save->lval, + fp->name, DataTypeToString(fn->dtype)); + } + + if (existing_value_type != CF_DATA_TYPE_NONE) + { + // Already did this + VarRefDestroy(ref); + return PROMISE_RESULT_NOOP; + } + + FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp); + + if (res.status == FNCALL_FAILURE) + { + /* We do not assign variables to failed fn calls */ + if (EvalContextGetPass(ctx) == CF_DONEPASSES-1) { + // If we still fail at last pass, make a log + Log(LOG_LEVEL_VERBOSE, "While setting variable '%s' in bundle '%s', function '%s' failed - skipping", + pp->promiser, PromiseGetBundle(pp)->name, fp->name); + } + RvalDestroy(res.rval); + VarRefDestroy(ref); + return PROMISE_RESULT_NOOP; + } + else + { + rval = res.rval; + } + } + else + { + Buffer *conv = BufferNew(); + bool malformed = false, misprint = false; + + if (strcmp(opts.cp_save->lval, "int") == 0) + { + long int asint = IntFromString(opts.cp_save->rval.item); + if (asint == CF_NOINT) + { + malformed = true; + } + else if (0 > BufferPrintf(conv, "%ld", asint)) + { + misprint = true; + } + else + { + rval = RvalNew(BufferData(conv), opts.cp_save->rval.type); + } + } + else if (strcmp(opts.cp_save->lval, "real") == 0) + { + double real_value; + if (!DoubleFromString(opts.cp_save->rval.item, &real_value)) + { + malformed = true; + } + else if (0 > BufferPrintf(conv, "%lf", real_value)) + { + misprint = true; + } + else + { + rval = RvalNew(BufferData(conv), opts.cp_save->rval.type); + } + } + else + { + rval = RvalCopy(opts.cp_save->rval); + } + BufferDestroy(conv); + + if (malformed) + { + /* Arises when opts->cp_save->rval.item isn't yet expanded. */ + /* Has already been logged by *FromString */ + VarRefDestroy(ref); + return PROMISE_RESULT_FAIL; + } + else if (misprint) + { + /* Even though no problems with memory allocation can + * get here, there might be other problems. */ + UnexpectedError("Problems writing to buffer"); + VarRefDestroy(ref); + return PROMISE_RESULT_FAIL; + } + else if (rval.type == RVAL_TYPE_LIST) + { + Rlist *rval_list = RvalRlistValue(rval); + RlistFlatten(ctx, &rval_list); + rval.item = rval_list; + } + } + + if (Epimenides(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, pp->promiser, rval, 0)) + { + Log(LOG_LEVEL_ERR, "Variable '%s' contains itself indirectly - an unkeepable promise", pp->promiser); + DoCleanupAndExit(EXIT_FAILURE); + } + else + { + /* See if the variable needs recursively expanding again */ + + Rval returnval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), ref->ns, ref->scope, rval, true, pp); + + RvalDestroy(rval); + + // freed before function exit + rval = returnval; + } + + /* If variable did resolve but we're not allowed to modify it. */ + /* ok_redefine: only on second iteration, else we ignore broken promises. TODO wat? */ + if (existing_value_type != CF_DATA_TYPE_NONE && + !opts.ok_redefine) + { + if (!CompareRval(existing_value, DataTypeToRvalType(existing_value_type), + rval.item, rval.type)) + { + switch (rval.type) + { + /* TODO redefinition shouldn't be mentioned. Maybe handle like normal variable definition/ */ + case RVAL_TYPE_SCALAR: + Log(LOG_LEVEL_VERBOSE, "V: Skipping redefinition of constant scalar '%s': from '%s' to '%s'", + pp->promiser, (const char *)existing_value, RvalScalarValue(rval)); + PromiseRef(LOG_LEVEL_VERBOSE, pp); + break; + + case RVAL_TYPE_LIST: + { + Log(LOG_LEVEL_VERBOSE, "V: Skipping redefinition of constant list '%s'", pp->promiser); + Writer *w = StringWriter(); + RlistWrite(w, existing_value); + char *oldstr = StringWriterClose(w); + Log(LOG_LEVEL_DEBUG, "Old value: %s", oldstr); + free(oldstr); + + w = StringWriter(); + RlistWrite(w, rval.item); + char *newstr = StringWriterClose(w); + Log(LOG_LEVEL_DEBUG, "Skipped new value: %s", newstr); + free(newstr); + + PromiseRef(LOG_LEVEL_VERBOSE, pp); + } + break; + + case RVAL_TYPE_CONTAINER: + case RVAL_TYPE_FNCALL: + case RVAL_TYPE_NOPROMISEE: + break; + } + } + + RvalDestroy(rval); + VarRefDestroy(ref); + return PROMISE_RESULT_NOOP; + } + + if (IsCf3VarString(pp->promiser)) + { + // Unexpanded variables, we don't do anything with + RvalDestroy(rval); + VarRefDestroy(ref); + return PROMISE_RESULT_NOOP; + } + + if (!IsLegalVariableName(ctx, pp)) + { + Log(LOG_LEVEL_ERR, "Variable identifier '%s' is not legal", pp->promiser); + PromiseRef(LOG_LEVEL_ERR, pp); + RvalDestroy(rval); + VarRefDestroy(ref); + return PROMISE_RESULT_NOOP; + } + + if (rval.type == RVAL_TYPE_LIST) + { + if (opts.drop_undefined) + { + Rlist *stripped = RvalRlistValue(rval); + Rlist *entry = stripped; + while (entry) + { + Rlist *delete_me = NULL; + if (IsNakedVar(RlistScalarValue(entry), '@')) + { + delete_me = entry; + } + entry = entry->next; + RlistDestroyEntry(&stripped, delete_me); + } + rval.item = stripped; + } + + for (const Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next) + { + if (rp->val.type != RVAL_TYPE_SCALAR) + { + // Cannot assign variable because value is a list containing a non-scalar item + VarRefDestroy(ref); + RvalDestroy(rval); + return PROMISE_RESULT_NOOP; + } + } + } + + if (ref->num_indices > 0) + { + if (data_type == CF_DATA_TYPE_CONTAINER) + { + char *lval_str = VarRefToString(ref, true); + Log(LOG_LEVEL_ERR, "Cannot assign a container to an indexed variable name '%s'. Should be assigned to '%s' instead", + lval_str, ref->lval); + free(lval_str); + VarRefDestroy(ref); + RvalDestroy(rval); + return PROMISE_RESULT_NOOP; + } + else + { + DataType existing_type; + VarRef *base_ref = VarRefCopyIndexless(ref); + if (EvalContextVariableGet(ctx, ref, &existing_type) && existing_type == CF_DATA_TYPE_CONTAINER) + { + char *lval_str = VarRefToString(ref, true); + char *base_ref_str = VarRefToString(base_ref, true); + Log(LOG_LEVEL_ERR, "Cannot assign value to indexed variable name '%s', because a container is already assigned to the base name '%s'", + lval_str, base_ref_str); + free(lval_str); + free(base_ref_str); + VarRefDestroy(base_ref); + VarRefDestroy(ref); + RvalDestroy(rval); + return PROMISE_RESULT_NOOP; + } + VarRefDestroy(base_ref); + } + } + + + DataType required_datatype = DataTypeFromString(opts.cp_save->lval); + if (rval.type != DataTypeToRvalType(required_datatype)) + { + char *ref_str = VarRefToString(ref, true); + char *value_str = RvalToString(rval); + Log(LOG_LEVEL_ERR, "Variable '%s' expected a variable of type '%s', but was given incompatible value '%s'", + ref_str, DataTypeToString(required_datatype), value_str); + PromiseRef(LOG_LEVEL_ERR, pp); + + free(ref_str); + free(value_str); + VarRefDestroy(ref); + RvalDestroy(rval); + return PROMISE_RESULT_FAIL; + } + + StringSet *tags = StringSetNew(); + StringSetAdd(tags, xstrdup("source=promise")); + + Rlist *promise_meta = PromiseGetConstraintAsList(ctx, "meta", pp); + if (promise_meta) + { + Buffer *print; + for (const Rlist *rp = promise_meta; rp; rp = rp->next) + { + StringSetAdd(tags, xstrdup(RlistScalarValue(rp))); + if (WouldLog(LOG_LEVEL_DEBUG)) + { + print = StringSetToBuffer(tags, ','); + Log(LOG_LEVEL_DEBUG, + "Added tag %s to variable %s tags (now [%s])", + RlistScalarValue(rp), pp->promiser, BufferData(print)); + BufferDestroy(print); + } + } + } + + const char *comment = PromiseGetConstraintAsRval(pp, "comment", RVAL_TYPE_SCALAR); + + /* WRITE THE VARIABLE AT LAST. */ + bool success = EvalContextVariablePutTagsSetWithComment(ctx, ref, rval.item, required_datatype, + tags, comment); + if (success && (comment != NULL)) + { + Log(LOG_LEVEL_VERBOSE, "Added variable '%s' with comment '%s'", + pp->promiser, comment); + } + + if (!success) + { + Log(LOG_LEVEL_VERBOSE, + "Unable to converge %s.%s value (possibly empty or infinite regression)", + ref->scope, pp->promiser); + PromiseRef(LOG_LEVEL_VERBOSE, pp); + + VarRefDestroy(ref); + RvalDestroy(rval); + StringSetDestroy(tags); + return PROMISE_RESULT_FAIL; + } + + result = PROMISE_RESULT_NOOP; + } + else + { + Log(LOG_LEVEL_ERR, "Variable %s has no promised value", pp->promiser); + Log(LOG_LEVEL_ERR, "Rule from %s at/before line %zu", PromiseGetBundle(pp)->source_path, opts.cp_save->offset.line); + result = PROMISE_RESULT_FAIL; + } + + /* + * FIXME: Variable promise are exempt from normal evaluation logic still, so + * they are not pushed to evaluation stack before being evaluated. Due to + * this reason, we cannot call cfPS here to set classes, as it will error + * out with ProgrammingError. + * + * In order to support 'classes' body for variables as well, we call + * ClassAuditLog explicitly. + */ + ClassAuditLog(ctx, pp, &a, result); + + VarRefDestroy(ref); + RvalDestroy(rval); + + return result; +} + +static bool CompareRval(const void *rval1_item, RvalType rval1_type, + const void *rval2_item, RvalType rval2_type) +{ + if (rval1_type != rval2_type) + { + return false; + } + + switch (rval1_type) + { + case RVAL_TYPE_SCALAR: + + if (IsCf3VarString(rval1_item) || IsCf3VarString(rval2_item)) + { + return false; // inconclusive + } + + if (strcmp(rval1_item, rval2_item) != 0) + { + return false; + } + + break; + + case RVAL_TYPE_LIST: + return RlistEqual(rval1_item, rval2_item); + + case RVAL_TYPE_FNCALL: + return false; + + default: + return false; + } + + return true; +} + +static bool Epimenides(EvalContext *ctx, const char *ns, const char *scope, const char *var, Rval rval, int level) +{ + switch (rval.type) + { + case RVAL_TYPE_SCALAR: + + if (StringContainsVar(RvalScalarValue(rval), var)) + { + Log(LOG_LEVEL_ERR, "Scalar variable '%s' contains itself (non-convergent) '%s'", var, RvalScalarValue(rval)); + return true; + } + + if (IsCf3VarString(RvalScalarValue(rval))) + { + Buffer *exp = BufferNew(); + ExpandScalar(ctx, ns, scope, RvalScalarValue(rval), exp); + + if (strcmp(BufferData(exp), RvalScalarValue(rval)) == 0) + { + BufferDestroy(exp); + return false; + } + + if (level > 3) + { + BufferDestroy(exp); + return false; + } + + if (Epimenides(ctx, ns, scope, var, (Rval) { BufferGet(exp), RVAL_TYPE_SCALAR}, level + 1)) + { + BufferDestroy(exp); + return true; + } + + BufferDestroy(exp); + } + + break; + + case RVAL_TYPE_LIST: + for (const Rlist *rp = RvalRlistValue(rval); rp != NULL; rp = rp->next) + { + if (Epimenides(ctx, ns, scope, var, rp->val, level)) + { + return true; + } + } + break; + + case RVAL_TYPE_CONTAINER: + case RVAL_TYPE_FNCALL: + case RVAL_TYPE_NOPROMISEE: + return false; + } + + return false; +} + +/** + * @brief Collects variable constraints controlling how the promise should be converged + */ +static ConvergeVariableOptions CollectConvergeVariableOptions(EvalContext *ctx, const Promise *pp) +{ + ConvergeVariableOptions opts; + opts.drop_undefined = false; + opts.cp_save = NULL; /* main variable value */ + /* By default allow variable redefinition, use "policy" constraint + * to override. */ + opts.ok_redefine = true; + /* Main return value: becomes true at the end of the function. */ + opts.should_converge = false; + + if (!IsDefinedClass(ctx, pp->classes)) + { + return opts; + } + + int num_values = 0; + for (size_t i = 0; i < SeqLength(pp->conlist); i++) + { + Constraint *cp = SeqAt(pp->conlist, i); + + if (strcmp(cp->lval, "comment") == 0) + { + // Comments don't affect convergence + // Unclear why this is in the constraint list in the first place? + continue; + } + else if (cp->rval.item == NULL && cp->rval.type != RVAL_TYPE_LIST) + { + // No right value, considered empty + continue; + } + else if (strcmp(cp->lval, "ifvarclass") == 0 || + strcmp(cp->lval, "if") == 0) + { + switch (cp->rval.type) + { + case RVAL_TYPE_SCALAR: + if (!IsDefinedClass(ctx, cp->rval.item)) + { + return opts; + } + + break; + + case RVAL_TYPE_FNCALL: + { + bool excluded = false; + + /* eval it: e.g. ifvarclass => not("a_class") */ + + Rval res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), cp->rval.item, pp).rval; + + /* Don't continue unless function was evaluated properly */ + if (res.type != RVAL_TYPE_SCALAR) + { + RvalDestroy(res); + return opts; + } + + excluded = !IsDefinedClass(ctx, res.item); + + RvalDestroy(res); + + if (excluded) + { + return opts; + } + } + break; + + default: + Log(LOG_LEVEL_ERR, "Invalid if/ifvarclass type '%c': should be string or function", cp->rval.type); + } + } + else if (strcmp(cp->lval, "policy") == 0) + { + if (strcmp(cp->rval.item, "ifdefined") == 0) + { + opts.drop_undefined = true; + } + else if (strcmp(cp->rval.item, "constant") == 0) + { + opts.ok_redefine = false; + } + } + else if (DataTypeFromString(cp->lval) != CF_DATA_TYPE_NONE) + { + num_values++; + opts.cp_save = cp; + } + } + + if (opts.cp_save == NULL) + { + Log(LOG_LEVEL_WARNING, "Incomplete vars promise: %s", + pp->promiser); + PromiseRef(LOG_LEVEL_INFO, pp); + return opts; + } + + if (num_values > 2) + { + Log(LOG_LEVEL_ERR, + "Variable '%s' breaks its own promise with multiple (%d) values", + pp->promiser, num_values); + PromiseRef(LOG_LEVEL_ERR, pp); + return opts; + } + + /* All constraints look OK, and classes are defined. Move forward with + * this promise. */ + opts.should_converge = true; + + return opts; +} diff --git a/libpromises/verify_vars.h b/libpromises/verify_vars.h new file mode 100644 index 0000000000..b8445bf38f --- /dev/null +++ b/libpromises/verify_vars.h @@ -0,0 +1,36 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_VERIFY_VARS_H +#define CFENGINE_VERIFY_VARS_H + + +#include +#include + + +PromiseResult VerifyVarPromise(EvalContext *ctx, const Promise *pp, void *param); + + +#endif diff --git a/m4/acinclude.m4 b/m4/acinclude.m4 new file mode 100644 index 0000000000..acae012a77 --- /dev/null +++ b/m4/acinclude.m4 @@ -0,0 +1,207 @@ +dnl From http://ac-archive.sourceforge.net/ac-archive/acx_pthread.html + +AC_DEFUN([ACX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_SAVE +AC_LANG_C +acx_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes) + AC_MSG_RESULT($acx_pthread_ok) + if test x"$acx_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mt -mthreads pthread --thread-safe pthread-config pthreadGC2" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + acx_pthread_flags="-pthreads pthread -mt -pthread $acx_pthread_flags" + ;; +esac + +if test x"$acx_pthread_ok" = xno; then +for flag in $acx_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no) + if test x"$acx_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [acx_pthread_ok=yes]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($acx_pthread_ok) + if test "x$acx_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$acx_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_TRY_LINK([#include ], [int attr=$attr; return attr;], + [attr_name=$attr; break]) + done + AC_MSG_RESULT($attr_name) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; + *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with xlc_r or cc_r + if test x"$GCC" != xyes; then + AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) + else + PTHREAD_CC=$CC + fi +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$acx_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + acx_pthread_ok=no + $2 +fi +AC_LANG_RESTORE +])dnl ACX_PTHREAD + +m4_ifdef([AC_PROG_MKDIR_P], [ + dnl For automake-1.9.6 && autoconf < 2.62: Ensure MKDIR_P is AC_SUBSTed. + m4_define([AC_PROG_MKDIR_P], + m4_defn([AC_PROG_MKDIR_P])[ + AC_SUBST([MKDIR_P])])], [ + dnl For autoconf < 2.60: Backport of AC_PROG_MKDIR_P. + AC_DEFUN([AC_PROG_MKDIR_P], + [AC_REQUIRE([AM_PROG_MKDIR_P])dnl defined by automake + MKDIR_P='$(mkdir_p)' + AC_SUBST([MKDIR_P])])]) diff --git a/m4/adl_recursive_eval.m4 b/m4/adl_recursive_eval.m4 new file mode 100644 index 0000000000..56bf672cf0 --- /dev/null +++ b/m4/adl_recursive_eval.m4 @@ -0,0 +1,15 @@ +dnl adl_RECURSIVE_EVAL(VALUE, RESULT) +dnl ================================= +dnl Interpolate the VALUE in loop until it doesn't change, +dnl and set the result to $RESULT. +dnl WARNING: It's easy to get an infinite loop with some unsane input. +AC_DEFUN([adl_RECURSIVE_EVAL], +[_lcl_receval="$1" +$2=`(test "x$prefix" = xNONE && prefix="$ac_default_prefix" + test "x$exec_prefix" = xNONE && exec_prefix="${prefix}" + _lcl_receval_old='' + while test "[$]_lcl_receval_old" != "[$]_lcl_receval"; do + _lcl_receval_old="[$]_lcl_receval" + eval _lcl_receval="\"[$]_lcl_receval\"" + done + echo "[$]_lcl_receval")`]) diff --git a/m4/cf3_check_proper_func.m4 b/m4/cf3_check_proper_func.m4 new file mode 100644 index 0000000000..b706b835b5 --- /dev/null +++ b/m4/cf3_check_proper_func.m4 @@ -0,0 +1,83 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +dnl +dnl Arguments: +dnl $1 - function name +dnl $2 - headers (to compile $3) +dnl $3 - body for compilation +dnl $4 - function invocation +dnl +dnl This macro checks that the function (argument 1) is defined, +dnl and that the code piece (arguments 2, 3, like in AC_LANG_PROGRAM) can be +dnl compiled. +dnl +dnl If the code compiles successfully, it defines HAVE_$1_PROPER macro. +dnl +dnl If the code fails, it adds '$4' to $post_macros variable. +dnl If you want rpl_$1.c to be compiled as a replacement, call +dnl CF3_REPLACE_PROPER_FUNC with the same function name. +dnl +dnl ** How to use ** +dnl +dnl CF3_CHECK_PROPER_FUNC(function, [#include ], [void function(FILE *);], [#define function rpl_function]) +dnl CF3_REPLACE_PROPER_FUNC(function) +dnl +dnl Then in libutils/platform.h: +dnl +dnl #if !HAVE_FUNCTION_PROPER +dnl void rpl_function(FILE *); +dnl #endif +dnl +dnl And libcompat/rpl_function.c: +dnl +dnl #include "platform.h" +dnl +dnl void rpl_function(FILE *) { ... } +dnl + +AC_DEFUN([CF3_CHECK_PROPER_FUNC], +[ + AC_CHECK_FUNC([$1], [], [AC_MSG_ERROR([Unable to find function $1])]) + + AC_CACHE_CHECK([whether $1 is properly declared], + [hw_cv_func_$1_proper], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([$2],[$3])], + [hw_cv_func_$1_proper=yes], + [hw_cv_func_$1_proper=no])]) + + AC_SUBST([hw_cv_func_$1_proper]) + + AS_IF([test "$hw_cv_func_$1_proper" = yes], + [AC_DEFINE([HAVE_$1_PROPER], [1], [Define to 1 if you have properly defined `$1' function])], + [post_macros="$post_macros +$4"]) +]) + +AC_DEFUN([CF3_REPLACE_PROPER_FUNC], +[ + AS_IF([test "$hw_cv_func_$1_proper" = "no"], + [AC_LIBOBJ(rpl_$1)] + ) +]) diff --git a/m4/cf3_gcc_flags.m4 b/m4/cf3_gcc_flags.m4 new file mode 100644 index 0000000000..7bfc06b80a --- /dev/null +++ b/m4/cf3_gcc_flags.m4 @@ -0,0 +1,91 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +dnl #################################################################### +dnl Set GCC CFLAGS only if using GCC. +dnl #################################################################### + +AC_PREPROC_IFELSE([AC_LANG_SOURCE([[ +#if defined __HP_cc +#This is HP-UX ANSI C +#endif +]])], [ +HP_UX_AC="no"], [ +CFLAGS="$CFLAGS -Agcc" +CPPFLAGS="$CPPFLAGS -Agcc" +HP_UX_AC="yes"]) + +AC_MSG_CHECKING(for HP-UX aC) +if test "x$HP_UX_AC" = "xyes"; then + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + +AC_MSG_CHECKING(for GCC specific compile flags) +if test x"$GCC" = "xyes" && test x"$HP_UX_AC" != x"yes"; then + CFLAGS="$CFLAGS -g -Wall" + CPPFLAGS="$CPPFLAGS -std=gnu99" + AC_MSG_RESULT(yes) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -Wno-pointer-sign" + AC_MSG_CHECKING(for -Wno-pointer-sign) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([int main() {}])], + [AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no) + CFLAGS="$save_CFLAGS"]) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -Werror=implicit-function-declaration" + AC_MSG_CHECKING(for -Werror=implicit-function-declaration) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([int main() {}])], + [AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no) + CFLAGS="$save_CFLAGS"]) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -Wunused-parameter" + AC_MSG_CHECKING(for -Wunused-parameter) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([int main() {}])], + [AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no) + CFLAGS="$save_CFLAGS"]) + + dnl Clang does not like 'const const' construct arising from + dnl expansion of TYPED_SET_DECLARE macro + dnl + dnl This check is relying on explicit compilator detection due to + dnl GCC irregularities checking for -Wno-* command-line options + dnl (command line is not fully checked until actual warning occurs) + AC_MSG_CHECKING(for -Wno-duplicate-decl-specifier) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([#ifndef __clang__ +# error Not a clang +#endif +int main() {}])], + [AC_MSG_RESULT(yes) + CFLAGS="$save_CFLAGS -Wno-duplicate-decl-specifier"], + [AC_MSG_RESULT(no)]) +else + AC_MSG_RESULT(no) +fi diff --git a/m4/cf3_path_root_prog.m4 b/m4/cf3_path_root_prog.m4 new file mode 100644 index 0000000000..7c16d376bc --- /dev/null +++ b/m4/cf3_path_root_prog.m4 @@ -0,0 +1,56 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +# CF3_PATH_ROOT_PROG(variable, program, value-if-not-found, path = $PATH) +# -------------------------------------- +# +# This function has almost the same semantics as the AC_PATH_PROG +# function. The difference is that this will detect tools that are +# runnable by root, but not by the current user. These tools are +# typically used not by the build, but by CFEngine itself, after +# it is installed. +# +AC_DEFUN([CF3_PATH_ROOT_PROG], +[ + found=0 + AS_IF([test "x$4" = "x"], [ + path=$PATH + ], [ + path=$4 + ]) + AS_ECHO_N(["checking for $2... "]) + for i in $(echo $path | sed -e 's/:/ /g'); do + AS_IF([test -e $i/$2 && ls -ld $i/$2 | grep ['^[^ ][^ ][^ ][xs][^ ][^ ][^ ][^ ][^ ][^ ]'] > /dev/null], [ + $1=$i/$2 + found=1 + break + ]) + done + + AS_IF([test "$found" = "1"], [ + AS_ECHO(["$][$1"]) + ], [ + AS_ECHO([no]) + $1=$3 + ]) +]) diff --git a/m4/cf3_platforms.m4 b/m4/cf3_platforms.m4 new file mode 100644 index 0000000000..96e38a9180 --- /dev/null +++ b/m4/cf3_platforms.m4 @@ -0,0 +1,41 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +# +# OS kernels conditionals. Don't use those unless it is really needed (if code +# depends on the *kernel* feature, and even then -- some kernel features are +# shared by different kernels). +# +# Good example: use LINUX to select code which uses inotify and netlink sockets. +# Bad example: use LINUX to select code which parses output of coreutils' ps(1). +# +AM_CONDITIONAL([LINUX], [test -n "`echo ${target_os} | grep linux`"]) +AM_CONDITIONAL([MACOSX], [test -n "`echo ${target_os} | grep darwin`"]) +AM_CONDITIONAL([SOLARIS], [test -n "`(echo ${target_os} | egrep 'solaris|sunos')`"]) +AM_CONDITIONAL([NT], [test -n "`(echo ${target_os} | egrep 'mingw|cygwin')`"]) +AM_CONDITIONAL([CYGWIN], [test -n "`(echo ${target_os} | egrep 'cygwin')`"]) +AM_CONDITIONAL([AIX], [test -n "`(echo ${target_os} | grep aix)`"]) +AM_CONDITIONAL([HPUX], [test -n "`(echo ${target_os} | egrep 'hpux|hp-ux')`"]) +AM_CONDITIONAL([FREEBSD], [test -n "`(echo ${target_os} | grep freebsd)`"]) +AM_CONDITIONAL([NETBSD], [test -n "`(echo ${target_os} | grep netbsd)`"]) +AM_CONDITIONAL([XNU], [test -n "`(echo ${target_os} | grep darwin)`"]) diff --git a/m4/cf3_with_library.m4 b/m4/cf3_with_library.m4 new file mode 100644 index 0000000000..ee47d7d11d --- /dev/null +++ b/m4/cf3_with_library.m4 @@ -0,0 +1,105 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +# CF3_WITH_LIBRARY(library-name, checks) +# -------------------------------------- +# +# This function popluates CFLAGS, CPPFLAGS and LDFLAGS from the +# --with-$library=PATH and runs a second argument with those options. +# +# After execution flags are returned to previous state, but available in +# ${LIBRARY}_{CFLAGS,LDFLAGS}. Path is available in ${LIBRARY}_PATH. +# +# Libraries added to LIBS are available as ${LIBRARY}_LIBS afterwards. +# +AC_DEFUN([CF3_WITH_LIBRARY], +[ + m4_define([ULN],m4_toupper($1)) + + # + # Populate ${LIBRARY}_{PATH,CFLAGS,LDFLAGS} according to arguments + # + if test "x$with_[$1]" != xyes && + test "x$with_[$1]" != xcheck && + test "x$with_[$1]" != x + then + test -z "$ULN[]_PATH" && ULN[]_PATH="$with_[$1]" + if test "x$with_[$1]" != x/usr && + test "x$with_[$1]" != x/ + then + test -z "$ULN[]_CFLAGS" && ULN[]_CFLAGS="" + test -z "$ULN[]_CPPFLAGS" && ULN[]_CPPFLAGS="-I$with_[$1]/include" + test -z "$ULN[]_LDFLAGS" && ULN[]_LDFLAGS="-L$with_[$1]/lib" + fi + else + ULN[]_PATH="default path" + fi + + # + # Save old environment + # + save_CFLAGS="$CFLAGS" + save_CPPFLAGS="$CPPFLAGS" + save_LDFLAGS="$LDFLAGS" + save_LIBS="$LIBS" + + CFLAGS="$CFLAGS $ULN[]_CFLAGS" + CPPFLAGS="$CPPFLAGS $ULN[]_CPPFLAGS" + LDFLAGS="$LDFLAGS $ULN[]_LDFLAGS" + + # + # Run checks passed as argument + # + $2 + + # + # Pick up any libraries added by tests + # + test -z "$ULN[]_LIBS" && ULN[]_LIBS="$LIBS" + + # + # libtool understands -R$path, but we are not using libtool in configure + # snippets, so -R$path goes to $pkg_LDFLAGS only after autoconf tests + # + if test "x$with_[$1]" != xyes && + test "x$with_[$1]" != xcheck && + test "x$with_[$1]" != x/usr && + test "x$with_[$1]" != x/ + then + ULN[]_LDFLAGS="$ULN[]_LDFLAGS -R$with_[$1]/lib" + fi + + # + # Restore pristine environment + # + CFLAGS="$save_CFLAGS" + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + + AC_SUBST(ULN[]_PATH) + AC_SUBST(ULN[]_CPPFLAGS) + AC_SUBST(ULN[]_CFLAGS) + AC_SUBST(ULN[]_LDFLAGS) + AC_SUBST(ULN[]_LIBS) +]) diff --git a/m4/old-autoconf.m4 b/m4/old-autoconf.m4 new file mode 100644 index 0000000000..5d25e35f61 --- /dev/null +++ b/m4/old-autoconf.m4 @@ -0,0 +1,147 @@ +AC_DEFUN([AC_TYPE_LONG_DOUBLE], +[ + AC_CACHE_CHECK([for long double], [ac_cv_type_long_double], + [if test "$GCC" = yes; then + ac_cv_type_long_double=yes + else + AC_COMPILE_IFELSE( + [AC_LANG_BOOL_COMPILE_TRY( + [[/* The Stardent Vistra knows sizeof (long double), but does + not support it. */ + long double foo = 0.0L;]], + [[/* On Ultrix 4.3 cc, long double is 4 and double is 8. */ + sizeof (double) <= sizeof (long double)]])], + [ac_cv_type_long_double=yes], + [ac_cv_type_long_double=no]) + fi]) + if test $ac_cv_type_long_double = yes; then + AC_DEFINE([HAVE_LONG_DOUBLE], 1, + [Define to 1 if the system has the type `long double'.]) + fi +]) + +AC_DEFUN([AC_TYPE_LONG_LONG_INT], +[ + AC_CACHE_CHECK([for long long int], [ac_cv_type_long_long_int], + [AC_LINK_IFELSE( + [_AC_TYPE_LONG_LONG_SNIPPET], + [dnl This catches a bug in Tandem NonStop Kernel (OSS) cc -O circa 2004. + dnl If cross compiling, assume the bug isn't important, since + dnl nobody cross compiles for this platform as far as we know. + AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [[@%:@include + @%:@ifndef LLONG_MAX + @%:@ define HALF \ + (1LL << (sizeof (long long int) * CHAR_BIT - 2)) + @%:@ define LLONG_MAX (HALF - 1 + HALF) + @%:@endif]], + [[long long int n = 1; + int i; + for (i = 0; ; i++) + { + long long int m = n << i; + if (m >> i != n) + return 1; + if (LLONG_MAX / 2 < m) + break; + } + return 0;]])], + [ac_cv_type_long_long_int=yes], + [ac_cv_type_long_long_int=no], + [ac_cv_type_long_long_int=yes])], + [ac_cv_type_long_long_int=no])]) + if test $ac_cv_type_long_long_int = yes; then + AC_DEFINE([HAVE_LONG_LONG_INT], 1, + [Define to 1 if the system has the type `long long int'.]) + fi +]) + +AC_DEFUN([AC_TYPE_UNSIGNED_LONG_LONG_INT], +[ + AC_CACHE_CHECK([for unsigned long long int], + [ac_cv_type_unsigned_long_long_int], + [AC_LINK_IFELSE( + [_AC_TYPE_LONG_LONG_SNIPPET], + [ac_cv_type_unsigned_long_long_int=yes], + [ac_cv_type_unsigned_long_long_int=no])]) + if test $ac_cv_type_unsigned_long_long_int = yes; then + AC_DEFINE([HAVE_UNSIGNED_LONG_LONG_INT], 1, + [Define to 1 if the system has the type `unsigned long long int'.]) + fi +]) + +AC_DEFUN([AC_TYPE_INTMAX_T], +[ + AC_REQUIRE([AC_TYPE_LONG_LONG_INT]) + AC_CHECK_TYPE([intmax_t], + [AC_DEFINE([HAVE_INTMAX_T], 1, + [Define to 1 if the system has the type `intmax_t'.])], + [test $ac_cv_type_long_long_int = yes \ + && ac_type='long long int' \ + || ac_type='long int' + AC_DEFINE_UNQUOTED([intmax_t], [$ac_type], + [Define to the widest signed integer type + if and do not define.])]) +]) + +AC_DEFUN([AC_TYPE_UINTMAX_T], +[ + AC_REQUIRE([AC_TYPE_UNSIGNED_LONG_LONG_INT]) + AC_CHECK_TYPE([uintmax_t], + [AC_DEFINE([HAVE_UINTMAX_T], 1, + [Define to 1 if the system has the type `uintmax_t'.])], + [test $ac_cv_type_unsigned_long_long_int = yes \ + && ac_type='unsigned long long int' \ + || ac_type='unsigned long int' + AC_DEFINE_UNQUOTED([uintmax_t], [$ac_type], + [Define to the widest unsigned integer type + if and do not define.])]) +]) + +AC_DEFUN([AC_TYPE_UINTPTR_T], +[ + AC_CHECK_TYPE([uintptr_t], + [AC_DEFINE([HAVE_UINTPTR_T], 1, + [Define to 1 if the system has the type `uintptr_t'.])], + [for ac_type in 'unsigned int' 'unsigned long int' \ + 'unsigned long long int'; do + AC_COMPILE_IFELSE( + [AC_LANG_BOOL_COMPILE_TRY( + [AC_INCLUDES_DEFAULT], + [[sizeof (void *) <= sizeof ($ac_type)]])], + [AC_DEFINE_UNQUOTED([uintptr_t], [$ac_type], + [Define to the type of an unsigned integer type wide enough to + hold a pointer, if such a type exists, and if the system + does not define it.]) + ac_type=]) + test -z "$ac_type" && break + done]) +]) + +AC_DEFUN([_AC_TYPE_LONG_LONG_SNIPPET], +[ + AC_LANG_PROGRAM( + [[/* For now, do not test the preprocessor; as of 2007 there are too many + implementations with broken preprocessors. Perhaps this can + be revisited in 2012. In the meantime, code should not expect + #if to work with literals wider than 32 bits. */ + /* Test literals. */ + long long int ll = 9223372036854775807ll; + long long int nll = -9223372036854775807LL; + unsigned long long int ull = 18446744073709551615ULL; + /* Test constant expressions. */ + typedef int a[((-9223372036854775807LL < 0 && 0 < 9223372036854775807ll) + ? 1 : -1)]; + typedef int b[(18446744073709551615ULL <= (unsigned long long int) -1 + ? 1 : -1)]; + int i = 63;]], + [[/* Test availability of runtime routines for shift and division. */ + long long int llmax = 9223372036854775807ll; + unsigned long long int ullmax = 18446744073709551615ull; + return ((ll << 63) | (ll >> 63) | (ll < i) | (ll > i) + | (llmax / ll) | (llmax % ll) + | (ull << 63) | (ull >> 63) | (ull << i) | (ull >> i) + | (ullmax / ull) | (ullmax % ull));]]) +]) + diff --git a/m4/snprintf.m4 b/m4/snprintf.m4 new file mode 100644 index 0000000000..eb16c7f8f9 --- /dev/null +++ b/m4/snprintf.m4 @@ -0,0 +1,256 @@ +# $Id: snprintf.m4,v 1.1.1.1 2008/01/06 03:24:00 holger Exp $ + +# Copyright (c) 2008 Holger Weiss . +# +# This code may freely be used, modified and/or redistributed for any purpose. +# It would be nice if additions and fixes to this file (including trivial code +# cleanups) would be sent back in order to let me include them in the version +# available at . However, this is +# not a requirement for using or redistributing (possibly modified) versions of +# this file, nor is leaving this notice intact mandatory. + +# HW_HEADER_STDARG_H +# ------------------ +# Define HAVE_STDARG_H to 1 if is available. +AC_DEFUN([HW_HEADER_STDARG_H], +[ + AC_CHECK_HEADERS([stdarg.h]) +])# HW_HEADER_STDARG_H + +# HW_HEADER_VARARGS_H +# ------------------- +# Define HAVE_VARARGS_H to 1 if is available. +AC_DEFUN([HW_HEADER_VARARGS_H], +[ + AC_CHECK_HEADERS([varargs.h]) +])# HW_HEADER_VARARGS_H + +# HW_FUNC_VA_COPY +# --------------- +# Set $hw_cv_func_va_copy to "yes" or "no". Define HAVE_VA_COPY to 1 if +# $hw_cv_func_va_copy is set to "yes". Note that it's "unspecified whether +# va_copy and va_end are macros or identifiers declared with external linkage." +# (C99: 7.15.1, 1) Therefore, the presence of va_copy(3) cannot simply "be +# tested with #ifdef", as suggested by the Autoconf manual (5.5.1). +AC_DEFUN([HW_FUNC_VA_COPY], +[ + AC_REQUIRE([HW_HEADER_STDARG_H])dnl Our check evaluates HAVE_STDARG_H. + AC_REQUIRE([HW_HEADER_VARARGS_H])dnl Our check evaluates HAVE_VARARGS_H. + AC_CACHE_CHECK([for va_copy], + [hw_cv_func_va_copy], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [[#if HAVE_STDARG_H + #include + #elif HAVE_VARARGS_H + #include + #endif]], + [[va_list ap, aq; va_copy(aq, ap);]])], + [hw_cv_func_va_copy=yes], + [hw_cv_func_va_copy=no], + [hw_cv_func_va_copy=no])]) + AS_IF([test "$hw_cv_func_va_copy" = yes], + [AC_DEFINE([HAVE_VA_COPY], [1], + [Define to 1 if you have the `va_copy' function or macro.])]) +])# HW_FUNC_VA_COPY + +# HW_FUNC___VA_COPY +# ----------------- +# Set $hw_cv_func___va_copy to "yes" or "no". Define HAVE___VA_COPY to 1 if +# $hw_cv_func___va_copy is set to "yes". +AC_DEFUN([HW_FUNC___VA_COPY], +[ + AC_REQUIRE([HW_HEADER_STDARG_H])dnl Our check evaluates HAVE_STDARG_H. + AC_REQUIRE([HW_HEADER_VARARGS_H])dnl Our check evaluates HAVE_VARARGS_H. + AC_CACHE_CHECK([for __va_copy], + [hw_cv_func___va_copy], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [[#if HAVE_STDARG_H + #include + #elif HAVE_VARARGS_H + #include + #endif]], + [[va_list ap, aq; __va_copy(aq, ap);]])], + [hw_cv_func___va_copy=yes], + [hw_cv_func___va_copy=no], + [hw_cv_func___va_copy=no])]) + AS_IF([test "$hw_cv_func___va_copy" = yes], + [AC_DEFINE([HAVE___VA_COPY], [1], + [Define to 1 if you have the `__va_copy' function or macro.])]) +])# HW_FUNC___VA_COPY + +# HW_FUNC_VSNPRINTF +# ----------------- +# Set $hw_cv_func_vsnprintf and $hw_cv_func_vsnprintf_c99 to "yes" or "no", +# respectively. Define HAVE_VSNPRINTF to 1 only if $hw_cv_func_vsnprintf_c99 +# is set to "yes". Otherwise, define vsnprintf to rpl_vsnprintf and make sure +# the replacement function will be built. +AC_DEFUN([HW_FUNC_VSNPRINTF], +[ + AC_REQUIRE([HW_HEADER_STDARG_H])dnl Our check evaluates HAVE_STDARG_H. + + dnl The following checks are not *required* to HAVE_VSNPRINTF, but they + dnl should be checked (and pass!) for the test in snprintf.c to pass. + AC_CHECK_HEADERS([inttypes.h locale.h stddef.h stdint.h]) + AC_CHECK_MEMBERS([struct lconv.decimal_point, struct lconv.thousands_sep], + [], [], [#include ]) + AC_TYPE_LONG_DOUBLE + AC_TYPE_LONG_LONG_INT + AC_TYPE_UNSIGNED_LONG_LONG_INT + AC_TYPE_SIZE_T + AC_TYPE_INTMAX_T + AC_TYPE_UINTMAX_T + AC_TYPE_UINTPTR_T + AC_CHECK_TYPES([ptrdiff_t]) + AC_CHECK_FUNCS([localeconv]) + + AC_CHECK_FUNC([vsnprintf], + [hw_cv_func_vsnprintf=yes], + [hw_cv_func_vsnprintf=no]) + AS_IF([test "$hw_cv_func_vsnprintf" = yes], + [AC_CACHE_CHECK([whether vsnprintf is C99 compliant], + [hw_cv_func_vsnprintf_c99], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [[#if HAVE_STDARG_H + #include + #endif + #include + static int testprintf(char *buf, size_t size, const char *format, ...) + { + int result; + va_list ap; + va_start(ap, format); + result = vsnprintf(buf, size, format, ap); + va_end(ap); + return result; + }]], + [[char buf[43]; + if (testprintf(buf, 4, "The answer is %27.2g.", 42.0) != 42 || + testprintf(buf, 0, "No, it's %32zu.", (size_t)42) != 42 || + buf[0] != 'T' || buf[3] != '\0' || + testprintf(NULL, 0, "") != 0 || /* POSSIBLE SEGFAULT ON NON-C99 LIBCs!!! */ + testprintf(NULL, 0, "Some actual %18s formatting.\nblah %d.\n", "42", 1) != 51) + return 1;]])], + [hw_cv_func_vsnprintf_c99=yes], + [hw_cv_func_vsnprintf_c99=no], + [hw_cv_func_vsnprintf_c99=no])])], + [hw_cv_func_snprintf_c99=no]) + AS_IF( + [test "$hw_cv_func_vsnprintf_c99" = yes], + [AC_DEFINE([HAVE_VSNPRINTF], [1], + [Define to 1 if you have a C99 compliant `vsnprintf' function.])], + [ + AC_DEFINE([vsnprintf], [rpl_vsnprintf], + [Define to rpl_vsnprintf if the replacement function should be used.]) + AC_DEFINE([vprintf], [rpl_vprintf], + [Define to rpl_vprintf if the replacement function should be used.]) + AC_DEFINE([vfprintf], [rpl_vfprintf], + [Define to rpl_vfprintf if the replacement function should be used.]) + _HW_FUNC_XPRINTF_REPLACE + ]) +])# HW_FUNC_VSNPRINTF + +# HW_FUNC_SNPRINTF +# ---------------- +# Set $hw_cv_func_snprintf and $hw_cv_func_snprintf_c99 to "yes" or "no", +# respectively. Define HAVE_SNPRINTF to 1 only if $hw_cv_func_snprintf_c99 +# is set to "yes". Otherwise, define snprintf to rpl_snprintf and make sure +# the replacement function will be built. +AC_DEFUN([HW_FUNC_SNPRINTF], +[ + AC_REQUIRE([HW_FUNC_VSNPRINTF])dnl Our snprintf(3) calls vsnprintf(3). + AC_CHECK_FUNC([snprintf], + [hw_cv_func_snprintf=yes], + [hw_cv_func_snprintf=no]) + AS_IF([test "$hw_cv_func_snprintf" = yes], + [AC_CACHE_CHECK([whether snprintf is C99 compliant], + [hw_cv_func_snprintf_c99], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[#include ]], + [[char buf[43]; + if (snprintf(buf, 4, "The answer is %27.2g.", 42.0) != 42 || + snprintf(buf, 0, "No, it's %32zu.", (size_t)42) != 42 || + buf[0] != 'T' || buf[3] != '\0' || + snprintf(NULL, 0, "") != 0 || /* POSSIBLE SEGFAULT ON NON-C99 LIBCs!!! */ + snprintf(NULL, 0, "Some actual %18s formatting.\nblah %d.\n", "42", 1) != 51) + return 1;]])], + [hw_cv_func_snprintf_c99=yes], + [hw_cv_func_snprintf_c99=no], + [hw_cv_func_snprintf_c99=no])])], + [hw_cv_func_snprintf_c99=no]) + AS_IF( + [test "$hw_cv_func_snprintf_c99" = yes], + [AC_DEFINE([HAVE_SNPRINTF], [1], + [Define to 1 if you have a C99 compliant `snprintf' function.])], + [ + AC_DEFINE([snprintf], [rpl_snprintf], + [Define to rpl_snprintf if the replacement function should be used.]) + AC_DEFINE([printf], [rpl_printf], + [Define to rpl_printf if the replacement function should be used.]) + AC_DEFINE([fprintf], [rpl_fprintf], + [Define to rpl_fprintf if the replacement function should be used.]) + _HW_FUNC_XPRINTF_REPLACE + ]) +])# HW_FUNC_SNPRINTF + +# HW_FUNC_VASPRINTF +# ----------------- +# Set $hw_cv_func_vasprintf to "yes" or "no". Define HAVE_VASPRINTF to 1 if +# $hw_cv_func_vasprintf is set to "yes". Otherwise, define vasprintf to +# rpl_vasprintf and make sure the replacement function will be built. +AC_DEFUN([HW_FUNC_VASPRINTF], +[ + AC_REQUIRE([HW_FUNC_VSNPRINTF])dnl Our vasprintf(3) calls vsnprintf(3). + # Don't even bother checking for vasprintf if snprintf was standards + # incompliant, this one is going to be too. + AS_IF([test "$hw_cv_func_snprintf_c99" = yes], + [AC_CHECK_FUNCS([vasprintf], + [hw_cv_func_vasprintf=yes], + [hw_cv_func_vasprintf=no]) + ], + [hw_cv_func_vasprintf=no]) + AS_IF([test "$hw_cv_func_vasprintf" = no], + [AC_DEFINE([vasprintf], [rpl_vasprintf], + [Define to rpl_vasprintf if the replacement function should be used.]) + AC_CHECK_HEADERS([stdlib.h]) + HW_FUNC_VA_COPY + AS_IF([test "$hw_cv_func_va_copy" = no], + [HW_FUNC___VA_COPY]) + _HW_FUNC_XPRINTF_REPLACE]) +])# HW_FUNC_VASPRINTF + +# HW_FUNC_ASPRINTF +# ---------------- +# Set $hw_cv_func_asprintf to "yes" or "no". Define HAVE_ASPRINTF to 1 if +# $hw_cv_func_asprintf is set to "yes". Otherwise, define asprintf to +# rpl_asprintf and make sure the replacement function will be built. +AC_DEFUN([HW_FUNC_ASPRINTF], +[ + AC_REQUIRE([HW_FUNC_VASPRINTF])dnl Our asprintf(3) calls vasprintf(3). + # Don't even bother checking for asprintf if snprintf was standards + # incompliant, this one is going to be too. + AS_IF([test "$hw_cv_func_snprintf_c99" = yes], + [AC_CHECK_FUNCS([asprintf], + [hw_cv_func_asprintf=yes], + [hw_cv_func_asprintf=no]) + ], + [hw_cv_func_asprintf=no]) + AS_IF([test "$hw_cv_func_asprintf" = no], + [AC_DEFINE([asprintf], [rpl_asprintf], + [Define to rpl_asprintf if the replacement function should be used.]) + _HW_FUNC_XPRINTF_REPLACE]) +])# HW_FUNC_ASPRINTF + +# _HW_FUNC_XPRINTF_REPLACE +# ------------------------ +# Arrange for building snprintf.c. Must be called if one or more of the +# functions provided by snprintf.c are needed. +# XXX: We provide an empty version of this macro because building snprintf.c is +# taken care of in libntech/libutils and its libcompat not in libcfecompat. +AC_DEFUN([_HW_FUNC_XPRINTF_REPLACE], +[ +])# _HW_FUNC_XPRINTF_REPLACE + +dnl vim: set joinspaces textwidth=80: diff --git a/m4/strndup.m4 b/m4/strndup.m4 new file mode 100644 index 0000000000..99fc83f964 --- /dev/null +++ b/m4/strndup.m4 @@ -0,0 +1,51 @@ +# strndup.m4 serial 21 +dnl Copyright (C) 2002-2003, 2005-2013 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([cf3_FUNC_STRNDUP], +[ + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + AC_CHECK_DECLS([strndup]) + AC_REPLACE_FUNCS([strndup]) + if test $ac_cv_have_decl_strndup = no; then + HAVE_DECL_STRNDUP=0 + fi + + if test $ac_cv_func_strndup = yes; then + HAVE_STRNDUP=1 + # AIX 5.3 has a function that tries to copy the entire range specified + # by n, instead of just the length of src. + AC_CACHE_CHECK([for working strndup], [cf3_cv_func_strndup_works], + [AC_RUN_IFELSE([ + AC_LANG_PROGRAM([[#include + #include ]], [[ +#if !HAVE_DECL_STRNDUP + extern + #ifdef __cplusplus + "C" + #endif + char *strndup (const char *, size_t); +#endif + char *s; + // Will crash if strndup tries to traverse all 2GB. + s = strndup ("string", 2000000000); + return 0;]])], + [cf3_cv_func_strndup_works=yes], + [cf3_cv_func_strndup_works=no], + [ +changequote(,)dnl + case $host_os in + aix | aix[3-6]*) cf3_cv_func_strndup_works="guessing no";; + *) cf3_cv_func_strndup_works="guessing yes";; + esac +changequote([,])dnl + ])]) + case $cf3_cv_func_strndup_works in + *no) AC_LIBOBJ([strndup]) ;; + esac + else + HAVE_STRNDUP=0 + fi +]) diff --git a/m4/tar.m4 b/m4/tar.m4 new file mode 100644 index 0000000000..79a6bd1547 --- /dev/null +++ b/m4/tar.m4 @@ -0,0 +1,154 @@ +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004-2015 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of 'v7', 'ustar', or 'pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +# +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AC_SUBST([AMTAR], ['$${TAR-tar}']) + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' + +m4_if([$1], [v7], + [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], + + [m4_case([$1], + [ustar], + [# The POSIX 1988 'ustar' format is defined with fixed-size fields. + # There is notably a 21 bits limit for the UID and the GID. In fact, + # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 + # and bug#13588). + am_max_uid=2097151 # 2^21 - 1 + am_max_gid=$am_max_uid + # The $UID and $GID variables are not portable, so we need to resort + # to the POSIX-mandated id(1) utility. Errors in the 'id' calls + # below are definitely unexpected, so allow the users to see them + # (that is, avoid stderr redirection). + am_uid=`id -u || echo unknown` + am_gid=`id -g || echo unknown` + AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) + if test $am_uid -le $am_max_uid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi + AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) + if test $am_gid -le $am_max_gid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi], + + [pax], + [], + + [m4_fatal([Unknown tar format])]) + + AC_MSG_CHECKING([how to create a $1 tar archive]) + + # Go ahead even if we have the value already cached. We do so because we + # need to set the values for the 'am__tar' and 'am__untar' variables. + _am_tools=${am_cv_prog_tar_$1-$_am_tools} + + for _am_tool in $_am_tools; do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; do + AM_RUN_LOG([$_am_tar --version]) && break + done + + # Work around CFEngine redmine #6925 by using --hard-dereference. + AM_RUN_LOG([$_am_tar --hard-dereference 2>&1 | grep 'unrecognized option']) + # Check if --hard-dereference is supported by this version of GNU Tar + if test "$ac_status" -eq 0; then + _am_gnutar_hard_dereference=false + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + else + _am_gnutar_hard_dereference=true + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) --hard-dereference -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) --hard-dereference -chf - "'"$tardir"' + fi + + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works. + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar /dev/null 2>&1 && break + fi + done + rm -rf conftest.dir + + AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) + AC_MSG_RESULT([$am_cv_prog_tar_$1])]) + + if test $_am_tool = gnutar; then + # We've checked already, so we're just printing here + AC_MSG_CHECKING([if GNU tar supports --hard-dereference]) + if test x$_am_gnutar_hard_dereference = xtrue; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + fi + +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR diff --git a/misc/Makefile.am b/misc/Makefile.am new file mode 100644 index 0000000000..f2e7e49d10 --- /dev/null +++ b/misc/Makefile.am @@ -0,0 +1,44 @@ +SUBDIRS = selinux + +# include unconditionally in the distribution tarball +EXTRA_DIST= init.d/cfengine3.in \ + systemd/cf-apache.service.in \ + systemd/cf-execd.service.in \ + systemd/cf-hub.service.in \ + systemd/cf-monitord.service.in \ + systemd/cf-postgres.service.in \ + systemd/cf-serverd.service.in \ + systemd/cfengine3.service.in + + +# The following files are generated from the previous ones, during ./configure + +if WITH_INIT_D_SCRIPT +initddir = $(INIT_D_PATH) +initd_SCRIPTS = init.d/cfengine3 +endif + +if WITH_SYSTEMD_SERVICE +systemddir = $(SYSTEMD_SERVICE_PATH) +systemd_DATA = systemd/cfengine3.service +systemd_DATA += systemd/cf-apache.service +systemd_DATA += systemd/cf-execd.service +systemd_DATA += systemd/cf-hub.service +systemd_DATA += systemd/cf-reactor.service +systemd_DATA += systemd/cf-monitord.service +systemd_DATA += systemd/cf-postgres.service +systemd_DATA += systemd/cf-serverd.service + +install-data-hook: + -systemctl --no-ask-password daemon-reload + +endif +dist_bin_SCRIPTS = cf-support + +check-local: + @if command -v shellcheck 2>/dev/null; then \ + shellcheck --version; \ + shellcheck cf-support; \ + else \ + echo "Install shellcheck to check cf-support script."; \ + fi diff --git a/misc/cf-support b/misc/cf-support new file mode 100755 index 0000000000..ed00f9db5e --- /dev/null +++ b/misc/cf-support @@ -0,0 +1,397 @@ +#!/usr/bin/env sh +# this script works with plain old POSIX sh so have to allow legacy subshell `command` instead of using $(command) +# shellcheck disable=SC2006 + +# be double-certain that we have needed PATH entries regardless of execution environment +PATH=/usr/contrib/bin:$PATH; export PATH # hpux +PATH=/usr/sbin:$PATH; export PATH # solaris +PATH=/usr/bin:$PATH; export PATH # sol10sparc has showrev in /usr/bin + +WORKDIR=/var/cfengine; export WORKDIR +BINDIR="$WORKDIR/bin"; export BINDIR +non_interactive=0; export non_interactive + +if [ $# -gt 1 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then + echo "usage: `basename "$0"` [OPTIONS]" + echo '' + echo 'Options:' + echo ' --yes, -y - Non-interactive use. Assume no ticket number and assume include masterfiles.' + echo ' --help, -h - Print the help message' + echo '' + echo 'Website: https://cfengine.com' + echo 'This software is Copyright 2024 Northern.tech AS.' + exit 1 +fi + +if [ "$1" = "-M" ]; then + cat </dev/null; then + echo "Please install gzip. This is required in order to minimize size of support collection." + exit 1 +fi + +if [ $non_interactive -eq 0 ]; then + printf "If you have it, please enter your support case number: " + read -r case_number +fi +case_number="${case_number:-NA}" +timestamp=`date +%Y-%m-%d-%H%M` +collection="cfengine_support_case_$case_number-`hostname`-$timestamp" + +make_temp_dir() +{ + if command -v mktemp >/dev/null && [ "$OS" != "hpux" ]; then + mktemp -d + else + # shellcheck disable=SC2021 + # ^^ legacy/POSIX requires square brackets + _tmp="/tmp/`tr -dc "[A-Z][a-z][0-9]" /dev/null; then + OS="hpux" +elif command -v oslevel 2>/dev/null; then + OS=aix +elif [ -f /etc/release ]; then + OS=solaris + OS_VERSION=$(uname -r) +elif [ -f /etc/redhat-release ] || [ -f /etc/os-release ] || [ -f /etc/lsb-release ]; then + OS=linux +elif command -v system_profiler 2>/dev/null; then + OS=macos +else + echo "unable to determine operating system, will try generic unix commands." + OS=unix +fi + +tmpdir="`make_temp_dir`/$collection" +export tmpdir +mkdir -p "$tmpdir" + +echo "Analyzing CFEngine core dumps" +_core_log="$tmpdir"/core-dump.log +_sysctl_kernel_core_pattern="$(sysctl -n kernel.core_pattern)" +if expr "$_sysctl_kernel_core_pattern" : ".*/systemd-coredump.*" > /dev/null 2>&1; then + echo "Found systemd-coredump used in sysctl kernel.core_pattern \"$_sysctl_kernel_core_pattern\"" + echo "Using coredumpctl to analyze CFEngine core dumps" + coredumpctl info /var/cfengine/bin/cf-* 2>/dev/null >> "$_core_log" || true +elif command -v apport-unpack >/dev/null; then + echo "Using apport-unpack to analyze CFEngine core dumps" + # each crash report has a line with ExecutablePath: which tells us if it is a CFEngine core dump + crash_reports=`grep -H "ExecutablePath: /var/cfengine/bin" /var/crash/* | sed "s/:ExecutablePath.*$//"` + if [ -n "$crash_reports" ]; then + if ! command -v gdb >/dev/null; then + echo "CFEngine related core dumps were found but gdb is not installed. Please install gdb and retry the cf-support command." + exit 1 + fi + # process crash reports with tmp dirs and all + for report in $crash_reports; do + tmp=`make_temp_dir` + apport-unpack "$report" "$tmp" + exe=`cat "$tmp/ExecutablePath"` + # print out report up to the embedded core dump file + # --null-data separate lines by NUL characters + sed --null-data 's/CoreDump:.*$//' "$report" >> "$_core_log" + gdb "$exe" --core="$tmp/CoreDump" -batch -ex "thread apply all bt full" >> "$_core_log" 2>&1 + rm -rf "$tmp" + done + fi +else + if [ "$non_interactive" -eq 0 ]; then + printf "Analyze coredumps found under /var/cfengine/bin? [Y//n]: " + read -r response + fi + response=${response:-/var/cfengine/bin} + if [ "$response" != "n" ]; then + # file command on core files results in lines like the following which we parse for cf-* binaries + # core: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from '/var/cfengine/bin/cf-key', real uid: 0, effective uid: 0, realgid: 0, effective gid: 0, execfn: '/var/cfengine/bin/cf-key', platform: 'x86_64' + cf_core_files=`find "$response/." \( -name . -o -prune \) -name 'core*' -type f -exec file {} \; 2>/dev/null | grep "core file" | grep "cf-" | cut -d' ' -f1 | sed 's/:$//'` + if [ -n "$cf_core_files" ]; then + if [ "$OS" != "solaris" ]; then + if ! command -v gdb >/dev/null; then + echo "Please install gdb. This is required in order to analyze core dumps." + exit 1 + fi + fi + for core_file in $cf_core_files; do + file "$core_file" >> "$_core_log" + if [ "$OS" = "solaris" ]; then + pstack "$core_file" >> "$_core_log" 2>&1 + else + execfn=`file "$core_file" | sed 's/,/\n/g' | grep execfn | cut -d: -f2 | sed "s/[' ]//g"` + exe="`realpath "$execfn"`" + gdb "$exe" --core="$core_file" -batch -ex "thread apply all bt full" >> "$_core_log" 2>&1 + fi + done + fi + fi +fi + + +info_file="$tmpdir/system-info.txt" +export info_file + +file_add() +{ + filename="`basename "$1"`" + if [ -f "$1" ]; then + cp "$1" "$tmpdir"/"$filename" + echo "Added file $1" + else + echo "$1 file not found" >> "$info_file" + fi +} + +gzip_add() +{ + filename="`basename "$1"`" + if [ -f "$1" ]; then + gzip -c "$1" > "$tmpdir"/"$filename".gz + echo "Added compressed copy of file $1" + else + echo "$1 file not found" >> "$info_file" + fi +} + +log_cmd() +{ + cmd="`echo "$1" | awk '{print $1}'`" + if command -v "$cmd" 2>/dev/null >/dev/null; then + echo "** $1" >> "$info_file" + sh -c "$1" >> "$info_file" 2>&1 || true + echo >> "$info_file" # one newline for easier to read output + echo "Captured output of command $1" + else + echo "Command not found: $cmd" + fi +} + +# first, collect basic information about the system +log_cmd "uname -a" +log_cmd "$BINDIR/cf-promises -V" + +if [ "$OS" = "solaris" ]; then + log_cmd "prtconf" # system configuration, memory size, peripherals + log_cmd "psrinfo -v" # verbose information about each processor, on-line since, type, speed + if [ "$OS_VERSION" = "5.10" ]; then + log_cmd "showrev" # hostname, hostid, release, kernel architecture, application architecutre, kernel version + fi + log_cmd "vmstat" # equivalent of free -h + log_cmd "zonename" # either global, or a named zone + if command -v zonestat 2>/dev/null; then + log_cmd "zonestat 1 1" # get information about memory/cpu in this zone + fi +elif [ "$OS" = "hpux" ]; then + if [ -x /opt/ignite/bin/print_manifest ]; then + log_cmd "/opt/ignite/bin/print_manifest" # contains info provided by other commands below + else + log_cmd "swlist" # list of bundles installed, includes HPUX-OE base os version + log_cmd "model" # type of machine + # machinfo provides cpu, speed, memory, firmware, platform, id, serial, OS, nodename/hostname, release, sysname + if command -v machinfo 2>/dev/null; then + log_cmd "machinfo" + fi + fi + # regardless, report status of interfaces/volumes and memory + log_cmd "dmesg" # not syslog related like on linux +elif [ "$OS" = "aix" ]; then + log_cmd "prtconf" # model, type, cpus, speed, memory, firmware, network info, volume groups, peripherals/resources + log_cmd "oslevel -s" # OS version +else # linux and "generic" unices like macos/bsds? + log_cmd "lscpu" + log_cmd "free -m" + log_cmd "hostname" + [ -f "/etc/os-release" ] && log_cmd "cat /etc/os-release" +fi + +# filesystems and usage +if [ "$OS" != "solaris" ] && [ "$OS" != "aix" ]; then + file_add "/etc/fstab" +fi +log_cmd "mount" +if [ "$OS" != "hpux" ] && [ "$OS" != "aix" ]; then + log_cmd "df -h" +else + log_cmd "df -g" # only needed on hpux/aix, -h is not available +fi + +# processes +# shellcheck disable=SC2166 +# ^^ allow grouping in test expression +if [ "$OS" = "hpux" ] || [ \( "$OS" = "solaris" -a "$OS_VERSION" = "5.10" \) ]; then + ps -efl >processes.log 2>&1 +else + ps auwwx >processes.log 2>&1 +fi + +# top procs +if [ "$OS" = "hpux" ]; then + # use -f option to force -d1 and include no console codes + top -f /tmp/top.log + file_add /tmp/top.log + rm /tmp/top.log +elif [ "$OS" = "aix" ] || [ "$OS" = "solaris" ]; then + log_cmd "ps aux | head -1; ps aux | sed '1d' | sort -rn +2 | head -10" +else + log_cmd "top -b -H -c -n1" +fi + +# network interfaces +if [ "$OS" = "hpux" ] || [ "$OS" = "solaris" ] || [ "$OS" = "aix" ]; then + log_cmd "netstat -in" +else + log_cmd "netstat -ie" +fi +if [ "$OS" = "aix" ] || [ "$OS" = "solaris" ]; then + log_cmd "ifconfig -a" +elif [ "$OS" = "hpux" ]; then + lanscan -p | while read -r lan + do + log_cmd "ifconfig lan${lan}" + done 2>/dev/null +else + log_cmd "ifconfig" +fi + +# open file handles +if command -v lsof 2>/dev/null >/dev/null; then + lsof > "$tmpdir/lsof.txt" + echo "Captured output of command lsof" +fi + +# CFEngine specific +log_cmd "$BINDIR/cf-key -p $WORKDIR/ppkeys/localhost.pub" +log_cmd "grep 'version =' $WORKDIR/inputs/promises.cf" +log_cmd "$BINDIR/cf-key -s -n" +log_cmd "$BINDIR/cf-check diagnose" +$BINDIR/cf-promises --show-classes --show-vars > "$tmpdir/classes-and-vars.txt" 2>&1 +$BINDIR/cf-agent --no-lock --file update.cf --show-evaluated-classes --show-evaluated-vars > "$tmpdir/update-evaluated-classes-and-vars.txt" 2>&1 +$BINDIR/cf-agent --no-lock --file promises.cf --show-evaluated-classes --show-evaluated-vars > "$tmpdir/promises-evaluated-classes-and-vars.txt" 2>&1 + +if command -v systemctl >/dev/null; then + log_cmd "systemctl status cfengine3" + log_cmd "systemctl status cf-*" +elif [ -x /etc/init.d/cfengine3 ]; then + log_cmd "/etc/init.d/cfengine3 status" +else + echo "No way to check on cfengine service status" +fi +for f in /var/log/CFEngine-Install*; do + gzip_add "$f" +done + +# system log +[ -f /var/log/messages ] && syslog_cmd="cat /var/log/messages" +[ -f /var/log/syslog ] && syslog_cmd="cat /var/log/syslog" +command -v journalctl >/dev/null && syslog_cmd="journalctl" +[ -z "$syslog_cmd" ] && syslog_cmd="dmesg" +if [ "$OS" = "solaris" ]; then + syslog_cmd="cat /var/adm/messages*" +fi +_syslog_filtered="$tmpdir"/syslog-filtered-for-cfengine.log.gz + +if [ "$OS" = "aix" ]; then + syslog_cmd="errpt -a" + # error report can reference cfengine, reports are delimitted by bars of `-` characters + echo "r !errpt -a +g/cfengine/?---?,/---/p" | ed > "$_syslog_filtered" || true +else + $syslog_cmd | sed -n '/[Cc][Ff][Ee-]/p' | gzip -c > "$_syslog_filtered" || true +fi +echo "Captured output of $syslog_cmd filtered for cf-|CFEngine" + +gzip_add $WORKDIR/outputs/previous +file_add $WORKDIR/policy_server.dat + +if [ -f $WORKDIR/share/cf-support-nova-hub.sh ]; then + # shellcheck source=/dev/null + . $WORKDIR/share/cf-support-nova-hub.sh +fi + +# Here we create the tarball one directory up +# to preserve a top-level of $collection in the tarball. +# This gives a nice context of timestamp, hostname and support ticket +# if provided. (see $collection definition above) +tar cvf - -C "$tmpdir"/.. "$collection" | gzip > "$collection.tar.gz" +rm -rf "$tmpdir" +echo "Please send $collection.tar.gz to CFEngine support staff." diff --git a/misc/changelog-generator/changelog-generator b/misc/changelog-generator/changelog-generator new file mode 100755 index 0000000000..cef3137f79 --- /dev/null +++ b/misc/changelog-generator/changelog-generator @@ -0,0 +1,269 @@ +#!/usr/bin/python3 + +# Used to generate changelogs from the repository. + +from __future__ import print_function + +import subprocess +import re +import sys +import os +import os.path + +# Holds each changelog entry indexed by SHA +ENTRIES = {} +# Links SHAs together, if we have a "X cherry-picked from Y" situation, those +# two commits will be linked, and this will be used in cases where we have +# reverted a commit. +LINKED_SHAS = {} +# A map of shas to a list of bugtracker numbers, as extracted from the commit +# messages. +SHA_TO_TRACKER = {} + +# more relaxed regexp to find JIRA issues anywhere in commit message +JIRA_REGEX = r"(?:Jira:? *)?(?:https?://northerntech.atlassian.net/browse/)?((?:CFE|ENT|INF|ARCHIVE|MEN|QA)-[0-9]+)" +# more strict regexp to find JIRA issues only in the beginning of title +JIRA_TITLE_REGEX = r"^(?:CFE|ENT|INF|ARCHIVE|MEN|QA)-[0-9]+" +TRACKER_REGEX = r"\(?(?:Ref:? *)?%s\)?:? *" % (JIRA_REGEX) + +POSSIBLE_MISSED_TICKETS = {} + +# Only for testing. +SORT_CHANGELOG = True + +# Type of log to generate, this is bitwise. +LOG_TYPE = 0 +# Values for the above. +LOG_REPO = 1 +LOG_COMMUNITY = 2 +LOG_ENTERPRISE = 4 +LOG_MASTERFILES = 8 + +def add_entry(sha, msg): + if msg.lower().strip() == "none": + return + + sha_list = ENTRIES.get(sha) + if sha_list is None: + sha_list = [] + sha_list.append(msg) + ENTRIES[sha] = sha_list + + +if len(sys.argv) < 2 or sys.argv[1] == "-h" or sys.argv[1] == "--help": + sys.stderr.write('''Usage: +changelog-generator [options] + +The command accepts all the same options that git-log does. + +Options: + --community Automatically includes all repositories for community builds. + --enterprise Automatically includes Enterprise specific repositories. + --masterfiles Automatically includes masterfiles repository. + --repo Includes only the current repository. + +--community and --enterprise can be given together to generate one master log +for both. +''') + sys.exit(1) + +while True: + if sys.argv[1] == "--sort-changelog": + SORT_CHANGELOG = True + sys.argv[1:] = sys.argv[2:] + elif sys.argv[1] == "--community": + LOG_TYPE |= LOG_COMMUNITY + sys.argv[1:] = sys.argv[2:] + elif sys.argv[1] == "--enterprise": + LOG_TYPE |= LOG_ENTERPRISE + sys.argv[1:] = sys.argv[2:] + elif sys.argv[1] == "--masterfiles": + LOG_TYPE |= LOG_MASTERFILES + sys.argv[1:] = sys.argv[2:] + elif sys.argv[1] == "--repo": + LOG_TYPE |= LOG_REPO + sys.argv[1:] = sys.argv[2:] + else: + break + +if LOG_TYPE == 0: + sys.stderr.write("Must give one of --community, --enterprise, --masterfiles or --repo\n") + sys.exit(1) + +repos = [] +base_path = os.path.dirname(sys.argv[0]) +if LOG_TYPE & LOG_COMMUNITY != 0: + repos.append("../buildscripts") + repos.append("../core") + repos.append("../masterfiles") + repos.append("../design-center") +if LOG_TYPE & LOG_ENTERPRISE != 0: + repos.append("../enterprise") + repos.append("../nova") + repos.append("../mission-portal") +if LOG_TYPE & LOG_MASTERFILES != 0: + repos.append("../masterfiles") +if LOG_TYPE == LOG_REPO: + repos.append(".") +else: + os.chdir(base_path + "/../..") + +for repo in repos: + os.chdir(repo) + sha_list = subprocess.Popen( + ["git", "rev-list", "--no-merges", "--reverse"] + sys.argv[1:], + stdout=subprocess.PIPE) + for sha in sha_list.stdout: + sha = sha.decode().rstrip('\n') + blob = subprocess.Popen( + ["git", "log", "--format=%B", "-n", "1", sha], + stdout=subprocess.PIPE) + + title_fetched = False + title = "" + commit_msg = "" + log_entry_title = False + log_entry_commit = False + log_entry_local = False + log_entry = "" + for line in blob.stdout: + # ENT-7979: we don't want to see backticks ` in our changelogs + line = line.decode().rstrip('\r\n').replace('`',"'") + + if line == "" and log_entry: + add_entry(sha, log_entry) + log_entry = "" + log_entry_local = False + + # Tracker reference, remove from string. + for match in re.finditer(TRACKER_REGEX, line, re.IGNORECASE): + if not SHA_TO_TRACKER.get(sha): + SHA_TO_TRACKER[sha] = set() + SHA_TO_TRACKER[sha].add("".join(match.groups(""))) + tracker_removed = re.sub(TRACKER_REGEX, "", line, flags=re.IGNORECASE) + tracker_removed = tracker_removed.strip(' ') + if re.match(JIRA_TITLE_REGEX, line) and not title_fetched: + log_entry_title = True + line = tracker_removed + + if not title_fetched: + title = line + title_fetched = True + + match = re.match("^ *Changelog: *(.*)", line, re.IGNORECASE) + if match: + log_entry_title = False + if log_entry: + add_entry(sha, log_entry) + log_entry = "" + log_entry_local = False + + if re.match("^Title[ .]*$", match.group(1), re.IGNORECASE): + log_entry = title + elif re.match("^Commit[ .]*$", match.group(1), re.IGNORECASE): + log_entry_commit = True + elif re.match("^None[ .]*$", match.group(1), re.IGNORECASE): + pass + else: + log_entry_local = True + log_entry = match.group(1) + continue + + for cancel_expr in ["^ *Cancel-Changelog: *([0-9a-f]+).*", + "^This reverts commit ([0-9a-f]+).*"]: + match = re.match(cancel_expr, line, re.IGNORECASE) + if match: + if log_entry: + add_entry(sha, log_entry) + log_entry = "" + log_entry_local = False + + linked_shas = [match.group(1)] + if LINKED_SHAS.get(match.group(1)): + for linked_sha in LINKED_SHAS.get(match.group(1)): + linked_shas.append(linked_sha) + for linked_sha in linked_shas: + if LINKED_SHAS.get(linked_sha): + del LINKED_SHAS[linked_sha] + if ENTRIES.get(linked_sha): + del ENTRIES[linked_sha] + continue + + match = re.match("^\(cherry picked from commit ([0-9a-f]+)\)", line, re.IGNORECASE) + if match: + if log_entry: + add_entry(sha, log_entry) + log_entry = "" + log_entry_local = False + + if not LINKED_SHAS.get(sha): + LINKED_SHAS[sha] = [] + LINKED_SHAS[sha].append(match.group(1)) + if not LINKED_SHAS.get(match.group(1)): + LINKED_SHAS[match.group(1)] = [] + LINKED_SHAS[match.group(1)].append(sha) + continue + + match = re.match("^Signed-off-by:.*", line, re.IGNORECASE) + if match: + # Ignore such lines. + continue + + if log_entry_local: + log_entry += "\n" + line + else: + if commit_msg: + commit_msg += "\n" + commit_msg += line + + blob.wait() + + if log_entry_title: + add_entry(sha, title) + if log_entry_commit: + add_entry(sha, commit_msg) + if log_entry: + add_entry(sha, log_entry) + + sha_list.wait() + +entry_list = [] +for sha_entry in ENTRIES: + tracker = "" + if SHA_TO_TRACKER.get(sha_entry): + jiras = [ticket.upper() for ticket in SHA_TO_TRACKER[sha_entry]] + tracker = "" + if len(jiras) > 0: + tracker += "(" + ", ".join(sorted(jiras)) + ")" + for entry in ENTRIES[sha_entry]: + # Safety check. See if there are still numbers at least four digits long in + # the output, and if so, warn about it. This may be ticket references that + # we missed. + match = re.search("[0-9]{4,}", entry) + if match: + POSSIBLE_MISSED_TICKETS[sha_entry] = match.group(0) + entry = entry.strip("\n") + if tracker: + if len(entry) - entry.rfind("\n") + len(tracker) >= 70: + entry += "\n" + else: + entry += " " + entry += tracker + entry_list.append(entry) + +if SORT_CHANGELOG: + entry_list.sort() +for entry in entry_list: + entry = "\t- " + entry + # Blank lines look bad in changelog because entries don't have blank lines + # between them, so remove that from commit messages. + entry = re.sub("\n\n+", "\n", entry) + # Indent all lines. + entry = entry.replace("\n", "\n\t ") + print(entry) + +for missed in POSSIBLE_MISSED_TICKETS: + sys.stderr.write("*** Commit %s had a number %s which may be a ticket reference we missed. Should be manually checked.\n" + % (missed, POSSIBLE_MISSED_TICKETS[missed])) + +sys.exit(0) diff --git a/misc/changelog-generator/test-changelog-generator b/misc/changelog-generator/test-changelog-generator new file mode 100755 index 0000000000..3f5e8d2ba4 --- /dev/null +++ b/misc/changelog-generator/test-changelog-generator @@ -0,0 +1,427 @@ +#!/bin/sh + +################################################################################ +# Test that the changelog-generator produces the expected output. +################################################################################ + +set -e + +rm -rf /tmp/test-changelog-generator.$$ +mkdir -p /tmp/test-changelog-generator.$$ + +SRC_DIR="$(pwd)" +cd /tmp/test-changelog-generator.$$ +git init +echo dummy > dummy +git add dummy +git commit -m 'Dummy commit' + +# The numbers that are sprinkled about below are just to make it easier to +# pinpoint where a particular line came from. + +################################################################################ + +git commit --allow-empty -m 'Fix for changelog N1 + +Changelog: Title' + +################################################################################ + +git commit --allow-empty -m 'Fix not for changelog N2 + +Not for changelog N3' + +################################################################################ + +git commit --allow-empty -m 'Complete Fix for changelog N4 + +Should be included N5 + +Changelog: Commit' + +################################################################################ + +git commit --allow-empty -m 'Message not for changelog N6 + +Not for changelog N7 + +Changelog: This should be in changelog N8 +And this N9 + +But not this N10' + +################################################################################ + +git commit --allow-empty -m 'Canceled fix for changelog N11 + +Not for changelog N12 + +Changelog: Commit' +git commit --allow-empty -m 'Disable changelog N13 + +Not for changelog N14 + +Cancel-Changelog: '`git rev-parse HEAD` + +################################################################################ + +git commit --allow-empty -m 'Another canceled fix for changelog N15 + +Not for changelog N16 + +Changelog: Canceled entry should not be in changelog N17 +(cherry picked from commit 3714b61bc4a139c8f58554052775699816e47b62)' +git commit --allow-empty -m 'Disable changelog N18 + +Not for changelog N19 + +Cancel-Changelog: 3714b61bc4a139c8f58554052775699816e47b62' + +################################################################################ + +git commit --allow-empty -m 'Reverted fix for changelog N20 + +Not for changelog N21 + +Changelog: Commit' +git commit --allow-empty -m 'Disable changelog N22 + +Not for changelog N23 + +This reverts commit '`git rev-parse HEAD`'.' + +################################################################################ + +git commit --allow-empty -m 'Another Reverted fix for changelog N24 + +Not for changelog N25 + +Changelog: Reverted entry should not be in changelog N26 +(cherry picked from commit 13cf5bea954b2d55c2bb235f61316bb57b7d4189)' +git commit --allow-empty -m 'Disable changelog N27 + +Not for changelog N28 + +Changelog: Should be in changelog though. N29 +This reverts commit 13cf5bea954b2d55c2bb235f61316bb57b7d4189.' + +################################################################################ + +git commit --allow-empty -m 'Several entries for changelog N30 + +Should be included N31 + +Changelog: Commit +Changelog: Should also be included, but separately and not twice. N32 + +Changelog: This too. N33 +Changelog: As well as this. N34 + +And finally this, part of the commit message. N35' + +################################################################################ + +git commit --allow-empty -m 'Jira entry N36 + +MEN-645 Should be included N37 + +Changelog: Commit' + +################################################################################ + +git commit --allow-empty -m 'MEN-76: Jira entry in title N38 + +Changelog: Title' + +################################################################################ + +git commit --allow-empty -m 'MEN-123 Several Jira entries N39 + +Changelog: Should be there N40 + +Men-1234' + +################################################################################ + +git commit --allow-empty -m 'Jira entries in parentheses (title) N41 + +Changelog: Title + +Stuff. (Jira MEN-1234) (MEN-2345) N42' + +################################################################################ + +git commit --allow-empty -m 'Jira entries inline (title) N43 + +Changelog: Title + +Stuff Jira MEN-2345 N44' + +################################################################################ + +git commit --allow-empty -m 'Jira entries in parentheses (commit) N45 + +Changelog: Commit + +Stuff. (Jira MEN-4321) (MEN-54321) N46' + +################################################################################ + +git commit --allow-empty -m 'Jira entries inline (commit) N47 + +Changelog: Commit + +Stuff MEN-2345 N48' + +################################################################################ + +git commit --allow-empty -m 'Jira MEN-1234: Jira twice (commit) N49 + +Changelog: Commit + +More stuff. (Jira: MEN-2345) N50' + +################################################################################ + +git commit --allow-empty -m 'MEN-1234: Jira duplicate (commit) N51 + +Changelog: Commit + +More stuff. (MEN-1234) N52' + +################################################################################ + +git commit --allow-empty -m 'Changelog commit with trailing dot. N53 + +Changelog: Commit.' + +################################################################################ + +git commit --allow-empty -m 'Changelog title with trailing dot. N54 + +Changelog: Title.' + +################################################################################ + +git commit --allow-empty -m 'Jira MEN-1111: Changelog with Jira. N55 + +jira archive-2 +men-9 +https://northerntech.atlassian.net/browse/men-1 + +Changelog: commit' + +################################################################################ + +git commit --allow-empty -m 'Jira MEN-1111: Changelog with Jira tracker. N56 + +jira archive-2 +men-9 +https://northerntech.atlassian.net/browse/men-1 + +Changelog: commit' + +################################################################################ + +git commit --allow-empty -m 'Jira MEN-1111: Changelog with many Jira refs. N57 + +Ref: jira: archive-2 +Ref: men-9 +Ref: https://northerntech.atlassian.net/browse/men-1 +Ref: MEN-12345 + +Changelog: commit' + +################################################################################ + +git commit --allow-empty -m 'Changelog with inline Jira refs. N58a + +Changelog: Some inline jira men-80 reference. N58b +Changelog: Some other archive-7777 reference. N59 +Changelog: Some inline (jira men-81) reference. N60 +Changelog: Some other (men-7778) reference. N61 +Changelog: commit' + +################################################################################ + +git commit --allow-empty -m 'Changelog with suspicious number in it. N62 + +1234 + +Changelog: commit' + +################################################################################ + +git commit --allow-empty -m 'Changelog with suspicious number outside of it. N63 + +2345 + +Changelog: title' + +################################################################################ + +git commit --allow-empty -m 'Changelog with signed-off N64 + +Commit message +Signed-off-by: Hacker +Changelog: commit' + +################################################################################ + +git commit --allow-empty -m 'Changelog with invalid signed-off N65 + +Commit message +Mention "Signed-off-by: " mid sentence +Changelog: commit' + +################################################################################ + +# A bit tricky to make this commit, because normal git-commit cleans up \r. +# But it *is* in our Git history (1f900885d2aebda3e23ca5129d37f5e75eb83ee4) +TREE=$(git cat-file commit HEAD | grep '^tree ' | sed -e 's/^tree //') +git reset --hard $(git commit-tree -p $(git rev-parse HEAD) $TREE < 1461929235 -0400 +committer whatever 1461929235 -0400 +gpgsig -----BEGIN PGP SIGNATURE----- + Version: GnuPG v1 + + iQIcBAABAgAGBQJXI0UTAAoJEBHyPQpOS53uB9MP/06LBhE/i7BL37EANEMoUUM2 + gZwSiJ4HYF6lwNBBHNz/KYkUdh2B16m8mM1l9uePlMfhuA/xVlwP18shLaKDxdMA + zqt3+x6nPMHyx/WrPgOt+4hCyp+AYX/VQ0SaJGGKoOY9MJ9uOichu8iEfrNU333M + ePVKWRSA1p2+sxoq+oX63kx/x9/BK59GVKKF1/bsiAXjfNKfkmDLe4T4/0bpZYg1 + fDPXhUsHn2flF6sK3P/1hkgi/gJhsWRLZUqcj9HS3IwGSUGRIx5xzGvanD9nAnwg + eeFRAQ6UbNN032yjKr2cs9HoJuHTwpk1t6bDX/Ti17rrDtvSKFLBxMOFGJFl09/k + Ltmul9+KUbWZMVymtbWekgVA05FEBqpaJpZ89NvFM9vrl+lfn4AN84e1B/6M8jWI + VSS/C4jyAlKl7YWqBbzSh/ES+JBX3JwRO+KYCBYOE97TRZtgeFeNzF8HaRO4Ndnp + 7uJmeMhXLpBwXU0vUmoCRSJGEacnCZqDdrCE2XoUjzl1DKrdJT6RDXkgwZa8Hu3A + lEIXmIUMLTtbVSDOo+Ls5npaptlDOlG8cvCZFEcbKtjTfHcnpi8Kx+qNyGxuc79i + oGDhfxWFpCJK+TsrXZHoU6/d0ROmGynxYXWutlStreGQjMucML5vOXkKI5OHflVN + ioeZTiztfyD5AwfmioMb + =k68w + -----END PGP SIGNATURE----- + +Changelog with GPG entry N67 + +Changelog: Commit +EOF + +################################################################################ + +git commit --allow-empty -m 'MEN-1234 has no JIRA string in front. + +Changelog: Make sure the bugtracker reference is taken from the title + and goes after this message. The title should not be + included. N68' + +################################################################################ + +git commit --allow-empty -m 'MEN-1234 with JIRA tag in title. N68 + +Title should be included, rest of commit - not' + +################################################################################ + +git commit --allow-empty -m 'MEN-1234 with JIRA tag in title. N69 + +Changelog: None + +Nothing should be included' + +################################################################################ + + + +"$SRC_DIR/changelog-generator" --repo --sort-changelog HEAD > result.txt 2>stderr.txt || { + echo "Script failed with $?" + echo "--------" + echo "result.txt:" + cat result.txt + echo "--------" + echo "stderr.txt:" + cat stderr.txt + exit 1 +} +cat > expected.txt </dev/null || echo "") +if [ "x$current_sha" = "x$tag_sha" ] +then + # Tag exists and matches our current commit (HEAD): + print_version_without_sha +else + # Tag exists, but is on another commit: + print_version_with_sha +fi diff --git a/misc/githooks/commit-msg b/misc/githooks/commit-msg new file mode 100644 index 0000000000..e05713a0ed --- /dev/null +++ b/misc/githooks/commit-msg @@ -0,0 +1,11 @@ +#!/bin/sh + +if grep -i '^Changelog: *TODO' "$1" > /dev/null +then + echo "Aborting commit: You need to either add or delete the Changelog entry." + echo "This is what you wrote:" + grep -v "^#" "$1" + exit 1 +fi + +exit 0 diff --git a/misc/githooks/git-commit-template b/misc/githooks/git-commit-template new file mode 100644 index 0000000000..6c38e1c8f3 --- /dev/null +++ b/misc/githooks/git-commit-template @@ -0,0 +1,14 @@ + + +Changelog: Title +Ticket: CFE-1234 + +# Changelog: +# -or- +# Changelog: Title (Will use topmost line in commit) +# -or- +# Changelog: Commit (Will use entire commit message) +# -or- +# Changelog: None (Will not make a changelog entry) +# +# Always use past tense for the changelog entry, or the whole commit message. diff --git a/misc/githooks/install-hooks b/misc/githooks/install-hooks new file mode 100755 index 0000000000..f52a2e22a5 --- /dev/null +++ b/misc/githooks/install-hooks @@ -0,0 +1,45 @@ +#!/bin/sh + +DIRNAME="$(dirname $0)" +REPO_BASE="$DIRNAME/../../.." +FORCE=0 +DRY_RUN=0 + +maybe_run() +{ + echo "$@" + if [ $DRY_RUN = 0 ] + then + "$@" + fi +} + +if [ "$1" = "-f" ] +then + FORCE=1 +fi + +if [ $FORCE = 0 ] && [ -f "$HOME/.git-commit-template" ] +then + echo "$HOME/.git-commit-template already exists. Skipping (use -f to force)..." +else + maybe_run cp "$DIRNAME/git-commit-template" "$HOME/.git-commit-template" +fi +maybe_run git config --global commit.template "$HOME/.git-commit-template" + +for repo in core enterprise nova masterfiles buildscripts design-center mission-portal +do + if ! [ -d "$REPO_BASE/$repo" ] + then + echo "No repository found in $REPO_BASE/$repo. Skipping..." + continue + fi + + if [ $FORCE = 0 ] && [ -f "$REPO_BASE/$repo/.git/hooks/commit-msg" ] + then + echo "$REPO_BASE/$repo/.git/hooks/commit-msg already exists. Skipping (use -f to force)..." + else + maybe_run cp "$DIRNAME/commit-msg" "$REPO_BASE/$repo/.git/hooks/commit-msg" + maybe_run chmod ugo+x "$REPO_BASE/$repo/.git/hooks/commit-msg" + fi +done diff --git a/misc/init.d/cfengine3.in b/misc/init.d/cfengine3.in new file mode 100644 index 0000000000..6017e7e212 --- /dev/null +++ b/misc/init.d/cfengine3.in @@ -0,0 +1,687 @@ +#!/bin/sh +# +# cfengine3 +# +# +# Created by Nakarin Phooripoom on 22/6/2011. +# Copyright 2021 Northern.tech AS. +# +### BEGIN INIT INFO +# Provides: cfengine3 +# Required-Start: $local_fs $remote_fs $network $time +# Required-Stop: $local_fs $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: GNU configuration engine +# Description: Tool for configuring and maintaining network machines +### END INIT INFO +# +# chkconfig: 2345 60 40 +# description: Starts the cfengine daemons for remote and periodic \ +# execution of cfengine and for environment monitoring. +# set -e + +# +# Detect and replace non-POSIX shell +# +try_exec() { + type "$1" > /dev/null 2>&1 && exec "$@" +} + +broken_posix_shell() +{ + unset foo + local foo=1 + test "$foo" != "1" +} + +if broken_posix_shell >/dev/null 2>&1; then + try_exec /usr/xpg4/bin/sh "$0" "$@" + echo "No compatible shell script interpreter found." + echo "Please find a POSIX shell for your system." + exit 42 +fi + +##### start: Defined variables and conditios ##### + +PREFIX=${CFTEST_PREFIX:-@workdir@} +INPUTDIR=@inputdir@ +if [ "$INPUTDIR" = "default" ]; then + INPUTDIR="$PREFIX/inputs" +fi + +CFEXECD=${CFTEST_CFEXECD:-$PREFIX/bin/cf-execd} +CFSERVD=${CFTEST_CFSERVD:-$PREFIX/bin/cf-serverd} +CFMOND=${CFTEST_CFMOND:-$PREFIX/bin/cf-monitord} +CFAGENT=${CFTEST_CFAGENT:-$PREFIX/bin/cf-agent} + +PIDFILE_TEMPLATE=$PREFIX/%s.pid + +STOP_ATTEMPTS=6 +# From which point we start using SIGKILL. +KILL_THRESHOLD=4 +FORCE_KILL=0 + +CFENTERPRISE_INITD=$PREFIX/bin/cfengine3-nova-hub-init-d.sh + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PREFIX/bin + +# ensure that we use RPATH/loader entries from our binaries and not from the environment +unset LIBPATH # AIX < 5.3 +unset LD_LIBRARY_PATH + +# Has the package been 'removed' but not purged? +test -f $CFEXECD || exit 0 + +# default is no OS +SUSE=0 +REDHAT=0 +DEBIAN=0 + +# SuSE +if [ -d /var/lock/subsys ] && [ -f /usr/bin/zypper ]; then + SUSE=1 +fi + +# RedHat4/RedHat5/CentOS/Fedora +if [ -f /usr/bin/yum ] || [ -f /usr/sbin/up2date ]; then + if [ -d /var/lock/subsys ]; then + REDHAT=1 + fi +fi + +# Debian/Ubuntu +if [ -f /etc/debian_version ] && [ -f /usr/bin/apt-get ]; then + DEBIAN=1 + if [ -d /run/lock ]; then + LOCKDIR=/run/lock + else + LOCKDIR=/var/lock + fi +fi + +if [ -z "$LOCKDIR" ]; then + LOCKDIR=/var/lock +fi + +# default control file +if [ "$DEBIAN" = "1" ]; then + DEFAULT=/etc/default/cfengine3 + INIT_FUNCTIONS=/lib/lsb/init-functions + if [ -e "$INIT_FUNCTIONS" ]; then + . "$INIT_FUNCTIONS" + fi +else + DEFAULT=/etc/sysconfig/cfengine3 +fi + +if [ -f $DEFAULT ]; then + . $DEFAULT +else + RUN_CF_EXECD=1 + RUN_CF_SERVERD=1 + RUN_CF_MONITORD=1 +fi + +# Check /sbin/startproc for SUSE +if [ -x /sbin/startproc ] && [ -f /etc/rc.status ]; then + . /etc/rc.status + rc_reset + PROC=1 +else + PROC=0 +fi + +# Source function library. (Redhat/Centos/Fedora) +if [ -f /usr/bin/yum ] && [ -f /etc/rc.d/init.d/functions ]; then + . /etc/rc.d/init.d/functions + FUNC=1 +else + FUNC=0 +fi + +# Check something for Debian/Ubuntu +if [ -x /sbin/start-stop-daemon ]; then + SSD=1 +else + SSD=0 +fi + +CURRENT_PS_UID=__none__ +INSIDE_CONTAINER=-1 + +if ps --help 2>/dev/null | egrep -e '--cols\b' > /dev/null || ps --help output 2>/dev/null | egrep -e '--cols\b'; then + # There is a bug in SUSE which means that ps output will be truncated even + # when piped to grep, if the terminal size is small. However using --cols + # will override it. NOTE: On Suse 12 and 15, `ps` mentions `--cols` only + # in `ps --help output` only. + PS_OPTIONS="--cols 200" +else + PS_OPTIONS= +fi + +OS="$(uname)" + +##### end: Defined variables and conditions ##### + +##### start: Defined functions ##### + +# Takes a process string to match and returns a list of PIDs. +# Argument can optionally be preceded by "--ignore-user" to disable matching of +# the calling user's UID. +cf_get_matching_pids() +{ + # "ps" is an extremely incompatible tool, with differing options and output + # format almost everywhere. However, some things we can rely on: "ps -ef" + # seems to be supported everywhere, and we can usually rely on UID being the + # first field, PID and PPID the second and third, and command line the last + # field. In between there is a mess, and we stay away from it. We use this + # to match against the UID of the calling user. + # + # We also do some much more non-portable operations to try to avoid + # processes inside containers. + + if [ "$CURRENT_PS_UID" = "__none__" ]; then + CURRENT_PS_UID="$(ps $PS_OPTIONS -ef |egrep "^ *[^ ]+ +$$ "|awk -F' ' '{print $1}')" + fi + + if [ $INSIDE_CONTAINER = -1 ]; then + # Are we inside a container? + if [ -f /proc/self/cgroup ] && sed 's_/\(-\|system\|user\).slice__;s_/user-[0-9]*.slice__;s_/session-[0-9]*.scope__;s_/cfengine3.service__' /proc/self/cgroup | egrep '[0-9]+:devices:/[^:]' > /dev/null; then + INSIDE_CONTAINER=1 + else + INSIDE_CONTAINER=0 + fi + fi + + local ignore_user=0 + if [ "$1" = "--ignore-user" ]; then + ignore_user=1 + shift + fi + + case "$OS" in + SunOS) + local user_flag=-U + local user_string="$CURRENT_PS_UID" + if [ $ignore_user = 1 ]; then + user_flag= + user_string= + fi + + if [ -x /usr/bin/zonename ];then + /usr/bin/pgrep -f -z "$(/usr/bin/zonename)" $user_flag "$user_string" "$1" + else + /usr/bin/pgrep -f $user_flag "$user_string" "$1" + fi + ;; + *) + local user_string="$CURRENT_PS_UID" + if [ $ignore_user = 1 ]; then + # Match any user. + user_string="[^ ]+" + fi + + local pids="$(ps $PS_OPTIONS -ef|grep -v grep|egrep "^ *$user_string .*$1"|awk -F' ' '{print $2}')" + + for pid in $pids; do + # If the last character of the cgroup is not the root directory, + # then this is a container process. + if [ -f /proc/$pid/cgroup ] && sed 's_/\(-\|system\|user\).slice__;s_/user-[0-9]*.slice__;s_/session-[0-9]*.scope__;s_/cfengine3.service__;s_/cf-execd.service__;s_/cf-serverd.service__;s_/cf-monitord.service__' /proc/$pid/cgroup | egrep '[0-9]+:devices:/[^:]' > /dev/null; then + if [ $INSIDE_CONTAINER = 1 ]; then + echo $pid + fi + else + if [ $INSIDE_CONTAINER = 0 ]; then + echo $pid + fi + fi + done + ;; + esac +} + +cf_pidfile_pid() +{ + local pidfile="$1" + if egrep -v '^ *[0-9]+ *$' "$pidfile" > /dev/null 2>&1; then + echo "Malformed PID file: $pidfile." 1>&2 + elif [ -f "$pidfile" ]; then + cat "$pidfile" + fi +} + +cf_get_daemon_pid() +{ + local daemon="$1" + local base_daemon="$(basename "$daemon")" + local pidfile="$(printf "$PIDFILE_TEMPLATE" "$base_daemon")" + local pidfile_pid + local matching_pids + + # Try to get the PID of the daemon process from the pidfile and make sure it + # is valid. + pidfile_pid="$(cf_pidfile_pid "$pidfile")" + matching_pids="$(cf_get_matching_pids "$base_daemon")" + if [ -n "$pidfile_pid" ]; then + if echo "$matching_pids" | grep "^$pidfile_pid\$" >/dev/null; then + # A valid PID from the pidfile, let's report it. + echo "$pidfile_pid" + return + else + # PID from the pid file not in the matching pids -> must be a + # leftover. Let's remove it. + rm -f "$pidfile" + fi; + fi; + + # No or invalid pidfile, let's report the matching PIDs. + echo "$matching_pids" +} + +cf_start_daemon() +{ + local daemon="$1" + shift + local base_daemon="$(basename "$daemon")" + + echo -n "Starting $base_daemon..." + + # Redhat/Centos/Fedora + if [ "$REDHAT" = "1" ] && [ "$FUNC" = "1" ]; then + daemon "$daemon" "$@" + cf_touch "/var/lock/subsys/$base_daemon" + cf_touch "/var/lock/subsys/cfengine3" + elif [ "$REDHAT" = "1" ] && [ "$FUNC" = "0" ]; then + "$daemon" "$@" + cf_touch "/var/lock/subsys/$base_daemon" + cf_touch "/var/lock/subsys/cfengine3" + + # SUSE + elif [ "$SUSE" = "1" ] && [ "$PROC" = "1" ]; then + /sbin/startproc "$daemon" "$@" + rc_status -v + cf_touch "/var/run/$base_daemon.pid" + elif [ "$SUSE" = "1" ] && [ "$PROC" = "0" ]; then + "$daemon" "$@" + cf_touch "/var/run/$base_daemon.pid" + + # Debian/Ubuntu + elif [ "$DEBIAN" = "1" ] && [ "$SSD" = "1" ]; then + /sbin/start-stop-daemon --start --pidfile "$(printf "$PIDFILE_TEMPLATE" "$base_daemon")" --exec "$daemon" -- "$@" + cf_touch "$LOCKDIR/$base_daemon" + elif [ "$DEBIAN" = "1" ] && [ "$SSD" = "0" ]; then + "$daemon" "$@" + cf_touch "$LOCKDIR/$base_daemon" + + # All others + else + "$daemon" "$@" + fi + + echo +} + +# Return 0 if any daemon was attempted killed, 1 otherwise. +cf_stop_daemon() +{ + local daemon="$1" + local base_daemon="$(basename "$daemon")" + local pidfile="$(printf "$PIDFILE_TEMPLATE" "$base_daemon")" + local pids + local ret=1 + + pids="$(cf_get_daemon_pid $daemon)" + + if [ -z "$pids" ]; then + return 1 + fi + + echo "Shutting down $base_daemon..." + + local iter=1 + local signal=TERM + while [ $iter -le $STOP_ATTEMPTS ] && [ -n "$pids" ]; do + ret=0 + + if [ $iter -ge $KILL_THRESHOLD ] || [ $FORCE_KILL = 1 ]; then + signal=KILL + fi + + if [ $iter -ge 2 ]; then + # Give the daemon some time to do its cleanup and actually + # terminate. Then check its state again. + sleep 2 + pids="$(cf_get_daemon_pid $daemon)" + fi + + if [ "$signal" != "KILL" ] && [ -f "$pidfile" ] && [ "$DEBIAN" = "1" ] && [ "$SSD" = "1" ]; then + # Debian/Ubuntu with start-stop-daemon. + /sbin/start-stop-daemon -o --stop --pidfile "$pidfile" --retry 5 --name "$base_daemon" >/dev/null 2>&1 + else + # All others, or if there is no pidfile, or if using SIGKILL. + for pid in $pids; do + kill -$signal "$pid" >/dev/null 2>&1 + if [ "$signal" = "KILL" ]; then + # If using SIGKILL, the daemons won't be able to do this themselves. + rm -f "$pidfile" + fi + done + fi + + pids="$(cf_get_daemon_pid $daemon)" + iter=$(($iter+1)) + done + + # Redhat/Centos/Fedora + if [ "$REDHAT" = "1" ] && [ "$FUNC" = "1" ]; then + if [ -f "/var/lock/subsys/$base_daemon" ]; then + cf_rm -f "/var/lock/subsys/$base_daemon" + cf_rm -f "/var/lock/subsys/cfengine3" + fi + elif [ "$REDHAT" = "1" ] && [ "$FUNC" = "0" ]; then + if [ -f "/var/lock/subsys/$base_daemon" ]; then + cf_rm -f "/var/lock/subsys/$base_daemon" + cf_rm -f "/var/lock/subsys/cfengine3" + fi + + # SUSE + elif [ "$SUSE" = "1" ] && [ "$PROC" = "1" ]; then + rc_status -v + if [ -f "/var/run/$base_daemon.pid" ]; then + cf_rm "/var/run/$base_daemon.pid" + fi + elif [ "$SUSE" = "1" ] && [ "$PROC" = "0" ]; then + if [ -f "/var/run/$base_daemon.pid" ]; then + cf_rm "/var/run/$base_daemon.pid" + fi + + # Debian/Ubuntu + elif [ "$DEBIAN" = "1" ] && [ "$SSD" = "1" ]; then + if [ -f "$LOCKDIR/$base_daemon" ]; then + cf_rm "$LOCKDIR/$base_daemon" + fi + elif [ "$DEBIAN" = "1" ] && [ "$SSD" = "0" ]; then + if [ -f "$LOCKDIR/$base_daemon" ]; then + cf_rm "$LOCKDIR/$base_daemon" + fi + fi + + return $ret +} + +# Returns 0 if agents were found, 1 if not. +cf_stop_agents() +{ + local base_agent="$(basename "$CFAGENT")" + local not_found=1 + local ret=1 + local signal=TERM + local iter=1 + + while [ $iter -le $STOP_ATTEMPTS ]; do + if [ $iter -ge $KILL_THRESHOLD ] || [ $FORCE_KILL = 1 ]; then + signal=KILL + fi + + if [ $iter -gt 2 ]; then + # If we failed at first, give things time to settle. + sleep 2 + fi + + not_found=1 + for pid in $(cf_get_matching_pids "$CFAGENT"); do + not_found=0 + ret=0 + kill -$signal $pid + done + + if [ $not_found = 1 ]; then + break + fi + + iter=$(($iter+1)) + done + + return $ret +} + +cf_status_daemon() +{ + local daemon="$1" + local base_daemon="$(basename "$daemon")" + local pidfile="$(printf "$PIDFILE_TEMPLATE" "$base_daemon")" + local daemon_status="$(cf_pidfile_pid "$pidfile")" + local pids="$(cf_get_matching_pids "$base_daemon")" + + if [ -f "$pidfile" ] && ! echo "$pids" | grep "$daemon_status" > /dev/null; then + echo "Warning: PID file '$pidfile' does not contain the right PID ('$pids' != '$daemon_status'). Should restart CFEngine." + fi + + # https is expected to have multiple pids, skip detection of multiple + # pids to avoid false alarms + + if [ "${base_daemon}" != "httpd" ] && [ "${base_daemon}" != "cf-hub" ]; then + # Lack of quotes around $pids is important to eliminate newlines. + if [ -n "$(echo $pids | grep '[^0-9]')" ]; then + echo "Warning: Multiple $base_daemon processes present. Should restart CFEngine." + fi + fi + + if [ -n "$pids" ]; then + if [ "$REDHAT" = "1" ] && [ "$FUNC" = "1" ]; then + status "$daemon" + elif [ "$SUSE" = "1" ] && [ "$PROC" = "1" ]; then + printf 'Checking for %s ' "$base_daemon" + checkproc "$daemon" + rc_status -v + else + # The additional "echo" is to eliminate newlines in $pids. + echo "$base_daemon is running..." + fi + else + echo "$base_daemon is not running" + return 3 + fi +} + +cf_start_core_daemons() +{ + # start cf-execd + if [ "$RUN_CF_EXECD" = "1" ]; then + cf_start_daemon "$CFEXECD" + fi + + # start cf-serverd + if [ "$RUN_CF_SERVERD" = "1" ]; then + cf_start_daemon "$CFSERVD" + fi + + # start cf-monitord + if [ "$RUN_CF_MONITORD" = "1" ]; then + cf_start_daemon "$CFMOND" + fi +} + +cf_stop_core_daemons() +{ + # Stopping the CFEngine daemons is sometimes extremely tricky, because the + # killing of a daemon may coincide with the spawning of a new agent, which + # will spawn a new daemon and bring us right back to the start. So we always + # kill both, making sure they are dead, and once we have, we go back and do + # one more complete round of process detection to make *sure* that nothing + # is alive. As long as something is alive we will always require another + # round, up to STOP_ATTEMPTS times. + + local iter=1 + while [ $iter -le $STOP_ATTEMPTS ]; do + local any_process_present=0 + + if [ $iter -ge 2 ]; then + # Let's wait a bit before the second attempt. + sleep 2 + fi + + if [ $iter -ge $KILL_THRESHOLD ]; then + # The stopping functions internally use SIGKILL if they feel the + # need, but only after some time, so we still force it here, in case + # we have a fork()-bomb-like situation where each kill results in a + # new daemon or agent. + FORCE_KILL=1 + fi + + # First stop any agents running because they may start the daemons we + # will be stopping behind our back. + cf_stop_agents && any_process_present=1 + + # shutdown cf-execd + if [ "$RUN_CF_EXECD" = "1" ]; then + cf_stop_daemon "$CFEXECD" && any_process_present=1 + fi + + # shutdown cf-serverd + if [ "$RUN_CF_SERVERD" = "1" ]; then + cf_stop_daemon "$CFSERVD" && any_process_present=1 + fi + + # shutdown cf-monitord + if [ "$RUN_CF_MONITORD" = "1" ]; then + cf_stop_daemon "$CFMOND" && any_process_present=1 + fi + + # In case agents were spawned just as we were trying to kill the + # daemons, try killing those too. + cf_stop_agents && any_process_present=1 + + if [ $any_process_present = 0 ]; then + break + fi + + iter=$(($iter+1)) + done +} + +cf_status_core_daemons() +{ + local cf_status_core_daemons_exit=0 + # status cf-execd + if [ "$RUN_CF_EXECD" = "1" ]; then + cf_status_daemon "$CFEXECD" + if [ "$?" = "3" ]; then + cf_status_core_daemons_exit=3 + fi + fi + + # status cf-serverd + if [ "$RUN_CF_SERVERD" = "1" ]; then + cf_status_daemon "$CFSERVD" + if [ "$?" = "3" ]; then + cf_status_core_daemons_exit=3 + fi + fi + + # status cf-monitord + if [ "$RUN_CF_MONITORD" = "1" ]; then + cf_status_daemon "$CFMOND" + if [ "$?" = "3" ]; then + cf_status_core_daemons_exit=3 + fi + fi + + # Return the exit code + return $cf_status_core_daemons_exit +} + +cf_touch() +{ + # Not during testing. + if [ -z "$CFTEST_PREFIX" ]; then + touch "$@" + fi +} + +cf_rm() +{ + # Not during testing. + if [ -z "$CFTEST_PREFIX" ]; then + rm "$@" + fi +} + +##### end: Defined functions ##### + +##### Source enterprise script, if present ##### + +if [ -f "$CFENTERPRISE_INITD" ]; then + ENTERPRISE_SERVER=1 + CALLED_FROM_INITSCRIPT="1" . "$CFENTERPRISE_INITD" +else + ENTERPRISE_SERVER=0 + alias enterprise_start=true + alias enterprise_stop=true + alias enterprise_status=true +fi + +### start scripting here ### + +case "$1" in + start) + if ! [ -f $INPUTDIR/promises.cf ]; then + # Prevent the enterprise scripts from launching any non-web + # related stuff when we are not bootstrapped. + export CF_ONLY_WEB_SERVICES=1 + fi + + # Start Enterprise services. + enterprise_start + + if [ "$CF_ONLY_WEB_SERVICES" = "1" ]; then + + if [ "$ENTERPRISE_SERVER" = "1" ]; then + # Enterprise servers automatically run "start" as part of package installation, + # at which point they don't have policy in inputs/, because they are not bootstrapped. + # This is not an error, like for community or enterprise clients (case below). + exit 0 + fi + + # On hosts we do not start any process when there is no + # policy, so indicate this as an error. + echo "No policy found in $INPUTDIR/promises.cf, not starting core daemons" >&2 + exit 1 + fi + + cf_start_core_daemons + + exit 0 + ;; + stop) + if [ "$CF_ONLY_WEB_SERVICES" != "1" ]; then + cf_stop_core_daemons + fi + + # Stop Enterprise services. + enterprise_stop + + exit 0 + ;; + status) + if [ "$CF_ONLY_WEB_SERVICES" = "1" ]; then + exit 0 + fi + + # Status Enterprise services. + enterprise_status + + cf_status_core_daemons + + exit $? + ;; + restart|reload|force-reload) + $0 stop + $0 start + ;; + *) + echo "Usage: $0 {start|stop|status|restart|reload|force-reload}" >&2 + exit 1 + ;; +esac diff --git a/misc/selinux/Makefile.am b/misc/selinux/Makefile.am new file mode 100644 index 0000000000..870b866b30 --- /dev/null +++ b/misc/selinux/Makefile.am @@ -0,0 +1,22 @@ +if WITH_SELINUX +cfengine-enterprise.te: cfengine-enterprise.te.all $(PLATFORM_SELINUX_POLICIES) + cat cfengine-enterprise.te.all $(PLATFORM_SELINUX_POLICIES) > cfengine-enterprise.te + +cfengine-enterprise.pp: cfengine-enterprise.te cfengine-enterprise.fc + $(MAKE) -f /usr/share/selinux/devel/Makefile -j1 + +selinuxdir = $(prefix)/selinux +selinux_DATA = cfengine-enterprise.pp +selinux_DATA += cfengine-enterprise.te +selinux_DATA += cfengine-enterprise.fc + +clean-local: + rm -rf tmp +endif + +# explicit DISTFILES are required for these files to be part of a 'make dist' +# tarball even without running './configure --with-selinux-policy' +DISTFILES = Makefile.in Makefile.am cfengine-enterprise.fc cfengine-enterprise.te.all +DISTFILES += cfengine-enterprise.te.el9 + +CLEANFILES = cfengine-enterprise.pp cfengine-enterprise.if cfengine-enterprise.te diff --git a/misc/selinux/cfengine-enterprise.fc b/misc/selinux/cfengine-enterprise.fc new file mode 100644 index 0000000000..fc6aa0e7c2 --- /dev/null +++ b/misc/selinux/cfengine-enterprise.fc @@ -0,0 +1,13 @@ +/var/cfengine/bin/cf-execd -- gen_context(system_u:object_r:cfengine_execd_exec_t,s0) +/var/cfengine/bin/cf-serverd -- gen_context(system_u:object_r:cfengine_serverd_exec_t,s0) +/var/cfengine/bin/cf-monitord -- gen_context(system_u:object_r:cfengine_monitord_exec_t,s0) +/var/cfengine/bin/cf-agent -- gen_context(system_u:object_r:cfengine_agent_exec_t,s0) +/var/cfengine/bin/cf-hub -- gen_context(system_u:object_r:cfengine_hub_exec_t,s0) +/var/cfengine/bin/cf-reactor -- gen_context(system_u:object_r:cfengine_reactor_exec_t,s0) +/var/cfengine/bin/cfbs -- gen_context(system_u:object_r:cfengine_cfbs_exec_t,s0) +/var/cfengine/bin/pg.* -- gen_context(system_u:object_r:cfengine_postgres_exec_t,s0) +/var/cfengine/httpd/bin/apachectl -- gen_context(system_u:object_r:cfengine_apachectl_exec_t,s0) +/var/cfengine/httpd/bin/.* -- gen_context(system_u:object_r:cfengine_httpd_exec_t,s0) +/var/cfengine/httpd/php/bin/.* -- gen_context(system_u:object_r:cfengine_httpd_exec_t,s0) +/opt/cfengine(/.*)? gen_context(system_u:object_r:cfengine_var_lib_t,s0) +/opt/cfengine/notification_scripts(/.*)? gen_context(system_u:object_r:cfengine_action_script_exec_t,s0) diff --git a/misc/selinux/cfengine-enterprise.te.all b/misc/selinux/cfengine-enterprise.te.all new file mode 100644 index 0000000000..a75b92924b --- /dev/null +++ b/misc/selinux/cfengine-enterprise.te.all @@ -0,0 +1,899 @@ +# SELinux policy module for CFEngine Enterprise +# +# This is a complementary module for the upstream cfengine module [1]. +# +# [1] https://github.com/fedora-selinux/selinux-policy-contrib/blob/rawhide/cfengine.te +# +module cfengine-enterprise 1.0; + +# 'require' is something like 'import' -- we need to list here all the things +# used in this policy module +require { + attribute domain; + attribute entry_type; + attribute file_type; + attribute exec_type; + attribute non_security_file_type; + attribute non_auth_file_type; + type bin_t; + type cert_t; + type devlog_t; + type kernel_t; + type var_t; + type var_log_t; + type fs_t; + type unconfined_t; + type unreserved_port_t; + type user_cron_spool_t; + type cfengine_serverd_t; + type cfengine_execd_exec_t; + type net_conf_t; + type node_t; + type passwd_file_t; + type ping_exec_t; + type proc_t; + type proc_net_t; + type proc_xen_t; + type proc_security_t; + type cfengine_serverd_exec_t; + type http_port_t; + type ldap_port_t; + type postgresql_port_t; + type smtp_port_t; + type ssh_port_t; + type rpm_exec_t; + type rpm_var_lib_t; + type sssd_t; + type sssd_public_t; + type sssd_var_lib_t; + type sysfs_t; + type sysctl_net_t; + type system_cron_spool_t; + type systemd_unit_file_t; + type hugetlbfs_t; + type init_exec_t; + type init_var_run_t; + type ifconfig_t; + type ifconfig_exec_t; + type journalctl_exec_t; + type cfengine_execd_t; + type cfengine_log_t; + type systemd_systemctl_exec_t; + type useradd_exec_t; + type cfengine_monitord_t; + type dmidecode_exec_t; + type init_t; + type cfengine_monitord_exec_t; + type gpg_exec_t; + type shadow_t; + type cfengine_var_lib_t; + type crontab_exec_t; + type hostname_exec_t; + type groupadd_exec_t; + type shell_exec_t; + type semanage_exec_t; + type syslogd_var_run_t; + type system_dbusd_t; + type system_dbusd_var_run_t; + type tmp_t; + type tmpfs_t; + role system_r; + type tty_device_t; + type user_devpts_t; + type sysctl_t; + type postfix_etc_t; + type postfix_master_t; + type postfix_postdrop_exec_t; + type postfix_public_t; + type postfix_spool_t; + type sendmail_exec_t; + type sshd_t; + type ssh_exec_t; + type ssh_home_t; + type rpm_script_t; + class lockdown { confidentiality integrity }; + class tcp_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown name_connect accept listen name_bind node_bind }; + class mctp_socket { ioctl read write create getattr setattr lock relabelfrom relabelto append map bind connect listen accept getopt setopt shutdown recvfrom sendto recv_msg send_msg name_bind }; + class udp_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown node_bind }; + class sock_file { create write getattr setattr unlink }; + class rawip_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class packet_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class unix_stream_socket { create ioctl read getattr lock write setattr append bind connect connectto getopt setopt shutdown }; + class unix_dgram_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown sendto }; + class appletalk_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_route_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown nlmsg_read getopt }; + class netlink_firewall_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_tcpdiag_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_nflog_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_xfrm_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_selinux_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_audit_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_ip6fw_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_dnrt_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_kobject_uevent_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class tun_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_iscsi_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_fib_lookup_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_connector_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_netfilter_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_generic_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_scsitransport_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_rdma_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netlink_crypto_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class sctp_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class icmp_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class ax25_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class ipx_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class netrom_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class atmpvc_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class x25_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class xdp_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class rose_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class decnet_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class atmsvc_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class rds_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class irda_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class pppox_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class llc_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class can_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class tipc_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class bluetooth_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class iucv_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class rxrpc_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class isdn_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class phonet_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class ieee802154_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class caif_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class alg_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class nfc_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class vsock_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class kcm_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class qipcrtr_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class smc_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class bridge_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class dccp_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class ib_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class mpls_socket { create ioctl read getattr lock write setattr append bind connect getopt setopt shutdown }; + class process { setrlimit transition dyntransition execstack execheap execmem signull siginh getattr sigchld }; + class fd use; + class file { execute execute_no_trans getattr ioctl map open read unlink write entrypoint lock link rename append setattr create relabelfrom relabelto watch watch_reads }; + class fifo_file { create open getattr setattr read write append rename link unlink ioctl lock relabelfrom relabelto }; + class dir { getattr read search open write add_name remove_name lock ioctl create setattr rmdir }; + class filesystem getattr; + class lnk_file { create getattr read unlink }; + class capability { dac_read_search sys_module chown dac_read_search dac_override fowner fsetid sys_admin mknod net_raw net_admin sys_nice sys_rawio sys_resource setuid setgid sys_nice sys_ptrace kill net_bind_service }; + class cap_userns sys_ptrace; + class capability2 { mac_admin mac_override block_suspend syslog wake_alarm }; + class association { sendto recvfrom setcontext polmatch }; + class security setsecparam; + class service { start stop status reload enable disable }; + class system { module_request }; + class memprotect mmap_zero; + class peer recv; + class chr_file { getattr }; +} + + +#============= cfengine_agent_t ============= +# define an *unconfined* domain for the agent (so that it can access/do anything) +type cfengine_agent_t; +typeattribute cfengine_agent_t domain; +role system_r types cfengine_agent_t; + +# this is a macro invocation, the file has to be processed with +# make -f /usr/share/selinux/devel/Makefile +unconfined_domain(cfengine_agent_t) + +# /var/cfengine/bin/cf-agent has the 'cfengine_agent_exec_t' context which is an +# entrypoint for the 'cfengine_agent_t' domain +type cfengine_agent_exec_t; +typeattribute cfengine_agent_exec_t entry_type; +typeattribute cfengine_agent_exec_t exec_type; +typeattribute cfengine_agent_exec_t file_type, non_security_file_type, non_auth_file_type; +role object_r types cfengine_agent_exec_t; + +allow cfengine_agent_t cfengine_agent_exec_t:file entrypoint; +allow cfengine_agent_t cfengine_agent_exec_t:file { ioctl read getattr lock map execute open }; + +# cf-agent needs to be able to transition into the domain of RPM scriplets +allow cfengine_agent_t rpm_script_t:process transition; + +#============= cfengine_execd_t ============== +# allow cf-execd to run cf-agent and make sure the forked process run in the +# unconfined cfengine_agent_t domain +type_transition cfengine_execd_t cfengine_agent_exec_t:process cfengine_agent_t; +allow cfengine_execd_t cfengine_agent_t:process transition; +allow cfengine_execd_t cfengine_agent_exec_t:file { open read execute map getattr }; + +# allow cf-execd to use/execute libpromises.so +allow cfengine_execd_t cfengine_var_lib_t:file map; +allow cfengine_execd_t cfengine_var_lib_t:file execute; + +# allow cf-execd to execute cf-promises +allow cfengine_execd_t cfengine_var_lib_t:file execute_no_trans; + +# TODO: this should not be needed +allow cfengine_execd_t ssh_port_t:tcp_socket name_connect; +allow cfengine_execd_t proc_xen_t:dir search; + +allow cfengine_execd_t cfengine_log_t:file { read unlink write }; +allow cfengine_execd_t cfengine_log_t:lnk_file { create getattr read unlink }; +allow cfengine_execd_t cfengine_log_t:dir add_name; +allow cfengine_execd_t cfengine_monitord_exec_t:file getattr; +allow cfengine_execd_t cfengine_serverd_exec_t:file getattr; +allow cfengine_execd_t cfengine_hub_exec_t:file getattr; +allow cfengine_execd_t cfengine_reactor_exec_t:file getattr; + +# allow cf-execd to work with local/UNIX sockets +allow cfengine_execd_t cfengine_var_lib_t:sock_file { create unlink getattr setattr }; + +allow cfengine_execd_t self:capability sys_ptrace; + +allow cfengine_execd_t crontab_exec_t:file getattr; +allow cfengine_execd_t dmidecode_exec_t:file getattr; +allow cfengine_execd_t fs_t:filesystem getattr; +allow cfengine_execd_t gpg_exec_t:file getattr; +allow cfengine_execd_t groupadd_exec_t:file getattr; +allow cfengine_execd_t hostname_exec_t:file getattr; +allow cfengine_execd_t init_exec_t:file getattr; +allow cfengine_execd_t init_t:unix_stream_socket connectto; +allow cfengine_execd_t journalctl_exec_t:file getattr; +allow cfengine_execd_t ping_exec_t:file getattr; +allow cfengine_execd_t proc_net_t:file { getattr open read }; +allow cfengine_execd_t proc_net_t:lnk_file { getattr read }; +allow cfengine_execd_t proc_security_t:file { getattr open read }; +allow cfengine_execd_t rpm_exec_t:file getattr; +allow cfengine_execd_t rpm_var_lib_t:dir search; +allow cfengine_execd_t rpm_var_lib_t:file open; +allow cfengine_execd_t self:capability dac_read_search; +allow cfengine_execd_t shadow_t:file { getattr open read }; +allow cfengine_execd_t smtp_port_t:tcp_socket name_connect; +allow cfengine_execd_t system_cron_spool_t:dir getattr; +allow cfengine_execd_t systemd_systemctl_exec_t:file getattr; +allow cfengine_execd_t systemd_unit_file_t:dir search; +allow cfengine_execd_t systemd_unit_file_t:file getattr; +allow cfengine_execd_t unreserved_port_t:tcp_socket name_connect; +allow cfengine_execd_t user_cron_spool_t:dir getattr; +allow cfengine_execd_t useradd_exec_t:file getattr; +allow cfengine_execd_t var_t:dir read; +allow cfengine_execd_t semanage_exec_t:file getattr; +allow cfengine_execd_t tty_device_t:chr_file getattr; +allow cfengine_execd_t user_devpts_t:chr_file getattr; +allow cfengine_execd_t ssh_exec_t:file getattr; + +#============= cfengine_monitord_t ============== +# allow cf-monitord to use/execute libpromises.so +allow cfengine_monitord_t cfengine_var_lib_t:file map; +allow cfengine_monitord_t cfengine_var_lib_t:file execute; + +# allow cf-monitord to execute cf-promises +allow cfengine_monitord_t cfengine_var_lib_t:file execute_no_trans; + +allow cfengine_monitord_t cfengine_execd_exec_t:file getattr; +allow cfengine_monitord_t cfengine_serverd_exec_t:file getattr; +allow cfengine_monitord_t cfengine_agent_exec_t:file getattr; +allow cfengine_monitord_t cfengine_hub_exec_t:file getattr; +allow cfengine_monitord_t cfengine_reactor_exec_t:file getattr; + +allow cfengine_monitord_t var_log_t:file { open read }; + +allow cfengine_monitord_t self:capability { dac_override dac_read_search sys_ptrace }; +allow cfengine_monitord_t self:cap_userns sys_ptrace; + +allow cfengine_monitord_t crontab_exec_t:file getattr; +allow cfengine_monitord_t dmidecode_exec_t:file getattr; +allow cfengine_monitord_t groupadd_exec_t:file getattr; +allow cfengine_monitord_t hostname_exec_t:file getattr; +allow cfengine_monitord_t init_exec_t:file getattr; +allow cfengine_monitord_t journalctl_exec_t:file getattr; +allow cfengine_monitord_t ping_exec_t:file getattr; +allow cfengine_monitord_t rpm_exec_t:file getattr; +allow cfengine_monitord_t shadow_t:file getattr; +allow cfengine_monitord_t systemd_systemctl_exec_t:file getattr; +allow cfengine_monitord_t user_cron_spool_t:dir getattr; +allow cfengine_monitord_t useradd_exec_t:file getattr; +allow cfengine_monitord_t var_t:dir read; +allow cfengine_monitord_t semanage_exec_t:file getattr; +allow cfengine_monitord_t tty_device_t:chr_file getattr; +allow cfengine_monitord_t user_devpts_t:chr_file getattr; +allow cfengine_monitord_t sysctl_t:dir read; +allow cfengine_monitord_t ssh_exec_t:file getattr; +allow cfengine_monitord_t proc_net_t:file { getattr open read }; +allow cfengine_monitord_t proc_security_t:file { getattr open read }; + +# TODO: this should not be needed +allow cfengine_monitord_t proc_xen_t:dir search; + +#============= cfengine_serverd_t ============== +# allow cf-serverd to run cf-agent and make sure the forked process run in the +# unconfined cfengine_agent_t domain +allow cfengine_serverd_t cfengine_agent_exec_t:file { open read execute execute_no_trans map getattr }; +type_transition cfengine_serverd_t cfengine_agent_exec_t:process cfengine_agent_t; +allow cfengine_serverd_t cfengine_agent_t:process transition; + +# allow cf-serverd to use/execute libpromises.so +allow cfengine_serverd_t cfengine_var_lib_t:file map; +allow cfengine_serverd_t cfengine_var_lib_t:file execute; + +# allow cf-serverd to execute cf-promises +allow cfengine_serverd_t cfengine_var_lib_t:file execute_no_trans; + +# allow cf-serverd to connect to the CFEngine port and to write into a local socket (in case of +# call-collect on hosts and the hub itself, respectively) +allow cfengine_serverd_t unreserved_port_t:tcp_socket name_connect; +allow cfengine_serverd_t cfengine_var_lib_t:sock_file write; +allow cfengine_serverd_t cfengine_hub_t:unix_stream_socket connectto; + +# TODO: this should not be needed +allow cfengine_serverd_t ssh_port_t:tcp_socket name_connect; +allow cfengine_serverd_t proc_xen_t:dir search; + +allow cfengine_serverd_t cfengine_execd_exec_t:file getattr; +allow cfengine_serverd_t cfengine_monitord_exec_t:file getattr; +allow cfengine_serverd_t cfengine_hub_exec_t:file getattr; +allow cfengine_serverd_t cfengine_reactor_exec_t:file getattr; +allow cfengine_serverd_t cfengine_log_t:lnk_file getattr; + +allow cfengine_serverd_t crontab_exec_t:file getattr; +allow cfengine_serverd_t dmidecode_exec_t:file getattr; +allow cfengine_serverd_t fs_t:filesystem getattr; +allow cfengine_serverd_t groupadd_exec_t:file getattr; +allow cfengine_serverd_t hostname_exec_t:file getattr; +allow cfengine_serverd_t init_exec_t:file getattr; +allow cfengine_serverd_t init_t:dir { getattr open search read } ; # /proc/1 analysis +allow cfengine_serverd_t init_t:file { getattr open read }; +allow cfengine_serverd_t journalctl_exec_t:file getattr; +allow cfengine_serverd_t ping_exec_t:file getattr; +allow cfengine_serverd_t proc_net_t:file { getattr open read }; +allow cfengine_serverd_t proc_net_t:lnk_file { getattr read }; +allow cfengine_serverd_t proc_security_t:file { getattr open read }; +allow cfengine_serverd_t rpm_exec_t:file getattr; +allow cfengine_serverd_t self:process setrlimit; +allow cfengine_serverd_t self:tcp_socket { accept listen }; +allow cfengine_serverd_t shadow_t:file getattr; +allow cfengine_serverd_t systemd_systemctl_exec_t:file getattr; +allow cfengine_serverd_t unreserved_port_t:tcp_socket name_bind; +allow cfengine_serverd_t user_cron_spool_t:dir getattr; +allow cfengine_serverd_t useradd_exec_t:file getattr; +allow cfengine_serverd_t var_t:dir read; +allow cfengine_serverd_t semanage_exec_t:file getattr; +allow cfengine_serverd_t ssh_exec_t:file getattr; + + +#============= cfengine_hub_t ============== +type cfengine_hub_t; +typeattribute cfengine_hub_t domain; +role system_r types cfengine_hub_t; + +# cf-hub uses setuid/setgid to initiate scheduled reports as cfapache:cfpostgres +allow cfengine_hub_t self:capability { setgid setuid }; +# /var/cfengine/bin/cf-hub has the 'cfengine_hub_exec_t' context which is an +# entrypoint for the 'cfengine_hub_t' domain +type cfengine_hub_exec_t; +typeattribute cfengine_hub_exec_t entry_type; +typeattribute cfengine_hub_exec_t exec_type; +typeattribute cfengine_hub_exec_t file_type, non_security_file_type, non_auth_file_type; +role object_r types cfengine_hub_exec_t; + +type_transition init_t cfengine_hub_exec_t:process cfengine_hub_t; +allow init_t cfengine_hub_t:process transition; +allow init_t cfengine_hub_exec_t:file { execute open read }; +allow init_t cfengine_hub_t:process siginh; + +allow cfengine_hub_t cfengine_hub_exec_t:file entrypoint; +allow cfengine_hub_t cfengine_hub_exec_t:file { ioctl read getattr lock map execute open }; + +# allow cf-hub to use/execute libpromises.so +allow cfengine_hub_t cfengine_var_lib_t:file map; +allow cfengine_hub_t cfengine_var_lib_t:file execute; +allow cfengine_hub_t cfengine_var_lib_t:file { getattr open read }; + +# allow cf-hub to read/write from/to a socket owned by cf-serverd (passed in +# case of call-collect) +allow cfengine_hub_t cfengine_serverd_t:tcp_socket { read write setopt }; + +allow cfengine_hub_t cfengine_agent_exec_t:file getattr; +allow cfengine_hub_t cfengine_execd_exec_t:file getattr; +allow cfengine_hub_t cfengine_monitord_exec_t:file getattr; +allow cfengine_hub_t cfengine_serverd_exec_t:file getattr; +allow cfengine_hub_t cfengine_reactor_exec_t:file getattr; + +allow cfengine_hub_t cfengine_postgres_t:unix_stream_socket connectto; +allow cfengine_hub_t unreserved_port_t:tcp_socket name_connect; + +allow cfengine_hub_t cfengine_log_t:dir getattr; +allow cfengine_hub_t cfengine_var_lib_t:dir { add_name getattr open read search write remove_name }; +allow cfengine_hub_t cfengine_var_lib_t:file { create ioctl lock write unlink append setattr }; +allow cfengine_hub_t cfengine_var_lib_t:lnk_file { getattr read }; +allow cfengine_hub_t cfengine_var_lib_t:sock_file { create unlink }; + +# cf-hub executes PHP in case of scheduled reports +allow cfengine_hub_t cfengine_httpd_exec_t:file map; +allow cfengine_hub_t cfengine_httpd_exec_t:file { execute execute_no_trans getattr open read }; + +# sending scheduled reports via email +allow cfengine_hub_t postfix_etc_t:dir { getattr open read search }; +allow cfengine_hub_t postfix_etc_t:file { getattr open read }; +allow cfengine_hub_t postfix_master_t:unix_stream_socket connectto; +allow cfengine_hub_t postfix_postdrop_exec_t:file map; +allow cfengine_hub_t postfix_postdrop_exec_t:file { execute execute_no_trans open read }; +allow cfengine_hub_t postfix_public_t:dir search; +allow cfengine_hub_t postfix_public_t:sock_file { getattr write }; +allow cfengine_hub_t postfix_spool_t:dir { add_name remove_name search write }; +allow cfengine_hub_t postfix_spool_t:file { create getattr open read rename setattr write }; +allow cfengine_hub_t sendmail_exec_t:file map; +allow cfengine_hub_t sendmail_exec_t:file { execute execute_no_trans open read }; + +allow cfengine_hub_t bin_t:file map; +allow cfengine_hub_t bin_t:file { execute execute_no_trans }; +allow cfengine_hub_t cert_t:dir search; +allow cfengine_hub_t cert_t:file { getattr open read }; +allow cfengine_hub_t crontab_exec_t:file getattr; +allow cfengine_hub_t devlog_t:lnk_file read; +allow cfengine_hub_t devlog_t:sock_file write; +allow cfengine_hub_t dmidecode_exec_t:file getattr; +allow cfengine_hub_t fs_t:filesystem getattr; +allow cfengine_hub_t groupadd_exec_t:file getattr; +allow cfengine_hub_t hostname_exec_t:file getattr; +allow cfengine_hub_t init_exec_t:file getattr; +allow cfengine_hub_t init_t:dir { getattr open read search }; +allow cfengine_hub_t init_t:file { getattr open read }; +allow cfengine_hub_t init_t:unix_stream_socket { ioctl getattr read write }; # systemd, PAM? +allow cfengine_hub_t init_var_run_t:dir search; +allow cfengine_hub_t journalctl_exec_t:file getattr; +allow cfengine_hub_t kernel_t:unix_dgram_socket sendto; +allow cfengine_hub_t net_conf_t:file { getattr open read }; +allow cfengine_hub_t passwd_file_t:file { getattr open read }; +allow cfengine_hub_t ping_exec_t:file getattr; +allow cfengine_hub_t proc_net_t:file { getattr open read }; +allow cfengine_hub_t proc_net_t:lnk_file { getattr read }; +allow cfengine_hub_t proc_security_t:file { getattr open read }; +allow cfengine_hub_t proc_t:dir read; +allow cfengine_hub_t rpm_exec_t:file getattr; +allow cfengine_hub_t self:capability { dac_override chown dac_read_search }; +allow cfengine_hub_t self:process { execmem setrlimit }; +allow cfengine_hub_t self:tcp_socket { connect create getopt setopt read write }; +allow cfengine_hub_t self:udp_socket { connect create getattr ioctl setopt read write }; +allow cfengine_hub_t self:netlink_route_socket { create getopt setopt bind getattr nlmsg_read }; +allow cfengine_hub_t self:unix_dgram_socket { create connect read write }; +allow cfengine_hub_t semanage_exec_t:file getattr; +allow cfengine_hub_t shadow_t:file getattr; +allow cfengine_hub_t smtp_port_t:tcp_socket name_connect; +allow cfengine_hub_t sssd_public_t:dir search; +allow cfengine_hub_t sssd_public_t:file map; +allow cfengine_hub_t sssd_public_t:file { getattr open read }; +allow cfengine_hub_t sssd_t:unix_stream_socket connectto; +allow cfengine_hub_t sssd_var_lib_t:dir search; +allow cfengine_hub_t sssd_var_lib_t:sock_file write; +allow cfengine_hub_t sysctl_net_t:dir search; +allow cfengine_hub_t sysfs_t:dir read; +allow cfengine_hub_t sysfs_t:file { getattr open read }; +allow cfengine_hub_t syslogd_var_run_t:dir search; +allow cfengine_hub_t systemd_systemctl_exec_t:file getattr; +allow cfengine_hub_t tmp_t:sock_file write; +allow cfengine_hub_t user_cron_spool_t:dir getattr; +allow cfengine_hub_t useradd_exec_t:file getattr; +allow cfengine_hub_t var_t:dir read; +allow cfengine_hub_t ssh_exec_t:file getattr; +allow cfengine_hub_t tmp_t:dir read; + +# Use of the TLS kernel module +allow cfengine_hub_t kernel_t:system module_request; + +# TODO: these should not be needed +# this is a macro invocation, the file has to be processed with +# make -f /usr/share/selinux/devel/Makefile +sysnet_domtrans_ifconfig(cfengine_hub_t) +allow cfengine_hub_t shell_exec_t:file map; +allow cfengine_hub_t shell_exec_t:file { execute execute_no_trans }; +allow cfengine_hub_t proc_xen_t:dir search; + + +#============= cfengine_postgres_t ============== +type cfengine_postgres_t; +typeattribute cfengine_postgres_t domain; +role system_r types cfengine_postgres_t; + +# /var/cfengine/bin/cf-postgres has the 'cfengine_postgres_exec_t' context which is an +# entrypoint for the 'cfengine_postgres_t' domain +type cfengine_postgres_exec_t; +typeattribute cfengine_postgres_exec_t entry_type; +typeattribute cfengine_postgres_exec_t exec_type; +typeattribute cfengine_postgres_exec_t file_type, non_security_file_type, non_auth_file_type; +role object_r types cfengine_postgres_exec_t; + +type_transition init_t cfengine_postgres_exec_t:process cfengine_postgres_t; +allow init_t cfengine_postgres_t:process transition; +allow init_t cfengine_postgres_exec_t:file { execute open read }; +allow init_t cfengine_postgres_t:process siginh; + +allow cfengine_postgres_t cfengine_postgres_exec_t:file entrypoint; +allow cfengine_postgres_t cfengine_postgres_exec_t:file { ioctl read getattr lock map execute open }; + +# TODO: Why are 'map', 'execute' and 'execute_no_trans' needed for postgres? +allow cfengine_postgres_t cfengine_var_lib_t:file map; +allow cfengine_postgres_t cfengine_var_lib_t:file { create execute execute_no_trans getattr link open read rename unlink write rename }; +allow cfengine_postgres_t cfengine_var_lib_t:lnk_file read; +allow cfengine_postgres_t cfengine_var_lib_t:dir { add_name getattr open create read remove_name search write }; + +allow cfengine_postgres_t postgresql_port_t:tcp_socket name_bind; + +allow cfengine_postgres_t cert_t:dir search; +allow cfengine_postgres_t cert_t:file { getattr open read }; +allow cfengine_postgres_t hugetlbfs_t:file map; +allow cfengine_postgres_t hugetlbfs_t:file { read write }; +allow cfengine_postgres_t init_t:unix_stream_socket { getattr ioctl read write }; # pg_ctl, systemd, PAM? +allow cfengine_postgres_t init_var_run_t:dir search; +allow cfengine_postgres_t system_dbusd_var_run_t:dir search; +allow cfengine_postgres_t net_conf_t:file { getattr open read }; +allow cfengine_postgres_t node_t:tcp_socket node_bind; +allow cfengine_postgres_t node_t:udp_socket node_bind; +allow cfengine_postgres_t proc_t:file { getattr open read }; +allow cfengine_postgres_t self:netlink_route_socket { bind create getattr nlmsg_read read write }; +allow cfengine_postgres_t self:tcp_socket { bind create listen setopt read write }; +allow cfengine_postgres_t self:udp_socket { bind connect create getattr getopt read write }; +allow cfengine_postgres_t self:unix_stream_socket connectto; +allow cfengine_postgres_t sssd_public_t:dir search; +allow cfengine_postgres_t sssd_public_t:file map; +allow cfengine_postgres_t sssd_public_t:file { getattr open read }; +allow cfengine_postgres_t sssd_var_lib_t:sock_file write; +allow cfengine_postgres_t sssd_var_lib_t:dir search; +allow cfengine_postgres_t sssd_t:unix_stream_socket connectto; +allow cfengine_postgres_t tmp_t:dir { add_name write remove_name }; +allow cfengine_postgres_t tmp_t:file { create open write unlink }; +allow cfengine_postgres_t tmp_t:sock_file { create setattr unlink write }; +allow cfengine_postgres_t tmpfs_t:dir { add_name write remove_name }; +allow cfengine_postgres_t tmpfs_t:file { create open read write map unlink getattr }; +allow cfengine_postgres_t tmpfs_t:filesystem getattr; +allow cfengine_postgres_t var_log_t:file { append open }; + +# so that PostgreSQL can check if cfpostgres user/group exists +allow cfengine_postgres_t passwd_file_t:file { open read getattr }; + +# Needed for systemd to be able to check PostgreSQL's PID file +allow init_t cfengine_var_lib_t:dir { read remove_name write }; +allow init_t cfengine_var_lib_t:file { getattr open read unlink ioctl }; + +# TODO: these should not be needed +allow cfengine_postgres_t shell_exec_t:file map; +allow cfengine_postgres_t shell_exec_t:file { execute execute_no_trans }; + + +#============= cfengine_httpd_t ============== +type cfengine_httpd_t; +typeattribute cfengine_httpd_t domain; +role system_r types cfengine_httpd_t; + +# /var/cfengine/httpd/bin/* files have the 'cfengine_httpd_exec_t' context which +# is an entrypoint for the 'cfengine_httpd_t' domain +type cfengine_httpd_exec_t; +typeattribute cfengine_httpd_exec_t entry_type; +typeattribute cfengine_httpd_exec_t exec_type; +typeattribute cfengine_httpd_exec_t file_type, non_security_file_type, non_auth_file_type; +role object_r types cfengine_httpd_exec_t; + +type_transition init_t cfengine_httpd_exec_t:process cfengine_httpd_t; +allow init_t cfengine_httpd_t:process transition; +allow init_t cfengine_httpd_exec_t:file { execute getattr open read }; +allow init_t cfengine_httpd_t:process siginh; + +allow cfengine_httpd_t cfengine_httpd_exec_t:file entrypoint; +allow cfengine_httpd_t cfengine_httpd_exec_t:file { ioctl read getattr lock map execute open }; + +allow cfengine_httpd_t cert_t:dir search; +allow cfengine_httpd_t cert_t:file { getattr open read }; +allow cfengine_httpd_t cert_t:lnk_file read; +allow cfengine_httpd_t cfengine_httpd_exec_t:file execute_no_trans; +allow cfengine_httpd_t cfengine_postgres_t:unix_stream_socket connectto; + +# allow httpd to use our custom compiled module +allow cfengine_httpd_t cfengine_var_lib_t:file map; +allow cfengine_httpd_t cfengine_var_lib_t:file { append create execute getattr ioctl lock open read setattr unlink write rename }; + +allow cfengine_httpd_t cfengine_var_lib_t:dir { add_name getattr open read remove_name search write create }; +allow cfengine_httpd_t cfengine_var_lib_t:lnk_file read; + +# allow httpd/php to work with cf-execd sockets +allow cfengine_httpd_t cfengine_execd_t:unix_stream_socket connectto; +allow cfengine_httpd_t cfengine_var_lib_t:sock_file write; + +# allow httpd/php to upload notification/alert scripts +allow cfengine_httpd_t cfengine_action_script_exec_t:dir { add_name getattr search write remove_name }; +allow cfengine_httpd_t cfengine_action_script_exec_t:file { create write setattr unlink }; + +# sending reports via email +allow cfengine_httpd_t postfix_etc_t:dir { getattr open read search }; +allow cfengine_httpd_t postfix_etc_t:file { getattr open read }; +allow cfengine_httpd_t postfix_master_t:unix_stream_socket connectto; +allow cfengine_httpd_t postfix_postdrop_exec_t:file { execute execute_no_trans map open read }; +allow cfengine_httpd_t postfix_public_t:dir search; +allow cfengine_httpd_t postfix_public_t:sock_file { getattr write }; +allow cfengine_httpd_t postfix_spool_t:dir { add_name remove_name search write }; +allow cfengine_httpd_t postfix_spool_t:file { create getattr open read rename setattr write }; +allow cfengine_httpd_t self:process setrlimit; +allow cfengine_httpd_t sendmail_exec_t:file { execute execute_no_trans getattr map open read }; + +allow cfengine_httpd_t devlog_t:lnk_file read; +allow cfengine_httpd_t devlog_t:sock_file write; +allow cfengine_httpd_t http_port_t:tcp_socket { name_bind name_connect }; +allow cfengine_httpd_t init_t:dbus send_msg; +allow cfengine_httpd_t init_t:unix_stream_socket { getattr ioctl read write }; # apachectl +allow cfengine_httpd_t init_var_run_t:dir search; +allow cfengine_httpd_t kernel_t:unix_dgram_socket sendto; +allow cfengine_httpd_t net_conf_t:file { getattr open read }; +allow cfengine_httpd_t node_t:tcp_socket node_bind; +allow cfengine_httpd_t self:capability { dac_override dac_read_search kill net_bind_service setgid setuid net_admin }; +allow cfengine_httpd_t self:netlink_route_socket { bind create getattr nlmsg_read read write }; +allow cfengine_httpd_t self:process execmem; +allow cfengine_httpd_t unconfined_t:process signull; +allow cfengine_httpd_t self:tcp_socket { accept bind connect create getattr getopt listen setopt shutdown read write ioctl setattr append name_connect }; +allow cfengine_httpd_t self:udp_socket { create ioctl read getattr write setattr append connect getopt setopt shutdown setattr }; +allow cfengine_httpd_t self:unix_dgram_socket { connect create }; +allow cfengine_httpd_t sssd_public_t:dir search; +allow cfengine_httpd_t sssd_public_t:file map; +allow cfengine_httpd_t sssd_public_t:file { getattr open read }; +allow cfengine_httpd_t sssd_t:unix_stream_socket connectto; +allow cfengine_httpd_t sssd_var_lib_t:dir search; +allow cfengine_httpd_t sssd_var_lib_t:sock_file write; +allow cfengine_httpd_t syslogd_var_run_t:dir search; +allow cfengine_httpd_t tmp_t:sock_file write; +allow cfengine_httpd_t tmp_t:file { create setattr unlink write rename open }; +allow cfengine_httpd_t tmp_t:dir { add_name remove_name write read }; +allow cfengine_httpd_t var_t:dir read; + +# apparently, httpd creates some temporary bits in /tmp that it needs to mmap() +allow cfengine_httpd_t tmp_t:file map; + +# httpd/PHP needs to be able to send emails via an external SMTP server +allow cfengine_httpd_t smtp_port_t:tcp_socket name_connect; + +# httpd/PHP needs to be able to contact LDAP servers +allow cfengine_httpd_t ldap_port_t:tcp_socket name_connect; + +# Bidirectional DBus communication between httpd and systemd +allow cfengine_httpd_t system_dbusd_t:dbus send_msg; +allow cfengine_httpd_t system_dbusd_t:unix_stream_socket connectto; +allow cfengine_httpd_t system_dbusd_var_run_t:dir search; +allow cfengine_httpd_t system_dbusd_var_run_t:sock_file write; +allow init_t cfengine_httpd_t:dbus send_msg; + +# allow httpd to run 'ps' and thus gather information about all running processes on the system +# this is a macro invocation, the file has to be processed with +# make -f /usr/share/selinux/devel/Makefile +ps_process_pattern(cfengine_httpd_t, domain) +allow cfengine_httpd_t bin_t:file { map execute execute_no_trans }; +allow cfengine_httpd_t proc_t:dir read; +allow cfengine_httpd_t proc_t:file { open read }; + +# TODO: these should not be needed +allow cfengine_httpd_t passwd_file_t:file { getattr open read }; +allow cfengine_httpd_t shell_exec_t:file map; +allow cfengine_httpd_t shell_exec_t:file { execute execute_no_trans }; + + +#============= cfengine_apachectl_t ============== +type cfengine_apachectl_t; +typeattribute cfengine_apachectl_t domain; +role system_r types cfengine_apachectl_t; + +# /var/cfengine/httpd/bin/apachectl has the 'cfengine_apachectl_exec_t' context which +# is an entrypoint for the 'cfengine_apachectl_t' domain +type cfengine_apachectl_exec_t; +typeattribute cfengine_apachectl_exec_t entry_type; +typeattribute cfengine_apachectl_exec_t exec_type; +typeattribute cfengine_apachectl_exec_t file_type, non_security_file_type, non_auth_file_type; +role object_r types cfengine_apachectl_exec_t; + +# allow transitions from init_t (systemd) to cfengine_apachectl_t to cfengine_httpd_t +type_transition init_t cfengine_apachectl_exec_t:process cfengine_apachectl_t; +allow init_t cfengine_apachectl_t:process transition; +allow init_t cfengine_apachectl_exec_t:file { execute getattr open read }; +allow init_t cfengine_apachectl_t:process siginh; + +type_transition cfengine_apachectl_t cfengine_httpd_exec_t:process cfengine_httpd_t; +allow cfengine_apachectl_t cfengine_httpd_t:process transition; +allow cfengine_apachectl_t cfengine_httpd_exec_t:file { execute getattr open read }; +allow cfengine_apachectl_t cfengine_httpd_t:process siginh; + +allow cfengine_apachectl_t cfengine_apachectl_exec_t:file entrypoint; +allow cfengine_apachectl_t cfengine_apachectl_exec_t:file { ioctl read getattr lock map execute open }; + +allow cfengine_apachectl_t cfengine_var_lib_t:dir search; +allow cfengine_apachectl_t cfengine_var_lib_t:file { getattr open read }; +allow cfengine_apachectl_t init_t:unix_stream_socket ioctl; +allow cfengine_apachectl_t passwd_file_t:file { getattr open read }; +allow cfengine_apachectl_t shell_exec_t:file { map execute }; +allow cfengine_apachectl_t sssd_public_t:dir search; +allow cfengine_apachectl_t sssd_public_t:file { getattr open read map }; +allow cfengine_apachectl_t sssd_t:unix_stream_socket connectto; +allow cfengine_apachectl_t sssd_var_lib_t:dir search; +allow cfengine_apachectl_t sssd_var_lib_t:sock_file write; +allow cfengine_apachectl_t bin_t:file { execute execute_no_trans map }; +allow cfengine_apachectl_t proc_t:dir read; +allow cfengine_apachectl_t proc_t:file { open read }; + +# allow apachectl to run 'ps' and thus gather information about all running processes on the system +# this is a macro invocation, the file has to be processed with +# make -f /usr/share/selinux/devel/Makefile +ps_process_pattern(cfengine_apachectl_t, domain) + +#============= cfengine_reactor_t ============== +type cfengine_reactor_t; +typeattribute cfengine_reactor_t domain; +role system_r types cfengine_reactor_t; + +# /var/cfengine/bin/cf-reactor has the 'cfengine_reactor_exec_t' context which is an +# entrypoint for the 'cfengine_reactor_t' domain +type cfengine_reactor_exec_t; +typeattribute cfengine_reactor_exec_t entry_type; +typeattribute cfengine_reactor_exec_t exec_type; +typeattribute cfengine_reactor_exec_t file_type, non_security_file_type, non_auth_file_type; +role object_r types cfengine_reactor_exec_t; + +type_transition init_t cfengine_reactor_exec_t:process cfengine_reactor_t; +allow init_t cfengine_reactor_t:process transition; +allow init_t cfengine_reactor_exec_t:file { execute open read }; +allow init_t cfengine_reactor_t:process siginh; + +type_transition cfengine_reactor_t cfengine_cfbs_exec_t:process cfengine_cfbs_t; +allow cfengine_reactor_t cfengine_cfbs_t:process transition; +allow cfengine_reactor_t cfengine_cfbs_exec_t:file { execute open read }; + +# cf-reactor runs PHP code to evaluate alerts (as cfapache user) +allow cfengine_reactor_t cfengine_httpd_exec_t:file { execute execute_no_trans getattr open read map }; +allow cfengine_reactor_t self:capability { setgid setuid }; +allow cfengine_reactor_t self:process execmem; + +allow cfengine_reactor_t cfengine_reactor_exec_t:file entrypoint; +allow cfengine_reactor_t cfengine_reactor_exec_t:file { ioctl read getattr lock map execute open }; + +# allow cf-reactor to use/execute libpromises.so +allow cfengine_reactor_t cfengine_var_lib_t:file map; +allow cfengine_reactor_t cfengine_var_lib_t:file execute; +allow cfengine_reactor_t cfengine_var_lib_t:file { getattr open read }; + +allow cfengine_reactor_t cfengine_postgres_t:unix_stream_socket connectto; + +allow cfengine_reactor_t cfengine_log_t:dir getattr; +allow cfengine_reactor_t cfengine_var_lib_t:dir { add_name getattr create open read search write remove_name setattr rmdir }; +allow cfengine_reactor_t cfengine_var_lib_t:file { create ioctl lock write unlink append setattr link rename execute execute_no_trans }; +allow cfengine_reactor_t cfengine_var_lib_t:lnk_file { getattr read create unlink }; + +allow cfengine_reactor_t passwd_file_t:file { open read getattr }; +allow cfengine_reactor_t self:capability { dac_override chown fsetid }; +allow cfengine_reactor_t self:unix_dgram_socket { create connect }; +allow cfengine_reactor_t sssd_var_lib_t:dir search; +allow cfengine_reactor_t sssd_var_lib_t:sock_file write; +allow cfengine_reactor_t sssd_public_t:dir search; +allow cfengine_reactor_t sssd_public_t:file { open read getattr map }; +allow cfengine_reactor_t sssd_t:unix_stream_socket connectto; +allow cfengine_reactor_t tmp_t:sock_file write; +allow cfengine_reactor_t tmp_t:dir { add_name remove_name write }; +allow cfengine_reactor_t tmp_t:file { create open setattr unlink write }; +allow cfengine_reactor_t devlog_t:sock_file write; +allow cfengine_reactor_t devlog_t:lnk_file read; +allow cfengine_reactor_t syslogd_var_run_t:dir search; +allow cfengine_reactor_t kernel_t:unix_dgram_socket sendto; +allow cfengine_reactor_t kernel_t:unix_stream_socket connectto; +allow cfengine_reactor_t init_var_run_t:dir search; +allow cfengine_reactor_t init_t:unix_stream_socket { getattr ioctl }; + +allow cfengine_reactor_t var_t:dir read; +allow cfengine_reactor_t bin_t:file { execute execute_no_trans map }; +allow cfengine_reactor_t fs_t:filesystem getattr; +allow cfengine_reactor_t shell_exec_t:file map; +allow cfengine_reactor_t shell_exec_t:file { execute execute_no_trans }; + +allow cfengine_reactor_t cert_t:dir search; +allow cfengine_reactor_t cert_t:file { getattr open read }; +allow cfengine_reactor_t cert_t:lnk_file read; + +allow cfengine_reactor_t http_port_t:tcp_socket name_connect; +allow cfengine_reactor_t net_conf_t:file { getattr open read }; +allow cfengine_reactor_t self:capability { chown fsetid }; +allow cfengine_reactor_t self:netlink_route_socket { bind create getattr nlmsg_read }; +allow cfengine_reactor_t self:tcp_socket { create ioctl read getattr write setattr append connect getopt setopt shutdown name_connect }; +allow cfengine_reactor_t self:udp_socket { create ioctl read getattr write setattr append connect getopt setopt shutdown }; +allow cfengine_reactor_t self:unix_stream_socket connectto; + +allow cfengine_reactor_t ssh_exec_t:file map; +allow cfengine_reactor_t ssh_exec_t:file { execute execute_no_trans getattr open read }; +allow cfengine_reactor_t ssh_home_t:dir { getattr search }; +allow cfengine_reactor_t ssh_port_t:tcp_socket name_connect; + + +#============= cfengine_action_script_t ============== +# A special type and domain for action (notification/alert) scripts executed by +# PHP. They can do anything, so they need to run in an unconstrained domain. At +# the same time we don't want our Apache and PHP to do anything so these scripts +# cannot just run in the http_t domain. + +type cfengine_action_script_t; +typeattribute cfengine_action_script_t domain; +role system_r types cfengine_action_script_t; + +# this is a macro invocation, the file has to be processed with +# make -f /usr/share/selinux/devel/Makefile +unconfined_domain(cfengine_action_script_t) + +# /opt/cfengine/notification_scripts/* files have the +# 'cfengine_action_script_exec_t' context which is an entrypoint for the +# 'cfengine_action_script_t' domain +type cfengine_action_script_exec_t; +typeattribute cfengine_action_script_exec_t entry_type; +typeattribute cfengine_action_script_exec_t exec_type; +typeattribute cfengine_action_script_exec_t file_type, non_security_file_type, non_auth_file_type; +role object_r types cfengine_action_script_exec_t; + +# cf-apache/httpd manipulates with the action scripts +allow cfengine_httpd_t cfengine_action_script_exec_t:file { getattr open read }; + +# cf-reactor runs alerts periodically and these can trigger custom action scripts +type_transition cfengine_reactor_t cfengine_action_script_exec_t:process cfengine_action_script_t; +allow cfengine_reactor_t cfengine_action_script_t:process transition; +allow cfengine_reactor_t cfengine_action_script_exec_t:file { execute execute_no_trans getattr open read }; +allow cfengine_reactor_t cfengine_action_script_exec_t:dir { getattr search }; +allow cfengine_reactor_t cfengine_action_script_t:process siginh; + +allow cfengine_action_script_t cfengine_action_script_exec_t:file entrypoint; +allow cfengine_action_script_t cfengine_action_script_exec_t:file { ioctl read getattr lock map execute open }; + +#============= cfengine_cfbs_t ============== +type cfengine_cfbs_t; +typeattribute cfengine_cfbs_t domain; +role system_r types cfengine_cfbs_t; + +# /var/cfengine/bin/cf-cfbs has the 'cfengine_cfbs_exec_t' context which is an +# entrypoint for the 'cfengine_cfbs_t' domain +type cfengine_cfbs_exec_t; +typeattribute cfengine_cfbs_exec_t entry_type; +typeattribute cfengine_cfbs_exec_t exec_type; +typeattribute cfengine_cfbs_exec_t file_type, non_security_file_type, non_auth_file_type; +role object_r types cfengine_cfbs_exec_t; + +allow cfengine_cfbs_t cfengine_cfbs_exec_t:file entrypoint; +allow cfengine_cfbs_t cfengine_cfbs_exec_t:file { ioctl read getattr lock map execute open }; + +allow cfengine_cfbs_t cfengine_var_lib_t:dir { add_name getattr create open read search write remove_name setattr }; +allow cfengine_cfbs_t cfengine_var_lib_t:file { create ioctl lock write unlink append setattr link rename execute execute_no_trans map getattr open read }; +allow cfengine_cfbs_t cfengine_var_lib_t:lnk_file { getattr read create unlink }; + +allow cfengine_cfbs_t cfengine_reactor_t:fifo_file { getattr ioctl read write }; + +allow cfengine_cfbs_t bin_t:file { map execute }; + +allow cfengine_cfbs_t cert_t:dir search; +allow cfengine_cfbs_t cert_t:file { getattr open read }; +allow cfengine_cfbs_t cert_t:lnk_file read; +allow cfengine_cfbs_t http_port_t:tcp_socket name_connect; +allow cfengine_cfbs_t net_conf_t:file { getattr open read }; +allow cfengine_cfbs_t passwd_file_t:file { getattr open read }; +allow cfengine_cfbs_t self:capability dac_override; +allow cfengine_cfbs_t self:netlink_route_socket { bind create getattr nlmsg_read }; +allow cfengine_cfbs_t self:tcp_socket { create ioctl read getattr write setattr append connect getopt setopt shutdown name_connect }; +allow cfengine_cfbs_t self:udp_socket { create ioctl read getattr write setattr append connect getopt setopt shutdown }; +allow cfengine_cfbs_t sssd_public_t:dir search; +allow cfengine_cfbs_t sssd_public_t:file { map getattr open read }; +allow cfengine_cfbs_t sssd_t:unix_stream_socket connectto; +allow cfengine_cfbs_t sssd_var_lib_t:dir search; +allow cfengine_cfbs_t sssd_var_lib_t:sock_file write; + + +#============= special rules for Federated Reporting ============= +# sshd needs access to files in /opt/cfengine +allow sshd_t cfengine_var_lib_t:file { getattr open read }; + + +#============= TO REMOVE ============== +# Daemons should have proper access to files based on DAC rules (file +# permissions), not just because they run under root. +dontaudit cfengine_execd_t self:capability dac_override; +dontaudit cfengine_serverd_t self:capability { dac_override dac_read_search }; + +# cf-promises run by the daemons shouldn't check if a function is available in +# PostgreSQL, the respective returnszero() is in an agent bundle +dontaudit cfengine_execd_t cfengine_postgres_t:unix_stream_socket connectto; +dontaudit cfengine_execd_t tmp_t:sock_file write; +dontaudit cfengine_serverd_t cfengine_postgres_t:unix_stream_socket connectto; +dontaudit cfengine_serverd_t tmp_t:sock_file write; diff --git a/misc/selinux/cfengine-enterprise.te.el9 b/misc/selinux/cfengine-enterprise.te.el9 new file mode 100644 index 0000000000..e5641ede37 --- /dev/null +++ b/misc/selinux/cfengine-enterprise.te.el9 @@ -0,0 +1,10 @@ +require { + type systemd_userdbd_runtime_t; +} + +# PAM module for dynamic users +allow cfengine_httpd_t systemd_userdbd_runtime_t:dir { getattr open read search }; +allow cfengine_httpd_t systemd_userdbd_runtime_t:sock_file write; +allow cfengine_httpd_t kernel_t:unix_stream_socket connectto; +allow cfengine_reactor_t systemd_userdbd_runtime_t:dir { getattr open read search }; +allow cfengine_reactor_t systemd_userdbd_runtime_t:sock_file write; diff --git a/misc/systemd/README.md b/misc/systemd/README.md new file mode 100644 index 0000000000..b9a409bede --- /dev/null +++ b/misc/systemd/README.md @@ -0,0 +1,5 @@ +# systemd service files for CFEngine + +On systems running systemd as their init process, the cfengine agents should be +controlled via "systemctl start" and "systemctl stop". This will put cfengine +into separate cgroups and handle logging appropriately. diff --git a/misc/systemd/cf-apache.service.in b/misc/systemd/cf-apache.service.in new file mode 100644 index 0000000000..e0acc7a75c --- /dev/null +++ b/misc/systemd/cf-apache.service.in @@ -0,0 +1,20 @@ +[Unit] +Description=CFEngine Enterprise Webserver +After=syslog.target +Wants=cf-postgres.service +After=cf-postgres.service +ConditionPathExists=@workdir@/httpd/bin/apachectl +PartOf=cfengine3.service + +[Service] +Type=forking +ExecStart=@workdir@/httpd/bin/apachectl start +ExecStop=@workdir@/httpd/bin/apachectl stop +ExecReload=@workdir@/httpd/bin/apachectl graceful +PIDFile=@workdir@/httpd/httpd.pid +Restart=always +RestartSec=10 +UMask=0177 + +[Install] +WantedBy=multi-user.target diff --git a/misc/systemd/cf-execd.service.in b/misc/systemd/cf-execd.service.in new file mode 100644 index 0000000000..66f1e235e3 --- /dev/null +++ b/misc/systemd/cf-execd.service.in @@ -0,0 +1,16 @@ +[Unit] +Description=CFEngine Execution Scheduler +After=syslog.target +ConditionPathExists=@bindir@/cf-execd +ConditionPathExists=@workdir@/inputs/promises.cf +PartOf=cfengine3.service + +[Service] +Type=simple +ExecStart=@bindir@/cf-execd --no-fork +Restart=always +RestartSec=10 +KillMode=process + +[Install] +WantedBy=multi-user.target diff --git a/misc/systemd/cf-hub.service.in b/misc/systemd/cf-hub.service.in new file mode 100644 index 0000000000..a6027ce206 --- /dev/null +++ b/misc/systemd/cf-hub.service.in @@ -0,0 +1,19 @@ +[Unit] +Description=CFEngine Enterprise Hub Report Collector +PartOf=cfengine3.service +ConditionPathExists=@bindir@/cf-hub +ConditionPathExists=@workdir@/inputs/promises.cf +After=syslog.target +After=network.target + +Wants=cf-postgres.service +After=cf-postgres.service + +[Service] +Type=simple +ExecStart=@bindir@/cf-hub --no-fork +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/misc/systemd/cf-monitord.service.in b/misc/systemd/cf-monitord.service.in new file mode 100644 index 0000000000..351090d6e6 --- /dev/null +++ b/misc/systemd/cf-monitord.service.in @@ -0,0 +1,15 @@ +[Unit] +Description=CFEngine Monitor Daemon +After=syslog.target +ConditionPathExists=@bindir@/cf-monitord +ConditionPathExists=@workdir@/inputs/promises.cf +PartOf=cfengine3.service + +[Service] +Type=simple +ExecStart=@bindir@/cf-monitord --no-fork +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/misc/systemd/cf-postgres.service.in b/misc/systemd/cf-postgres.service.in new file mode 100644 index 0000000000..393efad1ee --- /dev/null +++ b/misc/systemd/cf-postgres.service.in @@ -0,0 +1,35 @@ +[Unit] +Description=CFEngine Enterprise PostgreSQL Database +After=syslog.target +ConditionPathExists=@bindir@/pg_ctl +PartOf=cfengine3.service + +[Service] +Type=forking +WorkingDirectory=/tmp +User=cfpostgres +Restart=always +RestartSec=10 + +PIDFile=@workdir@/state/pg/data/postmaster.pid +SyslogIdentifier=postgres + +# Disable OOM kill on the postmaster +OOMScoreAdjust=-1000 +# ... but allow it still to be effective for child processes +# (note that these settings are ignored by Postgres releases before 9.5) +Environment=PG_OOM_ADJUST_FILE=/proc/self/oom_score_adj +Environment=PG_OOM_ADJUST_VALUE=0 + +# Maximum number of seconds pg_ctl will wait for postgres to start. Note that +# PGSTARTTIMEOUT should be less than TimeoutSec value. +#Environment=PGSTARTTIMEOUT=270 + +Environment=PGDATA=@workdir@/state/pg/data + +ExecStart=@bindir@/pg_ctl -w -D ${PGDATA} -l /var/log/postgresql.log start +ExecStop=@bindir@/pg_ctl -w -D ${PGDATA} -l /var/log/postgresql.log stop -m fast +ExecReload=@bindir@/pg_ctl -w -D ${PGDATA} -l /var/log/postgresql.log reload -m fast + +[Install] +WantedBy=multi-user.target diff --git a/misc/systemd/cf-reactor.service.in b/misc/systemd/cf-reactor.service.in new file mode 100644 index 0000000000..9e751abec5 --- /dev/null +++ b/misc/systemd/cf-reactor.service.in @@ -0,0 +1,19 @@ +[Unit] +Description=CFEngine Enterprise event reaction daemon +PartOf=cfengine3.service +ConditionPathExists=@bindir@/cf-reactor +ConditionPathExists=@workdir@/inputs/promises.cf +After=syslog.target +After=network.target + +Wants=cf-postgres.service +After=cf-postgres.service + +[Service] +Type=simple +ExecStart=@bindir@/cf-reactor --no-fork +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/misc/systemd/cf-serverd.service.in b/misc/systemd/cf-serverd.service.in new file mode 100644 index 0000000000..03945f8fea --- /dev/null +++ b/misc/systemd/cf-serverd.service.in @@ -0,0 +1,17 @@ +[Unit] +Description=CFEngine Enterprise file server +After=syslog.target +After=network-online.target +ConditionPathExists=@bindir@/cf-serverd +ConditionPathExists=@workdir@/policy_server.dat +ConditionPathExists=@workdir@/inputs/promises.cf +PartOf=cfengine3.service + +[Service] +Type=simple +ExecStart=@bindir@/cf-serverd --no-fork +Restart=always +RestartSec=10 + +[Install] +WantedBy=network-online.target diff --git a/misc/systemd/cf-serverd.socket b/misc/systemd/cf-serverd.socket new file mode 100644 index 0000000000..a59cba534f --- /dev/null +++ b/misc/systemd/cf-serverd.socket @@ -0,0 +1,12 @@ +[Unit] +Description=CFEngine Enterprise file server socket +PartOf=cfengine3.service +Before=cf-serverd.service + +[Socket] +ListenStream = 5308 + +[Install] +WantedBy=multi-user.target +WantedBy=cfengine3.service +WantedBy=sockets.target diff --git a/misc/systemd/cfengine3.service.in b/misc/systemd/cfengine3.service.in new file mode 100644 index 0000000000..0e75842a7b --- /dev/null +++ b/misc/systemd/cfengine3.service.in @@ -0,0 +1,35 @@ +[Unit] +Description=CFEngine 3 umbrella service +Documentation=https://docs.cfengine.com/ https://northerntech.atlassian.net +After=syslog.target + +# Try to start all the sub-services. 'Wants' is fault-tolerant so if some are +# missing or impossible to start, no big deal. +Wants=cf-serverd.service +Wants=cf-execd.service +Wants=cf-monitord.service +Wants=cf-postgres.service +Wants=cf-apache.service +Wants=cf-hub.service +Wants=cf-reactor.service + +# Ensure synchronous stop behavior +Before=cf-serverd.service +Before=cf-execd.service +Before=cf-monitord.service +Before=cf-postgres.service +Before=cf-apache.service +Before=cf-hub.service +Before=cf-reactor.service + +[Install] +WantedBy=multi-user.target + +[Service] +Type=oneshot +RemainAfterExit=yes + +# Nothing to do here, we just need to make sure the specific services to be +# started/stopped. +ExecStart=/bin/true +ExecStop=/bin/true diff --git a/python/Makefile.am b/python/Makefile.am new file mode 100644 index 0000000000..fe2e898f4c --- /dev/null +++ b/python/Makefile.am @@ -0,0 +1,33 @@ +# +# Copyright 2024 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +pythonbindingsdir = $(libdir)/python +dist_pythonbindings_DATA = README + +if !NDEBUG +if LINUX +# The dbm_test_api is only compile in on Linux in debug builds. +dist_pythonbindings_DATA += test_cfe_db.py +endif +endif diff --git a/python/README b/python/README new file mode 100644 index 0000000000..6a58c63851 --- /dev/null +++ b/python/README @@ -0,0 +1,10 @@ +This directory contains Python packages and modules for working with CFEngine +and its libraries. + +If you want to use one of these, PYTHONPATH and LD_LIBRARY_PATH variables can +make things easier when used like this: + + PYTHONPATH=/var/cfengine/lib/python LD_LIBRARY_PATH=/var/cfengine/lib python3 + +That will make sure that the bindings are found by Python and that CFEngine's +libraries are found by dlopen() (used by ctypes.CDLL() etc.). diff --git a/python/test_cfe_db.py b/python/test_cfe_db.py new file mode 100644 index 0000000000..ccb14eab28 --- /dev/null +++ b/python/test_cfe_db.py @@ -0,0 +1,76 @@ +import ctypes + +# Based on the dbid enum from libpromises/dbm_api.h +dbs = ( + "classes", # Deprecated + "variables", # Deprecated + "performance", + "checksums", # Deprecated + "filestats", # Deprecated + "changes", + "observations", + "state", + "lastseen", + "audit", + "locks", + "history", + "measure", + "static", + "scalars", + "windows_registry", + "cache", + "license", + "value", + "agent_execution", + "bundles", # Deprecated + "packages_installed", # new package promise installed packages list + "packages_updates", # new package promise list of available updates + "cookies", # Enterprise reporting cookies for duplicate host detection +) + +def get_db_id(db_name): + return dbs.index(db_name) + + +class _SimulationStruct(ctypes.Structure): + pass + +class _FilamentStruct(ctypes.Structure): + pass + +_promises = ctypes.CDLL("libpromises.so.3.0.6") + +_promises.SimulateDBLoad.restype = ctypes.POINTER(_SimulationStruct) +_promises.SimulateDBLoad.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_long, ctypes.c_long, + ctypes.c_int, ctypes.c_int, ctypes.c_long, ctypes.c_long, + ctypes.c_long, ctypes.c_long] +def SimulateDBLoad(db_id, + read_keys_refresh_s=5, + read_min_interval_ms=100, + read_max_interval_ms=200, + write_sample_size_pct=50, + write_prune_interval_s=10, + write_min_interval_ms=200, + write_max_interval_ms=400, + iter_min_interval_ms=1000, + iter_max_interval_ms=2000): + return _promises.SimulateDBLoad(db_id, + read_keys_refresh_s, read_min_interval_ms, read_max_interval_ms, + write_sample_size_pct, + write_prune_interval_s, write_min_interval_ms, write_max_interval_ms, + iter_min_interval_ms, iter_max_interval_ms) + +_promises.StopSimulation.restype = None # void +_promises.StopSimulation.argtypes = [ctypes.POINTER(_SimulationStruct)] +def StopSimulation(simulation): + _promises.StopSimulation(simulation) + +_promises.FillUpDB.restype = ctypes.POINTER(_FilamentStruct) +_promises.FillUpDB.argtypes = [ctypes.c_int, ctypes.c_int] +def FillUpDB(db_id, usage): + return _promises.FillUpDB(db_id, usage) + +_promises.RemoveFilament.restype = None # void +_promises.RemoveFilament.argtypes = [ctypes.POINTER(_FilamentStruct)] +def RemoveFilament(filament): + _promises.RemoveFilament(filament) diff --git a/splint-check.sh b/splint-check.sh new file mode 100755 index 0000000000..e574b81cd1 --- /dev/null +++ b/splint-check.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# Unfortunately splint is crashing in system libraries, so it's not that useful + +# Other possibly useful flags: +# +weak -warnposixheaders -unrecog + +splint \ + +posixstrictlib +unixlib +gnuextensions \ + -I/usr/include/x86_64-linux-gnu/ -Ilibutils/ \ + -DHAVE_CONFIG_H -D__linux__ -D__gnuc_va_list=va_list \ + libutils/*.[ch] diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000000..bda4922858 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,24 @@ +*.xml +xml.tmp +xml_tmp_* +*.trs + +# acceptance tests +/acceptance/*.log +/acceptance/.succeeded +/acceptance/workdir +/acceptance/mock[-_]package[-_]manager +/acceptance/mock[-_]package[-_]manager.exe +/acceptance/xml-c14nize +/acceptance/xml-c14nize.exe +/acceptance/dnswrapper.so + +# unit tests +/unit/*_test +/unit/exec-config-test +/unit/rb-tree-test + +# load tests +/load/db_load +/load/lastseen_load +/load/lastseen_threaded_load diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000000..340cbf403d --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,24 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +SUBDIRS = unit load acceptance static-check valgrind-check diff --git a/tests/README b/tests/README new file mode 100644 index 0000000000..c21d4a2a66 --- /dev/null +++ b/tests/README @@ -0,0 +1,10 @@ +============================================================================== +CFEngine testsuite +============================================================================== + +`make check' + +For information on how to add acceptance tests, see tests/acceptance/README. +For information on how to add unit tests, see tests/unit/README. + + diff --git a/tests/acceptance/.gitignore b/tests/acceptance/.gitignore new file mode 100644 index 0000000000..f8f7a6054e --- /dev/null +++ b/tests/acceptance/.gitignore @@ -0,0 +1,4 @@ +/Makefile.testall +/no_fds +/elevate.exe +custom_promise_binary diff --git a/tests/acceptance/00_basics/01_compiler/001.cf b/tests/acceptance/00_basics/01_compiler/001.cf new file mode 100644 index 0000000000..29fea30665 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/001.cf @@ -0,0 +1,66 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile).SOURCE" + create => "true"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + move_obstructions => "true", + link_from => test_link; + + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine4608" }; +} + +body link_from test_link +{ + source => "$(G.testfile).SOURCE"; + link_type => "hardlink"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/002.x.cf b/tests/acceptance/00_basics/01_compiler/002.x.cf new file mode 100644 index 0000000000..d12d7ba343 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/002.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + move_obstructions => "true", + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_motd)"; + link_type => "banana"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/003.x.cf b/tests/acceptance/00_basics/01_compiler/003.x.cf new file mode 100644 index 0000000000..0670dbc62f --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/003.x.cf @@ -0,0 +1,61 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "type" string => "banana"; + + files: + "$(G.testfile)" + move_obstructions => "true", + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_motd)"; + link_type => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/004.x.cf b/tests/acceptance/00_basics/01_compiler/004.x.cf new file mode 100644 index 0000000000..2470bf1c86 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/004.x.cf @@ -0,0 +1,59 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + move_obstructions => "true", + link_from => test_link("banana"); +} + +body link_from test_link(type) +{ + source => "$(G.etc_motd)"; + link_type => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/007.cf b/tests/acceptance/00_basics/01_compiler/007.cf new file mode 100644 index 0000000000..6a11e85cdc --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/007.cf @@ -0,0 +1,57 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + log_level => "verbose"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/008.x.cf b/tests/acceptance/00_basics/01_compiler/008.x.cf new file mode 100644 index 0000000000..ab04b43cfb --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/008.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + log_level => "banana"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/013.cf b/tests/acceptance/00_basics/01_compiler/013.cf new file mode 100644 index 0000000000..e51ee3a1e4 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/013.cf @@ -0,0 +1,57 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + log_priority => "alert"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/014.x.cf b/tests/acceptance/00_basics/01_compiler/014.x.cf new file mode 100644 index 0000000000..19e3971da2 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/014.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + log_priority => "banana"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/019.cf b/tests/acceptance/00_basics/01_compiler/019.cf new file mode 100644 index 0000000000..1ff4ae0a6f --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/019.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + action_policy => "warn"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + "This test should not create $(G.testfile)"; + ok:: + "$(this.promise_filename) FAIL"; + !ok:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/01_compiler/020.x.cf b/tests/acceptance/00_basics/01_compiler/020.x.cf new file mode 100644 index 0000000000..e33187bd17 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/020.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + action_policy => "banana"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/025.cf b/tests/acceptance/00_basics/01_compiler/025.cf new file mode 100644 index 0000000000..a9432442bd --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/025.cf @@ -0,0 +1,57 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + report_level => "verbose"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/026.x.cf b/tests/acceptance/00_basics/01_compiler/026.x.cf new file mode 100644 index 0000000000..723fc5b7d2 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/026.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + report_level => "banana"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/031.cf b/tests/acceptance/00_basics/01_compiler/031.cf new file mode 100644 index 0000000000..69645ae0f9 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/031.cf @@ -0,0 +1,51 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/032.x.cf b/tests/acceptance/00_basics/01_compiler/032.x.cf new file mode 100644 index 0000000000..74b95b4a09 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/032.x.cf @@ -0,0 +1,52 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "banana"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/033.x.cf b/tests/acceptance/00_basics/01_compiler/033.x.cf new file mode 100644 index 0000000000..e29f8411af --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/033.x.cf @@ -0,0 +1,55 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "type" string => "banana"; + + files: + "$(G.testfile)" + create => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/034.x.cf b/tests/acceptance/00_basics/01_compiler/034.x.cf new file mode 100644 index 0000000000..4d338397ba --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/034.x.cf @@ -0,0 +1,52 @@ +###################################################### +# +# Issue 375 (duplicate, to preserve pattern of 5) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "banana"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/037.cf b/tests/acceptance/00_basics/01_compiler/037.cf new file mode 100644 index 0000000000..1c516d0f78 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/037.cf @@ -0,0 +1,55 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + classes => test_action; +} + +body classes test_action +{ + promise_repaired => { "ok" }; + persist_time => "10"; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/038.x.cf b/tests/acceptance/00_basics/01_compiler/038.x.cf new file mode 100644 index 0000000000..5c2c1bfaa6 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/038.x.cf @@ -0,0 +1,56 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + promise_repaired => { "ok" }; + persist_time => "banana"; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/040.x.cf b/tests/acceptance/00_basics/01_compiler/040.x.cf new file mode 100644 index 0000000000..875f3d0790 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/040.x.cf @@ -0,0 +1,53 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("banana"); +} + +body action test_action(type) +{ + promise_repaired => { "ok" }; + persist_time => "$(type)"; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/041.x.cf b/tests/acceptance/00_basics/01_compiler/041.x.cf new file mode 100644 index 0000000000..dfdd944eae --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/041.x.cf @@ -0,0 +1,56 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + promise_repaired => { "ok" }; + persist_time => "$(type)"; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/042.x.cf b/tests/acceptance/00_basics/01_compiler/042.x.cf new file mode 100644 index 0000000000..64c785fd86 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/042.x.cf @@ -0,0 +1,62 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(v) +{ + methods: + "any" usebundle => do_test(v); +} + +bundle agent do_test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + promise_repaired => { "ok" }; + persist_time => "$(type)"; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/044.x.cf b/tests/acceptance/00_basics/01_compiler/044.x.cf new file mode 100644 index 0000000000..3eff3af820 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/044.x.cf @@ -0,0 +1,53 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + guest_environments: + "host1" + environment_host => "...com._", + classes => whatever("ok"); +} + +body classes whatever(c) +{ + promise_kept => { "$(c)" }; + promise_repaired => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/045.x.cf b/tests/acceptance/00_basics/01_compiler/045.x.cf new file mode 100644 index 0000000000..e53214888e --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/045.x.cf @@ -0,0 +1,53 @@ +###################################################### +# +# Issue 375 (duplicate, to with pattern of 6) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + guest_environments: + "host1" + environment_host => "...com._", + classes => whatever("ok"); +} + +body classes whatever(c) +{ + promise_kept => { "$(c)" }; + promise_repaired => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/100.cf b/tests/acceptance/00_basics/01_compiler/100.cf new file mode 100644 index 0000000000..5e87709a93 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/100.cf @@ -0,0 +1,50 @@ +###################################################### +# +# Test that ${this.promiser} is expanded in comments (Issue 691) +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kvf $(this.promise_filename).sub | grep Comment", "useshell"); + meta: + "test_skip_needs_work" string => "windows"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok1" not => regcmp(".*this\.promiser.*", "$(test.subout)"); + "ok2" expression => regcmp(".*foobar.*", "$(test.subout)"); + + "ok" and => { "ok1", "ok2" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + DEBUG:: + "$(test.subout)"; +} + diff --git a/tests/acceptance/00_basics/01_compiler/100.cf.sub b/tests/acceptance/00_basics/01_compiler/100.cf.sub new file mode 100644 index 0000000000..a07dd2345c --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/100.cf.sub @@ -0,0 +1,11 @@ +body common control +{ + bundlesequence => { 'test' }; +} + +bundle agent test +{ +files: + "/tmp/foobar" + comment => "${this.promiser}"; +} diff --git a/tests/acceptance/00_basics/01_compiler/101.cf b/tests/acceptance/00_basics/01_compiler/101.cf new file mode 100644 index 0000000000..3306829509 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/101.cf @@ -0,0 +1,45 @@ +###################################################### +# +# Test that \ expands correctly by eating newline (Issue 696) +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "mystring" string => "some\ +thing"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(test.mystring)", "something"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + DEBUG:: + "$(test.subout)"; +} diff --git a/tests/acceptance/00_basics/01_compiler/102.x.cf b/tests/acceptance/00_basics/01_compiler/102.x.cf new file mode 100644 index 0000000000..3d12631a71 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/102.x.cf @@ -0,0 +1,25 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + reports: + cfengine_3:: + "$()"; +} + +bundle agent check +{ + cfengine_3:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/01_compiler/103.cf b/tests/acceptance/00_basics/01_compiler/103.cf new file mode 100644 index 0000000000..0bca26017b --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/103.cf @@ -0,0 +1,23 @@ +# Check that empty input lists work + +body common control +{ + inputs => { @(g.inputs) }; + bundlesequence => { check }; + version => "1.0"; +} + +bundle common g +{ + vars: + "inputs" slist => { }; +} + +bundle agent check +{ + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + any:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/01_compiler/104.cf b/tests/acceptance/00_basics/01_compiler/104.cf new file mode 100644 index 0000000000..e8aead81a2 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/104.cf @@ -0,0 +1,23 @@ +# Check that it's OK to have an empty list in inputs + +body common control +{ + inputs => { @(g.inputs) }; + bundlesequence => { check }; + version => "1.0"; +} + +bundle common g +{ + vars: + "inputs" slist => { }; +} + +bundle agent check +{ + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + any:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/01_compiler/105.cf b/tests/acceptance/00_basics/01_compiler/105.cf new file mode 100644 index 0000000000..b7b11db0cf --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/105.cf @@ -0,0 +1,22 @@ +# Check that empty bundles work (Redmine #2411) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/01_compiler/200.x.cf b/tests/acceptance/00_basics/01_compiler/200.x.cf new file mode 100644 index 0000000000..5b9418ecaa --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/200.x.cf @@ -0,0 +1,41 @@ +# +# Test that overlong identifier fails +# + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +# 8192 symbols +bundle agent xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + + diff --git a/tests/acceptance/00_basics/01_compiler/704.x.cf b/tests/acceptance/00_basics/01_compiler/704.x.cf new file mode 100644 index 0000000000..4480cceb65 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/704.x.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Related to Issue 377 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + ".*" + process_select => test_plain, + signals => { "child" }; +} + +body process_select test_plain +{ + command => "should-never-find-this"; + process_result => "command|bfd.pms.wtf!"; # Illegal test names +} + +####################################################### + +bundle agent check +{ + vars: + "count" string => execresult("$(G.wc) $(test.counter)", "noshell"); + + classes: + "ok" expression => regcmp("\s*3\s.*", "$(count)"); + + reports: + DEBUG:: + "Expected to crash - illegal test names"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/710.x.cf b/tests/acceptance/00_basics/01_compiler/710.x.cf new file mode 100644 index 0000000000..fb0ce84d10 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/710.x.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Related to bug introduced in core r1900 - allowed any body constraint name +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/shouldnotexist" + create => "true", + nonexistent_attribute => "abc"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testdir)/shouldnotexist"); + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/800.cf b/tests/acceptance/00_basics/01_compiler/800.cf new file mode 100644 index 0000000000..7dbed70ef4 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/800.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test whether parsing of /etc/issue works on Debian +# (Acceptance test for redmine #2988) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + debian:: + "myflavor" string => execresult("/bin/grep Debian.*GNU /etc/issue | /usr/bin/cut -d' ' -f3 | cut -d. -f1 | cut -d/ -f1", "useshell"); +} + +####################################################### + +bundle agent check +{ + classes: + debian.!ubuntu:: + "ok" expression => strcmp("$(sys.flavor)", "debian_$(test.myflavor)"); + ubuntu:: + "ok" expression => "any"; + !debian:: + "ok" expression => "any"; + + reports: + DEBUG:: + "Compared $(sys.flavor) to debian_$(test.myflavor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/README b/tests/acceptance/00_basics/01_compiler/README new file mode 100644 index 0000000000..290318f149 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/README @@ -0,0 +1,15 @@ +0*.cf: + +This suite of tests is essentially 5 different tests repeated for various +attributes. + +1) prove that the test case works +2) show that it fails as expected with an illegal value +3) attempt to show failure when that illegal value is a parameter to the body +4) attempt to show failure when that illegal value is a parameter to the bundle +5) attempt to show failure when that illegal value is a parameter to a method + +7*.cf: + +These tests are slightly different, but also address issues in constraint +checking diff --git a/tests/acceptance/00_basics/01_compiler/cf-promises-99999999999.cf b/tests/acceptance/00_basics/01_compiler/cf-promises-99999999999.cf new file mode 100644 index 0000000000..3a88cf4e18 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/cf-promises-99999999999.cf @@ -0,0 +1,62 @@ +####################################################### +# +# Related to redmine #6531 +# +# In short, on 32-bit platforms the maximum number that cf-agent +# understands is LONG_MAX. However the number "99999999999" (eleven +# nines) has been historically used in many places, so this test makes +# sure that it's still being accepted, even though on 32-bit platforms +# it will be truncated to LONG_MAX. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +# With this body we're making sure cf-promises won't bail out on +# this. This is exactly the body that cf-promises was rejecting before +# the fix. +body classes u_always_forever(theclass) +{ + persist_time => 99999999999; +} + +####################################################### + +bundle agent init +{ + vars: + # truncation will happen on 32-bit architectures + "x" int => "99999999999"; + "y" int => "-99999999999"; +} + +bundle agent test +{ + classes: + "x_ok" or => { strcmp("$(init.x)", "99999999999"), # 64-bit arch + strcmp("$(init.x)", "2147483647") # 32-bit arch + }, scope => "namespace"; + "y_ok" or => { strcmp("$(init.y)", "-99999999999"), # 64-bit arch + strcmp("$(init.y)", "-2147483648") # 32-bit arch + }, scope => "namespace"; +} + +bundle agent check +{ + reports: + + DEBUG:: + "x is $(init.x), should be 99999999999 or LONG_MAX"; + "y is $(init.y), should be -99999999999 or LONG_MIN"; + x_ok.y_ok:: + "$(this.promise_filename) Pass"; + !x_ok|!y_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/cf-promises-common-bundles-only.cf b/tests/acceptance/00_basics/01_compiler/cf-promises-common-bundles-only.cf new file mode 100644 index 0000000000..a3bccef9cb --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/cf-promises-common-bundles-only.cf @@ -0,0 +1,42 @@ +############################################################################## +# +# Redmine #3576: cf-promises should not run agent bundles +# NOTE: changed to if --eval-functions=no +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + +} + + +bundle agent test +{ +vars: + "subout" string => execresult("$(sys.cf_promises) --eval-functions=no -v -f $(this.promise_filename).sub | $(G.grep) PURPLE", "useshell"); +} + +bundle agent check +{ +# If the output contains the string, we fail +classes: + "ok1" not => regcmp(".*PURPLE_DINOSAUR.*", "$(test.subout)"); + "ok" and => { "ok1" }; + +reports: + DEBUG:: + "PURPLE TEST: $(test.subout)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/cf-promises-common-bundles-only.cf.sub b/tests/acceptance/00_basics/01_compiler/cf-promises-common-bundles-only.cf.sub new file mode 100644 index 0000000000..4464ecf2b5 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/cf-promises-common-bundles-only.cf.sub @@ -0,0 +1,10 @@ +body common control +{ + bundlesequence => { runme }; +} + +bundle agent runme +{ + vars: + "x" string => execresult("/bin/echo PURPLE_DINOSAUR", "noshell"); +} diff --git a/tests/acceptance/00_basics/01_compiler/cf-promises-json-long-strings.cf b/tests/acceptance/00_basics/01_compiler/cf-promises-json-long-strings.cf new file mode 100644 index 0000000000..f93376f781 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/cf-promises-json-long-strings.cf @@ -0,0 +1,35 @@ +# Test that cf-promises doesn't fail if ppkeys are missing + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "2.0"; +} + +bundle agent test +{ + meta: + + "description" -> { "CFE-2383", "CFE-2310" } + string => "Test that json representation of policy is not truncated."; + + vars: + "output" + string => execresult( "$(sys.cf_promises) -p json -f $(this.promise_filename).sub", "noshell" ); + +} + +bundle agent check +{ + classes: + + "validated_ok" expression => regcmp( ".*endflag.*", $(test.output) ); + + reports: + + validated_ok:: + "$(this.promise_filename) Pass"; + !validated_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/cf-promises-json-long-strings.cf.sub b/tests/acceptance/00_basics/01_compiler/cf-promises-json-long-strings.cf.sub new file mode 100644 index 0000000000..3c2beb8b91 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/cf-promises-json-long-strings.cf.sub @@ -0,0 +1,47 @@ +bundle agent main +{ +vars: + "content" string => + "flag +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +This is a file template containing variables to expan +endflag"; + +} diff --git a/tests/acceptance/00_basics/01_compiler/cf-promises-without-ppkeys.cf b/tests/acceptance/00_basics/01_compiler/cf-promises-without-ppkeys.cf new file mode 100644 index 0000000000..bef8002a46 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/cf-promises-without-ppkeys.cf @@ -0,0 +1,30 @@ +# Test that cf-promises doesn't fail if ppkeys are missing + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + methods: + "clear ppkeys" usebundle => dcs_fini("$(sys.workdir)/ppkeys"); +} + +bundle agent test +{ + commands: + "$(sys.cf_promises) -f $(this.promise_filename)" + classes => scoped_classes_generic("namespace", "validated"); +} + +bundle agent check +{ + reports: + validated_ok:: + "$(this.promise_filename) Pass"; + validated_not_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/classes_without_constraints.cf b/tests/acceptance/00_basics/01_compiler/classes_without_constraints.cf new file mode 100644 index 0000000000..8f9747da36 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/classes_without_constraints.cf @@ -0,0 +1,27 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + + "description" -> { "CFE-2000" } + string => "Test that classes can be defined without any + constraints/attributes"; + + classes: + + "supercalifragilisticexpialidocious"; + + reports: + + supercalifragilisticexpialidocious:: + "$(this.promise_filename) Pass"; + + !supercalifragilisticexpialidocious:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/depends_on.cf b/tests/acceptance/00_basics/01_compiler/depends_on.cf new file mode 100644 index 0000000000..813d3848cb --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/depends_on.cf @@ -0,0 +1,92 @@ +# Test depends_on + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "expandit" slist => { "hc2" }; + + methods: + "zebra" usebundle => zebra:zebra; + + classes: + "c1" expression => "any", + scope => "namespace", + handle => "hc1", + depends_on => { }; + + "c2" expression => "any", + scope => "namespace", + handle => "hc2", + depends_on => { "hc1", "me2" }; + + "c3" expression => "any", + scope => "namespace", + handle => "hc3", + depends_on => { "$(expandit)" }; + + "c4" expression => "any", + scope => "namespace", + handle => "hc4", + depends_on => { "nosuchhandle" }; + + "c5" expression => "any", + scope => "namespace", + handle => "hc5", + depends_on => { "zebra.zebra" }; + + "c6" expression => "any", + scope => "namespace", + handle => "hc6", + depends_on => { "zebra:zebra" }; + + reports: + "me too" handle => "me2"; +} + +bundle agent check +{ + classes: + "ok" and => { "c1", "c2", "c3", "!c4", "!c5", "!c6", }; + + reports: + DEBUG.!c1:: + "bad: c1 was NOT defined"; + DEBUG.!c2:: + "bad: c2 was NOT defined"; + DEBUG.!c3:: + "bad: c3 was NOT defined"; + DEBUG.c4:: + "bad: c4 WAS defined"; + DEBUG.c5:: + "bad: c5 WAS defined"; + DEBUG.c6:: + "bad: c6 WAS defined"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body file control +{ + namespace => "zebra"; +} + +bundle agent zebra +{ + classes: + "zebra" expression => "any", + scope => "namespace", + handle => "zebra"; +} diff --git a/tests/acceptance/00_basics/01_compiler/depends_on_canonify.cf b/tests/acceptance/00_basics/01_compiler/depends_on_canonify.cf new file mode 100644 index 0000000000..54635db5d1 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/depends_on_canonify.cf @@ -0,0 +1,37 @@ +# Test depends_on with a canonify() function call + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + classes: + # this works + "foo1" expression => "any", + depends_on => { canonify($(G.testfile)) }; + + methods: + "" usebundle => test2($(G.testfile)); +} + +bundle agent test2(file) +{ + classes: + # this fails + "foo2" expression => "any", + depends_on => { canonify($(file)) }; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/01_compiler/double_comma_arglist.x.cf b/tests/acceptance/00_basics/01_compiler/double_comma_arglist.x.cf new file mode 100644 index 0000000000..bbf6931b91 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/double_comma_arglist.x.cf @@ -0,0 +1,17 @@ +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + methods: + "check" + usebundle => check("a", "b", "c"); +} + +bundle agent check(a, b,, c) +{ + reports: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/leading_comma_arglist.x.cf b/tests/acceptance/00_basics/01_compiler/leading_comma_arglist.x.cf new file mode 100644 index 0000000000..fd8cecf4fc --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/leading_comma_arglist.x.cf @@ -0,0 +1,17 @@ +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + methods: + "check" + usebundle => check("a", "b", "c"); +} + +bundle agent check(, a, b, c) +{ + reports: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/no_error_when_key_contains_space.cf b/tests/acceptance/00_basics/01_compiler/no_error_when_key_contains_space.cf new file mode 100644 index 0000000000..8cba164686 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/no_error_when_key_contains_space.cf @@ -0,0 +1,61 @@ +# This test should be renamed from .x.cf to .cf when the bug is fixed. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3320" } + string => "Test that data container keys can contain spaces"; + + vars: + "d" data => '{ + "thing s": [ + { + "Title": "ExpectedPick", + "classexpr": "$(sys.class)" + } + ] + }'; + + "selected" + string => "$(d[thing s][0][Title])", + if => "$(d[thing s][0][classexpr])"; + # Note: wrapping with concat or classify prevents the error + # if => concat("$(d[thing s][$(di)][classexpr])"); + # if => classify("$(d[thing s][$(di)][classexpr])"); + + "sanity_check" + string => "You are sane", + if => "$(sys.class)"; + + reports: + EXTRA|DEBUG:: + "See iteration/expansion: $(d[thing s][ExpectedPick][Title]) has classexpr $(d[thing s][ExpectedPick][classexpr])"; + "See sanity_check: $(sanity_check) because $(sys.class) is a defined class"; + + "Picked: $(d[thing s][ExpectedPick][Title]) has classexpr $(d[thing s][ExpectedPick][classexpr])" + if => "$(d[thing s][ExpectedPick][classexpr])"; +} + +####################################################### + +bundle agent check +{ + vars: + "expected_selection" string => "ExpectedPick"; + + methods: + "Pass/Fail" + usebundle => dcs_check_strcmp($(expected_selection), $(test.selected), + $(this.promise_filename), "no"); + +} + diff --git a/tests/acceptance/00_basics/01_compiler/staging/005.x.cf b/tests/acceptance/00_basics/01_compiler/staging/005.x.cf new file mode 100644 index 0000000000..ed9d31b817 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/005.x.cf @@ -0,0 +1,59 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + files: + "$(G.testfile)" + move_obstructions => "true", + link_from => test_link("$(val)"); +} + +body link_from test_link(type) +{ + source => "$(G.etc_motd)"; + link_type => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/006.x.cf b/tests/acceptance/00_basics/01_compiler/staging/006.x.cf new file mode 100644 index 0000000000..ff75216873 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/006.x.cf @@ -0,0 +1,65 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(v) +{ + methods: + "any" usebundle => do_test(v); +} + +bundle agent do_test(val) +{ + files: + "$(G.testfile)" + move_obstructions => "true", + link_from => test_link("$(val)"); +} + +body link_from test_link(type) +{ + source => "$(G.etc_motd)"; + link_type => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/009.x.cf b/tests/acceptance/00_basics/01_compiler/staging/009.x.cf new file mode 100644 index 0000000000..c0757fec52 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/009.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "type" string => "banana"; + + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + log_level => "$(g.type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/010.x.cf b/tests/acceptance/00_basics/01_compiler/staging/010.x.cf new file mode 100644 index 0000000000..8e1b5622a3 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/010.x.cf @@ -0,0 +1,55 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("banana"); +} + +body action test_action(type) +{ + log_level => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/011.x.cf b/tests/acceptance/00_basics/01_compiler/staging/011.x.cf new file mode 100644 index 0000000000..02afcadcea --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/011.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + log_level => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/012.x.cf b/tests/acceptance/00_basics/01_compiler/staging/012.x.cf new file mode 100644 index 0000000000..013cd4ad5b --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/012.x.cf @@ -0,0 +1,64 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(v) +{ + methods: + "any" usebundle => do_test(v); +} + +bundle agent do_test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + log_level => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/015.x.cf b/tests/acceptance/00_basics/01_compiler/staging/015.x.cf new file mode 100644 index 0000000000..d6d815f48b --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/015.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "type" string => "banana"; + + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + log_priority => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/016.x.cf b/tests/acceptance/00_basics/01_compiler/staging/016.x.cf new file mode 100644 index 0000000000..d1c2460f46 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/016.x.cf @@ -0,0 +1,55 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("banana"); +} + +body action test_action(type) +{ + log_priority => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/017.x.cf b/tests/acceptance/00_basics/01_compiler/staging/017.x.cf new file mode 100644 index 0000000000..a2ba44717d --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/017.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + log_priority => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/018.x.cf b/tests/acceptance/00_basics/01_compiler/staging/018.x.cf new file mode 100644 index 0000000000..9e41579802 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/018.x.cf @@ -0,0 +1,64 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(v) +{ + methods: + "any" usebundle => do_test(v); +} + +bundle agent do_test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + log_priority => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/021.x.cf b/tests/acceptance/00_basics/01_compiler/staging/021.x.cf new file mode 100644 index 0000000000..abf6044dac --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/021.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "type" string => "banana"; + + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + action_policy => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/022.x.cf b/tests/acceptance/00_basics/01_compiler/staging/022.x.cf new file mode 100644 index 0000000000..f0ab21dc26 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/022.x.cf @@ -0,0 +1,55 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("banana"); +} + +body action test_action(type) +{ + action_policy => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/023.x.cf b/tests/acceptance/00_basics/01_compiler/staging/023.x.cf new file mode 100644 index 0000000000..8bf6669563 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/023.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + action_policy => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/024.x.cf b/tests/acceptance/00_basics/01_compiler/staging/024.x.cf new file mode 100644 index 0000000000..efce8aaf01 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/024.x.cf @@ -0,0 +1,64 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(v) +{ + methods: + "any" usebundle => do_test(v); +} + +bundle agent do_test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + action_policy => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/027.x.cf b/tests/acceptance/00_basics/01_compiler/staging/027.x.cf new file mode 100644 index 0000000000..aa5dcc676f --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/027.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "type" string => "banana"; + + files: + "$(G.testfile)" + create => "true", + action => test_action; +} + +body action test_action +{ + report_level => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/028.x.cf b/tests/acceptance/00_basics/01_compiler/staging/028.x.cf new file mode 100644 index 0000000000..b91c0375f6 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/028.x.cf @@ -0,0 +1,55 @@ +###################################################### +# +# Issue 375 +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("banana"); +} + +body action test_action(type) +{ + report_level => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/029.x.cf b/tests/acceptance/00_basics/01_compiler/staging/029.x.cf new file mode 100644 index 0000000000..7ce9c42afa --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/029.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + report_level => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/030.x.cf b/tests/acceptance/00_basics/01_compiler/staging/030.x.cf new file mode 100644 index 0000000000..8f2106b090 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/030.x.cf @@ -0,0 +1,64 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(v) +{ + methods: + "any" usebundle => do_test(v); +} + +bundle agent do_test(val) +{ + files: + "$(G.testfile)" + create => "true", + action => test_action("$(val)"); +} + +body action test_action(type) +{ + report_level => "$(type)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/035.x.cf b/tests/acceptance/00_basics/01_compiler/staging/035.x.cf new file mode 100644 index 0000000000..9285a6a22c --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/035.x.cf @@ -0,0 +1,52 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + files: + "$(G.testfile)" + create => "$(val)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/036.x.cf b/tests/acceptance/00_basics/01_compiler/staging/036.x.cf new file mode 100644 index 0000000000..d7687dff2b --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/036.x.cf @@ -0,0 +1,58 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("banana"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(v) +{ + methods: + "any" usebundle => do_test(v); +} + +bundle agent do_test(val) +{ + files: + "$(G.testfile)" + create => "$(val)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/039.x.cf b/tests/acceptance/00_basics/01_compiler/staging/039.x.cf new file mode 100644 index 0000000000..15eb40cb15 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/039.x.cf @@ -0,0 +1,56 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("...com._"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + guest_environments: + "host1" + environment_host => "$(val)", + classes => whatever("ok"); +} + +body classes whatever(c) +{ + promise_kept => { "$(c)" }; + promise_repaired => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/043.cf b/tests/acceptance/00_basics/01_compiler/staging/043.cf new file mode 100644 index 0000000000..2c2e079db0 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/043.cf @@ -0,0 +1,53 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + guest_environments: + "host1" + environment_host => "test", + classes => whatever("ok"); +} + +body classes whatever(c) +{ + promise_kept => { "$(c)" }; + promise_repaired => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should pass as a precursor to a bunch of related failures"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/046.x.cf b/tests/acceptance/00_basics/01_compiler/staging/046.x.cf new file mode 100644 index 0000000000..e53214888e --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/046.x.cf @@ -0,0 +1,53 @@ +###################################################### +# +# Issue 375 (duplicate, to with pattern of 6) +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + guest_environments: + "host1" + environment_host => "...com._", + classes => whatever("ok"); +} + +body classes whatever(c) +{ + promise_kept => { "$(c)" }; + promise_repaired => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/047.x.cf b/tests/acceptance/00_basics/01_compiler/staging/047.x.cf new file mode 100644 index 0000000000..15eb40cb15 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/047.x.cf @@ -0,0 +1,56 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("...com._"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(val) +{ + guest_environments: + "host1" + environment_host => "$(val)", + classes => whatever("ok"); +} + +body classes whatever(c) +{ + promise_kept => { "$(c)" }; + promise_repaired => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/048.x.cf b/tests/acceptance/00_basics/01_compiler/staging/048.x.cf new file mode 100644 index 0000000000..dfb90f931a --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/048.x.cf @@ -0,0 +1,62 @@ +###################################################### +# +# Issue 375 +# +##################################################### +# This test falls out of the usual test-syntax, because 1) It is designed to +# fail and 2) Two of the set of tests requires a non-standard bundlesequence + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { init, test("...com._"), check }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test(v) +{ + methods: + "any" usebundle => do_test(v); +} + +bundle agent do_test(val) +{ + guest_environments: + "host1" + environment_host => "$(val)", + classes => whatever("ok"); +} + +body classes whatever(c) +{ + promise_kept => { "$(c)" }; + promise_repaired => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This test should fail"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/701.x.cf b/tests/acceptance/00_basics/01_compiler/staging/701.x.cf new file mode 100644 index 0000000000..e5a70f1152 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/701.x.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Issue 377 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "files" slist => { "1", "2", "3" }; + + files: + "$(G.testdir)/." + create => "true"; + + "$(G.testdir)/$(files)" + copy_from => init_copy("$(G.etc_group)"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +####################################################### + +bundle agent test +{ + vars: + "counter" string => "$(G.testdir)/counter"; + + files: + "$(G.testdir)" + transformer => "/bin/sh -c 'echo $(this.promiser) >> $(counter)'", + file_select => test_plain, + depth_search => test_recurse; +} + +body file_select test_plain +{ + file_types => { "plain" }; + file_result => "file_types|bfd.pms.wtf!"; # Illegal test names +} + +body depth_search test_recurse +{ + depth => "inf"; +} + +####################################################### + +bundle agent check +{ + vars: + "count" string => execresult("$(G.wc) $(test.counter)", "noshell"); + + classes: + "ok" expression => regcmp("\s*3\s.*", "$(count)"); + + reports: + DEBUG:: + "3 transformations expected, saw '$(count)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/702.x.cf b/tests/acceptance/00_basics/01_compiler/staging/702.x.cf new file mode 100644 index 0000000000..be28277318 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/702.x.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Issue 378 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + syslog_host => "...com..._"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any"; + + reports: + DEBUG:: + "This is expected to crash"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/staging/703.x.cf b/tests/acceptance/00_basics/01_compiler/staging/703.x.cf new file mode 100644 index 0000000000..9a4c1cba0c --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/staging/703.x.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Issue 378 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + bindtointerface => "...com..._"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any"; + + reports: + DEBUG:: + "This is expected to crash"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/01_compiler/string_contexts.cf b/tests/acceptance/00_basics/01_compiler/string_contexts.cf new file mode 100644 index 0000000000..835fd8f3f6 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/string_contexts.cf @@ -0,0 +1,86 @@ +# fixes https://dev.cfengine.com/issues/2504 +# using variables in string contexts + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + defaults: + "s1" string => "1"; + "s2" string => "2"; + "s3" string => "3"; + "s4" string => "4"; + "s5" string => "5"; + "s6" string => "6"; + "s7" string => "7"; + "s8" string => "8"; + + classes: + "$(mylist)" expression => "any"; + + vars: + "mylist" slist => { "a", "b", "c" }; + "myvar" string => "me"; + "myclass" string => "any"; + "s1" string => "hello, this is $(myvar)", + if => "$(myclass)"; + any:: + "s2" string => "any hello"; + 'any':: + "s3" string => "string hello"; + "$(myclass)":: + "s4" string => "variable context hello!!!"; + "s4_2" string => "variable context hello!!!"; + "$(myclass).any":: + "s5" string => "compound variable context hello!!!"; + "not$(myclass)":: + "s6" string => "BAD variable context hello!!!"; + "$(myclass)":: + "s7" string => "augmented variable context hello!!!", + if => "any"; + + "$(myclass)":: + "s8" string => "BAD augmented variable context hello!!!", + if => "nonesuch"; + "$(mylist).cfengine":: + "iterated_$(mylist)" string => "iteration class $(mylist)"; + + "myclass . cfengine | any":: + "whitespace1" string => "$(this.promiser): quoted with spaces"; + + myclass . cfengine | any:: + "whitespace2" string => "$(this.promiser): unquoted with spaces, starts with valid class"; + + nosuchclass . any:: + "whitespace3" string => "$(this.promiser): unquoted with spaces, should be missing"; + + nosuchclass | any:: + "whitespace4" string => "$(this.promiser): unquoted with spaces, starts with missing class but is true"; + + any | ( cfengine | linux ):: + "whitespace5" string => "$(this.promiser): remove spaces and tabs from an expression"; + + any . ( nosuchclass . any ):: + "whitespace6" string => "$(this.promiser): remove spaces and tabs from an expression that is false in the end, should be missing"; + + "cf engine":: + "whitespace7" string => "$(this.promiser): quoted with spaces but no operators, should be missing with an error"; + +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/01_compiler/string_contexts.cf.expected.json b/tests/acceptance/00_basics/01_compiler/string_contexts.cf.expected.json new file mode 100644 index 0000000000..6e7e69c4b2 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/string_contexts.cf.expected.json @@ -0,0 +1,25 @@ +{ + "iterated_a": "iteration class a", + "iterated_b": "iteration class b", + "iterated_c": "iteration class c", + "myclass": "any", + "mylist": [ + "a", + "b", + "c" + ], + "myvar": "me", + "s1": "hello, this is me", + "s2": "any hello", + "s3": "string hello", + "s4": "variable context hello!!!", + "s4_2": "variable context hello!!!", + "s5": "compound variable context hello!!!", + "s6": "6", + "s7": "augmented variable context hello!!!", + "s8": "8", + "whitespace1": "whitespace1: quoted with spaces", + "whitespace2": "whitespace2: unquoted with spaces, starts with valid class", + "whitespace4": "whitespace4: unquoted with spaces, starts with missing class but is true", + "whitespace5": "whitespace5: remove spaces and tabs from an expression" +} diff --git a/tests/acceptance/00_basics/01_compiler/string_contexts.x.cf b/tests/acceptance/00_basics/01_compiler/string_contexts.x.cf new file mode 100644 index 0000000000..f72f5a96c3 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/string_contexts.x.cf @@ -0,0 +1,30 @@ +# fixes https://dev.cfengine.com/issues/2504 +# using variables in string contexts +# should fail: class names in string contexts must be separated by operators + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + reports: + cf e ngine:: + "hello"; + + cf engine:: + "hello"; +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_pass($(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/01_compiler/string_contexts_rval.cf b/tests/acceptance/00_basics/01_compiler/string_contexts_rval.cf new file mode 100644 index 0000000000..762f9e5531 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/string_contexts_rval.cf @@ -0,0 +1,21 @@ +# fixes https://dev.cfengine.com/issues/2504 using variables in string +# contexts when a promise has a previous if *with a FnCall* + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ +} + +bundle agent check +{ + reports: + "any":: + "$(this.promise_filename) Pass" + if => and("any"); +} diff --git a/tests/acceptance/00_basics/01_compiler/trailing_comma_arglist.cf b/tests/acceptance/00_basics/01_compiler/trailing_comma_arglist.cf new file mode 100644 index 0000000000..ee419aac13 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/trailing_comma_arglist.cf @@ -0,0 +1,17 @@ +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + methods: + "check" + usebundle => check("a", "b", "c"); +} + +bundle agent check(a, b, c,) +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/01_compiler/with_iteration.cf b/tests/acceptance/00_basics/01_compiler/with_iteration.cf new file mode 100644 index 0000000000..9ea16ee9a0 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/with_iteration.cf @@ -0,0 +1,41 @@ +########################################################### +# +# Test the "with" promise attribute +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + vars: + "probe" string => "0"; + "iter" slist => { "1", "2", "3" }; + "A$(with)" string => "promiser addition = $(with)", with => canonify(concat("promiser ", canonify("addition"))); + "B$(with)" string => "clean = $(with)", with => canonify("clean"); + "C$(with)" string => "my name is = $(with)", with => canonify("my name is canonified"); + "D1$(with)" string => "iter1 = $(with)", with => $(iter); + "D2$(with)" string => "iter2 = $(with)", with => $(iter); + "E$(with)" string => "probe = $(with)", with => $(probe); + "F$(with)" string => "iter = $(with)", with => concat("iter_canon_", canonify($(iter))); + "G1$(with)" string => "iter = $(with)", with => concat("iter_canon_", canonify($(iter))); + "G2$(with)" string => "iter = $(with)", with => canonify($(iter)); + "H" string => "$(with)", with => format("%S", iter); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/01_compiler/with_iteration.cf.expected.json b/tests/acceptance/00_basics/01_compiler/with_iteration.cf.expected.json new file mode 100644 index 0000000000..ac4da550f0 --- /dev/null +++ b/tests/acceptance/00_basics/01_compiler/with_iteration.cf.expected.json @@ -0,0 +1,28 @@ +{ + "Apromiser_addition": "promiser addition = promiser_addition", + "Bclean": "clean = clean", + "Cmy_name_is_canonified": "my name is = my_name_is_canonified", + "D11": "iter1 = 1", + "D12": "iter1 = 2", + "D13": "iter1 = 3", + "D21": "iter2 = 1", + "D22": "iter2 = 2", + "D23": "iter2 = 3", + "E0": "probe = 0", + "Fiter_canon_1": "iter = iter_canon_1", + "Fiter_canon_2": "iter = iter_canon_2", + "Fiter_canon_3": "iter = iter_canon_3", + "G1iter_canon_1": "iter = iter_canon_1", + "G1iter_canon_2": "iter = iter_canon_2", + "G1iter_canon_3": "iter = iter_canon_3", + "G21": "iter = 1", + "G22": "iter = 2", + "G23": "iter = 3", + "H": "{ \"1\", \"2\", \"3\" }", + "iter": [ + "1", + "2", + "3" + ], + "probe": "0" +} diff --git a/tests/acceptance/00_basics/02_switches/001.cf b/tests/acceptance/00_basics/02_switches/001.cf new file mode 100644 index 0000000000..410e81255c --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/001.cf @@ -0,0 +1,29 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,ok"; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; +} + diff --git a/tests/acceptance/00_basics/02_switches/001.cf.sub b/tests/acceptance/00_basics/02_switches/001.cf.sub new file mode 100644 index 0000000000..8939cef691 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/001.cf.sub @@ -0,0 +1,46 @@ +####################################################### +# +# Test class creation with -D +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + +reports: + DEBUG:: + "This should only pass if you run it with: -D ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) Pass"; + !ok:: + "$(fn[1]) FAIL"; +} + diff --git a/tests/acceptance/00_basics/02_switches/002.cf b/tests/acceptance/00_basics/02_switches/002.cf new file mode 100644 index 0000000000..867f677050 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/002.cf @@ -0,0 +1,30 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,ok,"; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; + "Test results come from $(this.promise_filename).sub"; +} + diff --git a/tests/acceptance/00_basics/02_switches/002.cf.sub b/tests/acceptance/00_basics/02_switches/002.cf.sub new file mode 100644 index 0000000000..8939cef691 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/002.cf.sub @@ -0,0 +1,46 @@ +####################################################### +# +# Test class creation with -D +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + +reports: + DEBUG:: + "This should only pass if you run it with: -D ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) Pass"; + !ok:: + "$(fn[1]) FAIL"; +} + diff --git a/tests/acceptance/00_basics/02_switches/003.cf b/tests/acceptance/00_basics/02_switches/003.cf new file mode 100644 index 0000000000..f6aa2640b8 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/003.cf @@ -0,0 +1,30 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,ok_"; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; + "Test results come from $(this.promise_filename).sub"; +} + diff --git a/tests/acceptance/00_basics/02_switches/003.cf.sub b/tests/acceptance/00_basics/02_switches/003.cf.sub new file mode 100644 index 0000000000..0081a42e24 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/003.cf.sub @@ -0,0 +1,46 @@ +####################################################### +# +# Test class creation with -D +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + +reports: + DEBUG:: + "This should only pass if you do not run it with: -D ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) FAIL"; + !ok:: + "$(fn[1]) Pass"; +} + diff --git a/tests/acceptance/00_basics/02_switches/004.cf b/tests/acceptance/00_basics/02_switches/004.cf new file mode 100644 index 0000000000..b931e08c6c --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/004.cf @@ -0,0 +1,30 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,ok."; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; + "Test results come from $(this.promise_filename).sub"; +} + diff --git a/tests/acceptance/00_basics/02_switches/004.cf.sub b/tests/acceptance/00_basics/02_switches/004.cf.sub new file mode 100644 index 0000000000..0081a42e24 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/004.cf.sub @@ -0,0 +1,46 @@ +####################################################### +# +# Test class creation with -D +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + +reports: + DEBUG:: + "This should only pass if you do not run it with: -D ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) FAIL"; + !ok:: + "$(fn[1]) Pass"; +} + diff --git a/tests/acceptance/00_basics/02_switches/005.cf b/tests/acceptance/00_basics/02_switches/005.cf new file mode 100644 index 0000000000..1fd66175e5 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/005.cf @@ -0,0 +1,30 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO -N ok"; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; + "Test results come from $(this.promise_filename).sub"; +} + diff --git a/tests/acceptance/00_basics/02_switches/005.cf.sub b/tests/acceptance/00_basics/02_switches/005.cf.sub new file mode 100644 index 0000000000..92c99f6d76 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/005.cf.sub @@ -0,0 +1,46 @@ +####################################################### +# +# Test initial class negation with -N +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + +reports: + DEBUG:: + "This should pass if you run it with: -N ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) FAIL"; + !ok:: + "$(fn[1]) Pass"; +} + diff --git a/tests/acceptance/00_basics/02_switches/006.cf b/tests/acceptance/00_basics/02_switches/006.cf new file mode 100644 index 0000000000..9b445d8950 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/006.cf @@ -0,0 +1,30 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,MAIN -N ok"; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; + "Test results come from $(this.promise_filename).sub"; +} + diff --git a/tests/acceptance/00_basics/02_switches/006.cf.sub b/tests/acceptance/00_basics/02_switches/006.cf.sub new file mode 100644 index 0000000000..efeda0b9db --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/006.cf.sub @@ -0,0 +1,44 @@ +####################################################### +# +# Test initial class negation with -N +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ +classes: + "ok" expression => "any"; + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + +reports: + DEBUG:: + "This should pass even if you run it with: -N ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) Pass"; + !ok:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/00_basics/02_switches/008.cf b/tests/acceptance/00_basics/02_switches/008.cf new file mode 100644 index 0000000000..d4c89484d9 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/008.cf @@ -0,0 +1,30 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,MAIN -N ok,foo"; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; + "Test results come from $(this.promise_filename).sub"; +} + diff --git a/tests/acceptance/00_basics/02_switches/008.cf.sub b/tests/acceptance/00_basics/02_switches/008.cf.sub new file mode 100644 index 0000000000..efeda0b9db --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/008.cf.sub @@ -0,0 +1,44 @@ +####################################################### +# +# Test initial class negation with -N +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ +classes: + "ok" expression => "any"; + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + +reports: + DEBUG:: + "This should pass even if you run it with: -N ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) Pass"; + !ok:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/00_basics/02_switches/010.cf b/tests/acceptance/00_basics/02_switches/010.cf new file mode 100644 index 0000000000..84b515c5c6 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/010.cf @@ -0,0 +1,34 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd1" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub1 -D AUTO -N bingo"; + "cmd2" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub2 -D AUTO"; + + commands: + "$(cmd1)"; + "$(cmd2)"; + + reports: + DEBUG:: + "Running: $(cmd1)"; + "Running: $(cmd2)"; + "Setup comes from $(this.promise_filename).sub1, results come from $(this.promise_filename).sub2"; +} + diff --git a/tests/acceptance/00_basics/02_switches/010.cf.sub1 b/tests/acceptance/00_basics/02_switches/010.cf.sub1 new file mode 100644 index 0000000000..d03c6c51a3 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/010.cf.sub1 @@ -0,0 +1,55 @@ +####################################################### +# +# Test initial class negation with -N +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +files: + "$(G.testfile)" + create => "true", + classes => set_bingo; +} + +body classes set_bingo +{ +promise_kept => { "bingo" }; +promise_repaired => { "bingo" }; +} + + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub\d", $(this.promise_filename), "fn"); + "ok" expression => "bingo"; + +reports: + DEBUG:: + "This is $(this.promise_filename), the setup stage of the test only"; + DEBUG.bingo:: + "bingo is set in $(this.promise_filename)"; + DEBUG.!bingo:: + "bingo is not set in $(this.promise_filename)"; +} + diff --git a/tests/acceptance/00_basics/02_switches/010.cf.sub2 b/tests/acceptance/00_basics/02_switches/010.cf.sub2 new file mode 100644 index 0000000000..1e08a61448 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/010.cf.sub2 @@ -0,0 +1,48 @@ +####################################################### +# +# Test class creation with -D +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub\d", $(this.promise_filename), "fn"); + +reports: + DEBUG:: + "This is $(this.promise_filename)"; + "This should only pass if you run it with the 'bingo' class defined"; + "The main tester does NOT set it, but it is created in the 'setup'"; + "config file, $(fn[1]).sub1"; + bingo:: + "$(fn[1]) FAIL"; + !bingo:: + "$(fn[1]) Pass"; +} + diff --git a/tests/acceptance/00_basics/02_switches/README b/tests/acceptance/00_basics/02_switches/README new file mode 100644 index 0000000000..382a1a606c --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/README @@ -0,0 +1,4 @@ +There are tests in this directory that consist of two files. The first +file is the nnn.cf file, which is called by the test runner. The second +file is the nnn.cf.sub file, which is called by the nnn.cf file. In each case +we are testing the effects of various runtime flags. diff --git a/tests/acceptance/00_basics/02_switches/dry_run_perms_doesnt_lie.cf b/tests/acceptance/00_basics/02_switches/dry_run_perms_doesnt_lie.cf new file mode 100644 index 0000000000..6755819227 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/dry_run_perms_doesnt_lie.cf @@ -0,0 +1,42 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testdir)/." + create => "true"; + + "$(G.testdir)/redmine_7082" + create => "true", + perms => m("777"), + comment => "We first ensure a file exists with specific permissions so + that we can test if we get unexpected output when later + running with dry-run."; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Knf $(this.promise_filename).sub -I -b test"; + + methods: + # Since the agent is run with dry-run (-n) there should be no statement of permissions changing. + # In fact, permissions are not changed, the agent only says they are, so it is only the false statement that needs to be checked. + "test_agent_output" + usebundle => dcs_passif_output(".*Should change permissions of .* from 0777 to 0700.*", + ".*had permissions 0777, changed it to 0700.*", + $(command), $(this.promise_filename)); + +} diff --git a/tests/acceptance/00_basics/02_switches/dry_run_perms_doesnt_lie.cf.sub b/tests/acceptance/00_basics/02_switches/dry_run_perms_doesnt_lie.cf.sub new file mode 100644 index 0000000000..3fa489730e --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/dry_run_perms_doesnt_lie.cf.sub @@ -0,0 +1,14 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + files: + "$(G.testdir)/redmine_7082" + create => "true", + perms => m("go-rwx"); +} diff --git a/tests/acceptance/00_basics/02_switches/show-evaluated-vars.cf b/tests/acceptance/00_basics/02_switches/show-evaluated-vars.cf new file mode 100644 index 0000000000..86ee0f98ff --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/show-evaluated-vars.cf @@ -0,0 +1,38 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "ENT-7724", "ENT-7678" } + string => "Test --show-evaluated-vars does not crash the agent when there are vars defined by the module protocol that do not have explicit tags defined."; + + "test_skip_unsupported" + string => "windows", + comment => "The subtest policy uses /bin/echo"; + + commands: + "$(sys.cf_agent) -Kf $(this.promise_filename).sub --show-evaluated-vars" + classes => results( "namespace", "sub_agent" ); +} +bundle agent check +{ + methods: + + "Pass/Fail" + usebundle => dcs_passif_expected( "sub_agent_repaired", "sub_agent_not_kept", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/00_basics/02_switches/show-evaluated-vars.cf.sub b/tests/acceptance/00_basics/02_switches/show-evaluated-vars.cf.sub new file mode 100644 index 0000000000..bf3f167e15 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/show-evaluated-vars.cf.sub @@ -0,0 +1,11 @@ +# Here we execute echo as a module to define some variables that do not have /explicit/ tags. +# As noted in ENT-7724, this can cause a segfault when the agent is run with --show-evaluated-vars +bundle agent main +{ + commands: + `/bin/echo '=string_from_module= value of string from module +@list_from_module= { "one", "two", "three" } +%data1_from_module=[1,2,3] +%data2_from_module={ "my_stuff": [1,2,3] }'` + module => "true"; +} diff --git a/tests/acceptance/00_basics/02_switches/staging/007.cf b/tests/acceptance/00_basics/02_switches/staging/007.cf new file mode 100644 index 0000000000..9b445d8950 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/staging/007.cf @@ -0,0 +1,30 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,MAIN -N ok"; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; + "Test results come from $(this.promise_filename).sub"; +} + diff --git a/tests/acceptance/00_basics/02_switches/staging/007.cf.sub b/tests/acceptance/00_basics/02_switches/staging/007.cf.sub new file mode 100644 index 0000000000..28c7359b23 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/staging/007.cf.sub @@ -0,0 +1,53 @@ +####################################################### +# +# Test initial class negation with -N +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +files: + "$(g.testfile)" + create => "true", + classes => set_bingo; +} + +body classes set_bingo +{ +promise_kept => { "bingo" }; +promise_repaired => { "bingo" }; +} + + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "ok" expression => "bingo"; + +reports: + DEBUG:: + "This should pass even if you run it with: -N ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) Pass"; + !ok:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/00_basics/02_switches/staging/009.cf b/tests/acceptance/00_basics/02_switches/staging/009.cf new file mode 100644 index 0000000000..d4c89484d9 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/staging/009.cf @@ -0,0 +1,30 @@ +####################################################### +# +# This is a special test - it exercises runtime switches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => + "$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,MAIN -N ok,foo"; + + commands: + "$(cmd)"; + + reports: + DEBUG:: + "Running: $(cmd)"; + "Test results come from $(this.promise_filename).sub"; +} + diff --git a/tests/acceptance/00_basics/02_switches/staging/009.cf.sub b/tests/acceptance/00_basics/02_switches/staging/009.cf.sub new file mode 100644 index 0000000000..28c7359b23 --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/staging/009.cf.sub @@ -0,0 +1,53 @@ +####################################################### +# +# Test initial class negation with -N +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +files: + "$(g.testfile)" + create => "true", + classes => set_bingo; +} + +body classes set_bingo +{ +promise_kept => { "bingo" }; +promise_repaired => { "bingo" }; +} + + +####################################################### + +bundle agent check +{ +classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "ok" expression => "bingo"; + +reports: + DEBUG:: + "This should pass even if you run it with: -N ok"; + "Look at $(fn[1]) to see which flags are passed in"; + ok:: + "$(fn[1]) Pass"; + !ok:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/00_basics/02_switches/staging/negate_hard_class.cf b/tests/acceptance/00_basics/02_switches/staging/negate_hard_class.cf new file mode 100644 index 0000000000..f3085e0fcb --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/staging/negate_hard_class.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test hard class negation with -N +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle common test +{ + classes: + "rejected" not => returnszero("$(sys.cf_agent) -Kf $(this.promise_filename).sub -Ncfengine", "noshell"); +} + +####################################################### + +bundle agent check +{ +classes: + "ok" and => { "rejected" }; + +reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/02_switches/staging/negate_hard_class.cf.sub b/tests/acceptance/00_basics/02_switches/staging/negate_hard_class.cf.sub new file mode 100644 index 0000000000..46c7224ded --- /dev/null +++ b/tests/acceptance/00_basics/02_switches/staging/negate_hard_class.cf.sub @@ -0,0 +1,11 @@ +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + reports: + DEBUG:: + "$(this.bundle): running from $(this.promise_filename)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/001.cf b/tests/acceptance/00_basics/03_bodies/001.cf new file mode 100644 index 0000000000..a05d6315fa --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/001.cf @@ -0,0 +1,35 @@ +# Test that log_failed without log_kept does not segfault (Mantis #1107) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + files: + "$(G.testdir)/foo.txt" + create => "true", + action => log; +} + +body action log +{ + log_failed => "stdout"; + log_string => ""; +} + +bundle agent check +{ + reports: + cfengine_3:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/03_bodies/002.cf b/tests/acceptance/00_basics/03_bodies/002.cf new file mode 100644 index 0000000000..d8a793b614 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/002.cf @@ -0,0 +1,45 @@ +# Test that log_failed and log_kept set to the same value do not cause stack overflow (Redmine #2317) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile).src" + create => "yes"; + "$(G.testfile).dest" + create => "yes"; +} + +bundle agent test +{ + files: + "$(G.testfile).dest" + link_from => hard("$(G.testfile).src"), + action => log; +} + +body action log +{ + log_failed => "$(G.testfile).action.log"; + log_kept => "$(G.testfile).action.log"; + log_string => ""; +} + +body link_from hard(from) +{ + source => "$(from)"; + link_type => "hardlink"; +} + +bundle agent check +{ + reports: + cfengine_3:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/03_bodies/003.cf b/tests/acceptance/00_basics/03_bodies/003.cf new file mode 100644 index 0000000000..8bc9826699 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/003.cf @@ -0,0 +1,36 @@ +# Test that log_failed and log_kept set to the same value do not cause stack overflow (Redmine #2317) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + commands: + "$(G.true)" + action => log; +} + +body action log +{ + log_repaired => "$(G.testfile).action.log"; + log_failed => "$(G.testfile).action.log"; + log_kept => "$(G.testfile).action.log"; + log_string => ""; +} + +bundle agent check +{ + reports: + cfengine_3:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/03_bodies/101.cf b/tests/acceptance/00_basics/03_bodies/101.cf new file mode 100644 index 0000000000..17efb2596c --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/101.cf @@ -0,0 +1,143 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "G", "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/102.cf b/tests/acceptance/00_basics/03_bodies/102.cf new file mode 100644 index 0000000000..12b9b2628d --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/102.cf @@ -0,0 +1,145 @@ +####################################################### +# +# Test multiple promise_repaired and cancel_repaired (contrived example) +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_repaired => { "promise_repaired", "p2_repaired" }; + cancel_repaired => { "cancel_repaired", "cancel_kept", "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[p2_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "p2_repaired", + "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/103.cf b/tests/acceptance/00_basics/03_bodies/103.cf new file mode 100644 index 0000000000..2f66ec5a22 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/103.cf @@ -0,0 +1,149 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired (different location in edit_line) +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/104.cf b/tests/acceptance/00_basics/03_bodies/104.cf new file mode 100644 index 0000000000..022bb1d535 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/104.cf @@ -0,0 +1,150 @@ +####################################################### +# +# Test promise_kept and cancel_kept +# Insert lines to a file, then verify that insert promise is kept +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + edit_line => init_insert("$(init.body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/109.cf b/tests/acceptance/00_basics/03_bodies/109.cf new file mode 100644 index 0000000000..31b82e768a --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/109.cf @@ -0,0 +1,170 @@ +####################################################### +# +# Same as 105.cf, but two separate edit_line promises (correctly done!) +# Test promise_kept+repaired and cancel_kept+repaired (multiple promises set classes) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + "body2" string => + "BEGIN + One potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(init.body2)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and we call test_insert from test, the promises are + # the same, and are only executed ONCE, so it only sets the repaired + # promises. + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/112.cf b/tests/acceptance/00_basics/03_bodies/112.cf new file mode 100644 index 0000000000..6373c38963 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/112.cf @@ -0,0 +1,149 @@ +####################################################### +# +# Test repair_failed and cancel_notkept +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + # create => "true", # file will not exist! + edit_line => init_insert("$(body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => "ON"; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/113.cf b/tests/acceptance/00_basics/03_bodies/113.cf new file mode 100644 index 0000000000..f875bc79d7 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/113.cf @@ -0,0 +1,150 @@ +####################################################### +# +# Test repair_failed and cancel_notkept (different location and results) +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + # create => "true", # file will not exist! + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + # Promise never executed, so classes not set! + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/114.cf b/tests/acceptance/00_basics/03_bodies/114.cf new file mode 100644 index 0000000000..d196d584e6 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/114.cf @@ -0,0 +1,146 @@ +####################################################### +# +# Test setting two classes in one promise +# Copy file with one mode, then copy again with different mode +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile)" + copy_from => local("$(G.etc_group)"), + perms => mode("666"); +} + +body copy_from local(f) +{ + source => "$(f)"; +} + +body perms mode(m) +{ + mode => "$(m)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; + + files: + "$(G.testfile)" + copy_from => local("$(G.etc_group)"), # Same file + perms => mode("644"), # Different mode + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/115.cf b/tests/acceptance/00_basics/03_bodies/115.cf new file mode 100644 index 0000000000..f704bf610c --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/115.cf @@ -0,0 +1,148 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired with command and no *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) BEGIN $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/116.cf b/tests/acceptance/00_basics/03_bodies/116.cf new file mode 100644 index 0000000000..f60f987384 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/116.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test promise_kept and cancel_kept with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "0" }; + repaired_returncodes => { "1" }; + failed_returncodes => { "2" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) BEGIN $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/117.cf b/tests/acceptance/00_basics/03_bodies/117.cf new file mode 100644 index 0000000000..8437f00c90 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/117.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "0" }; + repaired_returncodes => { "1" }; + failed_returncodes => { "2" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/118.cf b/tests/acceptance/00_basics/03_bodies/118.cf new file mode 100644 index 0000000000..d301e465fb --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/118.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test repair_failed and cancel_notkept with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "0" }; + repaired_returncodes => { "1" }; + failed_returncodes => { "2" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) BEGIN $(G.testfile).missing" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => "ON"; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/119.cf b/tests/acceptance/00_basics/03_bodies/119.cf new file mode 100644 index 0000000000..e8eeea3ba3 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/119.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test no classes set with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "1" }; + repaired_returncodes => { "2" }; + failed_returncodes => { "3" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) BEGIN $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => "ON"; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/120.cf b/tests/acceptance/00_basics/03_bodies/120.cf new file mode 100644 index 0000000000..a4cb564d52 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/120.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test overlapping codes with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "0", "1" }; + repaired_returncodes => { "1" }; + failed_returncodes => { "1", "2" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => "ON"; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/121.cf b/tests/acceptance/00_basics/03_bodies/121.cf new file mode 100644 index 0000000000..d218e3f88c --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/121.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test overlapping codes (with no match) with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "10", "11" }; + repaired_returncodes => { "11" }; + failed_returncodes => { "11", "12" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => "ON"; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/122.x.cf b/tests/acceptance/00_basics/03_bodies/122.x.cf new file mode 100644 index 0000000000..2eaea0ef9c --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/122.x.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test illegal codes (string) with command and *_returncodes (issue 751) +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "1", "3" }; + repaired_returncodes => { "fred", "7" }; # Must be an integer + failed_returncodes => { "1", "2" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/123.cf b/tests/acceptance/00_basics/03_bodies/123.cf new file mode 100644 index 0000000000..80dbc041fc --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/123.cf @@ -0,0 +1,47 @@ +# Test a context defined in body classes with scope => bundle + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + processes: + any:: + "$(G.true)" + classes => bundle_class; + + bundle_context:: + "$(G.true)" + classes => namespace_class; +} + +body classes namespace_class +{ + # default to namespace scoping + promise_kept => { "namespace_context" }; +} + +body classes bundle_class +{ + scope => "bundle"; + promise_kept => { "bundle_context" }; +} + +bundle agent check +{ + reports: + namespace_context.!bundle_context:: + "$(this.promise_filename) Pass"; + bundle_context:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/124.cf b/tests/acceptance/00_basics/03_bodies/124.cf new file mode 100644 index 0000000000..822ded866d --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/124.cf @@ -0,0 +1,62 @@ +# Test a context defined in body classes with scope => bundle, +# check that the bundle context goes away after the bundle is done + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + methods: + !pass_one:: + "whatever" + usebundle => set_context("yes"); + pass_one:: + "whatever2" + usebundle => set_context("no"); +} + +bundle agent set_context(doit) +{ + classes: + "set_bundle_class" expression => strcmp(${doit}, "yes"); + + processes: + set_bundle_class:: + "$(G.true)" + classes => bundle_class; + + "$(G.true)" + classes => namespace_class; +} + + +body classes namespace_class +{ + scope => "namespace"; + promise_kept => { "pass_one" }; +} + +body classes bundle_class +{ + scope => "bundle"; + promise_kept => { "bundle_context" }; +} + +bundle agent check +{ + reports: + !bundle_context:: + "$(this.promise_filename) Pass"; + bundle_context:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/125.cf b/tests/acceptance/00_basics/03_bodies/125.cf new file mode 100644 index 0000000000..674cb06dd7 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/125.cf @@ -0,0 +1,49 @@ +# Test a context defined in body classes with scope => bundle, +# cancelling a namespace class using a bundle class + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + processes: + any:: + "$(G.true)" + classes => set_cancel_this; +} + +bundle agent test +{ + processes: + any:: + "$(G.true)" + classes => bundle_class; +} + +body classes set_cancel_this +{ + promise_kept => { "cancel_this" }; +} + +body classes bundle_class +{ + scope => "bundle"; + promise_kept => { "bundle_class" }; + cancel_kept => { "cancel_this" }; +} + +bundle agent check +{ + reports: + !cancel_this:: + "$(this.promise_filename) Pass"; + cancel_this|bundle_class:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/401.x.cf b/tests/acceptance/00_basics/03_bodies/401.x.cf new file mode 100644 index 0000000000..859e100ad0 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/401.x.cf @@ -0,0 +1,41 @@ +####################################################### +# +# Test handling of duplicate handles +# Should error, was crashing in Issue 2267 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + vars: + "firstvar" + handle => "dupehandle", + string => "Some item with a handle"; + "secondvar" + handle => "dupehandle", + string => "Another variable with an intentionally duplicated handle"; +} + +bundle agent check +{ + reports: + DEBUG:: + "Expected to return an error"; + !ok:: + "$(this.promise_filename) Pass"; + ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app00_zero.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app00_zero.cf.sub new file mode 100644 index 0000000000..7ae094ebe3 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app00_zero.cf.sub @@ -0,0 +1,5 @@ +bundle agent app00_zero +{ + reports: + "$(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_one.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_one.cf.sub new file mode 100644 index 0000000000..166a489f6f --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_one.cf.sub @@ -0,0 +1,5 @@ +bundle agent app_one +{ + reports: + "$(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_two.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_two.cf.sub new file mode 100644 index 0000000000..b2f13a6ef9 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_two.cf.sub @@ -0,0 +1,5 @@ +bundle agent app_two +{ + reports: + "$(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_zero.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_zero.cf.sub new file mode 100644 index 0000000000..49fae6601c --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/app_zero.cf.sub @@ -0,0 +1,7 @@ +bundle agent app_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/test.cf b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/test.cf new file mode 100644 index 0000000000..83239c4506 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/test.cf @@ -0,0 +1,36 @@ +####################################################### +# +# Redmine#3315: Test dynamic inputs and bundlesequence +# +####################################################### + +body common control +{ + inputs => { + "../../../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + + "description" -> { "CFE-1780" } + string => "Test that dynamic inputs from classic array structures can be loaded and used via body file control."; +} + +bundle agent check +{ + methods: + + "Check Agent Output" + usebundle => dcs_passif_output1(".*R: app00_zero +R: app_one +R: app_two.*", + "$(sys.cf_agent) -Kf $(this.promise_filename).sub", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/test.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/test.cf.sub new file mode 100644 index 0000000000..bb7c6efa93 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-common-control-inputs/test.cf.sub @@ -0,0 +1,78 @@ +body file control +{ + inputs => { @(map_role_bundles.inputs) }; +} + +bundle agent main +# +{ + methods: + "classify"; + "map_role_bundles"; + "go"; + +} + + +bundle common classify +# +{ + vars: + "variable" string => "any"; + + classes: + "group_class_from_variable" + expression => strcmp("$(variable)", "any"), + comment => "Highest level classification, other classes depend on this."; + + "group_class_from_string" + expression => strcmp("any", "any"), + comment => "Highest level classification, other classes depend on this."; + +} + +bundle common map_role_bundles +# +{ + vars: + # Define bundles + any:: + "role[app00_zero]" + string => "$(this.promise_dirname)/app00_zero.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + !any:: + "role[app_zero]" + string => "$(this.promise_dirname)/app_zero.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + group_class_from_variable:: + "role[app_one]" + string => "$(this.promise_dirname)/app_one.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + group_class_from_string:: + "role[app_two]" string => "$(this.promise_dirname)/app_two.cf.sub"; + + any:: + "bundles" slist => getindices(role); + "inputs" slist => getvalues(role); +} + +bundle agent go +# +{ + vars: + "bundles" slist => { @(map_role_bundles.bundles) }; + "sorted_bundles" slist => sort(bundles, "lex"); + + methods: + "$(sorted_bundles)" usebundle => $(sorted_bundles); +} + diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app00_zero.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app00_zero.cf.sub new file mode 100644 index 0000000000..7ae094ebe3 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app00_zero.cf.sub @@ -0,0 +1,5 @@ +bundle agent app00_zero +{ + reports: + "$(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_one.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_one.cf.sub new file mode 100644 index 0000000000..166a489f6f --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_one.cf.sub @@ -0,0 +1,5 @@ +bundle agent app_one +{ + reports: + "$(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_two.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_two.cf.sub new file mode 100644 index 0000000000..b2f13a6ef9 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_two.cf.sub @@ -0,0 +1,5 @@ +bundle agent app_two +{ + reports: + "$(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_zero.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_zero.cf.sub new file mode 100644 index 0000000000..49fae6601c --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/app_zero.cf.sub @@ -0,0 +1,7 @@ +bundle agent app_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/test.cf b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/test.cf new file mode 100644 index 0000000000..b3040ce5c2 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/test.cf @@ -0,0 +1,36 @@ +####################################################### +# +# Redmine#3315: Test dynamic inputs and bundlesequence +# +####################################################### + +body common control +{ + inputs => { + "../../../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + + "description" -> { "CFE-1780" } + string => "Test that dynamic inputs from classic array structures can be loaded and used."; +} + +bundle agent check +{ + methods: + + "Check Agent Output" + usebundle => dcs_passif_output1(".*R: app00_zero +R: app_one +R: app_two.*", + "$(sys.cf_agent) -Kf $(this.promise_filename).sub", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/test.cf.sub b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/test.cf.sub new file mode 100644 index 0000000000..f03f635f31 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/CFE-1780/body-file-control-inputs/test.cf.sub @@ -0,0 +1,68 @@ +body common control +# +{ + bundlesequence => { "classify", "map_role_bundles", "go" }; + inputs => { @(map_role_bundles.inputs) }; +} + +bundle common classify +# +{ + vars: + "variable" string => "any"; + + classes: + "group_class_from_variable" + expression => strcmp("$(variable)", "any"), + comment => "Highest level classification, other classes depend on this."; + + "group_class_from_string" + expression => strcmp("any", "any"), + comment => "Highest level classification, other classes depend on this."; + +} + +bundle common map_role_bundles +# +{ + vars: + # Define bundles + any:: + "role[app00_zero]" + string => "$(this.promise_dirname)/app00_zero.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + !any:: + "role[app_zero]" + string => "$(this.promise_dirname)/app_zero.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + group_class_from_variable:: + "role[app_one]" + string => "$(this.promise_dirname)/app_one.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + group_class_from_string:: + "role[app_two]" string => "$(this.promise_dirname)/app_two.cf.sub"; + + any:: + "bundles" slist => getindices(role); + "inputs" slist => getvalues(role); +} + +bundle agent go +# +{ + vars: + "bundles" slist => { @(map_role_bundles.bundles) }; + "sorted_bundles" slist => sort(bundles, "lex"); + + methods: + "$(sorted_bundles)" usebundle => $(sorted_bundles); +} diff --git a/tests/acceptance/00_basics/03_bodies/README b/tests/acceptance/00_basics/03_bodies/README new file mode 100644 index 0000000000..64d4232189 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/README @@ -0,0 +1,8 @@ +The files in this directory are tests of bodies that occur "anywhere": + +0xx.cf body action +1xx.cf body classes +2xx.cf body comments +3xx.cf body depends_on +4xx.cf body handle +5xx.cf body ifvarclass diff --git a/tests/acceptance/00_basics/03_bodies/cf_null_in_bundlesequence.cf b/tests/acceptance/00_basics/03_bodies/cf_null_in_bundlesequence.cf new file mode 100644 index 0000000000..fedeaca0f8 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/cf_null_in_bundlesequence.cf @@ -0,0 +1,24 @@ +# cf_null now is just another string, bundles names as such should be +# executed like every other bundle + + +body common control { + bundlesequence => { "cf_null", "test" }; +} + +bundle agent cf_null +{ + classes: + "ok" expression => "any", + scope => "namespace"; +} + +bundle agent test + { + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/control-body-fncall.cf b/tests/acceptance/00_basics/03_bodies/control-body-fncall.cf new file mode 100644 index 0000000000..8b06c192a4 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/control-body-fncall.cf @@ -0,0 +1,31 @@ +# Test that functions may be called from control bodies + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ +# this is just to trigger the desired behavior. here ifelse is evaluated +# without any caller promise. If it was a non-control body, the caller +# would have been the promise inlining the body. here there is no caller. + default_repository => ifelse("x", "false", "y", "false", "false"); +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + reports: + cfengine_3:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/03_bodies/default_files_action.cf b/tests/acceptance/00_basics/03_bodies/default_files_action.cf new file mode 100644 index 0000000000..27865b53a2 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/default_files_action.cf @@ -0,0 +1,76 @@ +####################################################### +# +# Test default body action and overriding with specific action +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body file control +{ + namespace => "bodydefault"; +} + +body action files_action +{ + action_policy => "warn"; +} + +body file control +{ + namespace => "default"; +} + +body action specific +{ + action_policy => "fix"; +} + +####################################################### + +bundle agent test_specified_action +{ + files: + "$(G.testdir)/specified" + create => "true", + action => specific; +} + +bundle agent test_default_action +{ + files: + "$(G.testdir)/default" + create => "true"; +} + +####################################################### + +bundle agent test +{ + methods: + "specified" + usebundle => test_specified_action; + "default" + usebundle => test_default_action; +} + +bundle agent check +{ + classes: + "default_created" expression => fileexists("$(G.testdir)/default"); + "specified_created" expression => fileexists("$(G.testdir)/specified"); + "ok" expression => "specified_created.!default_created"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/dynamic_inputs_findfiles.cf b/tests/acceptance/00_basics/03_bodies/dynamic_inputs_findfiles.cf new file mode 100644 index 0000000000..753e93cec3 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/dynamic_inputs_findfiles.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Redmine#3315: Test dynamic inputs and bundlesequence +# +####################################################### + +body common control +{ + inputs => { + "../../default.cf.sub", + @(dynamic.inputs), + }; + bundlesequence => { dynamic, default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent dynamic +{ + vars: + "todo" slist => bundlesmatching(".*included"); + "inputs" slist => findfiles("$(this.promise_filename).[s][u][b]"); + + methods: + "run" usebundle => $(todo); + + reports: + DEBUG:: + "Found dynamic bundle: $(todo)"; +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + classes: + "ok" expression => "class_defined_from_included_bundle"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/dynamic_inputs_findfiles.cf.sub b/tests/acceptance/00_basics/03_bodies/dynamic_inputs_findfiles.cf.sub new file mode 100644 index 0000000000..e4b4ef75e9 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/dynamic_inputs_findfiles.cf.sub @@ -0,0 +1,10 @@ +bundle agent dynamic_included +{ + meta: + "tags" string => "included"; + + classes: + "class_defined_from_included_bundle" + expression => "any", + scope => "namespace"; +} diff --git a/tests/acceptance/00_basics/03_bodies/dynamic_inputs_maplist.cf b/tests/acceptance/00_basics/03_bodies/dynamic_inputs_maplist.cf new file mode 100644 index 0000000000..f41108d1e6 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/dynamic_inputs_maplist.cf @@ -0,0 +1,52 @@ +####################################################### +# +# Redmine#3315: Test dynamic inputs and bundlesequence using maplist +# +####################################################### + +body common control +{ + inputs => { + "../../default.cf.sub", + @(dynamic.inputs), + }; + bundlesequence => { dynamic, default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent dynamic +{ + vars: + "todo" slist => bundlesmatching(".*included"); + "dynamic_input_names" slist => {"$(this.promise_filename).sub"}; + "inputs" slist => maplist("$(this)", "dynamic_input_names"); + + methods: + "run" usebundle => $(todo); + + reports: + DEBUG:: + "Found dynamic bundle: $(todo)"; +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + classes: + "ok" expression => "class_defined_from_included_bundle"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/dynamic_inputs_maplist.cf.sub b/tests/acceptance/00_basics/03_bodies/dynamic_inputs_maplist.cf.sub new file mode 100644 index 0000000000..65f3cd894d --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/dynamic_inputs_maplist.cf.sub @@ -0,0 +1,14 @@ +bundle agent dynamic_included +{ + meta: + "tags" string => "included"; + + classes: + "class_defined_from_included_bundle" + expression => "any", + scope => "namespace"; + + reports: + DEBUG:: + "$(this.promise_filename) activated $(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/evaluation-should-respect-ifvarclass.cf b/tests/acceptance/00_basics/03_bodies/evaluation-should-respect-ifvarclass.cf new file mode 100644 index 0000000000..9f64beb189 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/evaluation-should-respect-ifvarclass.cf @@ -0,0 +1,71 @@ +############################################################################## +# +# Redmine #3577: evaluation should respect ifvarclass +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + methods: + "rm" usebundle => dcs_fini("$(G.testfile).z"); + "rm" usebundle => dcs_fini("$(G.testfile).z2"); +} + + +bundle agent test +{ + classes: + "zclass" expression => returnszero("$(G.echo) xyz > $(G.testfile).z", + "useshell"), + ifvarclass => not("any"); + "zclass" expression => returnszero("$(G.echo) xyz > $(G.testfile).z", + "useshell"), + depends_on => { "this_handle_does_not_exist" }; + + + !any:: + "z2class" expression => returnszero("$(G.echo) xyz > $(G.testfile).z2", + "useshell"); + + vars: + "x" string => concat("a", "b"), + ifvarclass => "!any"; + + "x_not" string => concat("a", "b"), + ifvarclass => not("any"); + + !any:: + "y" string => concat("c", "d"); +} + + +bundle agent check +{ + # If the output contains the string, we fail + classes: + "eval_x" expression => strcmp("ab", "$(test.x)"); + "eval_x_not" expression => strcmp("ab", "$(test.x_not)"); + "eval_y" expression => strcmp("cd", "$(test.y)"); + "eval_z" expression => "zclass"; + "zfile_created" expression => fileexists("$(G.testfile).z"); + "z2file_created" expression => fileexists("$(G.testfile).z2"); + + methods: + "" usebundle => dcs_passif_expected("", + "eval_x,eval_x_not,eval_y,zclass,zfile_created,z2file_created", + $(this.promise_filename)), + inherit => "true"; + + reports: + DEBUG:: + "x $(test.x)"; + "x_not $(test.x_not)"; + "y $(test.y)"; +} diff --git a/tests/acceptance/00_basics/03_bodies/if-unless.cf b/tests/acceptance/00_basics/03_bodies/if-unless.cf new file mode 100644 index 0000000000..8a15cdafd7 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/if-unless.cf @@ -0,0 +1,47 @@ +############################################################################## +# +# Redmine #6179: if and unless +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + classes: + "check1" expression => "any", scope => "namespace"; + "check2" expression => "any", scope => "namespace"; +} + +bundle agent test +{ + methods: + "" usebundle => file_make("$(G.testfile).present", ""), + if => "check1"; + + "" usebundle => file_make("$(G.testfile).absent", ""), + unless => "check2"; + + "" usebundle => file_make("$(G.testfile).absent", ""), + if => "check1", + unless => "check2"; +} + +bundle agent check +{ + # If the output contains the string, we fail + classes: + "if_ok" expression => fileexists("$(G.testfile).present"); + "unless_ok" not => fileexists("$(G.testfile).absent"); + + methods: + "" usebundle => dcs_passif_expected("if_ok,unless_ok", + "", + $(this.promise_filename)), + inherit => "true"; +} diff --git a/tests/acceptance/00_basics/03_bodies/ifvarclass-with-function.cf b/tests/acceptance/00_basics/03_bodies/ifvarclass-with-function.cf new file mode 100644 index 0000000000..115561fea4 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/ifvarclass-with-function.cf @@ -0,0 +1,58 @@ +# Verify that functions returns are interpreted correctly in ifvarclass +# Redmine 6327 + +body common control +{ + +AUTO:: + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +!AUTO:: + bundlesequence => { "init", "check" }; +} + +bundle agent init +{ +vars: + "always" string => "any"; + "never" string => "!any"; +} + +bundle agent check +{ +classes: + # yes + "variable_with_set_class" expression => "any", + ifvarclass => canonify("$(init.always)"); + + # no + "variable_with_unset_class" expression => "any", + ifvarclass => canonify("$(init.never)"); + "just_a_class" expression => "any", + ifvarclass => not("any"); + "undefined_variable" expression => "any", + ifvarclass => canonify("$(init.unknown)"); + "undefined_bundle" expression => "any", + ifvarclass => canonify("$(never.never)"); + "invalid_function" expression => "any", + ifvarclass => now(); + + "fail" expression => "variable_with_unset_class|just_a_class|undefined_variable|undefined_bundle|invalid_function"; + "ok" and => { "variable_with_set_class", "!fail" }; + +reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG.just_a_class:: + "just_a_class"; + DEBUG.undefined_variable:: + "undefined_variable"; + DEBUG.undefined_bundle:: + "undefined_bundle"; + DEBUG.invalid_function:: + "invalid_function"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/inherit_from.cf b/tests/acceptance/00_basics/03_bodies/inherit_from.cf new file mode 100644 index 0000000000..13f2193eff --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/inherit_from.cf @@ -0,0 +1,54 @@ +############################################################################## +# +# Redmine #4309: body inheritance with inherit_from +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle common test +{ + reports: + "test_inherit" classes => classes_generic_inherit("test_inherit"); + "test_nonesuch" classes => classes_generic_nonesuch("test_nonesuch"); + "test_overwrite" classes => classes_generic_overwrite("test_overwrite"); +} + +body classes classes_generic_inherit(x) +{ + inherit_from => classes_generic($(x)); +} + +body classes classes_generic_nonesuch(x) +{ + inherit_from => classes_generic_missing_body; +} + +# the parent uses $(x) so this tests that inheritance goes from parent to child +body classes classes_generic_overwrite(overwrite_x) +{ + inherit_from => classes_generic($(overwrite_x)); + promise_repaired => { "promise_repaired_$(overwrite_x)", "$(overwrite_x)_repaired", "$(overwrite_x)_ok", "$(overwrite_x)_reached" }; + repair_failed => { "repair_failed_$(overwrite_x)", "$(overwrite_x)_failed", "$(overwrite_x)_not_ok", "$(overwrite_x)_error", "$(overwrite_x)_not_kept", "$(overwrite_x)_not_repaired", "$(overwrite_x)_reached" }; + repair_denied => { "repair_denied_$(overwrite_x)", "$(overwrite_x)_denied", "$(overwrite_x)_not_ok", "$(overwrite_x)_error", "$(overwrite_x)_not_kept", "$(overwrite_x)_not_repaired", "$(overwrite_x)_reached" }; + repair_timeout => { "repair_timeout_$(overwrite_x)", "$(overwrite_x)_timeout", "$(overwrite_x)_not_ok", "$(overwrite_x)_error", "$(overwrite_x)_not_kept", "$(overwrite_x)_not_repaired", "$(overwrite_x)_reached" }; + promise_kept => { "promise_kept_$(overwrite_x)", "$(overwrite_x)_kept", "$(overwrite_x)_ok", "$(overwrite_x)_not_repaired", "$(overwrite_x)_reached" }; +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("test_inherit_ok,test_overwrite_ok", + "test_nonesuch_ok", + $(this.promise_filename)), + inherit => "true"; +} diff --git a/tests/acceptance/00_basics/03_bodies/inherit_from_mismatched_args.x.cf b/tests/acceptance/00_basics/03_bodies/inherit_from_mismatched_args.x.cf new file mode 100644 index 0000000000..f191785452 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/inherit_from_mismatched_args.x.cf @@ -0,0 +1,29 @@ +############################################################################## +# +# Redmine #4309: body inheritance with inherit_from +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ + reports: + "test_inherit" classes => classes_generic_broken_inherit("test_inherit"); +} + +body classes classes_generic_broken_inherit(x) +{ + inherit_from => classes_generic("one", "two"); # should break +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_pass("$(this.promise_filename)"); +} diff --git a/tests/acceptance/00_basics/03_bodies/inherit_from_noargs.x.cf b/tests/acceptance/00_basics/03_bodies/inherit_from_noargs.x.cf new file mode 100644 index 0000000000..b6d92a164e --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/inherit_from_noargs.x.cf @@ -0,0 +1,29 @@ +############################################################################## +# +# Redmine #4309: body inheritance with inherit_from +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ + reports: + "test_inherit" classes => classes_generic_broken_inherit("test_inherit"); +} + +body classes classes_generic_broken_inherit(x) +{ + inherit_from => classes_generic; # should break +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_pass("$(this.promise_filename)"); +} diff --git a/tests/acceptance/00_basics/03_bodies/inherit_from_parameters.cf b/tests/acceptance/00_basics/03_bodies/inherit_from_parameters.cf new file mode 100644 index 0000000000..0f88628a73 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/inherit_from_parameters.cf @@ -0,0 +1,48 @@ +############################################################################## +# +# Redmine #4309: parameterized body inheritance with inherit_from +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle common test +{ + reports: + "test_inherit1" classes => scoped_classes_generic_inherit("namespace", "test_inherit"); + "test_inherit2" classes => scoped_classes_generic_some_inherit("test_inherit_some"); + "test_inherit3" classes => scoped_classes_generic_none_inherit; +} + +body classes scoped_classes_generic_inherit(myscope, myx) +{ + inherit_from => scoped_classes_generic($(myscope), $(myx)); +} + +body classes scoped_classes_generic_some_inherit(somex) +{ + inherit_from => scoped_classes_generic("namespace", $(somex)); +} + +body classes scoped_classes_generic_none_inherit +{ + inherit_from => scoped_classes_generic("namespace", "test_inherit_none"); +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("test_inherit_ok,test_inherit_some_ok,test_inherit_none_ok", + "", + $(this.promise_filename)), + inherit => "true"; +} diff --git a/tests/acceptance/00_basics/03_bodies/inherit_from_self.x.cf b/tests/acceptance/00_basics/03_bodies/inherit_from_self.x.cf new file mode 100644 index 0000000000..2cdef84702 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/inherit_from_self.x.cf @@ -0,0 +1,34 @@ +############################################################################## +# +# Redmine #4309: body inheritance with inherit_from +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle common test +{ + reports: + "foo" classes => classes_generic_nonesuch; +} + +body classes classes_generic_nonesuch +{ + inherit_from => classes_generic_nonesuch; +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("any", "", $(this.promise_filename)), + inherit => "true"; +} diff --git a/tests/acceptance/00_basics/03_bodies/inherit_from_with_global_var.cf b/tests/acceptance/00_basics/03_bodies/inherit_from_with_global_var.cf new file mode 100644 index 0000000000..c9dec4f368 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/inherit_from_with_global_var.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Test that bodies can inherit attributes containing global variables +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + + +bundle agent init +{ + vars: + "class" string => "_pass"; +} + +####################################################### + +body classes parent(p) +{ + promise_kept => { "${init.class}" }; +} + +body classes static(p) +{ + inherit_from => parent(p); +} + +bundle agent test { + meta: + "description" -> { "CFE-4254" } + string => "Test that bodies can inherit attributes containing global variables"; + + vars: + "test" string => "test", + classes => static("placeholder"); +} + +####################################################### + +bundle agent check +{ + methods: + _pass:: + "pass" usebundle => dcs_pass("$(this.promise_filename)"); + + !_pass:: + "pass" usebundle => dcs_fail("$(this.promise_filename)"); +} diff --git a/tests/acceptance/00_basics/03_bodies/log_file_permissions.cf b/tests/acceptance/00_basics/03_bodies/log_file_permissions.cf new file mode 100644 index 0000000000..d4a15c22d5 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/log_file_permissions.cf @@ -0,0 +1,48 @@ +# Test that log_failed and log_kept set to the same value do not cause stack overflow (Redmine #2317) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + commands: + "$(G.true)" + action => log; +} + +body action log +{ + log_repaired => "$(G.testfile).action.log"; + log_failed => "$(G.testfile).action.log"; + log_kept => "$(G.testfile).action.log"; + log_string => "ignore me"; +} + +bundle agent check +{ + vars: + "perms" string => filestat("$(G.testfile).action.log", "permoct"); + classes: + "ok" expression => strcmp("600", $(perms)); + + reports: + DEBUG:: + "Log file $(G.testfile).action.log had permissions $(perms)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/multilevel_inherit_from_parameters.cf b/tests/acceptance/00_basics/03_bodies/multilevel_inherit_from_parameters.cf new file mode 100644 index 0000000000..74d65692e5 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/multilevel_inherit_from_parameters.cf @@ -0,0 +1,41 @@ +############################################################################## +# +# Redmine #4309: parameterized 2-level body inheritance with inherit_from +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle common test +{ + reports: + "test_inherit" classes => scoped_classes_generic_none2_inherit(); +} + +body classes scoped_classes_generic_none_inherit +{ + inherit_from => scoped_classes_generic("namespace", "test_inherit_none"); +} + +body classes scoped_classes_generic_none2_inherit +{ + inherit_from => scoped_classes_generic_none_inherit; +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("test_inherit_none_ok", + "", + $(this.promise_filename)), + inherit => "true"; +} diff --git a/tests/acceptance/00_basics/03_bodies/report_error_when_body_missing.cf b/tests/acceptance/00_basics/03_bodies/report_error_when_body_missing.cf new file mode 100644 index 0000000000..c8435cde5d --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/report_error_when_body_missing.cf @@ -0,0 +1,24 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" string => "Test that a sensible error message is emitted when a body is undefined."; + +} + +bundle agent check +{ + classes: + "ok" expression => "class_defined_from_included_bundle"; + methods: + "" usebundle => dcs_passif_output(".*Undefined body my_undefined_body.*", "", "$(sys.cf_agent) -Kf $(this.promise_filename).sub", $(this.promise_filename)); + +} diff --git a/tests/acceptance/00_basics/03_bodies/report_error_when_body_missing.cf.sub b/tests/acceptance/00_basics/03_bodies/report_error_when_body_missing.cf.sub new file mode 100644 index 0000000000..ceb80e0e10 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/report_error_when_body_missing.cf.sub @@ -0,0 +1,6 @@ +bundle agent main +{ + commands: + "/bin/echo 123" + action => my_undefined_body("echo_repaired"); +} diff --git a/tests/acceptance/00_basics/03_bodies/staging/201.cf b/tests/acceptance/00_basics/03_bodies/staging/201.cf new file mode 100644 index 0000000000..d46de15ab3 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/201.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/203.cf b/tests/acceptance/00_basics/03_bodies/staging/203.cf new file mode 100644 index 0000000000..f71586d0b2 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/203.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired (different location in edit_line) +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/204.cf b/tests/acceptance/00_basics/03_bodies/staging/204.cf new file mode 100644 index 0000000000..626c5e605c --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/204.cf @@ -0,0 +1,133 @@ +####################################################### +# +# Test promise_kept and cancel_kept +# Insert lines to a file, then verify that insert promise is kept +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + edit_line => init_insert("$(init.body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/209.cf b/tests/acceptance/00_basics/03_bodies/staging/209.cf new file mode 100644 index 0000000000..7a3f7bd232 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/209.cf @@ -0,0 +1,153 @@ +####################################################### +# +# Same as 105.cf, but two separate edit_line promises (correctly done!) +# Test promise_kept+repaired and cancel_kept+repaired (multiple promises set classes) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + "body2" string => + "BEGIN + One potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(init.body2)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and we call test_insert from test, the promises are + # the same, and are only executed ONCE, so it only sets the repaired + # promises. + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/212.cf b/tests/acceptance/00_basics/03_bodies/staging/212.cf new file mode 100644 index 0000000000..77791b0f76 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/212.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test repair_failed and cancel_notkept +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + # create => "true", # file will not exist! + edit_line => init_insert("$(body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => "ON"; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/213.cf b/tests/acceptance/00_basics/03_bodies/staging/213.cf new file mode 100644 index 0000000000..e170a471a8 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/213.cf @@ -0,0 +1,133 @@ +####################################################### +# +# Test repair_failed and cancel_notkept (different location and results) +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + # create => "true", # file will not exist! + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)", + # Promise never executed, so classes not set! + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/214.cf b/tests/acceptance/00_basics/03_bodies/staging/214.cf new file mode 100644 index 0000000000..d42b0b7968 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/214.cf @@ -0,0 +1,128 @@ +####################################################### +# +# Test setting two classes in one promise +# Copy file with one mode, then copy again with different mode +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile)" + copy_from => local("$(G.etc_group)"), + perms => mode("666"); +} + +body copy_from local(f) +{ + source => "$(f)"; +} + +body perms mode(m) +{ + mode => "$(m)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + copy_from => local("$(G.etc_group)"), # Same file + perms => mode("644"), # Different mode + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/215.cf b/tests/acceptance/00_basics/03_bodies/staging/215.cf new file mode 100644 index 0000000000..c24a639a27 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/215.cf @@ -0,0 +1,131 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired with command and no *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) BEGIN $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/216.cf b/tests/acceptance/00_basics/03_bodies/staging/216.cf new file mode 100644 index 0000000000..38922d62d9 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/216.cf @@ -0,0 +1,134 @@ +####################################################### +# +# Test promise_kept and cancel_kept with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "0" }; + repaired_returncodes => { "1" }; + failed_returncodes => { "2" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) BEGIN $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/217.cf b/tests/acceptance/00_basics/03_bodies/staging/217.cf new file mode 100644 index 0000000000..5d839a96ec --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/217.cf @@ -0,0 +1,135 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "0" }; + repaired_returncodes => { "1" }; + failed_returncodes => { "2" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; + +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/218.cf b/tests/acceptance/00_basics/03_bodies/staging/218.cf new file mode 100644 index 0000000000..350c630751 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/218.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test repair_failed and cancel_notkept with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "0" }; + repaired_returncodes => { "1" }; + failed_returncodes => { "2" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) BEGIN $(G.testfile).missing" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => "ON"; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/staging/219.cf b/tests/acceptance/00_basics/03_bodies/staging/219.cf new file mode 100644 index 0000000000..ed9c0de952 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/219.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test no classes set with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "1" }; + repaired_returncodes => { "2" }; + failed_returncodes => { "3" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) BEGIN $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/staging/221.cf b/tests/acceptance/00_basics/03_bodies/staging/221.cf new file mode 100644 index 0000000000..6f6adade07 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/221.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test overlapping codes (with no match) with command and *_returncodes +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "10", "11" }; + repaired_returncodes => { "11" }; + failed_returncodes => { "11", "12" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/03_bodies/staging/223.x.cf b/tests/acceptance/00_basics/03_bodies/staging/223.x.cf new file mode 100644 index 0000000000..34c96430e8 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/223.x.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test illegal codes (negative num) with command and *_returncodes (issue 751) +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "-1", "3" }; # Must be positive + repaired_returncodes => { "12" }; + failed_returncodes => { "11", "12" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/224.x.cf b/tests/acceptance/00_basics/03_bodies/staging/224.x.cf new file mode 100644 index 0000000000..6e3a3eae75 --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/224.x.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test illegal codes (> 255) with command and *_returncodes (issue 751) +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "11", "13" }; + repaired_returncodes => { "12" }; + failed_returncodes => { "1111", "12" }; # Must be <= 255 +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/03_bodies/staging/225.cf b/tests/acceptance/00_basics/03_bodies/staging/225.cf new file mode 100644 index 0000000000..92c182e82c --- /dev/null +++ b/tests/acceptance/00_basics/03_bodies/staging/225.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test illegal codes (space in num) with command and *_returncodes (issue 751) +# Insert lines to a file, the grep for a line which is there +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "cancel_kept" expression => "any"; + "cancel_repaired" expression => "any"; + "cancel_notkept" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; + kept_returncodes => { "11", "13" }; + repaired_returncodes => { "12" }; + failed_returncodes => { " 1", "12" }; # Spaces in number should be ok +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.grep) linenotfound $(G.testfile)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + MAIN.ok:: + "$(this.promise_filename) Pass" + report_to_file => "$(G.logfile)"; + !ok:: + "$(this.promise_filename) FAIL"; + MAIN.!ok:: + "$(this.promise_filename) FAIL" + report_to_file => "$(G.logfile)"; +} + diff --git a/tests/acceptance/00_basics/04_bundles/agent-bundle-class-bundle-scope-default-perspective.cf b/tests/acceptance/00_basics/04_bundles/agent-bundle-class-bundle-scope-default-perspective.cf new file mode 100755 index 0000000000..f67074e04a --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/agent-bundle-class-bundle-scope-default-perspective.cf @@ -0,0 +1,73 @@ +#!/var/cfengine/bin/cf-agent -f- +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { + default("$(this.promise_filename)") }; + version => "1.0"; +} +body file control { namespace => "test"; } +bundle agent example +{ + classes: + "TRIGGERED" + expression => "any", + scope => "bundle"; # Bundle scoped classes are NOT visible from other bundles +} +body file control { namespace => "default"; } +bundle agent test +{ + meta: + "description" -> {"ENT-4681" } + string => "Test that bundle scoped classes defined from namespaced + agent bundles are properly scoped. Specifically from the default + namespace."; + + "test_soft_fail" + string => "any", + meta => { "ENT-4681" }; + + methods: + "test:example"; +} +bundle agent check +{ + classes: + # Expectation, default:TRIGGERED is NOT defined + "default_TRIGGERED_OK" + expression => "!default:TRIGGERED"; + + # Expectation, test:TRIGGERED is NOT defined (not visable since it's a bundle scoped class) + "namespaced_TRIGGERED_OK" + expression => "!test:TRIGGERED"; + + # Expectation, TRIGGERED is NOT defined, we are now in the default namespace, the TRIGGERED class was defined in the test namespace with a bundle scope + "TRIGGERED_OK" + expression => "!TRIGGERED"; + + + "pass" and => { + "default_TRIGGERED_OK", + "namespaced_TRIGGERED_OK", + "TRIGGERED_OK", + }; + reports: + pass:: + "Pass $(this.promise_filename)"; + !pass:: + "FAIL $(this.promise_filename)"; + !default_TRIGGERED_OK:: + "Expected 'default:TRIGGERED' to not be seen as defined, but expression '!default:TRIGGERED' resulted in true"; + !namespaced_TRIGGERED_OK:: + "Expected 'test:TRIGGERED' to NOT be seen as defined, but expression '!test:TRIGGERED' resulted in false"; + !TRIGGERED_OK:: + "Expected 'TRIGGERED' to NOT be seen as defined, but expression 'TRIGGERED' resulted in true even without specify the test namespace"; + +} +bundle agent __main__ +{ + + methods: + "test:example"; + "check"; +} diff --git a/tests/acceptance/00_basics/04_bundles/agent-bundle-class-bundle-scope-namespace-perspective.cf b/tests/acceptance/00_basics/04_bundles/agent-bundle-class-bundle-scope-namespace-perspective.cf new file mode 100755 index 0000000000..a62f4ce532 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/agent-bundle-class-bundle-scope-namespace-perspective.cf @@ -0,0 +1,80 @@ +#!/var/cfengine/bin/cf-agent -f- +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { + default("$(this.promise_filename)") }; + version => "1.0"; +} +body file control { namespace => "test"; } +bundle agent example +{ + classes: + "TRIGGERED" + expression => "any", + scope => "bundle"; # Namespace scoped classes are visible from other bundles +} +bundle agent check +{ + classes: + # Expectation, default:TRIGGERED is NOT defined + "default_TRIGGERED_OK" + expression => "!default:TRIGGERED"; + + # Expectation, test:TRIGGERED is NOT defined (not visable since it's a bundle scoped class) + "namespaced_TRIGGERED_OK" + expression => "!test:TRIGGERED"; + + # Expectation, TRIGGERED is NOT defined, we are now in the test namespace where the TRIGGERED class was defined, but it was bundle scoped, so not visable from another bundle + "TRIGGERED_OK" + expression => "!TRIGGERED"; + + + "pass" and => { + "default_TRIGGERED_OK", + "namespaced_TRIGGERED_OK", + "TRIGGERED_OK", + }; + reports: + pass:: + "Pass $(this.promise_filename)"; + !pass:: + "FAIL $(this.promise_filename)"; + !default_TRIGGERED_OK:: + "Expected 'default:TRIGGERED' to not be seen as defined, but expression '!default:TRIGGERED' resulted in true"; + !namespaced_TRIGGERED_OK:: + "Expected 'test:TRIGGERED' to NOT be seen as defined, but expression '!test:TRIGGERED' resulted in false"; + !TRIGGERED_OK:: + "Expected 'TRIGGERED' to NOT be seen as defined, but expression '!TRIGGERED' resulted in false"; + +} +body file control { namespace => "default"; } +bundle agent test +{ + meta: + "description" -> {"ENT-4681" } + + string => "Test that bundle scoped classes defined from namespaced + agent bundles are properly scoped. Specifically from the perspective of + the non-default namespace itself."; + + + "test_soft_fail" + string => "any", + meta => { "ENT-4681" }; + + methods: + "test:example"; +} +bundle agent check +{ + methods: + "test:check"; +} +bundle agent __main__ +{ + + methods: + "test"; + "check"; +} diff --git a/tests/acceptance/00_basics/04_bundles/agent-bundle-namespaced-class-default-perspective.cf b/tests/acceptance/00_basics/04_bundles/agent-bundle-namespaced-class-default-perspective.cf new file mode 100755 index 0000000000..a75dd14b86 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/agent-bundle-namespaced-class-default-perspective.cf @@ -0,0 +1,83 @@ +#!/var/cfengine/bin/cf-agent -f- +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { + default("$(this.promise_filename)") }; + version => "1.0"; +} +body file control { namespace => "test"; } +bundle agent example +{ + classes: + "TRIGGERED" + expression => "any", + scope => "namespace"; # Namespace scoped classes are visible from other bundles + + reports: + TRIGGERED:: + "TRIGGERED is defined within $(this.namespace).$(this.bundle)"; + + default:TRIGGERED:: + "default:TRIGGERED is defined within $(this.namespace).$(this.bundle)"; + + test:TRIGGERED:: + "$(this.namespace):TRIGGERED is defined within $(this.namespace).$(this.bundle)"; +} +body file control { namespace => "default"; } +bundle agent test +{ + meta: + "description" -> {"ENT-4681" } + string => "Test that namespace scoped classes defined from namespaced + agent bundles are properly scoped. Specifically from the default + namespace."; + + "test_soft_fail" + string => "any", + meta => { "ENT-4681" }; + + methods: + "test:example"; +} +bundle agent check +{ + classes: + # Expectation, default:TRIGGERED is NOT defined + "default_TRIGGERED_OK" + expression => "!default:TRIGGERED"; + + # Expectation, test:TRIGGERED is defined (visable since it's a namespace scoped class) + "namespaced_TRIGGERED_OK" + expression => "test:TRIGGERED"; + + # Expectation, TRIGGERED is NOT defined, we are now in the default namespace, the TRIGGERED class was defined in the test namespace and should not be seen without explicitly using it's namespace + "TRIGGERED_OK" + expression => "!TRIGGERED"; + + + "pass" and => { + "default_TRIGGERED_OK", + "namespaced_TRIGGERED_OK", + "TRIGGERED_OK", + }; + reports: + pass:: + "Pass $(this.promise_filename)"; + !pass:: + "FAIL $(this.promise_filename)"; + !default_TRIGGERED_OK:: + "Expected 'default:TRIGGERED' to not be seen as defined, but expression '!default:TRIGGERED' resulted in true"; + !namespaced_TRIGGERED_OK:: + "Expected 'test:TRIGGERED' to be seen as defined, but expression 'test:TRIGGERED' resulted in false"; + !TRIGGERED_OK:: + "Expected 'TRIGGERED' to NOT be seen as defined, but expression 'TRIGGERED' resulted in true even without specify the test namespace"; + +} +bundle agent __main__ +{ + + methods: + "test:example"; + "check"; +} diff --git a/tests/acceptance/00_basics/04_bundles/agent-bundle-namespaced-class-namespace-perspective.cf b/tests/acceptance/00_basics/04_bundles/agent-bundle-namespaced-class-namespace-perspective.cf new file mode 100755 index 0000000000..eacf60be5e --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/agent-bundle-namespaced-class-namespace-perspective.cf @@ -0,0 +1,80 @@ +#!/var/cfengine/bin/cf-agent -f- +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { + default("$(this.promise_filename)") }; + version => "1.0"; +} +body file control { namespace => "test"; } +bundle agent example +{ + classes: + "TRIGGERED" + expression => "any", + scope => "namespace"; # Namespace scoped classes are visible from other bundles +} +bundle agent check +{ + classes: + # Expectation, default:TRIGGERED is NOT defined + "default_TRIGGERED_OK" + expression => "!default:TRIGGERED"; + + # Expectation, test:TRIGGERED is defined (visable since it's a namespace scoped class) + "namespaced_TRIGGERED_OK" + expression => "test:TRIGGERED"; + + # Expectation, TRIGGERED is defined, we are now in the test namespace where the TRIGGERED class was defined. We should be able to reference classes defined in our current namespace without being explicit + "TRIGGERED_OK" + expression => "TRIGGERED"; + + + "pass" and => { + "default_TRIGGERED_OK", + "namespaced_TRIGGERED_OK", + "TRIGGERED_OK", + }; + reports: + pass:: + "Pass $(this.promise_filename)"; + !pass:: + "FAIL $(this.promise_filename)"; + !default_TRIGGERED_OK:: + "Expected 'default:TRIGGERED' to not be seen as defined, but expression '!default:TRIGGERED' resulted in true"; + !namespaced_TRIGGERED_OK:: + "Expected 'test:TRIGGERED' to be seen as defined, but expression 'test:TRIGGERED' resulted in false"; + !TRIGGERED_OK:: + "Expected 'TRIGGERED' to be seen as defined, but expression 'TRIGGERED' resulted in false"; + +} +body file control { namespace => "default"; } +bundle agent test +{ + meta: + "description" -> {"ENT-4681" } + + string => "Test that namespace scoped classes defined from namespaced + agent bundles are properly scoped. Specifically from the perspective of + the non-default namespace itself."; + + + "test_soft_fail" + string => "any", + meta => { "ENT-4681" }; + + methods: + "test:example"; +} +bundle agent check +{ + methods: + "test:check"; +} +bundle agent __main__ +{ + + methods: + "test"; + "check"; +} diff --git a/tests/acceptance/00_basics/04_bundles/bundle-iteration.cf b/tests/acceptance/00_basics/04_bundles/bundle-iteration.cf new file mode 100644 index 0000000000..44f621294e --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/bundle-iteration.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { + default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "bundles" slist => { "a", "b" }; + "values" slist => { "x", "y" }; + + methods: + "b" usebundle => $(bundles)("z"); # runs 2x + "bv" usebundle => $(bundles)($(values)); # runs 4x +} + +bundle agent a(var) +{ + classes: + "a_$(var)" expression => "any", scope => "namespace"; +} + +bundle agent b(var) +{ + classes: + "b_$(var)" expression => "any", scope => "namespace"; +} + +bundle agent check +{ + classes: + "ok" and => { "a_x", "b_x", "a_y", "b_y", "a_z", "b_z" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/call_namespaced_bundle_from_namespace_without_specifying_namespace.cf b/tests/acceptance/00_basics/04_bundles/call_namespaced_bundle_from_namespace_without_specifying_namespace.cf new file mode 100644 index 0000000000..9f373fdef1 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/call_namespaced_bundle_from_namespace_without_specifying_namespace.cf @@ -0,0 +1,46 @@ +# Test that namespaced bundles that call other namespaced bundles can do so +# without specifying their own namespace +# Redmine:4289 (https://cfengine.com/dev/issues/4289) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "agent_output" + string => execresult("$(sys.cf_agent) -KIf $(this.promise_filename).sub -b testing:one", "noshell"); + +} + +bundle agent check +{ + classes: + "OK_non_specified_namespace" expression => regcmp(".*OKI DOKI.*", $(test.agent_output)); + "OK_specified_namespace" expression => regcmp(".*artichokie.*", $(test.agent_output)); + + "ok" and => { + "OK_non_specified_namespace", + "OK_specified_namespace", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "agent output: +=============================================================================== +$(test.agent_output) +==============================================================================="; +} diff --git a/tests/acceptance/00_basics/04_bundles/call_namespaced_bundle_from_namespace_without_specifying_namespace.cf.sub b/tests/acceptance/00_basics/04_bundles/call_namespaced_bundle_from_namespace_without_specifying_namespace.cf.sub new file mode 100644 index 0000000000..4c88f396aa --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/call_namespaced_bundle_from_namespace_without_specifying_namespace.cf.sub @@ -0,0 +1,24 @@ +body file control +{ + namespace => "testing"; +} + +bundle agent one +{ + methods: + "call namespaced bundle from namespace" + usebundle => two; + "call namespaced bundle from namespace and specify the namespace" + usebundle => testing:three; +} + +bundle agent two +{ + reports: + "OKI DOKI"; +} +bundle agent three +{ + reports: + "artichokie"; +} diff --git a/tests/acceptance/00_basics/04_bundles/common-bundle-normal-ordering.cf b/tests/acceptance/00_basics/04_bundles/common-bundle-normal-ordering.cf new file mode 100644 index 0000000000..0ce685c9fc --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/common-bundle-normal-ordering.cf @@ -0,0 +1,39 @@ +# Tests that normal ordering (classes before vars) is adhered to in commmon +# bundles, leading to correct resolution regardless of policy order. + +# This check tests that +# 1. Common bundles are converged before other bundles. +# 2. Common bundles promises follow normal ordering + +body common control +{ + + bundlesequence => { check }; + version => "1.0"; +} + +bundle agent check +{ + reports: + any:: + # this was the only way I found to trigger the issue. Classifying + # on join seems to resolve the variable. + "$(this.promise_filename) $(g_stuff.two)"; +} + +# It is critical that the common bundle is declared after the agent bundles +bundle common g_stuff +{ + # promise type ordering here is critical + vars: + trigger:: + "one" string => "Pas"; + + any:: + "two" slist => { "$(one)s" }; + + classes: + "trigger" expression => "any"; + +} + diff --git a/tests/acceptance/00_basics/04_bundles/duplicate_promise_handles.cf b/tests/acceptance/00_basics/04_bundles/duplicate_promise_handles.cf new file mode 100644 index 0000000000..862346a2a2 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/duplicate_promise_handles.cf @@ -0,0 +1,47 @@ +# Test that we don't get errors about duplicate handles when using variables in +# handle name that do not expand identically +# Redmine:4682 (https://cfengine.com/dev/issues/4682) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "duplicate" + handle => "$(this.handle)_$(this.promiser)", + string => "foo"; +} + +bundle agent test +{ + vars: + "duplicate" + handle => "$(this.handle)_$(this.promiser)", + string => "bar"; + + + # Two promises with equal handle but different classes is allowed + reports: + debian:: + "this is debian" handle => "os_handle"; + redhat:: + "this is redhat" handle => "os_handle"; +} + +bundle agent check +{ + classes: + "ok" and => { "any" }, + comment => "Policy validation failing will cause us to never reach this if the test fails"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/app_based_inputs.cf b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/app_based_inputs.cf new file mode 100644 index 0000000000..557377e0da --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/app_based_inputs.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles listed in bundlesequence +# Bundle list built from array +# Bundles policy defined in separate file +# include only app files neaded based on list built from array +# Lexicly sorted bundles activated with methods promise +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output( +".*R: HEY I activated app00_zero +R: HEY I activated app_one +R: HEY I activated app_zero.*", ".*FAIL.*", $(command), $(this.promise_filename)); + +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/app_based_inputs.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/app_based_inputs.cf.sub new file mode 100644 index 0000000000..2974c40ffb --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/app_based_inputs.cf.sub @@ -0,0 +1,75 @@ +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles listed in bundlesequence +# Bundle list built from array +# Bundles policy defined in separate file +# include only app files neaded based on list built from array +# Lexicly sorted bundles activated with methods promise +body common control +# +{ + bundlesequence => { "classify", "map_role_bundles", "go" }; +} + +bundle common classify +# +{ + vars: + "variable" string => "vmware"; + + classes: + "group_one" + expression => strcmp("vmware", "vmware"), + comment => "Highest level classification, other classes depend on this."; +} + +bundle common map_role_bundles +# +{ + vars: + # Define bundles + any:: + "role[app00_zero]" + string => "$(this.promise_dirname)/apps/app00_zero.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + "role[app_zero]" + string => "$(this.promise_dirname)/apps/app_zero.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_one:: + "role[app_one]" + string => "$(this.promise_dirname)/apps/app_one.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_none:: + "role[app_two]" + string => "$(this.promise_dirname)/apps/app_two.cf.sub"; + + any:: + "inputs" slist => getvalues(role); + "bundles" slist => getindices(role); +} + +bundle agent go +# +{ + vars: + "bundles" slist => { @(map_role_bundles.bundles) }; + "sorted_bundles" slist => sort(bundles, "lex"); + + methods: + "$(sorted_bundles)" usebundle => $(sorted_bundles); + + reports: + DEBUG:: + "Should include '$(map_role_bundles.inputs)'"; +} + +body file control +{ + inputs => { @(map_role_bundles.inputs) }; +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app00_zero.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app00_zero.cf.sub new file mode 100644 index 0000000000..5ce49c2d59 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app00_zero.cf.sub @@ -0,0 +1,7 @@ +bundle agent app00_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_one.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_one.cf.sub new file mode 100644 index 0000000000..1541bb1dcf --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_one.cf.sub @@ -0,0 +1,7 @@ +bundle agent app_one +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_two.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_two.cf.sub new file mode 100644 index 0000000000..d29c043a83 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_two.cf.sub @@ -0,0 +1,7 @@ +bundle agent app_two +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_zero.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_zero.cf.sub new file mode 100644 index 0000000000..18ca015309 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/apps/app_zero.cf.sub @@ -0,0 +1,7 @@ +bundle agent app_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classes/enable_test_bundle b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classes/enable_test_bundle new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence.cf b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence.cf new file mode 100644 index 0000000000..2ef2776e0e --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles NOT listed in bundlesequence +# Classificaiton bundles defined in order used +# Bundle list built from array +# Lexicly sorted bundles activated with methods promise +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output( +".*R: HEY I activated app00_zero +R: HEY I activated app_one +R: HEY I activated app_zero.*", ".*FAIL.*", $(command), $(this.promise_filename)); + +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence.cf.sub new file mode 100644 index 0000000000..ba7a8c8b69 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence.cf.sub @@ -0,0 +1,91 @@ +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles NOT listed in bundlesequence +# Classificaiton bundles defined in order used +# Bundle list built from array +# Lexicly sorted bundles activated with methods promise +body common control +# +{ + bundlesequence => { "go" }; +} + +bundle agent go +# +{ + vars: + "bundles" slist => { @(map_role_bundles.bundles) }; + "sorted_bundles" slist => sort(bundles, "lex"); + + methods: + "$(sorted_bundles)" usebundle => $(sorted_bundles); +} + +bundle agent app_one +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle agent app_two +# +{ + reports: + "HEY I activated $(this.bundle)"; +} +bundle agent app00_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle agent app_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle common classify +# +{ + vars: + "variable" string => "vmware"; + + classes: + "group_one" + expression => strcmp("$(variable)", "vmware"), + comment => "Highest level classification, other classes depend on this."; +} + + +bundle common map_role_bundles +# +{ + vars: + # Define bundles + any:: + "role[app00_zero]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + "role[app_zero]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_one:: + "role[app_one]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_none:: + "role[app_two]" string => "/path/to_policy_file.cf"; + + any:: + "bundles" slist => getindices(role); +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence2.cf b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence2.cf new file mode 100644 index 0000000000..07b07876b5 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence2.cf @@ -0,0 +1,42 @@ +####################################################### +# +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles listed in bundlesequence +# Bundle list built from array +# Lexicly sorted bundles activated with methods promise +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output( +".*R: HEY I activated app00_zero +R: HEY I activated app_one +R: HEY I activated app_zero.*", ".*FAIL.*", $(command), $(this.promise_filename)); + +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence2.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence2.cf.sub new file mode 100644 index 0000000000..9850c34cbd --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence2.cf.sub @@ -0,0 +1,92 @@ +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles listed in bundlesequence +# Map role bundles (which uses the classify bundle) is defined BEFORE the classify bundle (linerally). +# Bundle list built from array +# Lexicly sorted bundles activated with methods promise +body common control +# +{ + bundlesequence => { "go" }; +} + +bundle agent go +# +{ + vars: + "bundles" slist => { @(map_role_bundles.bundles) }; + "sorted_bundles" slist => sort(bundles, "lex"); + + methods: + "$(sorted_bundles)" usebundle => $(sorted_bundles); +} + +bundle agent app_one +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle agent app_two +# +{ + reports: + "HEY I activated $(this.bundle)"; +} +bundle agent app00_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle agent app_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} +bundle common map_role_bundles +# +{ + vars: + # Define bundles + any:: + "role[app00_zero]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + "role[app_zero]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_one:: + "role[app_one]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_none:: + "role[app_two]" string => "/path/to_policy_file.cf"; + + any:: + "bundles" slist => getindices(role); +} + + +bundle common classify +# +{ + vars: + "variable" string => "vmware"; + + classes: + "group_one" + expression => strcmp("vmware", "vmware"), + comment => "Highest level classification, other classes depend on this."; +} + + diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence3.cf b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence3.cf new file mode 100644 index 0000000000..113fe28858 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence3.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles NOT listed in bundlesequence +# Classificaiton bundles defined in order used +# Bundle list built from array +# Lexicly sorted bundles activated from bundlesequence +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output( +".*R: HEY I activated app00_zero +R: HEY I activated app_one +R: HEY I activated app_zero.*", ".*FAIL.*", $(command), $(this.promise_filename)); + +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence3.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence3.cf.sub new file mode 100644 index 0000000000..83adfdcb1e --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/classification_bundles_not_in_sequence3.cf.sub @@ -0,0 +1,92 @@ +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles NOT listed in bundlesequence +# Classificaiton bundles defined in order used +# Classify bundle uses a variable on the RHS when defining a class +# Bundle list built from array +# Lexicly sorted bundles activated with methods promise +body common control +# +{ + bundlesequence => { "go", @(go.sorted_bundles) }; +} + +bundle agent go +# +{ + vars: + "bundles" slist => { @(map_role_bundles.bundles) }; + "sorted_bundles" slist => sort(bundles, "lex"); +} + +bundle agent app_one +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle agent app_two +# +{ + reports: + "HEY I activated $(this.bundle)"; +} +bundle agent app00_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle agent app_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle common classify +# +{ + vars: + "variable" string => "vmware"; + + classes: + "group_one" + expression => strcmp("$(variable)", "vmware"), + comment => "Highest level classification, other classes depend on this."; +} + + +bundle common map_role_bundles +# +{ + vars: + # Define bundles + any:: + "role[app00_zero]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + "role[app_zero]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_one:: + "role[app_one]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_none:: + "role[app_two]" string => "/path/to_policy_file.cf"; + + any:: + "bundles" slist => getindices(role); +} + + + diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf new file mode 100644 index 0000000000..05fe0493fb --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf @@ -0,0 +1,48 @@ +####################################################### +# +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles listed in bundlesequence +# Bundle list built from array +# Bundles policy defined in separate file +# include only app files neaded based on list built from array +# Lexicly sorted bundles activated with methods promise +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + meta: + "tags" + slist => { "redmine#6867", "zendesk#1600" }; + + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output( +".*R: HEY I activated app00_zero +R: HEY I activated app_one +R: HEY I activated app_zero.*", ".*FAIL.*", $(command), $(this.promise_filename)); + +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf.sub new file mode 100644 index 0000000000..b84b961f0f --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf.sub @@ -0,0 +1,67 @@ +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles listed in bundlesequence +# Bundle list built from array +# Bundles policy defined in separate file +# include only app files neaded based on list built from array +# Lexicly sorted bundles activated with methods promise +body common control +{ + bundlesequence => { "classify", "map_role_bundles", "go" }; +} + +bundle common classify +{ + vars: + "variable" string => "vmware"; + + classes: + "group_one" + expression => strcmp($(variable), "vmware"), + comment => "Highest level classification, other classes depend on this."; +} + +bundle common map_role_bundles +{ + vars: + # Define bundles + any:: + "role[app00_zero]" + string => "$(this.promise_dirname)/apps/app00_zero.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + "role[app_zero]" + string => "$(this.promise_dirname)/apps/app_zero.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_one:: + "role[app_one]" + string => "$(this.promise_dirname)/apps/app_one.cf.sub", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_none:: + "role[app_two]" + string => "$(this.promise_dirname)/apps/app_two.cf.sub"; + + any:: + "inputs" slist => getvalues(role); + "bundles" slist => getindices(role); +} + +bundle agent go +{ + vars: + "bundles" slist => { @(map_role_bundles.bundles) }; + "sorted_bundles" slist => sort(bundles, "lex"); + + methods: + "$(sorted_bundles)" usebundle => $(sorted_bundles); +} + +body file control +{ + inputs => { @(map_role_bundles.inputs) }; +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf new file mode 100644 index 0000000000..24a5516ed8 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf @@ -0,0 +1,33 @@ +body common control +{ + inputs => { @(inventory.inputs) }; + bundlesequence => { @(inventory.bundles) }; +} + +bundle common inventory +{ + classes: + "this_is_true" expression => "any"; + + vars: + !this_is_true:: + "inputs" slist => { }; + "bundles" slist => { "bad" }; + this_is_true.!windows:: + "inputs" slist => { "$(this.promise_filename).sub" }; + "bundles" slist => { "good" }; + windows:: + "bundles" slist => { "skip" }; +} + +bundle agent bad +{ + reports: + "$(this.promise_filename) FAIL"; +} + +bundle agent skip +{ + reports: + "$(this.promise_filename) Skip/unsupported"; +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf.sub new file mode 100644 index 0000000000..dae9f1b6aa --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf.sub @@ -0,0 +1,9 @@ +bundle agent good +{ + classes: + "dummy" expression => + regextract("(.*)\.sub", "$(this.promise_filename)", "fn"); + + reports: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/inputs/read_classes.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/inputs/read_classes.cf.sub new file mode 100644 index 0000000000..3ac574c4d1 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/inputs/read_classes.cf.sub @@ -0,0 +1,17 @@ +bundle common read_classes +{ + +vars: + linux:: + "local_classes" slist => lsdir("$(this.promise_dirname)/../classes","\w.*","false"); + +classes: + "$(local_classes)" expression => "any"; + "PASS_READ_CLASSES1" expression => "enable_test_bundle"; + +reports: + PASS_READ_CLASSES1:: + "PASS $(this.bundle) $(sys.cf_version)"; + !PASS_READ_CLASSES1:: + "FAIL $(this.bundle) $(sys.cf_version) Module Failed"; +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/inputs/test_bundle_common.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/inputs/test_bundle_common.cf.sub new file mode 100644 index 0000000000..c59c40434a --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/inputs/test_bundle_common.cf.sub @@ -0,0 +1,13 @@ +bundle common test_bundle_common +{ +classes: + "test_bundle_disabled_class" or => { "disable_test_bundle" }; + "test_bundle_supported_platform" or => { "linux.enable_test_bundle" }; + "test_bundle_supported_class" or => { "enable_test_bundle" }; + "test_bundle_enabled_class" and => { "test_bundle_supported_class", + "test_bundle_supported_platform", + "!test_bundle_disabled_class" }; +reports: + debug:: + "$(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/read_inputs.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/read_inputs.cf.sub new file mode 100644 index 0000000000..8f67d5cc54 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/read_inputs.cf.sub @@ -0,0 +1,19 @@ +bundle common prep_read_inputs +{ + vars: + linux.use_lsdir:: + "inputs" slist => lsdir("inputs","\w.*","true"); + + linux.use_lsdir_relative:: + "inputs" slist => lsdir("$(this.promise_dirname)/inputs","\w.*","true"); + + linux.no_use_lsdir:: + "inputs" slist => { "inputs/read_classes.cf.sub", "inputs/test_bundle_common.cf.sub" }; +} + +bundle common read_inputs +{ + vars: + linux:: + "inputs" slist => { @(prep_read_inputs.inputs) }; +} diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/simple.cf b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/simple.cf new file mode 100644 index 0000000000..07b07876b5 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/simple.cf @@ -0,0 +1,42 @@ +####################################################### +# +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles listed in bundlesequence +# Bundle list built from array +# Lexicly sorted bundles activated with methods promise +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output( +".*R: HEY I activated app00_zero +R: HEY I activated app_one +R: HEY I activated app_zero.*", ".*FAIL.*", $(command), $(this.promise_filename)); + +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/simple.cf.sub b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/simple.cf.sub new file mode 100644 index 0000000000..d89954ca6f --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/dynamic_bundlesequence/simple.cf.sub @@ -0,0 +1,89 @@ +# Test a simple dynamic bundlesequnece pattern +# Classification Bundles listed in bundlesequence +# Bundle list built from array +# Lexicly sorted bundles activated with methods promise +body common control +# +{ + bundlesequence => { "classify", "map_role_bundles", "go" }; +} + +bundle common classify +# +{ + vars: + "variable" string => "vmware"; + + classes: + "group_one" + expression => strcmp("$(variable)", "vmware"), + comment => "Highest level classification, other classes depend on this."; +} + +bundle common map_role_bundles +# +{ + vars: + # Define bundles + any:: + "role[app00_zero]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + + "role[app_zero]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_one:: + "role[app_one]" + string => "/path/to/policy_file.cf", + comment => + "Roles are based on groups. Roles + map to bundles. Bundles are defined in a specific file."; + group_none:: + "role[app_two]" string => "/path/to_policy_file.cf"; + + any:: + "bundles" slist => getindices(role); +} + +bundle agent go +# +{ + vars: + "bundles" slist => { @(map_role_bundles.bundles) }; + "sorted_bundles" slist => sort(bundles, "lex"); + + methods: + "$(sorted_bundles)" usebundle => $(sorted_bundles); +} + +bundle agent app_one +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle agent app_two +# +{ + reports: + "HEY I activated $(this.bundle)"; +} +bundle agent app00_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} + +bundle agent app_zero +# +{ + reports: + "HEY I activated $(this.bundle)"; +} diff --git a/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/absent_data_container_or_list_in_bundlesequence_triggers_error.x.cf b/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/absent_data_container_or_list_in_bundlesequence_triggers_error.x.cf new file mode 100644 index 0000000000..63337b58d7 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/absent_data_container_or_list_in_bundlesequence_triggers_error.x.cf @@ -0,0 +1,26 @@ +body common control +{ + bundlesequence => { def, test, check, @(def.bundlesequnece_end) }; +} + +bundle common def +{ + vars: + "v" slist => variablesmatching("default\:def.*"); + + reports: + DEBUG:: + "Def Varaiable: $(v)"; +} + +bundle agent test +{ + meta: + "description" string => "Test that a non existing data container or list triggers an undefined bundle error"; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/bundlesequence_iterate_over_data_container.cf b/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/bundlesequence_iterate_over_data_container.cf new file mode 100644 index 0000000000..02fbdad689 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/bundlesequence_iterate_over_data_container.cf @@ -0,0 +1,62 @@ +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { collect_stories_metadata, test_precheck, def, test, @(def.container), check }; +} + +bundle common def +{ + vars: + "v" slist => variablesmatching("default\:def.*"); + "m" data => mergedata( "def.missing" ); + "values" slist => getvalues(m); + "indices" slist => getindices(m); + "container" data => parsejson('[ "one", "two" ]'); +# "list" slist => { "one", "two" }; +# "container" data => mergedata(list); + + reports: + DEBUG:: + "Def Varaiable: $(v)"; + "container: $(container)"; +} + +bundle agent one +{ + classes: + # We check for this class in bundle agent check + "actuated_bundle_agent_$(this.bundle)" expression => "any", scope => "namespace"; + + reports: "one"; +} + +bundle agent two +{ + classes: + # We check for this class in bundle agent check + "actuated_bundle_agent_$(this.bundle)" expression => "any", scope => "namespace"; + + reports: "two"; +} + + +bundle agent test +{ + meta: + "description" string => "Test that bundlesequence in body common control can iterate over a data container."; + + # This was working in 3.7.4 but has regressed. + "test_soft_fail" + string => "!cfengine_3_7", + meta => { "CFE-2460"}; +} + +bundle agent check +{ + reports: + actuated_bundle_agent_one.actuated_bundle_agent_two:: + "$(this.promise_filename) Pass"; + + !(actuated_bundle_agent_one.actuated_bundle_agent_two):: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/empty_data_container_does_not_trigger_undefined_bundle.cf b/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/empty_data_container_does_not_trigger_undefined_bundle.cf new file mode 100644 index 0000000000..ef54e34b37 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/empty_data_container_does_not_trigger_undefined_bundle.cf @@ -0,0 +1,27 @@ +body common control +{ + bundlesequence => { def, test, check, @(def.bundlesequnece_end) }; +} + +bundle common def +{ + vars: + "v" slist => variablesmatching("default\:def.*"); + "bundlesequnece_end" data => parsejson('{}'); + + reports: + DEBUG:: + "Def Varaiable: $(v)"; +} + +bundle agent test +{ + meta: + "description" string => "Test that an empty data container does not trigger an undefined bundle error"; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/empty_list_does_not_trigger_undefined_bundle.cf b/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/empty_list_does_not_trigger_undefined_bundle.cf new file mode 100644 index 0000000000..f56ee61024 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/empty_list_or_datacontainer_should_not_error_bundlesequence/empty_list_does_not_trigger_undefined_bundle.cf @@ -0,0 +1,27 @@ +body common control +{ + bundlesequence => { def, test, check, @(def.bundlesequnece_end) }; +} + +bundle common def +{ + vars: + "v" slist => variablesmatching("default\:def.*"); + "bundlesequnece_end" slist => { }; + + reports: + DEBUG:: + "Def Varaiable: $(v)"; +} + +bundle agent test +{ + meta: + "description" string => "Test that an empty list does not trigger an undefined bundle error"; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/04_bundles/getindices-vars-need-secondpass.cf b/tests/acceptance/00_basics/04_bundles/getindices-vars-need-secondpass.cf new file mode 100644 index 0000000000..fa70031a49 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/getindices-vars-need-secondpass.cf @@ -0,0 +1,48 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { + default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "x" slist => { "PARAM1", "PARAM2" }; + + "conf[internal1]" string => "internalvalue1"; + "conf[internal2]" string => "internalvalue2"; + "conf[$(x)]" string => "$(x)"; + + "keys_unsorted" slist => getindices("conf"); + "keys" slist => sort(keys_unsorted, "lex"); + "keys_str" string => format("%S", keys); + + reports: + DEBUG:: + "conf[$(keys_unsorted)] = $(conf[$(keys_unsorted)])"; +} + +bundle agent check +{ + vars: + "expected" string => '{ "PARAM1", "PARAM2", "internal1", "internal2" }'; + + classes: + "ok" expression => strcmp($(test.keys_str), + $(expected)); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG.!ok:: + "Expected $(expected), actual $(test.keys_str)"; +} diff --git a/tests/acceptance/00_basics/04_bundles/main_duplicate_1.x.cf b/tests/acceptance/00_basics/04_bundles/main_duplicate_1.x.cf new file mode 100644 index 0000000000..17ccfbb894 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/main_duplicate_1.x.cf @@ -0,0 +1,11 @@ +bundle agent main +{ + reports: + "This should fail because __main__ will be renamed to main"; +} + +bundle agent __main__ +{ + reports: + "This should fail because __main__ will be renamed to main"; +} diff --git a/tests/acceptance/00_basics/04_bundles/main_duplicate_2.x.cf b/tests/acceptance/00_basics/04_bundles/main_duplicate_2.x.cf new file mode 100644 index 0000000000..18cb0e179c --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/main_duplicate_2.x.cf @@ -0,0 +1,11 @@ +bundle agent __main__ +{ + reports: + "This should fail because duplicate bundles aren't allowed"; +} + +bundle agent __main__ +{ + reports: + "This should fail because duplicate bundles aren't allowed"; +} diff --git a/tests/acceptance/00_basics/04_bundles/main_duplicate_3.x.cf b/tests/acceptance/00_basics/04_bundles/main_duplicate_3.x.cf new file mode 100644 index 0000000000..40eb09325b --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/main_duplicate_3.x.cf @@ -0,0 +1,11 @@ +bundle agent main +{ + reports: + "This should fail because duplicate bundles aren't allowed"; +} + +bundle agent main +{ + reports: + "This should fail because duplicate bundles aren't allowed"; +} diff --git a/tests/acceptance/00_basics/04_bundles/main_entry_point.cf b/tests/acceptance/00_basics/04_bundles/main_entry_point.cf new file mode 100644 index 0000000000..b56a082310 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/main_entry_point.cf @@ -0,0 +1,12 @@ +body file control +{ + inputs => { "main_entry_point.cf.sub" }; +} + +bundle agent __main__ +{ + methods: + "imported" usebundle => imported(); + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/04_bundles/main_entry_point.cf.sub b/tests/acceptance/00_basics/04_bundles/main_entry_point.cf.sub new file mode 100644 index 0000000000..a07b199159 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/main_entry_point.cf.sub @@ -0,0 +1,11 @@ +bundle agent __main__ +{ + reports: + "$(this.promise_filename) FAIL"; # This shouldn't run +} + +bundle agent imported +{ + reports: + "This file can be imported and the __main__ above will be ignored"; +} diff --git a/tests/acceptance/00_basics/04_bundles/main_rename.cf b/tests/acceptance/00_basics/04_bundles/main_rename.cf new file mode 100644 index 0000000000..b7889d5bc2 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/main_rename.cf @@ -0,0 +1,10 @@ +body common control +{ + bundlesequence => { "main" }; +} + +bundle agent __main__ +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/00_basics/04_bundles/main_rename.x.cf b/tests/acceptance/00_basics/04_bundles/main_rename.x.cf new file mode 100644 index 0000000000..d6fe863d0e --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/main_rename.x.cf @@ -0,0 +1,10 @@ +body common control +{ + bundlesequence => { "__main__" }; +} + +bundle agent __main__ +{ + reports: + "This should fail because __main__ will be renamed to main"; +} diff --git a/tests/acceptance/00_basics/04_bundles/namespaced.cf b/tests/acceptance/00_basics/04_bundles/namespaced.cf new file mode 100644 index 0000000000..6a10660840 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/namespaced.cf @@ -0,0 +1,59 @@ +# Redmine#4025 + +body common control +{ + inputs => { "../../default.cf.sub", "namespaced.cf.sub" }; + bundlesequence => { + ns1:mytest("x"), + ns2:mytest("x", "y"), + default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; +} + +bundle agent check +{ + vars: + "checks" slist => { "ns1:ns1global", "ns2:ns2global", "ns1:ns1global_from_ppkeys", "ns1:ns1global_from_ppkeys_explicit", + "ns1global", "ns2global", + "ok_ns1string", "ok_ns2string", "ok_ns2string2", + }; + + classes: + "ok_ns1string" expression => strcmp($(ns1:mytest.ns1string), $(this.promise_dirname)); + "ok_ns2string" expression => strcmp($(ns2:mytest.ns2string), $(sys.fqhost)); + "ok_ns2string2" expression => strcmp($(ns2:mytest.ns2string2), $(const.t)); + + "ok" and => { "ns1:ns1global", "ns2:ns2global", "ns1:ns1global_from_ppkeys", "ns1:ns1ppkeys", + "!ns1global", "!ns2global", + "ok_ns1string", "ok_ns2string", "ok_ns2string2", + }; + + reports: + EXTRA:: + "class $(checks) is ON" if => $(checks); + EXTRA:: + "class $(checks) is OFF" if => "!$(checks)"; + DEBUG.ns1:ns1global_from_ppkeys:: + "ns1:ns1global_from_ppkeys is on as expected"; + DEBUG.ns1:ns1global_from_ppkeys_explicit:: + "ns1:ns1global_from_ppkeys_explicit is on as expected"; + DEBUG.!ns1:ns1global_from_ppkeys:: + "ns1:ns1global_from_ppkeys is NOT on as expected"; + DEBUG.!ns1:ns1global_from_ppkeys_explicit:: + "ns1:ns1global_from_ppkeys_explicit is NOT on as expected"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/namespaced.cf.sub b/tests/acceptance/00_basics/04_bundles/namespaced.cf.sub new file mode 100644 index 0000000000..cbe0390b92 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/namespaced.cf.sub @@ -0,0 +1,49 @@ +body file control +{ + namespace => "ns1"; +} + +bundle agent mytest(a) +{ + classes: + "ns1global" expression => "any", scope => "namespace"; + + "ns1global_from_ppkeys" expression => "ns1ppkeys", scope => "namespace"; # should be defined + "ns1global_from_ppkeys_explicit" expression => "ns1:ns1ppkeys", scope => "namespace"; # prefix should not be necessary + + vars: + cfengine_3:: + "ns1string" string => $(this.promise_dirname); + + files: + "$(sys.workdir)/ppkeys/." + create => "true", + classes => default:always("ns1ppkeys"); + + reports: + EXTRA:: + "$(this.bundle): ns1string = '$(ns1string)'"; +} + +body file control +{ + namespace => "ns2"; +} + +bundle agent mytest(a,b) +{ + classes: + "ns2global" expression => "cfengine_3", scope => "namespace"; + + vars: + "ns2string" string => $(sys.fqhost); + "ns2string2" string => $(const.t); + + files: + "$(sys.workdir)/ppkeys/." + create => "true"; + + reports: + EXTRA:: + "$(this.bundle): ns2string = '$(ns2string)'"; +} diff --git a/tests/acceptance/00_basics/04_bundles/namespaced.cf.subx b/tests/acceptance/00_basics/04_bundles/namespaced.cf.subx new file mode 100644 index 0000000000..85fa68772d --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/namespaced.cf.subx @@ -0,0 +1,11 @@ +body file control +{ + namespace => "ns1"; +} + +bundle agent mytest +{ + files: + "$(sys.workdir)/ppkeys/." + create => "true"; +} diff --git a/tests/acceptance/00_basics/04_bundles/namespaced.x.cf b/tests/acceptance/00_basics/04_bundles/namespaced.x.cf new file mode 100644 index 0000000000..7aa425f68c --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/namespaced.x.cf @@ -0,0 +1,29 @@ +# Redmine#4025: should fail + +body common control +{ + inputs => { "../../default.cf.sub", "namespaced.cf.subx" }; + bundlesequence => { mytest, + default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + classes: + "ok" and => { "any" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/non_default_def.cf b/tests/acceptance/00_basics/04_bundles/non_default_def.cf new file mode 100644 index 0000000000..59f99b5e41 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/non_default_def.cf @@ -0,0 +1,45 @@ +# Test that 'def' bundle in a non-default namespace works + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +body file control +{ + namespace => "my_ns"; +} + +bundle common def +{ + vars: + "some_var" string => "some value"; +} + +body file control +{ + namespace => "default"; +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(my_ns:def.some_var)", "some value"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok.DEBUG:: + "my_ns:def.some_var: $(my_ns:def.some_var)"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/passing-vars.cf b/tests/acceptance/00_basics/04_bundles/passing-vars.cf new file mode 100644 index 0000000000..304d6afd01 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/passing-vars.cf @@ -0,0 +1,94 @@ +# Test that variables can be passed across bundles and namespaces + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "x" string => "datax"; + "y" slist => { "datay1", "datay2" }; + "z" data => parsejson('{ "hello": "there" }'); + + meta: + "test_skip_needs_work" string => "windows"; + + methods: + "x" usebundle => see_scalar($(x)); + "y" usebundle => see_slist(@(test.y)); + "z" usebundle => see_data(@(z)); +} + +bundle agent see_scalar(svar) +{ + classes: + "saw_scalar" scope => "namespace", + expression => strcmp($(svar), "datax"); + + reports: + DEBUG:: + "The passed-in string was $(svar)"; +} + +bundle agent see_slist(lvar) +{ + vars: + "concat" string => join(",", lvar); + + classes: + "saw_slist" scope => "namespace", + expression => strcmp($(concat), "datay1,datay2"); + + reports: + DEBUG:: + "The passed-in slist was $(lvar)"; +} + +bundle agent see_data(cvar_passed) +{ + vars: + "concat_passed" string => storejson(cvar_passed); + + "cvar_local" data => parsejson('{ "hello": "there" }'); + "concat_local" string => storejson(cvar_local); + + # generates a warning about "can't find container named cvar_passed" + "dummy" data => mergedata(cvar_local, cvar_passed); + + classes: + "saw_data" scope => "namespace", + expression => strcmp($(concat_passed), '{ + "hello": "there" +}'); + + reports: + DEBUG:: + "The passed-in container as string was $(concat_passed)"; + "The local container as string was $(concat_local)"; +} + +bundle agent check +{ + classes: + "ok" and => { "saw_scalar", "saw_slist", "saw_data" }; + + reports: + DEBUG.!saw_scalar:: + "The scalar passing didn't work"; + DEBUG.!saw_slist:: + "The slist passing didn't work"; + DEBUG.!saw_data:: + "The data passing didn't work"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/unless.cf b/tests/acceptance/00_basics/04_bundles/unless.cf new file mode 100644 index 0000000000..aab8111c60 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/unless.cf @@ -0,0 +1,271 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent test +{ + + meta: + + "description" -> { "CFE-2698" } + string => "This test ensures changes to unless are compatible with previous behavior"; + + vars: + + any:: + + # Simple expressions - unless is equivalent to if not: + "defined_variable_unless" + string => "value", + unless => "!any"; + + "defined_variable_if_not" + string => "value", + if => not("!any"); + + "skipped_variable_unless" + string => "value", + unless => "any"; + + "skipped_variable_if_not" + string => "value", + if => not("any"); + + # Function calls: + "defined_variable_function_call_unless" + string => "value", + unless => strcmp("a", "b"); + + "defined_variable_function_call_if_not" + string => "value", + if => not(strcmp("a", "b")); + + "skipped_variable_function_call_unless" + string => "value", + unless => strcmp("a", "a"); + + "skipped_variable_function_call_if_not" + string => "value", + if => not(strcmp("a", "a")); + + # If and unless precedence (skipping takes precedence): + "skipped_variable_precedence_if" + string => "value", + if => "!any", # Causes the promise to be skipped + unless => "!any"; + + "skipped_variable_precedence_unless" + string => "value", + if => "any", + unless => "any"; # Causes the promise to be skipped + + "skipped_variable_precedence_unexpanded_variable_if" + string => "value", + if => "$(undefined_var)", # Causes the promise to be skipped + unless => "$(undefined_var)"; + + "skipped_variable_precedence_unresolved_function_call_if" + string => "value", + if => not("$(undefined_var)"), # Causes the promise to be skipped + unless => not("$(undefined_var)"); + + # Edge case - unresolved function calls: + "skipped_variable_unresolved_function_call_if" + string => "value", + if => strcmp("$(no_such_var)", "$(no_such_var)"); + + "defined_variable_unresolved_function_call_unless" + string => "value", + unless => strcmp("$(no_such_var)", "$(no_such_var)"); + + # Edge case - this shows the difference between unless and if not: + "defined_variable_unresolved_variable_unless" + string => "value", + unless => "$(no_such_var)"; + + "skipped_variable_unresolved_variable_if_not" + string => "value", + if => not("$(no_such_var)"); + + no_such_class:: + + # Let's just test that unless doesn't bypass class guards: + "skipped_variable_class_guard_unless" + string => "value", + unless => "!any"; + + "skipped_variable_class_guard_if_not" + string => "value", + if => not("!any"); + + classes: + + any:: + + # Simple expressions - unless is equivalent to if not: + "defined_class_unless" + expression => "any", + scope => "namespace", + unless => "!any"; + + "defined_class_if_not" + expression => "any", + scope => "namespace", + if => not("!any"); + + "skipped_class_unless" + expression => "any", + scope => "namespace", + unless => "any"; + + "skipped_class_if_not" + expression => "any", + scope => "namespace", + if => not("any"); + + # Function calls: + "defined_class_function_call_unless" + expression => "any", + scope => "namespace", + unless => strcmp("a", "b"); + + "defined_class_function_call_if_not" + expression => "any", + scope => "namespace", + if => not(strcmp("a", "b")); + + "skipped_class_function_call_unless" + expression => "any", + scope => "namespace", + unless => strcmp("a", "a"); + + "skipped_class_function_call_if_not" + expression => "any", + scope => "namespace", + if => not(strcmp("a", "a")); + + # If and unless precedence (skipping takes precedence): + "skipped_class_precedence_if" + expression => "any", + scope => "namespace", + if => "!any", # Causes the promise to be skipped + unless => "!any"; + + "skipped_class_precedence_unless" + expression => "any", + scope => "namespace", + if => "any", + unless => "any"; # Causes the promise to be skipped + + "skipped_class_precedence_unexpanded_variable_if" + expression => "any", + scope => "namespace", + if => "$(undefined_var)", # Causes the promise to be skipped + unless => "$(undefined_var)"; + + "skipped_class_precedence_unresolved_function_call_if" + expression => "any", + scope => "namespace", + if => not("$(undefined_var)"), # Causes the promise to be skipped + unless => not("$(undefined_var)"); + + # Edge case - unresolved function calls: + "skipped_class_unresolved_function_call_if" + expression => "any", + scope => "namespace", + if => strcmp("$(no_such_var)", "$(no_such_var)"); + + "defined_class_unresolved_function_call_unless" + expression => "any", + scope => "namespace", + unless => strcmp("$(no_such_var)", "$(no_such_var)"); + + # Edge case - this shows the difference between unless and if not: + "defined_class_unresolved_variable_unless" + expression => "any", + scope => "namespace", + unless => "$(no_such_var)"; + + "skipped_class_unresolved_variable_if_not" + expression => "any", + scope => "namespace", + if => not("$(no_such_var)"); + + no_such_class:: + + # Let's just test that if/unless doesn't bypass class guards: + "skipped_class_class_guard_unless" + expression => "any", + scope => "namespace", + unless => "!any"; + + "skipped_class_class_guard_if_not" + expression => "any", + scope => "namespace", + if => not("!any"); + +} + +bundle agent check +{ + classes: + + any:: + + "positives" # Fail if any of these are not defined: + and => { + isvariable("test.defined_variable_unless"), + isvariable("test.defined_variable_if_not"), + isvariable("test.defined_variable_function_call_unless"), + isvariable("test.defined_variable_function_call_if_not"), + isvariable("test.defined_variable_unresolved_function_call_unless"), + isvariable("test.defined_variable_unresolved_variable_unless"), + "defined_class_unless", + "defined_class_if_not", + "defined_class_function_call_unless", + "defined_class_function_call_if_not", + "defined_class_unresolved_function_call_unless", + "defined_class_unresolved_variable_unless", + }; + + "negatives" # Fail if any of these are defined: + or => { + isvariable("test.skipped_variable_unless"), + isvariable("test.skipped_variable_if_not"), + isvariable("test.skipped_variable_function_call_unless"), + isvariable("test.skipped_variable_function_call_if_not"), + isvariable("test.skipped_variable_precedence_if"), + isvariable("test.skipped_variable_precedence_unless"), + isvariable("test.skipped_variable_precedence_unexpanded_variable_if"), + isvariable("test.skipped_variable_precedence_unresolved_function_call_if"), + isvariable("test.skipped_variable_unresolved_function_call_if"), + isvariable("test.skipped_variable_unresolved_variable_if_not"), + isvariable("test.skipped_variable_class_guard_unless"), + isvariable("test.skipped_variable_class_guard_if_not"), + "skipped_class_unless", + "skipped_class_if_not", + "skipped_class_function_call_unless", + "skipped_class_function_call_if_not", + "skipped_class_precedence_if", + "skipped_class_precedence_unless", + "skipped_class_precedence_unexpanded_variable_if", + "skipped_class_precedence_unresolved_function_call_if", + "skipped_class_unresolved_function_call_if", + "skipped_class_unresolved_variable_if_not", + "skipped_class_class_guard_unless", + "skipped_class_class_guard_if_not", + }; + + reports: + + !positives:: + "Some classes/vars were unexpectedly skipped"; + negatives:: + "Some classes/vars were unexpectedly evaluated"; + positives.!negatives:: + "$(this.promise_filename) Pass"; + !positives|negatives:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/unless_vars_order.cf b/tests/acceptance/00_basics/04_bundles/unless_vars_order.cf new file mode 100644 index 0000000000..a8b6ca555b --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/unless_vars_order.cf @@ -0,0 +1,41 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent test +{ + meta: + any:: + "description" -> { "CFE-2698" } + string => "Order of vars promises shouldn't matter"; + + vars: + any:: + "target_variable" + string => "value"; + + "vars_promise_which_should_be_skipped" + string => "value", + unless => isvariable( $(variable_defined_later) ); + + "variable_defined_later" + string => "target_variable"; +} + +bundle agent check +{ + classes: + any:: + "not_ok" + or => { + isvariable("test.vars_promise_which_should_be_skipped"), + }; + + reports: + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/04_bundles/unsupported_promise_types_in_common.x.cf b/tests/acceptance/00_basics/04_bundles/unsupported_promise_types_in_common.x.cf new file mode 100644 index 0000000000..eec961e8f2 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/unsupported_promise_types_in_common.x.cf @@ -0,0 +1,26 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common test +{ + meta: + "description" -> { "CFE-3672" } + string => "Test that cf-promises errors for incompatible promise types + in common bundle and doesnt crash."; + + methods: + "some_report" usebundle => reporter("123"); + + files: + "some_file"; +} + +bundle agent reporter(x) +{ + reports: + "${x}"; +} + diff --git a/tests/acceptance/00_basics/04_bundles/wrong-bundle-type.cf b/tests/acceptance/00_basics/04_bundles/wrong-bundle-type.cf new file mode 100644 index 0000000000..4015e75941 --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/wrong-bundle-type.cf @@ -0,0 +1,35 @@ +# Test that wrong bundle type is detected (Redmine #XXXX) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + vars: + "subout" string => execresult("${sys.cf_agent} -Kf ${this.promise_filename}.sub", "noshell"); +} + +bundle agent check +{ + classes: + "ok" expression => regcmp(".*Unknown bundle type.*", "${test.subout}"); + + reports: + DEBUG:: + "$(test.subout)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/04_bundles/wrong-bundle-type.cf.sub b/tests/acceptance/00_basics/04_bundles/wrong-bundle-type.cf.sub new file mode 100644 index 0000000000..a49f61633c --- /dev/null +++ b/tests/acceptance/00_basics/04_bundles/wrong-bundle-type.cf.sub @@ -0,0 +1,10 @@ +body common control +{ + bundlesequence => { "test" }; +} + +bundle wrongwrongwrong test +{ +vars: + "dummy" string => "dummy"; +} diff --git a/tests/acceptance/00_basics/05_licenses/001.cf b/tests/acceptance/00_basics/05_licenses/001.cf new file mode 100644 index 0000000000..58cbe71ecb --- /dev/null +++ b/tests/acceptance/00_basics/05_licenses/001.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test that we do not complain about license. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "agent" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO 2>&1", "useshell"); +} + +####################################################### + +bundle agent check +{ + classes: + "fail" expression => regcmp(".*Your configuration promises no host_licenses_paid in common control.*", $(test.agent)); + + reports: + DEBUG:: + "This should only pass if $(this.promise_filename).sub does not output anything about license"; + "Output from $(this.promise_filename).sub: $(test.agent)"; + !fail:: + "$(this.promise_filename) Pass"; + fail:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/00_basics/05_licenses/001.cf.sub b/tests/acceptance/00_basics/05_licenses/001.cf.sub new file mode 100644 index 0000000000..7e90ec7914 --- /dev/null +++ b/tests/acceptance/00_basics/05_licenses/001.cf.sub @@ -0,0 +1,38 @@ +####################################################### +# +# Test that we do not complain about license. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + host_licenses_paid => "0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; +} + diff --git a/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf b/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf new file mode 100644 index 0000000000..3007dfcc17 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf @@ -0,0 +1,59 @@ +# basic test of the host_specific.json functionality: 'vars' object +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + +} + +####################################################### + +bundle agent test +{ + vars: + "result" + data => execresult_as_data("$(sys.cf_agent) -Kf $(this.promise_filename).sub", + "noshell", "both"), + depends_on => { "prepare_host_specific_data" }; # ensure the sub-agent execution doesn't happen too early +} + +####################################################### + +bundle agent check +{ + classes: + "exit_code_ok" expression => strcmp("$(test.result[exit_code])", "0"); + "output_ok" expression => strcmp("$(test.result[output])", +'R: 4hfcattz2607yfksllzpf73eg7nmqprl +R: { "496btq4wxqs6rf07anbx95cj74ftdyhy" } +R: {"key":"tiyl4z5nybvlp8npd2faso4ctxhknpvk"}'); + + "ok" and => {"exit_code_ok", "output_ok"}; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + !ok.DEBUG:: + "exit code: $(test.result[exit_code])"; + "output: $(test.result[output])"; +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf.json b/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf.json new file mode 100644 index 0000000000..ee780c1e37 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf.json @@ -0,0 +1,7 @@ +{ + "vars" : { + "test_variable": "4hfcattz2607yfksllzpf73eg7nmqprl", + "scope1.test_variable": ["496btq4wxqs6rf07anbx95cj74ftdyhy"], + "ns1:scope1.test_variable": { "key" : "tiyl4z5nybvlp8npd2faso4ctxhknpvk" } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf.sub b/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf.sub new file mode 100644 index 0000000000..324cd1204e --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/01-vars.cf.sub @@ -0,0 +1,6 @@ +bundle agent __main__ { + reports: + "$(data:variables.test_variable)"; + "$(with)" with => format("%S", "data:scope1.test_variable"); + "$(with)" with => format("%S", "ns1:scope1.test_variable"); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf b/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf new file mode 100644 index 0000000000..9ad8edcda1 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf @@ -0,0 +1,58 @@ +# basic test of the host_specific.json functionality: 'variables' object +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + +} + +####################################################### + +bundle agent test +{ + vars: + "result" + data => execresult_as_data("$(sys.cf_agent) -Kf $(this.promise_filename).sub", + "noshell", "both"), + depends_on => { "prepare_host_specific_data" }; # ensure the sub-agent execution doesn't happen too early +} + +####################################################### + +bundle agent check +{ + classes: + "exit_code_ok" expression => strcmp("$(test.result[exit_code])", "0"); + "output_ok" expression => strcmp("$(test.result[output])", +'R: 4hfcattz2607yfksllzpf73eg7nmqprl +R: { "496btq4wxqs6rf07anbx95cj74ftdyhy" }'); + + "ok" and => {"exit_code_ok", "output_ok"}; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + !ok.DEBUG:: + "exit code: $(test.result[exit_code])"; + "output: $(test.result[output])"; +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf.json b/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf.json new file mode 100644 index 0000000000..14ba3b5e36 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf.json @@ -0,0 +1,6 @@ +{ + "variables" : { + "test_variable": "4hfcattz2607yfksllzpf73eg7nmqprl", + "scope1.test_variable": ["496btq4wxqs6rf07anbx95cj74ftdyhy"] + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf.sub b/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf.sub new file mode 100644 index 0000000000..bf78ed923b --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/02-variables.cf.sub @@ -0,0 +1,5 @@ +bundle agent __main__ { + reports: + "$(data:variables.test_variable)"; + "$(with)" with => format("%S", "data:scope1.test_variable"); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf b/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf new file mode 100644 index 0000000000..3bd5590e4c --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf @@ -0,0 +1,59 @@ +# basic test of the host_specific.json functionality: 'variables' object with the 'value' field +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + +} + +####################################################### + +bundle agent test +{ + vars: + "result" + data => execresult_as_data("$(sys.cf_agent) -Kf $(this.promise_filename).sub", + "noshell", "both"), + depends_on => { "prepare_host_specific_data" }; # ensure the sub-agent execution doesn't happen too early +} + +####################################################### + +bundle agent check +{ + classes: + "exit_code_ok" expression => strcmp("$(test.result[exit_code])", "0"); + "output_ok" expression => strcmp("$(test.result[output])", +'R: 4hfcattz2607yfksllzpf73eg7nmqprl +R: { "496btq4wxqs6rf07anbx95cj74ftdyhy" } +R: {"key":"tiyl4z5nybvlp8npd2faso4ctxhknpvk"}'); + + "ok" and => {"exit_code_ok", "output_ok"}; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + !ok.DEBUG:: + "exit code: $(test.result[exit_code])"; + "output: $(test.result[output])"; +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf.json b/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf.json new file mode 100644 index 0000000000..e5a9b89678 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf.json @@ -0,0 +1,7 @@ +{ + "variables" : { + "test_variable": { "value": "4hfcattz2607yfksllzpf73eg7nmqprl" }, + "scope1.test_variable": { "value": ["496btq4wxqs6rf07anbx95cj74ftdyhy"] }, + "ns1:scope1.test_variable": { "value": { "key" : "tiyl4z5nybvlp8npd2faso4ctxhknpvk" } } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf.sub b/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf.sub new file mode 100644 index 0000000000..324cd1204e --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/03-variables-value.cf.sub @@ -0,0 +1,6 @@ +bundle agent __main__ { + reports: + "$(data:variables.test_variable)"; + "$(with)" with => format("%S", "data:scope1.test_variable"); + "$(with)" with => format("%S", "ns1:scope1.test_variable"); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf b/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf new file mode 100644 index 0000000000..9be75a4c28 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf @@ -0,0 +1,64 @@ +# basic test of the host_specific.json functionality: 'classes' object +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + +} + +####################################################### + +bundle agent test +{ + vars: + "result" + data => execresult_as_data("$(sys.cf_agent) -Kf $(this.promise_filename).sub", + "noshell", "both"), + depends_on => { "prepare_host_specific_data" }; # ensure the sub-agent execution doesn't happen too early +} + +####################################################### + +bundle agent check +{ + classes: + "exit_code_ok" expression => strcmp("$(test.result[exit_code])", "0"); + "output_ok" expression => strcmp("$(test.result[output])", +'R: test_class1 defined as expected +R: test_class2 defined as expected +R: test_class3 defined as expected +R: test_class4 defined as expected +R: test_class5 defined as expected +R: test_class6 defined as expected +R: test_class7 defined as expected +R: test_class8 defined as expected'); + + "ok" and => {"exit_code_ok", "output_ok"}; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + !ok.DEBUG:: + "exit code: $(test.result[exit_code])"; + "output: $(test.result[output])"; +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf.json b/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf.json new file mode 100644 index 0000000000..96efb56d64 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf.json @@ -0,0 +1,12 @@ +{ + "classes" : { + "test_class1": "any::", + "test_class2": ["any::"], + "ns1:test_class3": "any::", + "ns2:test_class4" : { "class_expressions": ["any::"] }, + "ns2:test_class5" : { "regular_expressions": ["any"] }, + "ns3:test_class6" : { "class_expressions": [] }, + "ns3:test_class7" : { "regular_expressions": [] }, + "test_class8" : {} + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf.sub b/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf.sub new file mode 100644 index 0000000000..b5d2aad62d --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/04-classes.cf.sub @@ -0,0 +1,19 @@ +bundle agent __main__ { + reports: + data:test_class1:: + "test_class1 defined as expected"; + data:test_class2:: + "test_class2 defined as expected"; + ns1:test_class3:: + "test_class3 defined as expected"; + ns2:test_class4:: + "test_class4 defined as expected"; + ns2:test_class5:: + "test_class5 defined as expected"; + ns3:test_class6:: + "test_class6 defined as expected"; + ns3:test_class7:: + "test_class7 defined as expected"; + data:test_class8:: + "test_class8 defined as expected"; +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/05-variables-tag-comment.cf b/tests/acceptance/00_basics/06_host_specific_data/05-variables-tag-comment.cf new file mode 100644 index 0000000000..4e420abff9 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/05-variables-tag-comment.cf @@ -0,0 +1,38 @@ +# basic test of the host_specific.json functionality: 'variables' object with a tag and a comment +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + + "empty_promises_cf" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf | $(G.grep) test_variable"; + + methods: + "" usebundle => dcs_passif_output("data:variables.test_variable\s+4hfcattz2607yfksllzpf73eg7nmqprl\s+test_tag,source=cmdb\s+comment1", + "", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/05-variables-tag-comment.cf.json b/tests/acceptance/00_basics/06_host_specific_data/05-variables-tag-comment.cf.json new file mode 100644 index 0000000000..7315c83e7e --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/05-variables-tag-comment.cf.json @@ -0,0 +1,9 @@ +{ + "variables" : { + "test_variable": { + "value" : "4hfcattz2607yfksllzpf73eg7nmqprl", + "comment" : "comment1", + "tags" : ["test_tag"] + } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/06-classes-tag-comment.cf b/tests/acceptance/00_basics/06_host_specific_data/06-classes-tag-comment.cf new file mode 100644 index 0000000000..3b63e6e123 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/06-classes-tag-comment.cf @@ -0,0 +1,38 @@ +# basic test of the host_specific.json functionality: 'classes' object with a tag and a comment +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + + "empty_promises_cf" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf|$(G.grep) test_class"; + + methods: + "" usebundle => dcs_passif_output("ns1:test_class\s+test_tag,source=cmdb\s+comment1.*ns2:test_class2\s+test_tag2,source=cmdb\s+comment2", + "", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/06-classes-tag-comment.cf.json b/tests/acceptance/00_basics/06_host_specific_data/06-classes-tag-comment.cf.json new file mode 100644 index 0000000000..732144be97 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/06-classes-tag-comment.cf.json @@ -0,0 +1,13 @@ +{ + "classes" : { + "ns1:test_class": { + "class_expressions" : [], + "tags": ["test_tag"], + "comment" : "comment1" + }, + "ns2:test_class2": { + "tags": ["test_tag2"], + "comment" : "comment2" + } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/07-invalid-data.cf b/tests/acceptance/00_basics/06_host_specific_data/07-invalid-data.cf new file mode 100644 index 0000000000..065eb58bbe --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/07-invalid-data.cf @@ -0,0 +1,38 @@ +# basic test of the host_specific.json functionality: 'classes' object with a tag and a comment +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + + "empty_promises_cf" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf"; + + methods: + "" usebundle => dcs_passif_output("\s*warning: Invalid key 'clses'.*\s+ns1:test_class\s+.*", + "", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/07-invalid-data.cf.json b/tests/acceptance/00_basics/06_host_specific_data/07-invalid-data.cf.json new file mode 100644 index 0000000000..d91e28903b --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/07-invalid-data.cf.json @@ -0,0 +1,16 @@ +{ + "clses" : { + "ns1:test_class": { + "class_expressions" : [], + "tags": ["test_tag"], + "comment" : "comment1" + } + }, + "classes" : { + "ns1:test_class": { + "class_expressions" : [], + "tags": ["test_tag"], + "comment" : "comment1" + } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/08-class-expression.cf b/tests/acceptance/00_basics/06_host_specific_data/08-class-expression.cf new file mode 100644 index 0000000000..6de71e05c7 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/08-class-expression.cf @@ -0,0 +1,38 @@ +# basic test of the host_specific.json functionality: 'classes' object with a tag and a comment +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + + "empty_promises_cf" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf"; + + methods: + "" usebundle => dcs_passif_output("\s*error: Invalid class expression rules for class 'ns1:test_class1'.*\s+ns2:test_class2\s+.*", + "", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/08-class-expression.cf.json b/tests/acceptance/00_basics/06_host_specific_data/08-class-expression.cf.json new file mode 100644 index 0000000000..cb8aa18345 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/08-class-expression.cf.json @@ -0,0 +1,14 @@ +{ + "classes" : { + "ns1:test_class1": { + "class_expressions" : ["cfengine"], + "tags": ["test_tag"], + "comment" : "comment1" + }, + "ns2:test_class2": { + "class_expressions" : [], + "tags": ["test_tag"], + "comment" : "comment1" + } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/09-class-reg-expression.cf b/tests/acceptance/00_basics/06_host_specific_data/09-class-reg-expression.cf new file mode 100644 index 0000000000..83933041f1 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/09-class-reg-expression.cf @@ -0,0 +1,38 @@ +# basic test of the host_specific.json functionality: 'classes' object with a tag and a comment +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + + "empty_promises_cf" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf"; + + methods: + "" usebundle => dcs_passif_output("\s*error: Invalid regular expression rules for class 'ns1:test_class1'.*\s+ns2:test_class2\s+.*", + "", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/09-class-reg-expression.cf.json b/tests/acceptance/00_basics/06_host_specific_data/09-class-reg-expression.cf.json new file mode 100644 index 0000000000..1490652d45 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/09-class-reg-expression.cf.json @@ -0,0 +1,14 @@ +{ + "classes" : { + "ns1:test_class1": { + "regular_expressions" : ["cfengine"], + "tags": ["test_tag"], + "comment" : "comment1" + }, + "ns2:test_class2": { + "class_expressions" : [], + "tags": ["test_tag"], + "comment" : "comment1" + } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/10-variable-references.cf b/tests/acceptance/00_basics/06_host_specific_data/10-variable-references.cf new file mode 100644 index 0000000000..0f186d4946 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/10-variable-references.cf @@ -0,0 +1,38 @@ +# basic test of the host_specific.json functionality: 'classes' object with a tag and a comment +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + + "empty_promises_cf" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf"; + + methods: + "" usebundle => dcs_passif_output("\s*error: Invalid 'variables' CMDB data.*", + "test_variable2?", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/10-variable-references.cf.json b/tests/acceptance/00_basics/06_host_specific_data/10-variable-references.cf.json new file mode 100644 index 0000000000..5175c6c326 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/10-variable-references.cf.json @@ -0,0 +1,14 @@ +{ + "variables" : { + "test_variable": { + "value" : "$(sys.uptime)", + "comment" : "comment1", + "tags" : ["test_tag"] + }, + "test_variable2": { + "value" : "4hfcattz2607yfksllzpf73eg7nmqprl", + "comment" : "comment2", + "tags" : ["test_tag2"] + } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf b/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf new file mode 100644 index 0000000000..5493677f5f --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf @@ -0,0 +1,41 @@ +# basic test of the host_specific.json functionality: 'variables' object with a tag and a comment +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)." + create => "true", + perms => mog("0700", "$(sys.user_data[username])", "$(sys.user_data[username])"); + + methods: + "prepare_host_specific_data" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.workdir)$(const.dirsep)data$(const.dirsep)host_specific.json"), + handle => "prepare_host_specific_data"; + + "def_json" usebundle => file_copy("$(this.promise_filename).def.json", "$(sys.inputdir)/def.json"); + + "empty_promises_cf" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --verbose --show-vars -f $(sys.inputdir)/promises.cf | $(G.grep) test_variable"; + + methods: + "" usebundle => dcs_passif_output(concat(".*Cannot set variable ns1:scope1.test_variable from augments, already defined from host-specific data", + ".*ns1:scope1.test_variable\s+4hfcattz2607yfksllzpf73eg7nmqprl\s+test_tag,source=cmdb\s+comment1.*"), + "override_value", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf.def.json b/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf.def.json new file mode 100644 index 0000000000..cd8df3e2de --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf.def.json @@ -0,0 +1,9 @@ +{ + "variables" : { + "ns1:scope1.test_variable": { + "value" : "override_value", + "comment" : "comment2", + "tags" : ["test_tag2"] + } + } +} diff --git a/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf.json b/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf.json new file mode 100644 index 0000000000..8bcfc82555 --- /dev/null +++ b/tests/acceptance/00_basics/06_host_specific_data/11-augments-no-override.cf.json @@ -0,0 +1,9 @@ +{ + "variables" : { + "ns1:scope1.test_variable": { + "value" : "4hfcattz2607yfksllzpf73eg7nmqprl", + "comment" : "comment1", + "tags" : ["test_tag"] + } + } +} diff --git a/tests/acceptance/00_basics/CFE-1886.cf b/tests/acceptance/00_basics/CFE-1886.cf new file mode 100644 index 0000000000..c2fc8bcdc3 --- /dev/null +++ b/tests/acceptance/00_basics/CFE-1886.cf @@ -0,0 +1,27 @@ +###################################################### +# +# Test that we don't error parsing long lines +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-1886" } + string => "Test that we don't error because of long lines in policy."; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; + #"IP16lvcMb19Rg2BNQW1USktx7RzBZbyyGsZCgixY5TL0ynV4u0Cm1xKfta8n3ZQgovDEoPJp6FpbVhnhkJB4ILV3ubrnIKCKUlan7Msg4EN2o2qA6tYRDIBjQtlhXUqpnYznHHuvWeIVXhHTBo8uVoZEiWJuQkscjr74tOBcEhfXBMOc7dGNqsWjZIVV7hSwWWIb27DitY69KIYHGp9EsRS9ZIrL0xF48HMeFL83vZGnbm41PwQSzjLSZDd7CqmyERVfE0rQsneVjtSjZiQngMIRejfvWeWMU8aVjJeyIEg0YSYQAxBbxDxi4o537QbH8sDxArS0RL9Un69Ki3bvwtX3BA8piOIOwF6PZmEIeaydUP1I2w1PswQXdmb3wWIRrQhTqq46W0ZrfYsLKcT6UbWi8IAuxBVodrMxuoEM9KtUYzwbYHa1WwTEhs5GXgo8Jq47cuwleboqUOc8wjhOIb5ZL5DWfI65kJavC8gWXN3FAM3e7UhMUYolhXN9zxkYczfUiJEUUz3W7KJkB4bSBmZtUyL15cdO9efBsPXG4WqqCgWTZ6DUHIBKIu0SfBKIj9Vm6qffMK6eYN1RCmiHsEvbqSnAobZGGdby1amoyPC9crw8ocANLOm8gg1FGlmsEwaKvF3BH6AMQUr7d2YGfOc2BouiFW31nh1qizqHt0qcVOAUtQU83bLecp78GvNqW7HlaqT79gUk5dNQu6iUsMIkJi2DsvxTrS6D5NScwtvi48arYtLledM0iAuKLfJnG4mCEz9L2s7V40KlcvfQkfk1hYkzdYytjdXi1kx903k8R2rXQjpzdm1JwaAlQGaYIUdTjEcHw7unVPPwpQsoLErSVcgiw86KAPIi6KNzSBDfFSzl9Fi4mvucJF7ahkFxrII7HizOpM6h47cSopjfglw07e3hFacMbh2f7CMS3JBokYSLVHb2TDUL86FMbvfiFOWrTcdlSZJ9oWbzNQ3YY43Gx4YYXSFGQ8MZnMgV6Y2zubKVncbSWu9OPEKdwQ6D51g5086uCh5gxe20sSKGuHDjCBSks6KeBUix0J0dIvJGHGeNBSAxkYK3xgvo5OKDs9aWp367sxTM2YjCvCeaGIW3jM2fsVP7TPYNGCMu3zH3AUnOC8NfjXtS3a9kW1FjkO60OT05yMDgoFT4Xxou7Wn449YxEd6V2I6kdVCkngvHBxcboykuU1rIdONqKP1OifHngU1JXK5cRP4TwB7nmZ5PiB44MpaOL21JCkAUwh0sydaAEeFpGFDgSZjWwNaVYlUjquLV8YoyvZPnNAbWoH3sZrJuT0gqtPLpFVQ637K5XGrs0ezW58YeuN2JvxCrLzG6w1sc9sGeY6ITOVUKZw2KiPPdvj9p5QSFjtxkhzyuWQkhcBkURY6O2pv59ssG4LHrwM9z0hMoWkHQa4u9HxySJiJh6z5p7QIJQAiQ4ytn43p0BpJCiiWtmvy1CvD5tcDPvlASVywdENF1WqR0ycJWGIkw08HmLXPuBHsSl1mYLeJymTBMfbMfVxcrCuH0ETzTtE2HALpgwAfnjMd6g1Tq7rXPGIDaeHjOFoIzauNy098bChJuZQW53EE0EtxHeJNlr9rJMBHWcKBOILF6tBOgwAMa4pYNSQvTQC2JotKOnui6W9DztOrsmYF8WPuIdFq8AQSf0uT1Um90uFsW6aVLBTACbrp2Dt0ekmX8eVdIDSjXpeJHRLhtpz85kuoV5Tc3YHlBL8sjqBkcE22MEQCddjJz687RfV9punkS6ObuPeD7D86ifFBxLPjQUs9Q4LdB2j6PV2W4Rz2hAN7wVz2F76vmFQ2Ge5HT4JQI2MLSMIXIMuAe5ehnXQjsdSj90lSlos5ekR2sLyIExl8kTTFwfGMKt4bq1gEMsFqxPGscgm0k8mKNiWEnZMDrnbyTOsYmAUBHeoin19igAl5wqkctlpNVXPGVqTKl83qiipuPNkOvtEx4glLfeObkRWm8JqdG8zLI5glS3FKXbZrXieBGPw8gW2hJ9QasSpsrkCi9X4hPW83qMP31I7ypu6ZYASNrQc99RP4GURD7TyPEFKUzyjVYqjuyt8qNy6jCq4O2wogIhPaGpChuBsnTOErLlBpbfs8hNnXdxY46Mt6MjZtiwDWcQ0iwuO9xYmMuxv2MYfTIphAFaIdRMtS7NPUy2AIUNwlnWZVrYQY5BiKUdLpo1br6hjEWdqeBWduRCLJZS1OG2i8OwXrIONLox0T8gtPin1slEmcGEapOnxd6Ho1v09mA6fU7ChmPigxjICly7pmbaA6uJb1Hv5vyr7wHGBqWhfx8STJpoPfMjdB5GKwg4SOciLBFMGtuq7GC7sCBkV5XyWBTHgZahZdJZtS18aw6bLmW87s2nxqCgcdFRnVy9t7aOrlFWgHRzRDSEMp17N1MTySg3nBvTOAQBbMaKl7MhkTKKhqJk4t2HpBWO4RCqZtvFrw5ebuCguemSnH3oJ6Hvwsp1uqwfCTsq3Q1zIxJmEszNsZI7oeWXAUwRWwAKt3jagSMNgWiER1CzJBs8UwJXUrqQrk28FIPxvonXPsWaYslf3xtKuIPQ6xK6byote1JHuNs7d2n7Xaa0AhsokR07aM6LqUocSyuLTJ5PipXfwbhFDZ3tTBawUV2sY17e9qEqXDDmT2Yvu3Qe6kuwQ7Go0WsolEND4RJGrpWGJrKKgPAfVsmKboyd8ppCHLBB2PFvQ6uBYqtR9PUtXivYJ8cTzK8uYCoDqNhqJmUtKT8aKvdyBo3cOeNn55MHDIT83zKIB8JudLWdVy2jWqX6p5NxwOetqIvGK3pZrwuFvH6G37sZnhvDwhg8dcXzaI3s2mLaNeb3bwE16BLOpDzVunv4BrmZpbOlOSoAUtkwWboCLbCf9Xxd6ck4slHmOpouFDuDSjU1j38a2OEHxgas02wI5SgagxHfOaUIZvbxQCxuEEWEdQNi46b8K2P6XPt2BwpIAQL16ufy8PgkZQkeMZqyxqcjTr8tNMH2k7u9hq3uksOVP7B8T7IArs9GjeKVEmFABZOdy6Ems400oI7siewhW7Mt1yZX0YAjzKv1h0ybgAFAy2gLdasDQA3zWCjoScqXhfCbdyO4w1EVjb00LYRLRzcKzjj0ekOkATfmeZ30FwhFWCwgEadceC7t5X4noYQ1GTvimc1ZHPWTMZPE7JgQ0IL6W0TINhwWnKTplUmAClyr0AwSwMyyxH2i34OXehGdPdOfae71kqgCGNkcu80boPzGbInrWJ8Zlz0J0z0bss6okRGX3SvVImRwRC7oXfKsWQEVeoF8o7hS3UaSFSsQ70hpXTHGInQ8EI9c6mJZWNi5MA7ArBZydkOqQXI6iGPTDoNNx3wzd8nCAEThiYV4iLpNIqSUlG1pAO71siIPsSkZ8WTq0scMKkF0WhGDo7Lxk4b29CAwKOyp3pGlh7xWAZryeWvfbECWEkMSqfEgYBfkGFMQlUjWumOzMLEZZICwBxtaPhuepasEQmuPTOfB4Hr106aMAFasPjozsYwTcxYUqvew0Ddpt8DGTlyTtnrY0jsF6NkubSVV4hAQFJpBvO4KAlrUWCSrRcQOY3mJXMFWg6E5ckzjrIf9smIn3XpW3IABA96vRT22Bs9FYbEvAX4xlOPjCbU28oHySMcr0iCjGH1Vq0fX6lF0aej2nGcPwvFzmgqy65Glvcnff8mBNui5gSWfxUk6SdheBhqMVKmsq2ZshTK8dUuMzibEQsvAKAuRCGAOzjGNUozTmfOWZ2Ucz6Fp6yXUFbPDBGUAXR9htYSJShCG7hvcudvjJduBvc7AcxDJclrudYUfsOYMBfUWIG9puwDdySbzWu5HIRdV7MnBmPgJ5FLD2ix6UMHYq4yof3nKTplJcCJXtoRVg7JFSkykRUTazEVS4znZcTfneCIfsoN5Sx8YROztsPqtdBqnJlYVOTZYg4jW13f5BnTHzUqDC6pTrjcfuaR9sPf9c0NdAufhPQtgGh4eeflZUbbVWxZFbul6Xm8NtWOhrd5cxh2KhHSn6kzJAyVuxiOVHef7wcpZRdIzMdXXklvu0qvjjWS2KqOhdAxhhIqDSqwnh77w16KFT8Eva2vINUb1qp5qzKNWBGNS7TcvBcgkffcSH5PbnZYh1FOAouOptljDyLJu8YvJeUmue4jvy8kG8W2kesk3nUPjaKS7upfbLzYARtVCsMuuhJauSHvOZqpaVp8DMS3IaoIIMjiRdaQIHmtPvblOQkSnztKehsKkR3oUFUHlRI2HsE9lr5Usbta0NmYrT00wtR2AzzChCNi7QfzwhVvNlmZj6fiAY8pdVdYSPkqAoMG8v5yex6davoexsnVvgEs8hQW6tfxm6rOukcPdoZtMCS9s2Ft8xkg4VxVA2hs5u84vvzgl1vvjaaylwh1iyx04uxAiSiGRaqz6e7knDsmyVnHRxlsUh6OEzi3JQtmM8GR9xnT5DaBNODcWHQVBhI1vVarNsf5f88lMaY7EIZldODkWOKwf94sEOrqdY0gw7EA2Nth01d3j6D0o6Pe3RGptn5BpQbOYLDLL4CMZYQRfOqbs5RaI7WBGWtgmd3ZOAD3reSXVGwx8fgi7HfWEs8hjByFu3hclJszsmQGhUAZ92qlSeEh3K7BdDkuRoXAI4KZZbMw4PEHpQ5tVQ1hk6QIAyXmLSUH3wpcBfBXsM3aAHckQefP5zeOANQ1qE40y5eSqIZ8hzmEjNULwEhQddPIqvK8TTVwDhLghoN9UHIfUbPMgcYknQXzLqz6gK2LSWZXcr6TTV3QqxsOdCdKoW3dc33qmSJJLSGzPIiEjEjqQ67gNmAt2yCUYu8Tne0jxWOzyW8qtuF1LabTrnCcnGOGirs87JgvgOQNzY5HJKFKSUJcmDgLNxycdP8D0kCQeS7q0HvCM9sgLgx3fiEGxUPMXXqyPm7QVldAfm8LSrCjyJWWDb5gKSmOyHs98Bp2iwUwil1zh5FxAzIbm5Ev17O6XBVlPPvc6pIVZyNpcr4QsiDMERS7r56JWWNKwEF3PFHJ2Y6yoqPpohp8WiW3nZKta7FZDwE1iaR7DFY8vFxzCZhtMVL8oOQBPfxXvKwa2fN5S0tN7q22jAGsKOECJkQn3VZeQLGwHaXjIZ0r6XeA39BfZQiUSj5c9VFoBeROCohbwzH0CDda8g7WhieD68wxw7zFc3f5W6jFxyCBW8Z8vVxE9THtIFgwqrHMsohJhD1cu61hR5Qg2Th2GgAHfAcEDfMToilhxKKVyZwbg9lGyJzLaweeSD2e2K7nROabKh2D36iO0N3hWEvsfhvTrFO2UClLzfagEDUUDSZlqJeB60S9QPSXgSY40aYe78ZBff3nLHXvF8aUpgxwMDrU3zkSa2luYCVcgMEPGcP4JO1tbfobHlmSvTlodTFN5kuAvur6p46zK6r9Vg98poNN325sX66jHKHFrdu435MWoC6ac4GZ6AbqddUwLuBxMloXL33ig6WQOshXrHdB1RQzlExWzQJy58U9EVgZOyNrac2pUcG2rzgD98W4MbXmlPeYxH8a0drts1FET2WfQCEctgnud97qnEr57KM0L7SRw2wS1RArQd3nkuyMlXVCMRj82c8J3g31cSvBvvO3GKtSNc6WGYJxorRrOeMz5HgqmVxTbub6yBiRdenzVeHe1KGWd6wX297glKWfVy3i1C2xd72BpXep0prCiKps7XaZAf9yWUi3VwTTTpDQSN4DlswMI6LgYdiKMLBRdWrSbKvG0MKVZldNZnxmehPRRnZNh5lDf8Vx53DcfESCVQV6ad6Kk62l8SDQtNBgK4qFz8faq3h45FbDzWcBQT5PI98LWyAul4kKma8lTlDrFsqPPSLrsCY6qltFJlPZn26GXwvQWi3NthmOL2cUrMPlkS52AKNFGy6VNl8eZacEoU9fgH5bRQAcwJnpe59nvCdueBpJOwVtwRnCo35dZJxqhRn9MbWN4Mk6Rv2CCWjU6JCpSUWjfC176N3kcVDqPWMOM63s1ckgAOFbbKTTQU71elkfrvh8Depa3bGfXe1NrcOnN68hL9jFGwtzl1O5KwxBeO8dY2KPwRGmvWM251ecrVLUEd2PruzKOgycfclvaaU5PXxSoHtYqm6Rl2o27XDijIgEOgXM5v18QepNKej3cd3WdWvxwa3ezkbRayx0iDbeBBh9wLVydb7hG43gBteAWfLDK4sXobszdrRH5YDp2O8YjpSllvuHyIkv6NF9G83PkWsQ3EMHSP5sPrngT9cZ1NMeMkLZyX8NQm3esqPpY1h9zQMCxZnd0BTzCzShFd40z3jjSQfuSgcgowlkmueysZJSoTFObhoDGR96XTxFIyTSPTI9d86ojIzAGZnD6mkwYiN22DjEcGLNWvtpwnYAfUwB7x2Pi1rC1yKTjYSD0Risley6r0DcYbTITPtRH4lxzuMriCt2ZLlqYcHSI7w9CkDd8Jsmm30sOpKIg7euVfhuMHbAoS9JqaNIwZoOVSInlMFUPVlrFZselcxuUQkmyedz3EgtPEYnMJpr9mBeHanxjFxVcxkAOwxa1Ra5c0TINUlm8QANMRvq6yleZcyBdmsDmjJeEEzvtg27mIO3EcZgg2nrRqumZzyFrpClO3PmyQmIUfS3dXamGmNoY00PT8RpN2cAN7Hl5XpHS1JOSY3JHTaQ1I8fUqFlJpAYcWhP9zMcCFdRUD0AvmJx3iFyjEmLOAl9npbcqqiVTc1aTPfF52WAtFMqByUU98L9FNhTKmVbvsgJe4XNsQKju1w6ExLz1FSfAoSN5eFOno4UNTkaCJXfua15LIwiVKf1WNj0YHgL1TVHeU3750D5YX7GvVrInvTaFgCNVFkhKi4HyhBg0EthXoH49Q1FRhfZ3VgFt1xdtc762lPTMWtmDRmO5LwiATPhjKCFJjD1jEexxavorT0YiAVd833R8PrLpB7KKcvgCsf1fx8mOUZr0qKCdL7TMWCCIdlmFKMREZ41hIqdZkN8j4RzfAVvatxFpz5VCNY94SafJsw8M9xxViE1BGWbJ8BNqL5EYsFP0PM37J82BPSIFdmCtQI6M8yljDtZ3kf7aUADZiIMGjH5lP6BwQ3GLPxhcOe1Bf5BzX1wtbGa98OgnqI73diYK0yXcobC67YoEvqs6So9gbrWqc24WyTfDgCdAebUXb98VQuwhfGBNEDsPbPZDKique0lnI3j55pIgttlSTiNnvbQXRt4db5PU2pSOVdCnS6cE56H66R8vAPAjPTUwGsDmD8HFyi9sj8iQGzrxmduCfZ31k7Mgo7a4ln12biI2og5voU44sgrHxRCDcb1XmRhAympcItfQtjgDi8UggaBMYy3T5MF75kLzskeyEGb4Xd81wKoMSi8VmR8eFMwXOqhYSpmjrdf4gq9fLXlzWQi8RDcMqo2vxuCb3A1v1xn8Ksjl3CG9O3T6n1SK2kxf2k0pE4FHsLKqr441hHThLpIRwblFzOxovjCwaRwZEJzkHhHzwX5jljTvhEh0bjsBRnSoCvcMECoqn6waoLICRh4RTeO0NcyFLXlb2PXCR1rw1xs5srCkIjQqa4NnARsMgePn2d6LA9LG8b4ujFmPI4TqKqKtxb4EATL8QdD9DP1qUM7UMxwUtqC2uJl0zbkfcZaHUVMANsRzxVxT01qH6d3YWvbxfBHlX5WrYfQyveanS3dEJ4BN8PcdkCjFty0LCg7DMdl2HPEzT4eQNka8eAiyah4bkytvvigBxyW1AvHmSXIw8Z3PP981FP6nb3Tdp2pElK82wnNjQB4ea2HSzIgSe8fHoObnqwlPruvXsEkstYWElFeARgVFn0YgeycgIgbJtLGj3LABDMs9SplWoPv8CUS7oxgffpKQ0S5nO0w1Ymch6wTsEF5sCZL0cOF3KlmSYlgfArlZ1oGDLUl22DVPMuK0aBeSePEEmHfN8sS4oW5ggJdnYz2mhTu1dsB7UFFTWl4aNaBg4bfCger2strh1tPOrzzyhn6KXsUuFMc3zIzgcONilYT52A0caGKpDcPImhavV3NMSR7zLCBTzD00lGw4k9zY8yFlcJVmMfxiRrNXms9Zd90ZZPLxDvJIS3N8M19k3fddM1WDvaguZtBTTXi0hVz9aYUTeeAg151qOH5tXfDMqNqZMTk4KzStjst6Ilrv6GKpv3ynEbksWWD0dAYFLcwWjCJhzu7SXD22egw6hPPj2fennW2SfUDKPumc39biIdAUrkZ3LnYfAmePFXWItj14kh6KGRaEjXl6iWeIHau6rcoYRb9Z5L5MmRbicSEJsrUg9UifFobqKUHHv7WaP8ihg9yinwu9lTvJATiGdSbHis2uhVVYbsd9OBNA32yzMv76CfE9rBxxHgdSQkz0nNRTZJZKyTOq8K8DWoxWLUJOAQYKIcJqwd20VOho1JWsrCS45ecLDzAqP8LTQQs9ffgQW2wIeE1pTK2haOVUM1KxseDaD7xGgaPTluK1Tox84rtX77XBWXLYpYfiSFmZ28tqhJLyubgJwRh2QgLVtc2gZJhgQG7b1koPbvL2TYyCaH4oXU8kKKmo7m4jVFPaxz6LM6rgcD7heGAZykw2hleI8Q8eGn6gF2umX1RkRs7z4WmfaLijQsUefqOotHDSxOgMC4OqebTaqPJHbfwDuL2k8cyuOUNNdM5BR2UolCShqtssPNHAIXuKLlQZ35CEgzOdaliQcjH5leW6eQDKlAQAOMDsVe5u4LyaRyE9XyStjYENBLWBKNeHkgKJVK5Bez3FY91zueOFOQA5C0uQBVDRQoJEwSr8rywo8JqypxEc7WKfGMYluXbKIOd2CpHpI9u9zzqDQiNyWZiqgBn0znlm0H0ADLag8PX69Sndw3X6cghUDgm6UNbX1EczhFPzSxuTqKNrE8tYf0sflTIg3L9YGgktj2OjVE38suwmSmGju8a8v2hOuO5g5PgXquGPx25owEC90CcxYlrudSVSTxC1iyopBduh48ZNqdkOPkU6oCs1Mkk5VrwsfL0nrG4CoqKyFJYqlnJooJnsZlR9dQ9DwUhOZZXnbSTWuVoLSBTqzfyE8eQ86YdZofIS1on8YoOPrLtHZxdroULBhB1ZsryypL2qsTEQSMbr2EPR1Xk0qkFJ0GwPfbqCPBlbdOUc3usFjnHHKvsI7dZPGMQQ4zSZ2NCFuVxz0VnUYPajyNwWqTxtvVRB6waUIopH45NntyFBuB2woRiatB2MrDZGrlXFqjKiB1WT0XtqBZlvALfus8KTICMNvS66RD032hh0wJUAKPpYaMRqqoeWyHWpsAEXm5E1bMeDCcpjWVd1camrrS4RhUe0Zfv4kfwQQsiLjhiZLuxJ5IFgI6okGD6wXxE7sHBYId17WAJEVtUzGLjBsPfAd5dids6hsd6mQSiMuequwQY69rVqoWu7hIVxsTXdDyA9vjCtGVXoSTFeybz2VsS1CgID8sqcwOg152AOcjUw221slXQnRb8m4CuWdodrpxDaXDDIUt6i2Rq2PGmpqkXWEijc528Vc8XTvPTnDSVwVFRFqnOJtZq4nS5LxY72cuF4vNZPkP1QjZwwOzqMBJ90qOCGldYqPzw0gxXqqR74drZAdGjFxB4pQHp1sZYryRva7chyMcARs45qowRt246N3RKHQkY2QQim4SmSkiRUTtO16FpcSNyAaQU9kdCo6e6nDwhvORDSKZzq0LNsFbbqqCTPYVEvCUkqrCEnxajenGazy0L7NaoM9lNJxaO8m19iJEaEQbseVSml9Ni3vAkS4PKEuz2DYL7IXgRHZ1ejucxrSIvkmwNrTHsQFYUhAfRagZPoHUt22q6CNIv8iYPPT0j4cfy3jkR9MUiOqs0v83Zj29bEwXZUzhzFrozrkKPJrMxHksr8NvTPcDn4JC4s5WJgIG4rxofeofRBLKryZ1sZk5izFobtBGYLiqMtnYsKJnxnvNEk0Yhb4N6RmqC0mpYPXis3PDOWwh8j0IE4z3hCjvO3u1IxV8n8kLht0TOEjfnZyNpcr4QsiDMERS7r56JWWNKwEF3PFHJ2Y6yoqPpohp8WiW3nZKta7FZDwE1iaR7DFY8vFxzCZhtMVL8oOQBPfxXvKwa2fN5S0tN7q22jAGsKOECJkQn3VZeQLGwHaXjIZ0r6XeA39BfZQiUSj5c9VFoBeROCohbwzH0CDda8g7WhieD68wxw7zFc3f5W6jFxyCBW8Z8vVxE9THtIFgwqrHMsohJhD1cu61hR5Qg2Th2GgAHfAcEDfMToilhxKKVyZwbg9lGyJzLaweeSD2e2K7nROabKh2D36iO0N3hWEvsfhvTrFO2UClLzfagEDUUDSZlqJeB60S9QPSXgSY40aYe78ZBff3nLHXvF8aUpgxwMDrU3zkSa2luYCVcgMEPGcP4JO1tbfobHlmSvTlodTFN5kuAvur6p46zK6r9Vg98poNN325sX66jHKHFrdu435MWoC6ac4GZ6AbqddUwLuBxMloXL33ig6WQOshXrHdB1RQzlExWzQJy58U9EVgZOyNrac2pUcG2rzgD98W4MbXmlPeYxH8a0drts1FET2WfQCEctgnud97qnEr57KM0L7SRw2wS1RArQd3nkuyMlXVCMRj82c8J3g31cSvBvvO3GKtSNc6WGYJxorRrOeMz5HgqmVxTbub6yBiRdenzVeHe1KGWd6wX297glKWfVy3i1C2xd72BpXep0prCiKps7XaZAf9yWUi3VwTTTpDQSN4DlswMI6LgYdiKMLBRdWrSbKvG0MKVZldNZnxmehPRRnZNh5lDf8Vx53DcfESCVQV6ad6Kk62l8SDQtNBgK4qFz8faq3h45FbDzWcBQT5PI98LWyAul4kKma8lTlDrFsqPPSLrsCY6qltFJlPZn26GXwvQWi3NthmOL2cUrMPlkS52AKNFGy6VNl8eZacEoU9fgH5bRQAcwJnpe59nvCdueBpJOwVtwRnCo35dZJxqhRn9MbWN4Mk6Rv2CCWjU6JCpSUWjfC176N3kcVDqPWMOM63s1ckgAOFbbKTTQU71elkfrvh8Depa3bGfXe1NrcOnN68hL9jFGwtzl1O5KwxBeO8dY2KPwRGmvWM251ecrVLUEd2PruzKOgycfclvaaU5PXxSoHtYqm6Rl2o27XDijIgEOgXM5v18QepNKej3cd3WdWvxwa3ezkbRayx0iDbeBBh9wLVydb7hG43gBteAWfLDK4sXobszdrRH5YDp2O8YjpSllvuHyIkv6NF9G83PkWsQ3EMHSP5sPrngT9cZ1NMeMkLZyX8NQm3esqPpY1h9zQMCxZnd0BTzCzShFd40z3jjSQfuSgcgowlkmueysZJSoTFObhoDGR96XTxFIyTSPTI9d86ojIzAGZnD6mkwYiN22DjEcGLNWvtpwnYAfUwB7x2Pi1rC1yKTjYSD0Risley6r0DcYbTITPtRH4lxzuMriCt2ZLlqYcHSI7w9CkDd8Jsmm30sOpKIg7euVfhuMHbAoS9JqaNIwZoOVSInlMFUPVlrFZselcxuUQkmyedz3EgtPEYnMJpr9mBeHanxjFxVcxkAOwxa1Ra5c0TINUlm8QANMRvq6yleZcyBdmsDmjJeEEzvtg27mIO3EcZgg2nrRqumZzyFrpClO3PmyQmIUfS3dXamGmNoY00PT8RpN2cAN7Hl5XpHS1JOSY3JHTaQ1I8fUqFlJpAYcWhP9zMcCFdRUD0AvmJx3iFyjEmLOAl9npbcqqiVTc1aTPfF52WAtFMqByUU98L9FNhTKmVbvsgJe4XNsQKju1w6ExLz1FSfAoSN5eFOno4UNTkaCJXfua15LIwiVKf1WNj0YHgL1TVHeU3750D5YX7GvVrInvTaFgCNVFkhKi4HyhBg0EthXoH49Q1FRhfZ3VgFt1xdtc762lPTMWtmDRmO5LwiATPhjKCFJjD1jEexxavorT0YiAVd833R8PrLpB7KKcvgCsf1fx8mOUZr0qKCdL7TMWCCIdlmFKMREZ41hIqdZkN8j4RzfAVvatxFpz5VCNY94SafJsw8M9xxViE1BGWbJ8BNqL5EYsFP0PM37J82BPSIFdmCtQI6M8yljDtZ3kf7aUADZiIMGjH5lP6BwQ3GLPxhcOe1Bf5BzX1wtbGa98OgnqI73diYK0yXcobC67YoEvqs6So9gbrWqc24WyTfDgCdAebUXb98VQuwhfGBNEDsPbPZDKique0lnI3j55pIgttlSTiNnvbQXRt4db5PU2pSOVdCnS6cE56H66R8vAPAjPTUwGsDmD8HFyi9sj8iQGzrxmduCfZ31k7Mgo7a4ln12biI2og5voU44sgrHxRCDcb1XmRhAympcItfQtjgDi8UggaBMYy3T5MF75kLzskeyEGb4Xd81wKoMSi8VmR8eFMwXOqhYSpmjrdf4gq9fLXlzWQi8RDcMqo2vxuCb3A1v1xn8Ksjl3CG9O3T6n1SK2kxf2k0pE4FHsLKqr441hHThLpIRwblFzOxovjCwaRwZEJzkHhHzwX5jljTvhEh0bjsBRnSoCvcMECoqn6waoLICRh4RTeO0NcyFLXlb2PXCR1rw1xs5srCkIjQqa4NnARsMgePn2d6LA9LG8b4ujFmPI4TqKqKtxb4EATL8QdD9DP1qUM7UMxwUtqC2uJl0zbkfcZaHUVMANsRzxVxT01qH6d3YWvbxfBHlX5WrYfQyveanS3dEJ4BN8PcdkCjFty0LCg7DMdl2HPEzT4eQNka8eAiyah4bkytvvigBxyW1AvHmSXIw8Z3PP981FP6nb3Tdp2pElK82wnNjQB4ea2HSzIgSe8fHoObnqwlPruvXsEkstYWElFeARgVFn0YgeycgIgbJtLGj3LABDMs9SplWoPv8CUS7oxgffpKQ0S5nO0w1Ymch6wTsEF5sCZL0cOF3KlmSYlgfArlZ1oGDLUl22DVPMuK0aBeSePEEmHfN8sS4oW5ggJdnYz2mhTu1dsB7UFFTWl4aNaBg4bfCger2strh1tPOrzzyhn6KXsUuFMc3zIzgcONilYT52A0caGKpDcPImhavV3NMSR7zLCBTzD00lGw4k9zY8yFlcJVmMfxiRrNXms9Zd90ZZPLxDvJIS3N8M19k3fddM1WDvaguZtBTTXi0hVz9aYUTeeAg151qOH5tXfDMqNqZMTk4KzStjst6Ilrv6GKpv3ynEbksWWD0dAYFLcwWjCJhzu7SXD22egw6hPPj2fennW2SfUDKPumc39biIdAUrkZ3LnYfAmePFXWItj14kh6KGRaEjXl6iWeIHau6rcoYRb9Z5L5MmRbicSEJsrUg9UifFobqKUHHv7WaP8ihg9yinwu9lTvJATiGdSbHis2uhVVYbsd9OBNA32yzMv76CfE9rBxxHgdSQkz0nNRTZJZKyTOq8K8DWoxWLUJOAQYKIcJqwd20VOho1JWsrCS45ecLDzAqP8LTQQs9ffgQW2wIeE1pTK2haOVUM1KxseDaD7xGgaPTluK1Tox84rtX77XBWXLYpYfiSFmZ28tqhJLyubgJwRh2QgLVtc2gZJhgQG7b1koPbvL2TYyCaH4oXU8kKKmo7m4jVFPaxz6LM6rgcD7heGAZykw2hleI8Q8eGn6gF2umX1RkRs7z4WmfaLijQsUefqOotHDSxOgMC4OqebTaqPJHbfwDuL2k8cyuOUNNdM5BR2UolCShqtssPNHAIXuKLlQZ35CEgzOdaliQcjH5leW6eQDKlAQAOMDsVe5u4LyaRyE9XyStjYENBLWBKNeHkgKJVK5Bez3FY91zueOFOQA5C0uQBVDRQoJEwSr8rywo8JqypxEc7WKfGMYluXbKIOd2CpHpI9u9zzqDQiNyWZiqgBn0znlm0H0ADLag8PX69Sndw3X6cghUDgm6UNbX1EczhFPzSxuTqKNrE8tYf0sflTIg3L9YGgktj2OjVE38suwmSmGju8a8v2hOuO5g5PgXquGPx25owEC90CcxYlrudSVSTxC1iyopBduh48ZNqdkOPkU6oCs1Mkk5VrwsfL0nrG4CoqKyFJYqlnJooJnsZlR9dQ9DwUhOZZXnbSTWuVoLSBTqzfyE8eQ86YdZofIS1on8YoOPrLtHZxdroULBhB1ZsryypL2qsTEQSMbr2EPR1Xk0qkFJ0GwPfbqCPBlbdOUc3usFjnHHKvsI7dZPGMQQ4zSZ2NCFuVxz0VnUYPajyNwWqTxtvVRB6waUIopH45NntyFBuB2woRiatB2MrDZGrlXFqjKiB1WT0XtqBZlvALfus8KTICMNvS66RD032hh0wJUAKPpYaMRqqoeWyHWpsAEXm5E1bMeDCcpjWVd1camrrS4RhUe0Zfv4kfwQQsiLjhiZLuxJ5IFgI6okGD6wXxE7sHBYId17WAJEVtUzGLjBsPfAd5dids6hsd6mQSiMuequwQY69rVqoWu7hIVxsTXdDyA9vjCtGVXoSTFeybz2VsS1CgID8sqcwOg152AOcjUw221slXQnRb8m4CuWdodrpxDaXDDIUt6i2Rq2PGmpqkXWEijc528Vc8XTvPTnDSVwVFRFqnOJtZq4nS5LxY72cuF4vNZPkP1QjZwwOzqMBJ90qOCGldYqPzw0gxXqqR74drZAdGjFxB4pQHp1sZYryRva7chyMcARs45qowRt246N3RKHQkY2QQim4SmSkiRUTtO16FpcSNyAaQU9kdCo6e6nDwhvORDSKZzq0LNsFbbqqCTPYVEvCUkqrCEnxajenGazy0L7NaoM9lNJxaO8m19iJEaEQbseVSml9Ni3vAkS4PKEuz2DYL7IXgRHZ1ejucxrSIvkmwNrTHsQFYUhAfRagZPoHUt22q6CNIv8iYPPT0j4cfy3jkR9MUiOqs0v83Zj29bEwXZUzhzFrozrkKPJrMxHksr8NvTPcDn4JC4s5WJgIG4rxofeofRBLKryZ1sZk5izFobtBGYLiqMtnYsKJnxnvNEk0Yhb4N6RmqC0mpYPXis3PDOWwh8j0IE4z3hCjvO3u1IxV8n8kLht0TOEjfnZyNpcr4QsiDMERS7r56JWWNKwEF3PFHJ2Y6yoqPpohp8WiW3nZKta7FZDwE1iaR7DFY8vFxzCZhtMVL8oOQBPfxXvKwa2fN5S0tN7q22jAGsKOECJkQn3VZeQLGwHaXjIZ0r6XeA39BfZQiUSj5c9VFoBeROCohbwzH0CDda8g7WhieD68wxw7zFc3f5W6jFxyCBW8Z8vVxE9THtIFgwqrHMsohJhD1cu61hR5Qg2Th2GgAHfAcEDfMToilhxKKVyZwbg9lGyJzLaweeSD2e2K7nROabKh2D36iO0N3hWEvsfhvTrFO2UClLzfagEDUUDSZlqJeB60S9QPSXgSY40aYe78ZBff3nLHXvF8aUpgxwMDrU3zkSa2luYCVcgMEPGcP4JO1tbfobHlmSvTlodTFN5kuAvur6p46zK6r9Vg98poNN325sX66jHKHFrdu435MWoC6ac4GZ6AbqddUwLuBxMloXL33ig6WQOshXrHdB1RQzlExWzQJy58U9EVgZOyNrac2pUcG2rzgD98W4MbXmlPeYxH8a0drts1FET2WfQCEctgnud97qnEr57KM0L7SRw2wS1RArQd3nkuyMlXVCMRj82c8J3g31cSvBvvO3GKtSNc6WGYJxorRrOeMz5HgqmVxTbub6yBiRdenzVeHe1KGWd6wX297glKWfVy3i1C2xd72BpXep0prCiKps7XaZAf9yWUi3VwTTTpDQSN4DlswMI6LgYdiKMLBRdWrSbKvG0MKVZldNZnxmehPRRnZNh5lDf8Vx53DcfESCVQV6ad6Kk62l8SDQtNBgK4qFz8faq3h45FbDzWcBQT5PI98LWyAul4kKma8lTlDrFsqPPSLrsCY6qltFJlPZn26GXwvQWi3NthmOL2cUrMPlkS52AKNFGy6VNl8eZacEoU9fgH5bRQAcwJnpe59nvCdueBpJOwVtwRnCo35dZJxqhRn9MbWN4Mk6Rv2CCWjU6JCpSUWjfC176N3kcVDqPWMOM63s1ckgAOFbbKTTQU71elkfrvh8Depa3bGfXe1NrcOnN68hL9jFGwtzl1O5KwxBeO8dY2KPwRGmvWM251ecrVLUEd2PruzKOgycfclvaaU5PXxSoHtYqm6Rl2o27XDijIgEOgXM5v18QepNKej3cd3WdWvxwa3ezkbRayx0iDbeBBh9wLVydb7hG43gBteAWfLDK4sXobszdrRH5YDp2O8YjpSllvuHyIkv6NF9G83PkWsQ3EMHSP5sPrngT9cZ1NMeMkLZyX8NQm3esqPpY1h9zQMCxZnd0BTzCzShFd40z3jjSQfuSgcgowlkmueysZJSoTFObhoDGR96XTxFIyTSPTI9d86ojIzAGZnD6mkwYiN22DjEcGLNWvtpwnYAfUwB7x2Pi1rC1yKTjYSD0Risley6r0DcYbTITPtRH4lxzuMriCt2ZLlqYcHSI7w9CkDd8Jsmm30sOpKIg7euVfhuMHbAoS9JqaNIwZoOVSInlMFUPVlrFZselcxuUQkmyedz3EgtPEYnMJpr9mBeHanxjFxVcxkAOwxa1Ra5c0TINUlm8QANMRvq6yleZcyBdmsDmjJeEEzvtg27mIO3EcZgg2nrRqumZzyFrpClO3PmyQmIUfS3dXamGmNoY00PT8RpN2cAN7Hl5XpHS1JOSY3JHTaQ1I8fUqFlJpAYcWhP9zMcCFdRUD0AvmJx3iFyjEmLOAl9npbcqqiVTc1aTPfF52WAtFMqByUU98L9FNhTKmVbvsgJe4XNsQKju1w6ExLz1FSfAoSN5eFOno4UNTkaCJXfua15LIwiVKf1WNj0YHgL1TVHeU3750D5YX7GvVrInvTaFgCNVFkhKi4HyhBg0EthXoH49Q1FRhfZ3VgFt1xdtc762lPTMWtmDRmO5LwiATPhjKCFJjD1jEexxavorT0YiAVd833R8PrLpB7KKcvgCsf1fx8mOUZr0qKCdL7TMWCCIdlmFKMREZ41hIqdZkN8j4RzfAVvatxFpz5VCNY94SafJsw8M9xxViE1BGWbJ8BNqL5EYsFP0PM37J82BPSIFdmCtQI6M8yljDtZ3kf7aUADZiIMGjH5lP6BwQ3GLPxhcOe1Bf5BzX1wtbGa98OgnqI73diYK0yXcobC67YoEvqs6So9gbrWqc24WyTfDgCdAebUXb98VQuwhfGBNEDsPbPZDKique0lnI3j55pIgttlSTiNnvbQXRt4db5PU2pSOVdCnS6cE56H66R8vAPAjPTUwGsDmD8HFyi9sj8iQGzrxmduCfZ31k7Mgo7a4ln12biI2og5voU44sgrHxRCDcb1XmRhAympcItfQtjgDi8UggaBMYy3T5MF75kLzskeyEGb4Xd81wKoMSi8VmR8eFMwXOqhYSpmjrdf4gq9fLXlzWQi8RDcMqo2vxuCb3A1v1xn8Ksjl3CG9O3T6n1SK2kxf2k0pE4FHsLKqr441hHThLpIRwblFzOxovjCwaRwZEJzkHhHzwX5jljTvhEh0bjsBRnSoCvcMECoqn6waoLICRh4RTeO0NcyFLXlb2PXCR1rw1xs5srCkIjQqa4NnARsMgePn2d6LA9LG8b4ujFmPI4TqKqKtxb4EATL8QdD9DP1qUM7UMxwUtqC2uJl0zbkfcZaHUVMANsRzxVxT01qH6d3YWvbxfBHlX5WrYfQyveanS3dEJ4BN8PcdkCjFty0LCg7DMdl2HPEzT4eQNka8eAiyah4bkytvvigBxyW1AvHmSXIw8Z3PP981FP6nb3Tdp2pElK82wnNjQB4ea2HSzIgSe8fHoObnqwlPruvXsEkstYWElFeARgVFn0YgeycgIgbJtLGj3LABDMs9SplWoPv8CUS7oxgffpKQ0S5nO0w1Ymch6wTsEF5sCZL0cOF3KlmSYlgfArlZ1oGDLUl22DVPMuK0aBeSePEEmHfN8sS4oW5ggJdnYz2mhTu1dsB7UFFTWl4aNaBg4bfCger2strh1tPOrzzyhn6KXsUuFMc3zIzgcONilYT52A0caGKpDcPImhavV3NMSR7zLCBTzD00lGw4k9zY8yFlcJVmMfxiRrNXms9Zd90ZZPLxDvJIS3N8M19k3fddM1WDvaguZtBTTXi0hVz9aYUTeeAg151qOH5tXfDMqNqZMTk4KzStjst6Ilrv6GKpv3ynEbksWWD0dAYFLcwWjCJhzu7SXD22egw6hPPj2fennW2SfUDKPumc39biIdAUrkZ3LnYfAmePFXWItj14kh6KGRaEjXl6iWeIHau6rcoYRb9Z5L5MmRbicSEJsrUg9UifFobqKUHHv7WaP8ihg9yinwu9lTvJATiGdSbHis2uhVVYbsd9OBNA32yzMv76CfE9rBxxHgdSQkz0nNRTZJZKyTOq8K8DWoxWLUJOAQYKIcJqwd20VOho1JWsrCS45ecLDzAqP8LTQQs9ffgQW2wIeE1pTK2haOVUM1KxseDaD7xGgaPTluK1Tox84rtX77XBWXLYpYfiSFmZ28tqhJLyubgJwRh2QgLVtc2gZJhgQG7b1koPbvL2TYyCaH4oXU8kKKmo7m4jVFPaxz6LM6rgcD7heGAZykw2hleI8Q8eGn6gF2umX1RkRs7z4WmfaLijQsUefqOotHDSxOgMC4OqebTaqPJHbfwDuL2k8cyuOUNNdM5BR2UolCShqtssPNHAIXuKLlQZ35CEgzOdaliQcjH5leW6eQDKlAQAOMDsVe5u4LyaRyE9XyStjYENBLWBKNeHkgKJVK5Bez3FY91zueOFOQA5C0uQBVDRQoJEwSr8rywo8JqypxEc7WKfGMYluXbKIOd2CpHpI9u9zzqDQiNyWZiqgBn0znlm0H0ADLag8PX69Sndw3X6cghUDgm6UNbX1EczhFPzSxuTqKNrE8tYf0sflTIg3L9YGgktj2OjVE38suwmSmGju8a8v2hOuO5g5PgXquGPx25owEC90CcxYlrudSVSTxC1iyopBduh48ZNqdkOPkU6oCs1Mkk5VrwsfL0nrG4CoqKyFJYqlnJooJnsZlR9dQ9DwUhOZZXnbSTWuVoLSBTqzfyE8eQ86YdZofIS1on8YoOPrLtHZxdroULBhB1ZsryypL2qsTEQSMbr2EPR1Xk0qkFJ0GwPfbqCPBlbdOUc3usFjnHHKvsI7dZPGMQQ4zSZ2NCFuVxz0VnUYPajyNwWqTxtvVRB6waUIopH45NntyFBuB2woRiatB2MrDZGrlXFqjKiB1WT0XtqBZlvALfus8KTICMNvS66RD032hh0wJUAKPpYaMRqqoeWyHWpsAEXm5E1bMeDCcpjWVd1camrrS4RhUe0Zfv4kfwQQsiLjhiZLuxJ5IFgI6okGD6wXxE7sHBYId17WAJEVtUzGLjBsPfAd5dids6hsd6mQSiMuequwQY69rVqoWu7hIVxsTXdDyA9vjCtGVXoSTFeybz2VsS1CgID8sqcwOg152AOcjUw221slXQnRb8m4CuWdodrpxDaXDDIUt6i2Rq2PGmpqkXWEijc528Vc8XTvPTnDSVwVFRFqnOJtZq4nS5LxY72cuF4vNZPkP1QjZwwOzqMBJ90qOCGldYqPzw0gxXqqR74drZAdGjFxB4pQHp1sZYryRva7chyMcARs45qowRt246N3RKHQkY2QQim4SmSkiRUTtO16FpcSNyAaQU9kdCo6e6nDwhvORDSKZzq0LNsFbbqqCTPYVEvCUkqrCEnxajenGazy0L7NaoM9lNJxaO8m19iJEaEQbseVSml9Ni3vAkS4PKEuz2DYL7IXgRHZ1ejucxrSIvkmwNrTHsQFYUhAfRagZPoHUt22q6CNIv8iYPPT0j4cfy3jkR9MUiOqs0v83Zj29bEwXZUzhzFrozrkKPJrMxHksr8NvTPcDn4JC4s5WJgIG4rxofeofRBLKryZ1sZk5izFobtBGYLiqMtnYsKJnxnvNEk0Yhb4N6RmqC0mpYPXis3PDOWwh8j0IE4z3hCjvO3u1IxV8n8kLht0TOEjfn"; +} diff --git a/tests/acceptance/00_basics/def.json/augments.cf b/tests/acceptance/00_basics/def.json/augments.cf new file mode 100644 index 0000000000..1598f42c1c --- /dev/null +++ b/tests/acceptance/00_basics/def.json/augments.cf @@ -0,0 +1,43 @@ +# basic test of the def.json facility: augments +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ' +body common control +{ + inputs => { "@(def.inputs)" }; +} +'); + + + "" usebundle => file_make("$(sys.inputdir)/secondary.cf", ' +bundle common x +{ + classes: + "test_class_f03ab8289d0775ca821b39a724c6f6f08ef8fb17" expression => "any"; + } +'); + + "" usebundle => file_copy("$(this.promise_filename).augmenter.json", "$(sys.inputdir)/augmenter.json"); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf|$(G.grep) test_class"; + + methods: + "" usebundle => dcs_passif_output("test_class_f03ab8289d0775ca821b39a724c6f6f08ef8fb17\s+source=promise", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/augments.cf.augmenter.json b/tests/acceptance/00_basics/def.json/augments.cf.augmenter.json new file mode 100644 index 0000000000..6bd2f5971f --- /dev/null +++ b/tests/acceptance/00_basics/def.json/augments.cf.augmenter.json @@ -0,0 +1,5 @@ +{ + "vars" : { + "inputs": [ "$(sys.inputdir)/secondary.cf" ] + } +} diff --git a/tests/acceptance/00_basics/def.json/augments.cf.json b/tests/acceptance/00_basics/def.json/augments.cf.json new file mode 100644 index 0000000000..69238c0912 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/augments.cf.json @@ -0,0 +1,3 @@ +{ + "augments": [ "$(sys.inputdir)/augmenter.json" ] +} diff --git a/tests/acceptance/00_basics/def.json/augments_classes/augments_classes.cf b/tests/acceptance/00_basics/def.json/augments_classes/augments_classes.cf new file mode 100644 index 0000000000..7a2f7cd1eb --- /dev/null +++ b/tests/acceptance/00_basics/def.json/augments_classes/augments_classes.cf @@ -0,0 +1,40 @@ +# basic test of the def.json facility: augments +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + + "description" -> { "CFE-2956" } + string => "Test that negative lookahead works as expected, when what is being looked for is defined from augments earlier."; + + "test_soft_fail" + string => "any", + meta => { "CFE-2956" }; + + vars: + "expect_defined" slist => { "MY_ALWAYS", "BECAUSE_MY_ALWAYS", "BECAUSE_MISSING_ABSENT" }; + "expect_undefined" slist => { "BECAUSE_MY_ALWAYS_ABSENT" }; + + classes: + + # When all expect_defined are found and none of the expect_undefined are found. + "pass" expression => concat( "(", join( ".", expect_defined ), ").!(", join( "|", expect_undefined), ")" ); + + reports: + + "$(this.promise_filename) $(with)" with => ifelse( "pass", "Pass", "FAIL" ); + + DEBUG|EXTRA:: + "Expected $(expect_defined) to be defined, but was not." unless => "$(expect_defined)"; + "Found $(expect_defined) to be defined, as expected." if => "$(expect_defined)"; + "Expected $(expect_undefined) to not be defined, but was." if => "$(expect_undefined)"; + "Found $(expect_undefined) to not be defined, as expected." unless => "$(expect_undefined)"; +} diff --git a/tests/acceptance/00_basics/def.json/augments_classes/def.json b/tests/acceptance/00_basics/def.json/augments_classes/def.json new file mode 100644 index 0000000000..740bdd9354 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/augments_classes/def.json @@ -0,0 +1,9 @@ +{ + "classes": + { + "MY_ALWAYS": [ "cfengine" ], + "BECAUSE_MY_ALWAYS": [ "MY_ALWAYS" ], + "BECAUSE_MISSING_ABSENT": [ "^(?!MISSING).*" ], + "BECAUSE_MY_ALWAYS_ABSENT": [ "^(?!MY_ALWAYS).*" ] + } +} diff --git a/tests/acceptance/00_basics/def.json/augments_order.cf b/tests/acceptance/00_basics/def.json/augments_order.cf new file mode 100644 index 0000000000..bb229e882e --- /dev/null +++ b/tests/acceptance/00_basics/def.json/augments_order.cf @@ -0,0 +1,38 @@ +# test that 'augments' in def.json are applied last +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-2844" } + string => "'augments' in def.json should be applied last and thus override values of variables from 'vars'"; + + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ' +body common control +{ + inputs => { "@(def.inputs)" }; +} +'); + + "" usebundle => file_copy("$(this.promise_filename).augmenter.json", "$(sys.inputdir)/augmenter.json"); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf|$(G.grep) test_var"; + + methods: + "" usebundle => dcs_passif_output("default:def.test_var\s+\{\"defined in augments.vars\"\}\s+source=augments_file", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/augments_order.cf.augmenter.json b/tests/acceptance/00_basics/def.json/augments_order.cf.augmenter.json new file mode 100644 index 0000000000..7dfebf0a53 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/augments_order.cf.augmenter.json @@ -0,0 +1,5 @@ +{ + "vars" : { + "test_var": [ "defined in augments.vars" ] + } +} diff --git a/tests/acceptance/00_basics/def.json/augments_order.cf.json b/tests/acceptance/00_basics/def.json/augments_order.cf.json new file mode 100644 index 0000000000..be61e220c0 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/augments_order.cf.json @@ -0,0 +1,6 @@ +{ + "augments": [ "$(sys.inputdir)/augmenter.json" ], + "vars": { + "test_var" : "defined in vars" + } +} diff --git a/tests/acceptance/00_basics/def.json/bad-json.cf b/tests/acceptance/00_basics/def.json/bad-json.cf new file mode 100644 index 0000000000..2eb0c23bf9 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/bad-json.cf @@ -0,0 +1,30 @@ +# test of the def.json facility: complain if a bad JSON file is found +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + methods: + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)$(const.dirsep)def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) -v|$(G.grep) JSON"; + + methods: + "" usebundle => dcs_passif_output(".*Could not parse JSON file $(sys.inputdir)$(const.dirsep)def.json.*", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/bad-json.cf.json b/tests/acceptance/00_basics/def.json/bad-json.cf.json new file mode 100644 index 0000000000..9ce431376d --- /dev/null +++ b/tests/acceptance/00_basics/def.json/bad-json.cf.json @@ -0,0 +1,3 @@ +bad +JSON +data diff --git a/tests/acceptance/00_basics/def.json/class_from_classexpression.cf b/tests/acceptance/00_basics/def.json/class_from_classexpression.cf new file mode 100644 index 0000000000..80531c1716 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/class_from_classexpression.cf @@ -0,0 +1,62 @@ +# basic test of the def.json facility: augments +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + + meta: + "description" -> { "CFE-2971" } + string => "Test that class expressions can be used to define classes via augments"; + + methods: + "Test all expected classes defined from augments" + usebundle => file_make("$(sys.inputdir)/promises.cf", ' +bundle agent main +{ + classes: + "pass" and => { + "augments_class_from_regex", + "augments_class_from_single_class_as_regex", + "augments_class_from_single_class_as_expression", + "augments_class_from_classexpression_and", + "augments_class_from_classexpression_not", + "augments_class_from_classexpression_or", + "augments_class_from_classexpression_complex", + }; + + + reports: + pass:: + "PASS: All expected classes found"; + !pass:: + "FAIL: Not all expected classes found"; +} +'); + + "Place Augments for test" + usebundle => file_copy("$(this.promise_filename).json", + "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" + string => "$(sys.cf_agent) --show-evaluated-classes=augments_classes_from_ -f $(sys.inputdir)/promises.cf|$(G.grep) PASS"; + + methods: + "PASS/FAIL" + usebundle => dcs_passif_output(".*PASS.*", + "", + $(command), + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/class_from_classexpression.cf.json b/tests/acceptance/00_basics/def.json/class_from_classexpression.cf.json new file mode 100644 index 0000000000..cdbb1484eb --- /dev/null +++ b/tests/acceptance/00_basics/def.json/class_from_classexpression.cf.json @@ -0,0 +1,11 @@ +{ + "classes": { + "augments_class_from_regex": [ "cfengine_\\d+" ], + "augments_class_from_single_class_as_regex": [ "cfengine" ], + "augments_class_from_single_class_as_expression": [ "cfengine::" ], + "augments_class_from_classexpression_and": [ "cfengine.cfengine_3::" ], + "augments_class_from_classexpression_not": [ "!MISSING::" ], + "augments_class_from_classexpression_or": [ "cfengine|cfengine_3::" ], + "augments_class_from_classexpression_complex": [ "(cfengine|cfengine_3).!MISSING::" ] + } +} diff --git a/tests/acceptance/00_basics/def.json/classes.cf b/tests/acceptance/00_basics/def.json/classes.cf new file mode 100644 index 0000000000..a68989fcbe --- /dev/null +++ b/tests/acceptance/00_basics/def.json/classes.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: classes +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf|$(G.grep) test_class"; + + methods: + "" usebundle => dcs_passif_output("test_class_29665402e2b4331f10b8d767b512cd916eeb5db9\s+source=augments_file.+test_class_29665402e2b4331f10b8d767b512cd916eeb5db9_2\s+source=augments_file", "test_class_cfengine_and_cfengine_version\s+source=augments_file", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/classes.cf.json b/tests/acceptance/00_basics/def.json/classes.cf.json new file mode 100644 index 0000000000..c1ecaf9ad6 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/classes.cf.json @@ -0,0 +1,9 @@ +{ + "classes": + { + "test_class_29665402e2b4331f10b8d767b512cd916eeb5db9": [ "one", "of", "these", "matches_classmatch", "any" ], + "test_class_29665402e2b4331f10b8d767b512cd916eeb5db9_2": [ "cfengine" ], + "test_class_cfengine_and_cfengine_version": [ "cfengine.cfengine_3" ], + "my_other_example": [ "server[34]", "debian.*" ] + } +} diff --git a/tests/acceptance/00_basics/def.json/classes_with_ns.cf b/tests/acceptance/00_basics/def.json/classes_with_ns.cf new file mode 100644 index 0000000000..6c6e47d615 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/classes_with_ns.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: classes with namespace specification +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf|$(G.grep) test_class"; + + methods: + "" usebundle => dcs_passif_output("my_ns:test_class_29665402e2b4331f10b8d767b512cd916eeb5db9\s+source=augments_file.+my_ns:test_class_29665402e2b4331f10b8d767b512cd916eeb5db9_2\s+source=augments_file", "my_ns2:test_class_cfengine_and_cfengine_version\s+source=augments_file", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/classes_with_ns.cf.json b/tests/acceptance/00_basics/def.json/classes_with_ns.cf.json new file mode 100644 index 0000000000..5d5ee8fa4b --- /dev/null +++ b/tests/acceptance/00_basics/def.json/classes_with_ns.cf.json @@ -0,0 +1,9 @@ +{ + "classes": + { + "my_ns:test_class_29665402e2b4331f10b8d767b512cd916eeb5db9": [ "one", "of", "these", "matches_classmatch", "any" ], + "my_ns:test_class_29665402e2b4331f10b8d767b512cd916eeb5db9_2": [ "cfengine" ], + "my_ns2:test_class_cfengine_and_cfengine_version": [ "cfengine.cfengine_3" ], + "my_ns2:my_other_example": [ "server[34]", "debian.*" ] + } +} diff --git a/tests/acceptance/00_basics/def.json/classes_with_ns_tags.cf b/tests/acceptance/00_basics/def.json/classes_with_ns_tags.cf new file mode 100644 index 0000000000..f7203e8e32 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/classes_with_ns_tags.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: classes with namespace specification and tags +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf|$(G.grep) test_class"; + + methods: + "" usebundle => dcs_passif_output("my_ns:test_class_29665402e2b4331f10b8d767b512cd916eeb5db9\s+test_tag,source=augments_file\s+comment1.+my_ns:test_class_29665402e2b4331f10b8d767b512cd916eeb5db9_2\s+test_tag,source=augments_file\s+comment2", "my_ns2:test_class_cfengine_and_cfengine_version\s+test_tag2,source=augments_file", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/classes_with_ns_tags.cf.json b/tests/acceptance/00_basics/def.json/classes_with_ns_tags.cf.json new file mode 100644 index 0000000000..f1df8344e4 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/classes_with_ns_tags.cf.json @@ -0,0 +1,24 @@ +{ + "classes": + { + "my_ns:test_class_29665402e2b4331f10b8d767b512cd916eeb5db9": { + "class_expressions": [ "one", "of", "these", "matches_classmatch", "any" ], + "tags": ["test_tag"], + "comment": "comment1" + }, + "my_ns:test_class_29665402e2b4331f10b8d767b512cd916eeb5db9_2": { + "regular_expressions": [ "cfengine" ], + "tags": ["test_tag"], + "comment": "comment2" + + }, + "my_ns2:test_class_cfengine_and_cfengine_version": { + "class_expressions": [ "cfengine.cfengine_3" ], + "tags": ["test_tag2"] + }, + "my_ns2:my_other_example": { + "regular_expressions": [ "server[34]", "debian.*" ], + "tags": ["test_tag2"] + } + } +} diff --git a/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/both-can-define-vars.cf b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/both-can-define-vars.cf new file mode 100644 index 0000000000..0e8e4a808e --- /dev/null +++ b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/both-can-define-vars.cf @@ -0,0 +1,37 @@ +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3975" } + string => "Both 'vars' and 'variables' in Augments are able to define variables concurrently."; + + # The variables key was introduced in 3.18.0, so 3.17 and prior should + # skip this test since the functionality is unsupported. + # Note: This before_version macro was introduced in 3.16.0, 3.15.1, 3.12.4. +@if before_version(3.18.0) + "test_skip_unsupported" string => "any"; +@endif +} + +bundle agent check +{ + vars: + "expected_value" string => concat( "Only in vars", "Only in variables"); + "actual_value" string => concat( "$(default:def.in_vars)", "$(default:def.in_variables)"); + "expected_difference" string=> "no"; + "test" string => "$(this.promise_filename)"; + + methods: + "Pass/Fail" usebundle => dcs_check_strcmp($(expected_value), + $(actual_value), + $(test), + $(expected_difference)); +} diff --git a/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/def.json b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/def.json new file mode 100644 index 0000000000..e1756f505a --- /dev/null +++ b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/def.json @@ -0,0 +1,10 @@ +{ + "vars": { + "my_var": "Set by vars key", + "in_vars": "Only in vars" + }, + "variables": { + "default:def.my_var": "Set by variables key", + "in_variables": "Only in variables" + } +} diff --git a/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins-variables-listed-first/def.json b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins-variables-listed-first/def.json new file mode 100644 index 0000000000..3c3a01359c --- /dev/null +++ b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins-variables-listed-first/def.json @@ -0,0 +1,10 @@ +{ + "variables": { + "default:def.my_var": "Set by variables key", + "in_variables": "Only in variables" + }, + "vars": { + "my_var": "Set by vars key", + "in_vars": "Only in vars" + } +} diff --git a/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins-variables-listed-first/variables-wins.cf b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins-variables-listed-first/variables-wins.cf new file mode 100644 index 0000000000..1b659fa5a4 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins-variables-listed-first/variables-wins.cf @@ -0,0 +1,36 @@ +body common control +{ + inputs => { "../../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3975" } + string => "'variables' in def.json should win if both 'vars' and 'variables' keys define the same variable, even when variables is listed first in augments."; + # The variables key was introduced in 3.18.0, so 3.17 and prior should + # skip this test since the functionality is unsupported. + # Note: This before_version macro was introduced in 3.16.0, 3.15.1, 3.12.4. +@if before_version(3.18.0) + "test_skip_unsupported" string => "any"; +@endif +} + +bundle agent check +{ + vars: + "expected_value" string => "Set by variables key"; + "actual_value" string => "$(default:def.my_var)"; + "expected_difference" string=> "no"; + "test" string => "$(this.promise_filename)"; + + methods: + "Pass/Fail" usebundle => dcs_check_strcmp($(expected_value), + $(actual_value), + $(test), + $(expected_difference)); +} diff --git a/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins.cf b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins.cf new file mode 100644 index 0000000000..0078280244 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/concurrent-vars-and-variables-keys/variables-wins.cf @@ -0,0 +1,36 @@ +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3975" } + string => "'variables' in def.json should win if both 'vars' and 'variables' keys define the same variable."; + # The variables key was introduced in 3.18.0, so 3.17 and prior should + # skip this test since the functionality is unsupported. + # Note: This before_version macro was introduced in 3.16.0, 3.15.1, 3.12.4. +@if before_version(3.18.0) + "test_skip_unsupported" string => "any"; +@endif +} + +bundle agent check +{ + vars: + "expected_value" string => "Set by variables key"; + "actual_value" string => "$(default:def.my_var)"; + "expected_difference" string=> "no"; + "test" string => "$(this.promise_filename)"; + + methods: + "Pass/Fail" usebundle => dcs_check_strcmp($(expected_value), + $(actual_value), + $(test), + $(expected_difference)); +} diff --git a/tests/acceptance/00_basics/def.json/def.json b/tests/acceptance/00_basics/def.json/def.json new file mode 100644 index 0000000000..b6cc058a51 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/def.json @@ -0,0 +1,10 @@ +{ + "vars": { + "domain": "mydomain", + "acl": [ ".*$(def.domain) note this will remain unexpanded!!!" ], + + "input_name_patterns": [ ".*\\.cf",".*\\.dat",".*\\.txt", ".*\\.conf", ".*\\.mustache", + ".*\\.sh", ".*\\.pl", ".*\\.py", ".*\\.rb", + "cf_promises_release_id", ".*\\.json", ".*\\.yaml", ".*\\.js" ] + } +} diff --git a/tests/acceptance/00_basics/def.json/def_preferred.json/def.json b/tests/acceptance/00_basics/def.json/def_preferred.json/def.json new file mode 100644 index 0000000000..0956ddc712 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/def_preferred.json/def.json @@ -0,0 +1,6 @@ +{ + "vars": + { + "bad_var": "foo" + } +} diff --git a/tests/acceptance/00_basics/def.json/def_preferred.json/def_preferred.cf b/tests/acceptance/00_basics/def.json/def_preferred.json/def_preferred.cf new file mode 100644 index 0000000000..b12827224f --- /dev/null +++ b/tests/acceptance/00_basics/def.json/def_preferred.json/def_preferred.cf @@ -0,0 +1,42 @@ +body common control +{ + # Feel free to avoid including "default.cf.sub" and define your + # own bundlesequence for simple tests + + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +# Initialize the environment and prepare for test +{ + +} + +bundle agent test +# Activate policy for behaviour you wish to inspect +{ + meta: + "description" -> { "CFE-3656" } + string => "def_preferred.json is preferred over def.json if it exists."; + classes: + "ok" + if => and( + strcmp(length(variablesmatching(".*\.good_var")), "1"), + strcmp(length(variablesmatching(".*\.bad_var")), "0"), + fileexists("$(this.promise_dirname)/def.json"), + fileexists("$(this.promise_dirname)/def_preferred.json")), + scope => "namespace"; +} + +bundle agent check +# Check the result of the test +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/def.json/def_preferred.json/def_preferred.json b/tests/acceptance/00_basics/def.json/def_preferred.json/def_preferred.json new file mode 100644 index 0000000000..921ad5459d --- /dev/null +++ b/tests/acceptance/00_basics/def.json/def_preferred.json/def_preferred.json @@ -0,0 +1,6 @@ +{ + "vars": + { + "good_var": "foo" + } +} diff --git a/tests/acceptance/00_basics/def.json/inputs.cf b/tests/acceptance/00_basics/def.json/inputs.cf new file mode 100644 index 0000000000..45b2f83d92 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/inputs.cf @@ -0,0 +1,40 @@ +# basic test of the def.json facility: inputs +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ' +body common control +{ + inputs => { @(def.augments_inputs) }; +} +'); + "" usebundle => file_make("$(sys.inputdir)/secondary.cf", ' +bundle common x +{ + classes: + "test_class_9f606e44752ce34ef39ee7d4754c5c84890d2b14" expression => "any"; +} +'); + + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf|$(G.grep) test_class"; + + methods: + "" usebundle => dcs_passif_output("test_class_9f606e44752ce34ef39ee7d4754c5c84890d2b14\s+source=promise", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/inputs.cf.json b/tests/acceptance/00_basics/def.json/inputs.cf.json new file mode 100644 index 0000000000..4986384df1 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/inputs.cf.json @@ -0,0 +1,3 @@ +{ + "inputs": [ "$(sys.inputdir)/secondary.cf" ] +} diff --git a/tests/acceptance/00_basics/def.json/json-inputs-not-array.cf b/tests/acceptance/00_basics/def.json/json-inputs-not-array.cf new file mode 100644 index 0000000000..446fe6b259 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/json-inputs-not-array.cf @@ -0,0 +1,35 @@ +# Test that a def.json inputs section that doesn't contain an array produces an +# error message. +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ' +body common control +{ + inputs => { @(def.augments_inputs) }; +} +'); + + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) -f $(sys.inputdir)/promises.cf"; + + methods: + "" usebundle => dcs_passif_output(".*Trying to augment inputs in [^\n]* but the value was not a list of string.*", + "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/json-inputs-not-array.cf.json b/tests/acceptance/00_basics/def.json/json-inputs-not-array.cf.json new file mode 100644 index 0000000000..be055fc28a --- /dev/null +++ b/tests/acceptance/00_basics/def.json/json-inputs-not-array.cf.json @@ -0,0 +1,3 @@ +{ + "inputs": { "there-should-not-be-a-key-here": "nor-a-value" } +} diff --git a/tests/acceptance/00_basics/def.json/json-inputs-not-auto-applied.cf b/tests/acceptance/00_basics/def.json/json-inputs-not-auto-applied.cf new file mode 100644 index 0000000000..6578101594 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/json-inputs-not-auto-applied.cf @@ -0,0 +1,40 @@ +# Test that def.json:inputs is not auto loaded. +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ' +body common control +{ + inputs => { }; +} +'); + "" usebundle => file_make("$(sys.inputdir)/secondary.cf", ' +bundle common x +{ + classes: + "test_class_8f606e44752ce34ef39ee7d4754c5c84890d2b14" expression => "any"; +} +'); + + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf|$(G.grep) test_class"; + + methods: + "" usebundle => dcs_passif_output("", ".*test_class_8f606e44752ce34ef39ee7d4754c5c84890d2b14.*", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/json-inputs-not-auto-applied.cf.json b/tests/acceptance/00_basics/def.json/json-inputs-not-auto-applied.cf.json new file mode 100644 index 0000000000..4986384df1 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/json-inputs-not-auto-applied.cf.json @@ -0,0 +1,3 @@ +{ + "inputs": [ "$(sys.inputdir)/secondary.cf" ] +} diff --git a/tests/acceptance/00_basics/def.json/policy_server_classes.cf b/tests/acceptance/00_basics/def.json/policy_server_classes.cf new file mode 100644 index 0000000000..9d2361d8d5 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/policy_server_classes.cf @@ -0,0 +1,29 @@ +# basic test of the def.json facility: classes +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_make("$(sys.statedir)/am_policy_hub", ''); + "" usebundle => file_make("$(sys.workdir)/policy_server.dat", 'fake-server'); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-classes -f $(sys.inputdir)/promises.cf|$(G.grep) test_class"; + + methods: + "" usebundle => dcs_passif_output("test_class_policy_server\s+source=augments_file", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/policy_server_classes.cf.json b/tests/acceptance/00_basics/def.json/policy_server_classes.cf.json new file mode 100644 index 0000000000..bd84880689 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/policy_server_classes.cf.json @@ -0,0 +1,6 @@ +{ + "classes": + { + "test_class_policy_server": [ "am_policy_hub" ], + } +} diff --git a/tests/acceptance/00_basics/def.json/show_vars_JSON.cf b/tests/acceptance/00_basics/def.json/show_vars_JSON.cf new file mode 100644 index 0000000000..5c26d1865f --- /dev/null +++ b/tests/acceptance/00_basics/def.json/show_vars_JSON.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: classes +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -w $(sys.workdir)|$(G.grep) test_var"; + + methods: + "" usebundle => dcs_passif_output('default:def.test_var\\s+\\{"387c108886725091c6fe2433a9fcafa0820dd61f":\\["8e92eeab70dbef876aeac9d97a74cfdde6f53e86"\\]\\}.*', "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/show_vars_JSON.cf.json b/tests/acceptance/00_basics/def.json/show_vars_JSON.cf.json new file mode 100644 index 0000000000..5c694985f3 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/show_vars_JSON.cf.json @@ -0,0 +1,6 @@ +{ + "vars": + { + "test_var": { "387c108886725091c6fe2433a9fcafa0820dd61f": [ "8e92eeab70dbef876aeac9d97a74cfdde6f53e86" ] } + } +} diff --git a/tests/acceptance/00_basics/def.json/state.cf b/tests/acceptance/00_basics/def.json/state.cf new file mode 100644 index 0000000000..5fee065ed2 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/state.cf @@ -0,0 +1,26 @@ +# full test of the def.json facility +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(def, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); + reports: + "augments policy = $(this.promise_filename)"; + "domain = $(def.domain)"; +} diff --git a/tests/acceptance/00_basics/def.json/state.cf.expected.json b/tests/acceptance/00_basics/def.json/state.cf.expected.json new file mode 100644 index 0000000000..979593ffff --- /dev/null +++ b/tests/acceptance/00_basics/def.json/state.cf.expected.json @@ -0,0 +1,22 @@ +{ + "acl": [ + ".*$(def.domain) note this will remain unexpanded!!!" + ], + "domain": "mydomain", + "input_name_patterns": [ + ".*\\.cf", + ".*\\.dat", + ".*\\.txt", + ".*\\.conf", + ".*\\.mustache", + ".*\\.sh", + ".*\\.pl", + ".*\\.py", + ".*\\.rb", + "cf_promises_release_id", + ".*\\.json", + ".*\\.yaml", + ".*\\.js" + ], + "jq": "jq --compact-output --monochrome-output --ascii-output --unbuffered --sort-keys" +} diff --git a/tests/acceptance/00_basics/def.json/variables.cf b/tests/acceptance/00_basics/def.json/variables.cf new file mode 100644 index 0000000000..9280795336 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: variables +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf|$(G.grep) test_variable"; + + methods: + "" usebundle => dcs_passif_output("default:def.test_variable\s+37bff81c825bd57a613ff4770b7a0679ff147bdf\s+test_tag,source=augments_file\s+comment1", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/variables.cf.json b/tests/acceptance/00_basics/def.json/variables.cf.json new file mode 100644 index 0000000000..2e9d0e0def --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables.cf.json @@ -0,0 +1,9 @@ +{ + "variables": { + "test_variable": { + "value": "37bff81c825bd57a613ff4770b7a0679ff147bdf", + "tags": ["test_tag"], + "comment": "comment1" + } + } +} diff --git a/tests/acceptance/00_basics/def.json/variables_bare_array.cf b/tests/acceptance/00_basics/def.json/variables_bare_array.cf new file mode 100644 index 0000000000..76ea51d0e1 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables_bare_array.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: variables +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf | $(G.grep) bare_test"; + + methods: + "" usebundle => dcs_passif_output("default:def.bare_test_array\s+.*bare_test_array_element.*", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/variables_bare_array.cf.json b/tests/acceptance/00_basics/def.json/variables_bare_array.cf.json new file mode 100644 index 0000000000..a8b34ba5b9 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables_bare_array.cf.json @@ -0,0 +1,5 @@ +{ + "variables": { + "bare_test_array": ["bare_test_array_element"] + } +} diff --git a/tests/acceptance/00_basics/def.json/variables_bare_string.cf b/tests/acceptance/00_basics/def.json/variables_bare_string.cf new file mode 100644 index 0000000000..3a41ebcdc3 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables_bare_string.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: variables +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf | $(G.grep) bare_test"; + + methods: + "" usebundle => dcs_passif_output("default:def.bare_test_string\s+bare_test_string_value\s.*", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/variables_bare_string.cf.json b/tests/acceptance/00_basics/def.json/variables_bare_string.cf.json new file mode 100644 index 0000000000..f7f4359102 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables_bare_string.cf.json @@ -0,0 +1,5 @@ +{ + "variables": { + "bare_test_string": "bare_test_string_value" + } +} diff --git a/tests/acceptance/00_basics/def.json/variables_with_bundle.cf b/tests/acceptance/00_basics/def.json/variables_with_bundle.cf new file mode 100644 index 0000000000..62d94417f9 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables_with_bundle.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: variables with bundle specification +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf|$(G.grep) test_variable"; + + methods: + "" usebundle => dcs_passif_output("default:config.test_variable\s+37bff81c825bd57a613ff4770b7a0679ff147bdf\s+test_tag,source=augments_file", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/variables_with_bundle.cf.json b/tests/acceptance/00_basics/def.json/variables_with_bundle.cf.json new file mode 100644 index 0000000000..556a959802 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables_with_bundle.cf.json @@ -0,0 +1,8 @@ +{ + "variables": { + "config.test_variable": { + "value": "37bff81c825bd57a613ff4770b7a0679ff147bdf", + "tags": ["test_tag"] + } + } +} diff --git a/tests/acceptance/00_basics/def.json/variables_with_ns.cf b/tests/acceptance/00_basics/def.json/variables_with_ns.cf new file mode 100644 index 0000000000..e59d9a9ba3 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables_with_ns.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: variables with namespace and bundle specification +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf|$(G.grep) test_variable"; + + methods: + "" usebundle => dcs_passif_output("my_ns:config.test_variable\s+37bff81c825bd57a613ff4770b7a0679ff147bdf\s+test_tag,source=augments_file", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/variables_with_ns.cf.json b/tests/acceptance/00_basics/def.json/variables_with_ns.cf.json new file mode 100644 index 0000000000..a227981fd7 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/variables_with_ns.cf.json @@ -0,0 +1,8 @@ +{ + "variables": { + "my_ns:config.test_variable": { + "value": "37bff81c825bd57a613ff4770b7a0679ff147bdf", + "tags": ["test_tag"] + } + } +} diff --git a/tests/acceptance/00_basics/def.json/vars.cf b/tests/acceptance/00_basics/def.json/vars.cf new file mode 100644 index 0000000000..fe2b0afe7c --- /dev/null +++ b/tests/acceptance/00_basics/def.json/vars.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: vars +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf|$(G.grep) test_variable"; + + methods: + "" usebundle => dcs_passif_output("default:def.test_variable\s+37bff81c825bd57a613ff4770b7a0679ff147bdf\s+source=augments_file", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/vars.cf.json b/tests/acceptance/00_basics/def.json/vars.cf.json new file mode 100644 index 0000000000..c88eba552d --- /dev/null +++ b/tests/acceptance/00_basics/def.json/vars.cf.json @@ -0,0 +1,11 @@ +{ + "classes": + { + "class_to_define_if": [ "one", "of", "these", "matches_classmatch" ], + "my_other_example": [ "server[34]", "debian.*" ] + }, + + "vars": { + "test_variable": "37bff81c825bd57a613ff4770b7a0679ff147bdf" + } +} diff --git a/tests/acceptance/00_basics/def.json/vars_with_bundle.cf b/tests/acceptance/00_basics/def.json/vars_with_bundle.cf new file mode 100644 index 0000000000..9f35de0762 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/vars_with_bundle.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: vars with bundle specification +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf|$(G.grep) test_variable"; + + methods: + "" usebundle => dcs_passif_output("default:config.test_variable\s+37bff81c825bd57a613ff4770b7a0679ff147bdf\s+source=augments_file", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/vars_with_bundle.cf.json b/tests/acceptance/00_basics/def.json/vars_with_bundle.cf.json new file mode 100644 index 0000000000..de61361e82 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/vars_with_bundle.cf.json @@ -0,0 +1,5 @@ +{ + "vars": { + "config.test_variable": "37bff81c825bd57a613ff4770b7a0679ff147bdf" + } +} diff --git a/tests/acceptance/00_basics/def.json/vars_with_ns.cf b/tests/acceptance/00_basics/def.json/vars_with_ns.cf new file mode 100644 index 0000000000..a10f97c283 --- /dev/null +++ b/tests/acceptance/00_basics/def.json/vars_with_ns.cf @@ -0,0 +1,27 @@ +# basic test of the def.json facility: vars with namespace and bundle specification +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => file_make("$(sys.inputdir)/promises.cf", ''); + "" usebundle => file_copy("$(this.promise_filename).json", "$(sys.inputdir)/def.json"); +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_promises) --show-vars -f $(sys.inputdir)/promises.cf|$(G.grep) test_variable"; + + methods: + "" usebundle => dcs_passif_output("my_ns:config.test_variable\s+37bff81c825bd57a613ff4770b7a0679ff147bdf\s+source=augments_file", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/def.json/vars_with_ns.cf.json b/tests/acceptance/00_basics/def.json/vars_with_ns.cf.json new file mode 100644 index 0000000000..27678912fe --- /dev/null +++ b/tests/acceptance/00_basics/def.json/vars_with_ns.cf.json @@ -0,0 +1,11 @@ +{ + "classes": + { + "class_to_define_if": [ "one", "of", "these", "matches_classmatch" ], + "my_other_example": [ "server[34]", "debian.*" ] + }, + + "vars": { + "my_ns:config.test_variable": "37bff81c825bd57a613ff4770b7a0679ff147bdf" + } +} diff --git a/tests/acceptance/00_basics/environment/hpux.cf b/tests/acceptance/00_basics/environment/hpux.cf new file mode 100644 index 0000000000..f95d4e21bc --- /dev/null +++ b/tests/acceptance/00_basics/environment/hpux.cf @@ -0,0 +1,53 @@ +############################################################################## +# +# Redmine #3842: ensure correct sys.flavor, sys.arch on HP-UX +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + + +bundle agent test +{ +} + + +bundle agent check +{ + vars: + "expected[flavor]" string => ifelse("hpux", "hp-ux_.+", + "unknown"); + + "expected[arch]" string => ifelse("hpux", "ia64", + "unknown"); + + "checks" slist => getindices("expected"); + + # If the output contains the string, we fail + classes: + "ok_$(checks)" expression => regcmp("$(expected[$(checks)])", "$(sys.$(checks))"); + "unknown_$(checks)" expression => strcmp("$(expected[$(checks)])", "unknown"); + + "ok" and => { "ok_flavor", "ok_arch" }; + "skipped" and => { "unknown_flavor", "unknown_arch" }; + + reports: + DEBUG:: + "check $(checks) was OK" if => "ok_$(checks)"; + "check $(checks) was not OK" if => "!ok_$(checks).!unknown_$(checks)"; + "check $(checks) was unknown (skipped)" if => "unknown_$(checks)"; + + ok||skipped:: + "$(this.promise_filename) Pass"; + !ok.!skipped:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/environment/proc-net-functions.cf b/tests/acceptance/00_basics/environment/proc-net-functions.cf new file mode 100644 index 0000000000..dba5dda097 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc-net-functions.cf @@ -0,0 +1,17 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!linux"; + + commands: + "$(G.env) CFENGINE_TEST_OVERRIDE_PROCDIR=$(this.promise_dirname)/proc $(sys.cf_agent) -DAUTO -f $(this.promise_filename).sub"; +} diff --git a/tests/acceptance/00_basics/environment/proc-net-functions.cf.sub b/tests/acceptance/00_basics/environment/proc-net-functions.cf.sub new file mode 100644 index 0000000000..84ecb38768 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc-net-functions.cf.sub @@ -0,0 +1,32 @@ +# you can run this test directly with + +# CFENGINE_TEST_OVERRIDE_PROCDIR=`pwd`/00_basics/environment/proc testall 00_basics/environment/proc-net-functions.cf.sub + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "connections" data => network_connections(); +} + +bundle agent check +{ + vars: + "testname" string => regex_replace($(this.promise_filename), "\\.sub$", "", ""); + + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(testname)); + + test_debug:: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/environment/proc-net-functions.cf.sub.expected.json b/tests/acceptance/00_basics/environment/proc-net-functions.cf.sub.expected.json new file mode 100644 index 0000000000..e967f2ebe8 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc-net-functions.cf.sub.expected.json @@ -0,0 +1,73470 @@ +{ + "connections": { + "tcp": [ + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "0.0.0.0", + "port": "48367" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "22" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "631" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8888" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "57983" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "127.0.0.1", + "port": "8000" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0.0.0.0", + "port": "24800" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "192.168.2.50", + "port": "35164" + }, + "remote": { + "address": "173.194.123.46", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "55774" + }, + "remote": { + "address": "74.125.226.67", + "port": "443" + }, + "state": "TIME_WAIT" + }, + { + "local": { + "address": "192.168.2.50", + "port": "47250" + }, + "remote": { + "address": "74.125.22.188", + "port": "5228" + }, + "state": "ESTABLISHED" + } + ], + "tcp6": [ + { + "local": { + "address": "0:0:0:0:0:0:0:0", + "port": "22" + }, + "remote": { + "address": "0:0:0:0:0:0:0:0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0:0:0:0:0:0:100:0", + "port": "631" + }, + "remote": { + "address": "0:0:0:0:0:0:0:0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0:0:0:0:0:0:0:0", + "port": "57983" + }, + "remote": { + "address": "0:0:0:0:0:0:0:0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0:0:0:0:0:0:100:0", + "port": "8000" + }, + "remote": { + "address": "0:0:0:0:0:0:0:0", + "port": "0" + }, + "state": "UNKNOWN" + }, + { + "local": { + "address": "0:0:0:0:0:0:100:0", + "port": "45530" + }, + "remote": { + "address": "0:0:0:0:0:0:100:0", + "port": "631" + }, + "state": "CLOSE_WAIT" + }, + { + "local": { + "address": "0:0:0:0:0:0:100:0", + "port": "46070" + }, + "remote": { + "address": "0:0:0:0:0:0:100:0", + "port": "631" + }, + "state": "CLOSE_WAIT" + } + ], + "udp": [ + { + "local": { + "address": "0.0.0.0", + "port": "1900" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "CLOSE" + }, + { + "local": { + "address": "192.168.2.50", + "port": "46629" + }, + "remote": { + "address": "192.168.2.1", + "port": "5351" + }, + "state": "ESTABLISHED" + }, + { + "local": { + "address": "127.0.1.1", + "port": "53" + }, + "remote": { + "address": "0.0.0.0", + "port": "0" + }, + "state": "CLOSE" + } + ], + "udp6": [ + { + "local": { + "address": "0:0:0:0:0:0:0:0", + "port": "5353" + }, + "remote": { + "address": "0:0:0:0:0:0:0:0", + "port": "0" + }, + "state": "CLOSE" + }, + { + "local": { + "address": "0:0:0:0:0:0:0:0", + "port": "47782" + }, + "remote": { + "address": "0:0:0:0:0:0:0:0", + "port": "0" + }, + "state": "CLOSE" + }, + { + "local": { + "address": "0:0:0:0:0:0:0:0", + "port": "16411" + }, + "remote": { + "address": "0:0:0:0:0:0:0:0", + "port": "0" + }, + "state": "CLOSE" + } + ] + } +} diff --git a/tests/acceptance/00_basics/environment/proc-net.cf b/tests/acceptance/00_basics/environment/proc-net.cf new file mode 100644 index 0000000000..dba5dda097 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc-net.cf @@ -0,0 +1,17 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!linux"; + + commands: + "$(G.env) CFENGINE_TEST_OVERRIDE_PROCDIR=$(this.promise_dirname)/proc $(sys.cf_agent) -DAUTO -f $(this.promise_filename).sub"; +} diff --git a/tests/acceptance/00_basics/environment/proc-net.cf.sub b/tests/acceptance/00_basics/environment/proc-net.cf.sub new file mode 100644 index 0000000000..53c317c26c --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc-net.cf.sub @@ -0,0 +1,39 @@ +# you can run this test directly with + +# CFENGINE_TEST_OVERRIDE_PROCDIR=`pwd`/00_basics/environment/proc-net.cf.proc testall 00_basics/environment/proc-net.cf.sub + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "todo" slist => { + "inet", "inet6", + "interfaces_data" + }; + + "$(todo)" data => mergedata("sys.$(todo)"); + # make sure deep lookups work + "docker" string => "$(sys.inet6[addresses][docker0][address])"; + "default_gateway" string => "$(sys.inet[default_gateway])"; +} + +bundle agent check +{ + vars: + "testname" string => regex_replace($(this.promise_filename), "\\.sub$", "", ""); + + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(testname)); + test_debug:: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/environment/proc-net.cf.sub.expected.json b/tests/acceptance/00_basics/environment/proc-net.cf.sub.expected.json new file mode 100644 index 0000000000..788126b719 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc-net.cf.sub.expected.json @@ -0,0 +1,438 @@ +{ + "default_gateway": "192.168.2.1", + "docker": "fe80:0:0:0:42:49ff:febd:d7b4", + "inet": { + "default_gateway": "192.168.2.1", + "default_route": { + "active_default_gateway": true, + "dest": "0.0.0.0", + "flags": [ + "up", + "net", + "default", + "gateway" + ], + "gateway": "192.168.2.1", + "interface": "enp4s0", + "irtt": 0, + "mask": "0.0.0.0", + "metric": 100, + "mtu": 0, + "refcnt": 0, + "use": 0, + "window": 0 + }, + "routes": [ + { + "active_default_gateway": true, + "dest": "0.0.0.0", + "flags": [ + "up", + "net", + "default", + "gateway" + ], + "gateway": "192.168.2.1", + "interface": "enp4s0", + "irtt": 0, + "mask": "0.0.0.0", + "metric": 100, + "mtu": 0, + "refcnt": 0, + "use": 0, + "window": 0 + }, + { + "active_default_gateway": false, + "dest": "169.254.0.0", + "flags": [ + "up", + "net", + "not_default", + "local" + ], + "gateway": "0.0.0.0", + "interface": "enp4s0", + "irtt": 0, + "mask": "255.255.0.0", + "metric": 1000, + "mtu": 0, + "refcnt": 0, + "use": 0, + "window": 0 + }, + { + "active_default_gateway": false, + "dest": "192.168.2.0", + "flags": [ + "up", + "net", + "not_default", + "local" + ], + "gateway": "0.0.0.0", + "interface": "enp4s0", + "irtt": 0, + "mask": "255.255.255.0", + "metric": 100, + "mtu": 0, + "refcnt": 0, + "use": 0, + "window": 0 + } + ], + "stats": { + "IpExt": { + "InBcastOctets": "817859007", + "InBcastPkts": "3784495", + "InCEPkts": "543340", + "InCsumErrors": "1", + "InECT0Pkts": "4804476", + "InECT1Pkts": "18258", + "InMcastOctets": "334973818", + "InMcastPkts": "1304886", + "InNoECTPkts": "487495405", + "InNoRoutes": "0", + "InOctets": "437612883789", + "InTruncatedPkts": "0", + "OutBcastOctets": "284", + "OutBcastPkts": "6", + "OutMcastOctets": "8189234", + "OutMcastPkts": "130589", + "OutOctets": "422416538003" + }, + "TcpExt": { + "ArpFilter": "0", + "BusyPollRxPackets": "0", + "DelayedACKLocked": "302", + "DelayedACKLost": "313016", + "DelayedACKs": "2049614", + "EmbryonicRsts": "19896", + "IPReversePathFilter": "0", + "ListenDrops": "0", + "ListenOverflows": "0", + "LockDroppedIcmps": "0", + "OfoPruned": "0", + "OutOfWindowIcmps": "9", + "PAWSActive": "0", + "PAWSEstab": "3575", + "PAWSPassive": "0", + "PruneCalled": "7", + "RcvPruned": "0", + "SyncookiesFailed": "0", + "SyncookiesRecv": "0", + "SyncookiesSent": "0", + "TCPACKSkippedChallenge": "218", + "TCPACKSkippedFinWait2": "0", + "TCPACKSkippedPAWS": "484", + "TCPACKSkippedSeq": "21848", + "TCPACKSkippedSynRecv": "136", + "TCPACKSkippedTimeWait": "25", + "TCPAbortFailed": "0", + "TCPAbortOnClose": "13931", + "TCPAbortOnData": "211213", + "TCPAbortOnLinger": "0", + "TCPAbortOnMemory": "0", + "TCPAbortOnTimeout": "14556", + "TCPAutoCorking": "17925237", + "TCPBacklogDrop": "0", + "TCPChallengeACK": "7132", + "TCPDSACKIgnoredNoUndo": "65103", + "TCPDSACKIgnoredOld": "594", + "TCPDSACKOfoRecv": "4389", + "TCPDSACKOfoSent": "9062", + "TCPDSACKOldSent": "391776", + "TCPDSACKRecv": "174837", + "TCPDSACKUndo": "20959", + "TCPDeferAcceptDrop": "0", + "TCPDirectCopyFromBacklog": "130554", + "TCPDirectCopyFromPrequeue": "186252521", + "TCPFACKReorder": "175", + "TCPFastOpenActive": "0", + "TCPFastOpenActiveFail": "0", + "TCPFastOpenCookieReqd": "0", + "TCPFastOpenListenOverflow": "0", + "TCPFastOpenPassive": "0", + "TCPFastOpenPassiveFail": "0", + "TCPFastRetrans": "512579", + "TCPForwardRetrans": "72057", + "TCPFromZeroWindowAdv": "24", + "TCPFullUndo": "2028", + "TCPHPAcks": "42386136", + "TCPHPHits": "126381259", + "TCPHPHitsToUser": "21978", + "TCPHystartDelayCwnd": "49843", + "TCPHystartDelayDetect": "2253", + "TCPHystartTrainCwnd": "992", + "TCPHystartTrainDetect": "33", + "TCPKeepAlive": "503111", + "TCPLossFailures": "38869", + "TCPLossProbeRecovery": "150370", + "TCPLossProbes": "561590", + "TCPLossUndo": "148926", + "TCPLostRetransmit": "16709", + "TCPMD5NotFound": "0", + "TCPMD5Unexpected": "0", + "TCPMemoryPressures": "0", + "TCPMinTTLDrop": "0", + "TCPOFODrop": "0", + "TCPOFOMerge": "7020", + "TCPOFOQueue": "5236349", + "TCPOrigDataSent": "275201738", + "TCPPartialUndo": "483", + "TCPPrequeueDropped": "0", + "TCPPrequeued": "17283401", + "TCPPureAcks": "34307113", + "TCPRcvCoalesce": "36402752", + "TCPRcvCollapsed": "106", + "TCPRenoFailures": "267", + "TCPRenoRecovery": "481", + "TCPRenoRecoveryFail": "23", + "TCPRenoReorder": "11", + "TCPReqQFullDoCookies": "0", + "TCPReqQFullDrop": "0", + "TCPRetransFail": "15", + "TCPSACKDiscard": "585", + "TCPSACKReneging": "7", + "TCPSACKReorder": "316", + "TCPSYNChallenge": "4057", + "TCPSackFailures": "271328", + "TCPSackMerged": "0", + "TCPSackRecovery": "386568", + "TCPSackRecoveryFail": "59420", + "TCPSackShiftFallback": "2199955", + "TCPSackShifted": "0", + "TCPSchedulerFailed": "0", + "TCPSlowStartRetrans": "281202", + "TCPSpuriousRTOs": "100117", + "TCPSpuriousRtxHostQueues": "70", + "TCPSynRetrans": "693624", + "TCPTSReorder": "822", + "TCPTimeWaitOverflow": "0", + "TCPTimeouts": "375133", + "TCPToZeroWindowAdv": "30", + "TCPWantZeroWindowAdv": "71", + "TCPWinProbe": "10478", + "TW": "560727", + "TWKilled": "0", + "TWRecycled": "0" + } + } + }, + "inet6": { + "addresses": { + "docker0": { + "address": "fe80:0:0:0:42:49ff:febd:d7b4", + "device_number": 4, + "interface": "docker0", + "prefix_length": 64, + "raw_flags": "80", + "scope": 32 + }, + "enp4s0": { + "address": "fe80:0:0:0:c27c:d1ff:fe3e:ada6", + "device_number": 2, + "interface": "enp4s0", + "prefix_length": 64, + "raw_flags": "80", + "scope": 32 + }, + "lo": { + "address": "0:0:0:0:0:0:0:1", + "device_number": 1, + "interface": "lo", + "prefix_length": 128, + "raw_flags": "80", + "scope": 16 + } + }, + "routes": [ + { + "dest": "0:0:0:0:0:0:0:0", + "dest_prefix": "40", + "flags": [ + "up", + "net", + "local" + ], + "interface": "enp4s0", + "metric": 256, + "next_hop": "0:0:0:0:0:0:0:0", + "refcnt": 1, + "source_prefix": "00", + "use": 4 + }, + { + "dest": "0:0:0:0:0:0:0:0", + "dest_prefix": "80", + "flags": [ + "up", + "net", + "local" + ], + "interface": "lo", + "metric": 0, + "next_hop": "0:0:0:0:0:0:0:0", + "refcnt": 2, + "source_prefix": "00", + "use": 4 + } + ], + "stats": { + "Icmp6InCsumErrors": 0, + "Icmp6InDestUnreachs": 0, + "Icmp6InEchoReplies": 0, + "Icmp6InEchos": 0, + "Icmp6InErrors": 0, + "Icmp6InGroupMembQueries": 0, + "Icmp6InGroupMembReductions": 1, + "Icmp6InGroupMembResponses": 1, + "Icmp6InMLDv2Reports": 0, + "Icmp6InMsgs": 275, + "Icmp6InNeighborAdvertisements": 268, + "Icmp6InNeighborSolicits": 5, + "Icmp6InParmProblems": 0, + "Icmp6InPktTooBigs": 0, + "Icmp6InRedirects": 0, + "Icmp6InRouterAdvertisements": 0, + "Icmp6InRouterSolicits": 0, + "Icmp6InTimeExcds": 0, + "Icmp6InType131": 1, + "Icmp6InType132": 1, + "Icmp6InType135": 5, + "Icmp6InType136": 268, + "Icmp6OutDestUnreachs": 0, + "Icmp6OutEchoReplies": 0, + "Icmp6OutEchos": 0, + "Icmp6OutErrors": 0, + "Icmp6OutGroupMembQueries": 0, + "Icmp6OutGroupMembReductions": 0, + "Icmp6OutGroupMembResponses": 0, + "Icmp6OutMLDv2Reports": 1208, + "Icmp6OutMsgs": 1815, + "Icmp6OutNeighborAdvertisements": 5, + "Icmp6OutNeighborSolicits": 206, + "Icmp6OutParmProblems": 0, + "Icmp6OutPktTooBigs": 0, + "Icmp6OutRedirects": 0, + "Icmp6OutRouterAdvertisements": 0, + "Icmp6OutRouterSolicits": 396, + "Icmp6OutTimeExcds": 0, + "Icmp6OutType133": 396, + "Icmp6OutType135": 206, + "Icmp6OutType136": 5, + "Icmp6OutType143": 1208, + "Ip6FragCreates": 0, + "Ip6FragFails": 0, + "Ip6FragOKs": 0, + "Ip6InAddrErrors": 0, + "Ip6InBcastOctets": 0, + "Ip6InCEPkts": 0, + "Ip6InDelivers": 490145, + "Ip6InDiscards": 0, + "Ip6InECT0Pkts": 0, + "Ip6InECT1Pkts": 0, + "Ip6InHdrErrors": 0, + "Ip6InMcastOctets": 131896014, + "Ip6InMcastPkts": 488766, + "Ip6InNoECTPkts": 492196, + "Ip6InNoRoutes": 0, + "Ip6InOctets": 132343220, + "Ip6InReceives": 492189, + "Ip6InTooBigErrors": 0, + "Ip6InTruncatedPkts": 0, + "Ip6InUnknownProtos": 0, + "Ip6OutBcastOctets": 0, + "Ip6OutDiscards": 6, + "Ip6OutForwDatagrams": 0, + "Ip6OutMcastOctets": 1076616, + "Ip6OutMcastPkts": 10304, + "Ip6OutNoRoutes": 249070, + "Ip6OutOctets": 1522724, + "Ip6OutRequests": 12145, + "Ip6ReasmFails": 0, + "Ip6ReasmOKs": 0, + "Ip6ReasmReqds": 0, + "Ip6ReasmTimeout": 0, + "Udp6IgnoredMulti": 0, + "Udp6InCsumErrors": 0, + "Udp6InDatagrams": 486201, + "Udp6InErrors": 0, + "Udp6NoPorts": 0, + "Udp6OutDatagrams": 7273, + "Udp6RcvbufErrors": 0, + "Udp6SndbufErrors": 0, + "UdpLite6InCsumErrors": 0, + "UdpLite6InDatagrams": 0, + "UdpLite6InErrors": 0, + "UdpLite6NoPorts": 0, + "UdpLite6OutDatagrams": 0, + "UdpLite6RcvbufErrors": 0, + "UdpLite6SndbufErrors": 0 + } + }, + "interfaces_data": { + "enp4s0": { + "device": "enp4s0", + "receive_bytes": "446377831179", + "receive_compressed": "0", + "receive_drop": "0", + "receive_errors": "0", + "receive_fifo": "0", + "receive_frame": "0", + "receive_multicast": "0", + "receive_packets": "492136556", + "transmit_bytes": "428200856331", + "transmit_compressed": "0", + "transmit_drop": "0", + "transmit_errors": "0", + "transmit_fifo": "0", + "transmit_frame": "0", + "transmit_multicast": "0", + "transmit_packets": "499195545" + }, + "lo": { + "device": "lo", + "receive_bytes": "1210580426", + "receive_compressed": "0", + "receive_drop": "0", + "receive_errors": "0", + "receive_fifo": "0", + "receive_frame": "0", + "receive_multicast": "0", + "receive_packets": "1049790", + "transmit_bytes": "1210580426", + "transmit_compressed": "0", + "transmit_drop": "0", + "transmit_errors": "0", + "transmit_fifo": "0", + "transmit_frame": "0", + "transmit_multicast": "0", + "transmit_packets": "1049790" + }, + "wlp3s0": { + "device": "wlp3s0", + "receive_bytes": "0", + "receive_compressed": "0", + "receive_drop": "0", + "receive_errors": "0", + "receive_fifo": "0", + "receive_frame": "0", + "receive_multicast": "0", + "receive_packets": "0", + "transmit_bytes": "0", + "transmit_compressed": "0", + "transmit_drop": "0", + "transmit_errors": "0", + "transmit_fifo": "0", + "transmit_frame": "0", + "transmit_multicast": "0", + "transmit_packets": "0" + } + }, + "todo": [ + "inet", + "inet6", + "interfaces_data" + ] +} diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/dev b/tests/acceptance/00_basics/environment/proc/proc/net/dev new file mode 100644 index 0000000000..232827e8d3 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/dev @@ -0,0 +1,5 @@ +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed +enp4s0: 446377831179 492136556 0 0 0 0 0 0 428200856331 499195545 0 0 0 0 0 0 + lo: 1210580426 1049790 0 0 0 0 0 0 1210580426 1049790 0 0 0 0 0 0 +wlp3s0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/if_inet6 b/tests/acceptance/00_basics/environment/proc/proc/net/if_inet6 new file mode 100644 index 0000000000..1d9e885cb3 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/if_inet6 @@ -0,0 +1,3 @@ +00000000000000000000000000000001 01 80 10 80 lo +fe80000000000000004249fffebdd7b4 04 40 20 80 docker0 +fe80000000000000c27cd1fffe3eada6 02 40 20 80 enp4s0 diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/ipv6_route b/tests/acceptance/00_basics/environment/proc/proc/net/ipv6_route new file mode 100644 index 0000000000..196cc6d8ae --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/ipv6_route @@ -0,0 +1,6 @@ +fe800000000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000001 00000004 00000001 enp4s0 +00000000000000000000000000000000 00 00000000000000000000000000000000 00 00000000000000000000000000000000 ffffffff 00000001 0007e26c 00200200 lo +00000000000000000000000000000001 80 00000000000000000000000000000000 00 00000000000000000000000000000000 00000000 00000009 0000020b 80200001 lo +fe80000000000000c27cd1fffe3eada6 80 00000000000000000000000000000000 00 00000000000000000000000000000000 00000000 00000002 00000004 80200001 lo +ff000000000000000000000000000000 08 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000008 0003ffc5 00000001 enp4s0 +00000000000000000000000000000000 00 00000000000000000000000000000000 00 00000000000000000000000000000000 ffffffff 00000001 0007e26c 00200200 lo diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/netstat b/tests/acceptance/00_basics/environment/proc/proc/net/netstat new file mode 100644 index 0000000000..19fad2b068 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/netstat @@ -0,0 +1,4 @@ +TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed EmbryonicRsts PruneCalled RcvPruned OfoPruned OutOfWindowIcmps LockDroppedIcmps ArpFilter TW TWRecycled TWKilled PAWSPassive PAWSActive PAWSEstab DelayedACKs DelayedACKLocked DelayedACKLost ListenOverflows ListenDrops TCPPrequeued TCPDirectCopyFromBacklog TCPDirectCopyFromPrequeue TCPPrequeueDropped TCPHPHits TCPHPHitsToUser TCPPureAcks TCPHPAcks TCPRenoRecovery TCPSackRecovery TCPSACKReneging TCPFACKReorder TCPSACKReorder TCPRenoReorder TCPTSReorder TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo TCPLostRetransmit TCPRenoFailures TCPSackFailures TCPLossFailures TCPFastRetrans TCPForwardRetrans TCPSlowStartRetrans TCPTimeouts TCPLossProbes TCPLossProbeRecovery TCPRenoRecoveryFail TCPSackRecoveryFail TCPSchedulerFailed TCPRcvCollapsed TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv TCPAbortOnData TCPAbortOnClose TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger TCPAbortFailed TCPMemoryPressures TCPSACKDiscard TCPDSACKIgnoredOld TCPDSACKIgnoredNoUndo TCPSpuriousRTOs TCPMD5NotFound TCPMD5Unexpected TCPSackShifted TCPSackMerged TCPSackShiftFallback TCPBacklogDrop TCPMinTTLDrop TCPDeferAcceptDrop IPReversePathFilter TCPTimeWaitOverflow TCPReqQFullDoCookies TCPReqQFullDrop TCPRetransFail TCPRcvCoalesce TCPOFOQueue TCPOFODrop TCPOFOMerge TCPChallengeACK TCPSYNChallenge TCPFastOpenActive TCPFastOpenActiveFail TCPFastOpenPassive TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd TCPSpuriousRtxHostQueues BusyPollRxPackets TCPAutoCorking TCPFromZeroWindowAdv TCPToZeroWindowAdv TCPWantZeroWindowAdv TCPSynRetrans TCPOrigDataSent TCPHystartTrainDetect TCPHystartTrainCwnd TCPHystartDelayDetect TCPHystartDelayCwnd TCPACKSkippedSynRecv TCPACKSkippedPAWS TCPACKSkippedSeq TCPACKSkippedFinWait2 TCPACKSkippedTimeWait TCPACKSkippedChallenge TCPWinProbe TCPKeepAlive +TcpExt: 0 0 0 19896 7 0 0 9 0 0 560727 0 0 0 0 3575 2049614 302 313016 0 0 17283401 130554 186252521 0 126381259 21978 34307113 42386136 481 386568 7 175 316 11 822 2028 483 20959 148926 16709 267 271328 38869 512579 72057 281202 375133 561590 150370 23 59420 0 106 391776 9062 174837 4389 211213 13931 0 14556 0 0 0 585 594 65103 100117 0 0 0 0 2199955 0 0 0 0 0 0 0 15 36402752 5236349 0 7020 7132 4057 0 0 0 0 0 0 70 0 17925237 24 30 71 693624 275201738 33 992 2253 49843 136 484 21848 0 25 218 10478 503111 +IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets InCsumErrors InNoECTPkts InECT1Pkts InECT0Pkts InCEPkts +IpExt: 0 0 1304886 130589 3784495 6 437612883789 422416538003 334973818 8189234 817859007 284 1 487495405 18258 4804476 543340 diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/route b/tests/acceptance/00_basics/environment/proc/proc/net/route new file mode 100644 index 0000000000..7c750b612c --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/route @@ -0,0 +1,4 @@ +Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +enp4s0 00000000 0102A8C0 0003 0 0 100 00000000 0 0 0 +enp4s0 0000FEA9 00000000 0001 0 0 1000 0000FFFF 0 0 0 +enp4s0 0002A8C0 00000000 0001 0 0 100 00FFFFFF 0 0 0 diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/snmp6 b/tests/acceptance/00_basics/environment/proc/proc/net/snmp6 new file mode 100644 index 0000000000..653166a455 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/snmp6 @@ -0,0 +1,90 @@ +Ip6InReceives 492189 +Ip6InHdrErrors 0 +Ip6InTooBigErrors 0 +Ip6InNoRoutes 0 +Ip6InAddrErrors 0 +Ip6InUnknownProtos 0 +Ip6InTruncatedPkts 0 +Ip6InDiscards 0 +Ip6InDelivers 490145 +Ip6OutForwDatagrams 0 +Ip6OutRequests 12145 +Ip6OutDiscards 6 +Ip6OutNoRoutes 249070 +Ip6ReasmTimeout 0 +Ip6ReasmReqds 0 +Ip6ReasmOKs 0 +Ip6ReasmFails 0 +Ip6FragOKs 0 +Ip6FragFails 0 +Ip6FragCreates 0 +Ip6InMcastPkts 488766 +Ip6OutMcastPkts 10304 +Ip6InOctets 132343220 +Ip6OutOctets 1522724 +Ip6InMcastOctets 131896014 +Ip6OutMcastOctets 1076616 +Ip6InBcastOctets 0 +Ip6OutBcastOctets 0 +Ip6InNoECTPkts 492196 +Ip6InECT1Pkts 0 +Ip6InECT0Pkts 0 +Ip6InCEPkts 0 +Icmp6InMsgs 275 +Icmp6InErrors 0 +Icmp6OutMsgs 1815 +Icmp6OutErrors 0 +Icmp6InCsumErrors 0 +Icmp6InDestUnreachs 0 +Icmp6InPktTooBigs 0 +Icmp6InTimeExcds 0 +Icmp6InParmProblems 0 +Icmp6InEchos 0 +Icmp6InEchoReplies 0 +Icmp6InGroupMembQueries 0 +Icmp6InGroupMembResponses 1 +Icmp6InGroupMembReductions 1 +Icmp6InRouterSolicits 0 +Icmp6InRouterAdvertisements 0 +Icmp6InNeighborSolicits 5 +Icmp6InNeighborAdvertisements 268 +Icmp6InRedirects 0 +Icmp6InMLDv2Reports 0 +Icmp6OutDestUnreachs 0 +Icmp6OutPktTooBigs 0 +Icmp6OutTimeExcds 0 +Icmp6OutParmProblems 0 +Icmp6OutEchos 0 +Icmp6OutEchoReplies 0 +Icmp6OutGroupMembQueries 0 +Icmp6OutGroupMembResponses 0 +Icmp6OutGroupMembReductions 0 +Icmp6OutRouterSolicits 396 +Icmp6OutRouterAdvertisements 0 +Icmp6OutNeighborSolicits 206 +Icmp6OutNeighborAdvertisements 5 +Icmp6OutRedirects 0 +Icmp6OutMLDv2Reports 1208 +Icmp6InType131 1 +Icmp6InType132 1 +Icmp6InType135 5 +Icmp6InType136 268 +Icmp6OutType133 396 +Icmp6OutType135 206 +Icmp6OutType136 5 +Icmp6OutType143 1208 +Udp6InDatagrams 486201 +Udp6NoPorts 0 +Udp6InErrors 0 +Udp6OutDatagrams 7273 +Udp6RcvbufErrors 0 +Udp6SndbufErrors 0 +Udp6InCsumErrors 0 +Udp6IgnoredMulti 0 +UdpLite6InDatagrams 0 +UdpLite6NoPorts 0 +UdpLite6InErrors 0 +UdpLite6OutDatagrams 0 +UdpLite6RcvbufErrors 0 +UdpLite6SndbufErrors 0 +UdpLite6InCsumErrors 0 diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/tcp b/tests/acceptance/00_basics/environment/proc/proc/net/tcp new file mode 100644 index 0000000000..388259a364 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/tcp @@ -0,0 +1,6667 @@ + sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 + 0: 00000000:BCEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828769 1 0000000000000000 100 0 0 10 0 + 1: 0101007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 11263 1 0000000000000000 100 0 0 10 0 + 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23651 1 0000000000000000 100 0 0 10 0 + 3: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827515 1 0000000000000000 100 0 0 10 0 + 4: 0100007F:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12828786 1 0000000000000000 100 0 0 10 0 + 5: 00000000:E27F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016062 1 0000000000000000 100 0 0 10 0 + 6: 0100007F:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939376 1 0000000000000000 100 0 0 10 0 + 7: 00000000:60E0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 16124 1 0000000000000000 100 0 0 10 0 + 8: 3202A8C0:895C 2E7BC2AD:01BB 06 00000000:00000000 03:00001463 00000000 0 0 0 3 0000000000000000 + 9: 3202A8C0:D9DE 43E27D4A:01BB 06 00000000:00000000 03:00000E9A 00000000 0 0 0 3 0000000000000000 + 10: 3202A8C0:B892 BC167D4A:146C 01 00000000:00000000 02:00000600 00000000 1000 0 10728629 2 0000000000000000 23 4 0 10 -1 diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/tcp6 b/tests/acceptance/00_basics/environment/proc/proc/net/tcp6 new file mode 100644 index 0000000000..8ff0722d56 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/tcp6 @@ -0,0 +1,7 @@ + sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + 0: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 23653 1 0000000000000000 100 0 0 10 0 + 1: 00000000000000000000000001000000:0277 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12827514 1 0000000000000000 100 0 0 10 0 + 2: 00000000000000000000000000000000:E27F 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 1016064 1 0000000000000000 100 0 0 10 0 + 3: 00000000000000000000000001000000:1F40 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 11939375 1 0000000000000000 100 0 0 10 0 + 4: 00000000000000000000000001000000:B1DA 00000000000000000000000001000000:0277 08 00000000:00000001 00:00000000 00000000 0 0 14023 1 0000000000000000 20 4 24 10 -1 + 5: 00000000000000000000000001000000:B3F6 00000000000000000000000001000000:0277 08 00000000:00000001 00:00000000 00000000 0 0 12522538 1 0000000000000000 20 4 24 10 -1 diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/udp b/tests/acceptance/00_basics/environment/proc/proc/net/udp new file mode 100644 index 0000000000..9bb43b9d04 --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/udp @@ -0,0 +1,4 @@ + sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops + 576: 00000000:076C 00000000:0000 07 00000000:00000000 00:00000000 00000000 1000 0 12828775 2 0000000000000000 0 +12537: 3202A8C0:B625 0102A8C0:14E7 01 00000000:00000000 00:00000000 00000000 1000 0 1020761 2 0000000000000000 0 +15113: 0101007F:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 11262 2 0000000000000000 0 diff --git a/tests/acceptance/00_basics/environment/proc/proc/net/udp6 b/tests/acceptance/00_basics/environment/proc/proc/net/udp6 new file mode 100644 index 0000000000..a04e9749ad --- /dev/null +++ b/tests/acceptance/00_basics/environment/proc/proc/net/udp6 @@ -0,0 +1,4 @@ + sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops + 4029: 00000000000000000000000000000000:14E9 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 107 0 13195 2 0000000000000000 0 +13690: 00000000000000000000000000000000:BAA6 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 107 0 13197 2 0000000000000000 0 +15087: 00000000000000000000000000000000:401B 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 15931 2 0000000000000000 0 diff --git a/tests/acceptance/00_basics/environment/workdir_correct_after_files_promise.cf b/tests/acceptance/00_basics/environment/workdir_correct_after_files_promise.cf new file mode 100644 index 0000000000..4d326a7585 --- /dev/null +++ b/tests/acceptance/00_basics/environment/workdir_correct_after_files_promise.cf @@ -0,0 +1,31 @@ +############################################################################## +# +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check +{ + meta: + + # This test should be skipped on non linux hosts when its fixed (or + # improved to do something similar on platforms that don't have /bin/pwd + "test_soft_fail" + string => "any", + meta => { "redmine7569" }; + + vars: + # When the agent runs the test, pwd should not return /tmp + "fail_string" string => 'Q: ".*/bin/pwd": /tmp'; + "command" string => "$(sys.cf-agent) -KIf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*", escape($(fail_string)), $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/environment/workdir_correct_after_files_promise.cf.sub b/tests/acceptance/00_basics/environment/workdir_correct_after_files_promise.cf.sub new file mode 100644 index 0000000000..3b71fde81e --- /dev/null +++ b/tests/acceptance/00_basics/environment/workdir_correct_after_files_promise.cf.sub @@ -0,0 +1,42 @@ +############################################################################## +# +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test_exec(id, cmd) +{ + commands: + "$(cmd)" + handle => "$(id)"; + + reports: + DEBUG|DEBUG_test_exec:: + "DEBUG $(this.bundle): $(id) $(cmd)"; +} + +bundle agent test_touch(file) +{ + files: + "$(file)" + create => "true", + touch => "true"; + + reports: + DEBUG|DEBUG_test_touch:: + "DEBUG $(this.bundle): touch '$(file)'"; +} + +bundle agent test +{ + methods: + "pwd_1" usebundle => test_exec("pwd_1", "/bin/pwd"); + "touch_1" usebundle => test_touch("$(G.testfile)"); + "pwd_2" usebundle => test_exec("pwd_2", "/bin/pwd"); +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/expireafter.cf b/tests/acceptance/00_basics/ifelapsed_and_expireafter/expireafter.cf new file mode 100644 index 0000000000..f38df35173 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/expireafter.cf @@ -0,0 +1,71 @@ +############################################################################## +# +# Test that expireafter works with commands promise type +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +############################################################################## + +bundle agent init +{ + meta: + # Solaris 9 & 10 don't seem to handle the backgrounding + "test_skip_needs_work" + string => "windows|sunos_5_9|sunos_5_10"; + files: + "$(G.testfile)" + delete => tidy; +} + +############################################################################## + +body action background { + background => "true"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1188" } + string => "Test that expireafter works with commands promise type"; + + commands: + "$(sys.cf_agent) --inform --file $(this.promise_filename).sub --" + action => background, + comment => "I will get killed by the second agent"; + "$(G.sleep) 90" + comment => "I will wait for the first agent to expire"; + "$(sys.cf_agent) --inform --file $(this.promise_filename).sub" + comment => "I will kill the first agent"; +} + +############################################################################## + +bundle agent check +{ + vars: + "expected" + string => "Hello CFEngine"; + "actual" + string => readfile("$(G.testfile)"), + if => fileexists("$(G.testfile)"); + + classes: + "ok" + expression => strcmp("$(expected)", "$(actual)"); + + reports: + DEBUG:: + "Expected '$(expected)', found '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/expireafter.cf.sub b/tests/acceptance/00_basics/ifelapsed_and_expireafter/expireafter.cf.sub new file mode 100644 index 0000000000..bd772e0f98 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/expireafter.cf.sub @@ -0,0 +1,39 @@ +############################################################################## +# +# Test that expireafter works with commands promise type +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "hang" }; + version => "1.0"; +} + +############################################################################## + +body action timeout +{ + expireafter => "1"; + ifelapsed => "0"; +} + +bundle agent hang +{ + vars: + "content" + string => ifelse(fileexists("$(G.testfile)"), + "Hello CFEngine", + "Bye CFEngine"); + + commands: + "$(G.sleep) 120" + action => timeout, + handle => "done"; + + files: + "$(G.testfile)" + content => "$(content)", + depends_on => { "done" }; +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/locks.cf b/tests/acceptance/00_basics/ifelapsed_and_expireafter/locks.cf new file mode 100644 index 0000000000..29f792aafd --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/locks.cf @@ -0,0 +1,67 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_flakey_fail" string => "sunos_5_11.sparc", + meta => { "ENT-7068" }; + + vars: + "first" + string => + execresult("$(sys.cf_agent) -KIf $(this.promise_filename).sub -b example -Dfirst", + "noshell"), + comment => "Ignore locks in this first run - it will still write the locks!"; + + "second" + string => + execresult("$(sys.cf_agent) -If $(this.promise_filename).sub -b example -Dsecond", + "noshell"), + comment => "Don't ignore locks here so for pass_only_first_test_second case"; +} + +bundle agent check +{ + vars: + "pass_strings" slist => { "test", "call_report_now", "only_first" }; + "activations" slist => { "test.first", "test.second" }; + +# we want to see all of those in either "test.first" or "test.second" + "pass_classes" slist => { + "pass_test_test_first", + "pass_call_report_now_test_first", + "pass_test_test_second", + "pass_call_report_now_test_second", + "pass_only_first_test_first", + }; + +# we don't want to any of those in neither "test.first" nor "test.second" + "fail_classes" slist => { + "pass_only_first_test_second" + }; + + classes: + "pass_$(pass_strings)_$(activations)" expression => regcmp(".*R: Called from $(pass_strings).*", + "$($(activations))"); + + "ok_pass" and => { @(pass_classes) }; + "fail_pass" or => { @(fail_classes) }; + + "ok" expression => "ok_pass.!fail_pass"; + + reports: + DEBUG.!ok:: + "Failed activation $(activations): '$($(activations))'"; + DEBUG:: + "expected : $(pass_classes)" if => not("$(pass_classes)"); + "unexpected: $(fail_classes)" if => "$(fail_classes)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/locks.cf.sub b/tests/acceptance/00_basics/ifelapsed_and_expireafter/locks.cf.sub new file mode 100644 index 0000000000..d53047c993 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/locks.cf.sub @@ -0,0 +1,52 @@ +# Test that locking works +# Called twice from main.cf + +bundle agent example +{ + methods: + "test" usebundle => test, action => now; +} + +bundle agent test +{ + vars: + first:: + "count" string => "first"; + second:: + "count" string => "second"; + + methods: +# should print "Called from test" for both runs + "Report" + usebundle => report_now($(this.bundle)), + action => now; + +# should print "Called from call_report_now" for both runs + "Nested Report" + usebundle => call_report_now, + action => now; + +# should print "Called from only_first" only for the first run + "Only first" + usebundle => report_now("only_first"); +} + +bundle agent call_report_now +{ + methods: + "Call Report" + usebundle => report_now($(this.bundle)), + action => now; +} + +bundle agent report_now(caller) +{ + reports: + "Called from $(caller) ($(test.count))" + action => now; +} + +body action now +{ + ifelapsed => "0"; +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/redmine_6334.cf b/tests/acceptance/00_basics/ifelapsed_and_expireafter/redmine_6334.cf new file mode 100644 index 0000000000..52babc3728 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/redmine_6334.cf @@ -0,0 +1,69 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_flakey_fail" string => "sunos_5_11.sparc", + meta => { "ENT-7068" }; + + vars: + "first" + string => + execresult("$(sys.cf_agent) -KIf $(this.promise_filename).sub -b example -Dfirst", + "noshell"), + comment => "Ignore locks in this first run - it will still write the locks!"; + + "second" + string => + execresult("$(sys.cf_agent) -If $(this.promise_filename).sub -b example -Dsecond", + "noshell"), + comment => "Don't ignore locks here so for pass_only_first_test_second case"; +} + +bundle agent check +{ + vars: + "pass_strings" slist => { "test", "call_report_now", "only_first", "abort_now" }; + "activations" slist => { "test.first", "test.second" }; + +# we want to see all of those in either "test.first" or "test.second" + "pass_classes" slist => { + "pass_test_test_first", + "pass_call_report_now_test_first", + "pass_test_test_second", + "pass_call_report_now_test_second", + "pass_only_first_test_first", + }; + +# we don't want to any of those in neither "test.first" nor "test.second" + "fail_classes" slist => { + "pass_only_first_test_second", + "pass_abort_now_test_first", + "pass_abort_now_test_second" + }; + + classes: + "pass_$(pass_strings)_$(activations)" expression => regcmp(".*R: Called from $(pass_strings).*", + "$($(activations))"); + + "ok_pass" and => { @(pass_classes) }; + "fail_pass" or => { @(fail_classes) }; + + "ok" expression => "ok_pass.!fail_pass"; + + reports: + DEBUG.!ok:: + "Failed activation $(activations): '$($(activations))'"; + DEBUG:: + "expected : $(pass_classes)" if => not("$(pass_classes)"); + "unexpected: $(fail_classes)" if => "$(fail_classes)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/redmine_6334.cf.sub b/tests/acceptance/00_basics/ifelapsed_and_expireafter/redmine_6334.cf.sub new file mode 100644 index 0000000000..ae44ed4176 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/redmine_6334.cf.sub @@ -0,0 +1,72 @@ +# Test that locking works and that locks are clear upon abort +# Called twice from main.cf + +body agent control +{ + abortclasses => { "abort_agent_run" }; +} + +bundle agent example +{ + methods: + "test" usebundle => test, action => now; +} + +bundle agent test +{ + vars: + first:: + "count" string => "first"; + second:: + "count" string => "second"; + + methods: +# should print "Called from test" for both runs + "Report" + usebundle => report_now($(this.bundle)), + action => now; + +# should print "Called from call_report_now" for both runs + "Nested Report" + usebundle => call_report_now, + action => now; + +# should print "Called from only_first" only for the first run + "Only first" + usebundle => report_now("only_first"); + +# should never print anything, but abort the execution + "Nested Abort" + usebundle => abort_now, + action => now; +} + +bundle agent call_report_now +{ + methods: + "Call Report" + usebundle => report_now($(this.bundle)), + action => now; +} + +bundle agent report_now(caller) +{ + reports: + "Called from $(caller) ($(test.count))" + action => now; +} + +bundle agent abort_now +{ + classes: + "abort_agent_run" expression => "any"; + + reports: + "Called from abort_now ($(test.count))" + action => now; +} + +body action now +{ + ifelapsed => "0"; +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/defaults.cf b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/defaults.cf new file mode 100644 index 0000000000..ae7c717b89 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/defaults.cf @@ -0,0 +1,103 @@ +# Tests that the defaults to ifelapsed and expireafter are correct, +# and that changing them has an effect. + +# We repeatedly run four promises, all adding a line of text to a file. What we +# test is how many times the line gets added to the file, depending on the +# value of ifelapsed and expireafter. The promise is carried out three times, +# once at 0 seconds, once at 20 seconds, and once at 75 seconds. +# +# Seconds 0----------------20----------------75 No. lines +# LR + DE Add Noop Noop 1 +# LR + SE Add Noop Add 2 +# SR + DI Add Noop Add 2 +# SR + LI Add Noop Noop 1 +# +# LR = Long Running promise +# SR = Short Running promise +# DE = Default Expireafter +# SE = Short Expireafter +# DI = Default Ifelapsed +# LI = Long Ifelapsed + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default($(this.promiser_filename)) }; +} + +bundle agent init +{ + meta: + # Backgrounding doesn't work properly on Windows or with fakeroot. + # In addition, RHEL4 seems to have some problem killing the test + # afterwards, and HP-UX is too slow for timing to be accurate. Also, + # this test was disabled on all platforms in ENT-6939 + "test_skip_needs_work" string => "any"; + + methods: + test_pass_1:: + "any" usebundle => file_make("$(G.testfile).expireafter.expected", "one promise execution"); + "any" usebundle => file_make("$(G.testfile).short_expireafter.expected", "one promise execution +one promise execution"); + "any" usebundle => file_make("$(G.testfile).ifelapsed.expected", "one promise execution +one promise execution"); + "any" usebundle => file_make("$(G.testfile).long_ifelapsed.expected", "one promise execution"); + +} + +bundle agent test +{ + vars: + "cases" slist => { "expireafter", + "short_expireafter", + "ifelapsed", + "long_ifelapsed", + }; + + commands: + !DEBUG:: + "$(G.no_fds) $(sys.cf_agent) -Il -D $(cases) -f $(this.promise_filename).sub 1>>$(G.testfile).$(cases).output.log 2>&1 &" + contain => in_shell; + + DEBUG.test_pass_1:: + "$(G.no_fds) $(sys.cf_agent) -l --debug -D $(cases) -f $(this.promise_filename).sub 1>>$(G.testfile).$(cases).1.output.log 2>&1 & echo $!" + contain => in_shell; + DEBUG.test_pass_2:: + "$(G.no_fds) $(sys.cf_agent) -l --debug -D $(cases) -f $(this.promise_filename).sub 1>>$(G.testfile).$(cases).2.output.log 2>&1 & echo $!" + contain => in_shell; + DEBUG.test_pass_3:: + "$(G.no_fds) $(sys.cf_agent) -l --debug -D $(cases) -f $(this.promise_filename).sub 1>>$(G.testfile).$(cases).3.output.log 2>&1 & echo $!" + contain => in_shell; +} + +bundle agent check +{ + methods: + test_pass_1:: + "any" usebundle => dcs_wait($(this.promise_filename), 20); + test_pass_2:: + "any" usebundle => dcs_wait($(this.promise_filename), 55); + test_pass_3:: + # Final delay is just to make sure all background echos have happened, and + # all sub agents have exited. Failure to do so can make the sub agents try + # to look for locks inside the workdir, which by that point may have been + # deleted. + "any" usebundle => dcs_wait($(this.promise_filename), 140); + + vars: + test_pass_4:: + "pass_classes" slist => maplist("$(this)_pass", "test.cases"); + "pass_expr" string => join(".", "pass_classes"); + "fail_classes" slist => maplist("!$(this)_fail", "test.cases"); + "fail_expr" string => join(".", "fail_classes"); + + methods: + test_pass_4:: + "any" usebundle => dcs_if_diff("$(G.testfile).$(test.cases)", + "$(G.testfile).$(test.cases).expected", + "$(test.cases)_pass", + "$(test.cases)_fail"); + + "any" usebundle => dcs_passif("$(pass_expr).$(fail_expr)", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/defaults.cf.sub b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/defaults.cf.sub new file mode 100644 index 0000000000..59b7ac0965 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/defaults.cf.sub @@ -0,0 +1,41 @@ +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { "test" }; +} + +bundle agent test +{ + commands: + # Use "has_run" class to avoid reexecution. This may happen because of the + # sleep making the promise last for two minutes. Default ifelapsed time is + # 1 minute. + !has_run.expireafter:: + "$(G.echo) one promise execution >> $(G.testfile).expireafter ; $(G.sleep) 120" + contain => in_shell, + classes => always("has_run"); + !has_run.short_expireafter:: + "$(G.echo) one promise execution >> $(G.testfile).short_expireafter ; $(G.sleep) 120" + contain => in_shell, + action => set_expireafter(1), + classes => always("has_run"); + !has_run.ifelapsed:: + "$(G.echo) one promise execution >> $(G.testfile).ifelapsed" + contain => in_shell, + classes => always("has_run"); + !has_run.long_ifelapsed:: + "$(G.echo) one promise execution >> $(G.testfile).long_ifelapsed" + contain => in_shell, + action => set_ifelapsed(5), + classes => always("has_run"); +} + +body action set_ifelapsed(x) +{ + ifelapsed => "$(x)"; +} + +body action set_expireafter(x) +{ + expireafter => "$(x)"; +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf new file mode 100644 index 0000000000..76ab8a2908 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf @@ -0,0 +1,56 @@ +# Test whether successive package locks can be grabbed, both immediately +# following each other and after a time delay. (Redmine #7933). + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + meta: + # "no_fds" won't work correctly on Windows. + # Also Solaris 9 and 10 don't seem to handle the backgrounding used in + # this test. But it seems unrelated to the actual fix. + "test_skip_needs_work" string => "windows|sunos_5_9|sunos_5_10"; + + # The backgrounding doesn't work well under fakeroot. + "test_skip_unsupported" string => "using_fakeroot"; + + files: + test_pass_1:: + "$(sys.workdir)/modules/packages/." + create => "true"; + "$(sys.workdir)/modules/packages/test_module" + copy_from => local_cp("$(this.promise_filename).module"), + perms => m("ugo+x"); +} + +bundle agent test +{ + commands: + test_pass_1:: + # Note: No -K. We need locks. + # Also get rid of file descriptor ties to the parent with no_fds. + # Note that we need to redirect descriptors 0, 1 and 2, since there is + # apparently some unrelated bug in the output if we close them (otherwise + # we would have used --no-std argument to no_fds). + "$(G.no_fds) $(sys.cf_agent) -f $(this.promise_filename).sub < /dev/null > /dev/null 2>&1 &" + contain => in_shell; +} + +bundle agent check +{ + methods: + test_pass_1:: + # We wait 61 seconds in the sub invocation, but in practice test platforms + # experience all sorts of timing delays, let's give it plenty of time to + # make sure it finishes. 300 seconds = 5 minutes. + "any" usebundle => dcs_wait($(this.promise_filename), 300); + + test_pass_2:: + "any" usebundle => dcs_check_diff($(G.testfile), + "$(this.promise_filename).expected", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.expected b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.expected new file mode 100644 index 0000000000..db5293a843 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.expected @@ -0,0 +1,12 @@ +Name=first_pkg +Version=1.0 +Architecture=generic +Name=second_pkg +Version=1.0 +Architecture=generic +Name=third_pkg +Version=1.0 +Architecture=generic +Name=fourth_pkg +Version=1.0 +Architecture=generic diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.module b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.module new file mode 100644 index 0000000000..cf8adac022 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.module @@ -0,0 +1,64 @@ +#!/bin/sh + +set -e + +case "$1" in + supports-api-version) + echo 1 + ;; + get-package-data) + while read line; do + case "$line" in + File=*) + echo PackageType=repo + echo Name=${line#File=} + ;; + *) + true + ;; + esac + done + ;; + list-installed) + while read line; do + case "$line" in + options=*) + OUTPUT=${line#options=} + ;; + *) + exit 1 + ;; + esac + done + if [ -f "$OUTPUT" ]; then + cat "$OUTPUT" + fi + ;; + list-*) + # Drain input. + cat > /dev/null + ;; + repo-install) + while read line; do + case "$line" in + options=*) + OUTPUT=${line#options=} + ;; + Name=*) + NAME=${line#Name=} + ;; + *) + exit 1 + ;; + esac + done + echo "Name=$NAME" >> "$OUTPUT" + echo "Version=1.0" >> "$OUTPUT" + echo "Architecture=generic" >> "$OUTPUT" + ;; + *) + exit 1 + ;; +esac + +exit 0 diff --git a/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.sub b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.sub new file mode 100644 index 0000000000..e86035bf91 --- /dev/null +++ b/tests/acceptance/00_basics/ifelapsed_and_expireafter/timed/package_lock.cf.sub @@ -0,0 +1,66 @@ +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { "test" }; +} + +bundle agent test +{ + methods: + debug_package_lock:: + # Normally done by parent test. + "debug_init"; + any:: + "test1"; + "test2"; + "test3"; +} + +body package_module test_module +{ + query_installed_ifelapsed => "60"; + query_updates_ifelapsed => "14400"; + default_options => { "$(G.testfile)" }; +} + +bundle agent debug_init +{ + files: + "$(sys.workdir)/modules/packages/." + create => "true"; + "$(sys.workdir)/modules/packages/test_module" + copy_from => local_cp("$(this.promise_dirname)/package_lock.cf.module"), + perms => m("ugo+x"); +} + +bundle agent test1 +{ + packages: + "first_pkg" + policy => "present", + package_module => test_module, + action => immediate; + "second_pkg" + policy => "present", + package_module => test_module, + action => immediate; +} + +bundle agent test2 +{ + commands: + "$(G.sleep) 61"; +} + +bundle agent test3 +{ + packages: + "third_pkg" + policy => "present", + package_module => test_module, + action => immediate; + "fourth_pkg" + policy => "present", + package_module => test_module, + action => immediate; +} diff --git a/tests/acceptance/00_basics/linesep.cf b/tests/acceptance/00_basics/linesep.cf new file mode 100644 index 0000000000..1de55924fc --- /dev/null +++ b/tests/acceptance/00_basics/linesep.cf @@ -0,0 +1,29 @@ +body common control +{ + bundlesequence => { "test", "check" }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-10432" } + string => "Test platform-agnostic linesep constant"; +} + +bundle agent check +{ + classes: + windows:: + "ok" + expression => strcmp("$(const.linesep)", "$(const.r)$(const.n)"); + !windows:: + "ok" + expression => strcmp("$(const.linesep)", "$(const.n)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/macros/if.cf b/tests/acceptance/00_basics/macros/if.cf new file mode 100644 index 0000000000..47aa9cb3df --- /dev/null +++ b/tests/acceptance/00_basics/macros/if.cf @@ -0,0 +1,236 @@ +###################################################### +# +# Test that @if works +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +@if minimum_version(3) + classes: + "expected_3"; +@else + classes: + "not_expected_3"; +@endif + +@if minimum_version(3.7) + classes: + "expected_3_7"; +@else + classes: + "not_expected_3_7"; +@endif + +@if minimum_version(3.6) + classes: + "expected_3_6"; +@else + classes: + "not_expected_3_6"; +@endif + +@if minimum_version(2.100) + classes: + "expected_2_100"; +@else + classes: + "not_xpected_2_100"; +@endif + +@if minimum_version(300.700) + classes: + "not_expected_300_700"; +@else + classes: + "expected_300_700"; +@endif + +@if maximum_version(3.0) + classes: + "not_expected_3_0"; +@else + classes: + "expected_3_0"; +@endif + +@if maximum_version(4) + classes: + "expected_4"; +@else + classes: + "not_expected_4"; +@endif + +@if maximum_version(4.0.0) + classes: + "expected_4_0_0"; +@else + classes: + "not_expected_4_0_0"; +@endif + +@if between_versions(3.15.0, 4.0.0) + classes: + "expected_3_15_0_4_0_0"; +@else + classes: + "not_expected_3_15_0_4_0_0"; +@endif + +@if between_versions(3.11, 3.12) + classes: + "not_expected_3_11_3_12"; +@else + classes: + "expected_3_11_3_12"; +@endif + +@if before_version(4) + classes: + "expected_before_version_4"; +@else + classes: + "not_expected_before_version_4"; +@endif + +@if before_version(3) + classes: + "not_expected_before_version_3"; +@else + classes: + "expected_before_version_3"; +@endif + +@if at_version(3) + classes: + "expected_at_version_3"; +@else + classes: + "not_expected_at_version_3"; +@endif + +@if at_version(2) + classes: + "not_expected_at_version_2"; +@else + classes: + "expected_at_version_2"; +@endif + +@if after_version(2) + classes: + "expected_after_version_2"; +@else + classes: + "not_expected_after_version_2"; +@endif + +@if after_version(3) + classes: + "not_expected_after_version_3"; +@else + classes: + "expected_after_version_3"; +@endif +} + +bundle agent check +{ + vars: + "expected_classes" slist => classesmatching("expected.*"); + "not_expected_classes" slist => classesmatching("not_expected.*"); + "expected_length" int => length("expected_classes"); + "not_expected_length" int => length("not_expected_classes"); + classes: + "pass_expected" if => strcmp("$(expected_length)", "16"); + "pass_not_expected" if => strcmp("$(not_expected_length)", "0"); + "ok" and => { "pass_expected", "pass_not_expected" }; + methods: + ok:: + "" usebundle => dcs_pass($(this.promise_filename)); + reports: + DEBUG:: + "Expected classes: $(expected_classes)"; + "Not expected classes: $(not_expected_classes)"; + "Expected length: $(expected_length)"; + "Not expected length: $(not_expected_length)"; + DEBUG.pass_expected:: + "pass_expected"; + DEBUG.pass_not_expected:: + "pass_not_expected"; +} + +@if minimum_version(3.12) +@else +some invalid syntax here +body {} {}{}{{}} +@endif + +@if minimum_version(3) +@else +some invalid syntax here +body {} {}{}{{}} +@endif + +@if minimum_version(300.600) + +This text should never be seen, it's completely ignored +@endif + +@if minimum_version(300.600) + +Nor should this + +@endif + +@if minimum_version(300.600) + +Nor this + +Not this either + +@endif + +@if minimum_version(300.600) +Nothing should be seen here really + +Who knows, perhaps this text doesn't exist..? + +@endif + +@if maximum_version(3.6.0) +body files control { {} } +@endif + +@if between_versions(2.0, 3.0) +more invalid syntax +body body body body {{}};;:: +@endif + +@if between_versions(1, 3.6) +more invalid syntax +body body body body {{}};;:: +@endif + +@if at_version(2) +body invalid syntax +{{{reports:}}};;;::: +@endif + +@if before_version(3) +body invalid syntax +{{{reports:}}};;;::: +@endif + +@if after_version(3) +body invalid syntax +{{{reports:}}};;;::: +@endif diff --git a/tests/acceptance/00_basics/macros/if_eof_without_endif.x.cf b/tests/acceptance/00_basics/macros/if_eof_without_endif.x.cf new file mode 100644 index 0000000000..4d8c4ec731 --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_eof_without_endif.x.cf @@ -0,0 +1,24 @@ +###################################################### +# +# Test that @if works +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("any", + "", + $(this.promise_filename)); +} + +@if minimum_version(300.100) + +This text should never be seen, it's completely ignored diff --git a/tests/acceptance/00_basics/macros/if_eof_without_endif2.x.cf b/tests/acceptance/00_basics/macros/if_eof_without_endif2.x.cf new file mode 100644 index 0000000000..180d21635e --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_eof_without_endif2.x.cf @@ -0,0 +1,24 @@ +###################################################### +# +# Test that @if works +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("any", + "", + $(this.promise_filename)); +} + +@if minimum_version(1.1) + +# just testing for EOF diff --git a/tests/acceptance/00_basics/macros/if_feature.cf b/tests/acceptance/00_basics/macros/if_feature.cf new file mode 100644 index 0000000000..b1a60d6366 --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_feature.cf @@ -0,0 +1,73 @@ +###################################################### +# +# Test that @if feature() works +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +@if feature(xml) + classes: + "xml" expression => "any"; +@endif + +@if feature(yaml) + classes: + "yaml" expression => "any"; +@endif + +@if feature(unknown_123) + classes: + "not_expected" expression => "any"; +@endif +} + +bundle agent check +{ + vars: + !feature_yaml.!feature_xml:: + "result" string => ""; + feature_yaml.!feature_xml:: + "result" string => "yaml"; + !feature_yaml.feature_xml:: + "result" string => "xml"; + feature_yaml.feature_xml:: + "result" string => "yaml,xml"; + methods: + "" usebundle => dcs_passif_expected("$(result)", + "not_expected", + $(this.promise_filename)); +} + +@if feature(ABCD) + +This text should never be seen, it's completely ignored +@endif + +@if feature(ABCD) + +Nor should this + +@endif + +@if feature(ABCD) + +Nor this + +Not this either + +@endif + +@if feature(ABCD) +Nothing should be seen here really + +Who knows, perhaps this text doesn't exist..? + +@endif diff --git a/tests/acceptance/00_basics/macros/if_feature_nested.x.cf b/tests/acceptance/00_basics/macros/if_feature_nested.x.cf new file mode 100644 index 0000000000..8079aab73d --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_feature_nested.x.cf @@ -0,0 +1,47 @@ +###################################################### +# +# Test that nested @if with features fails +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +@if minimum_version(3.7) +@if feature(xml) + classes: + "expected" expression => "any"; +@endif +@endif + +@if feature(xml) +@if minimum_version(3.7) + classes: + "expected2" expression => "any"; +@endif +@endif + +@if minimum_version(300.700) + classes: + "not_expected" expression => "any"; +@endif +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("expected,expected2", + "not_expected", + $(this.promise_filename)); +} + +@if minimum_version(300.600) + +This text should never be seen, it's completely ignored +@endif diff --git a/tests/acceptance/00_basics/macros/if_ignore.cf b/tests/acceptance/00_basics/macros/if_ignore.cf new file mode 100644 index 0000000000..108585e8e0 --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_ignore.cf @@ -0,0 +1,30 @@ +###################################################### +# +# Test that @if works for greater versions +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +@if minimum_version(300.600) + Some new function here... + + + more text... +@endif +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("cfengine", + "", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/macros/if_ignore_with_addsign.cf b/tests/acceptance/00_basics/macros/if_ignore_with_addsign.cf new file mode 100644 index 0000000000..63dd940cdf --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_ignore_with_addsign.cf @@ -0,0 +1,30 @@ +###################################################### +# +# Test that @if works for greater versions +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +@if minimum_version(300.600) + Some new function here... +@should_pass + bundle agent netgroup(@netgroup_list) + more text... +@endif +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("cfengine", + "", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/macros/if_mismatched.x.cf b/tests/acceptance/00_basics/macros/if_mismatched.x.cf new file mode 100644 index 0000000000..75c0668cde --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_mismatched.x.cf @@ -0,0 +1,50 @@ +###################################################### +# +# Test that @if works +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +@if minimum_version(3.7) +@if minimum_version(3.6) + classes: + "expected" expression => "any"; +@endif +@endif +@endif +@endif +@endif + +@if minimum_version(3.6) + classes: + "expected2" expression => "any"; +@endif + +@if minimum_version(300.700) +@if minimum_version(3.6) + classes: + "not_expected" expression => "any"; +@endif +@endif +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("expected,expected2", + "not_expected", + $(this.promise_filename)); +} + +@if minimum_version(300.600) + +This text should never be seen, it's completely ignored +@endif diff --git a/tests/acceptance/00_basics/macros/if_multiple_endif.x.cf b/tests/acceptance/00_basics/macros/if_multiple_endif.x.cf new file mode 100644 index 0000000000..79cbb517b9 --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_multiple_endif.x.cf @@ -0,0 +1,44 @@ +###################################################### +# +# Test that @if works +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +@if minimum_version(3.7) + classes: + "expected" expression => "any"; +@endif +@endif + +@if minimum_version(3.6) + classes: + "expected2" expression => "any"; +@endif + +@if minimum_version(300.700) + classes: + "not_expected" expression => "any"; +@endif +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("expected,expected2", + "not_expected", + $(this.promise_filename)); +} + +@if minimum_version(300.600) + +This text should never be seen, it's completely ignored +@endif diff --git a/tests/acceptance/00_basics/macros/if_nested.x.cf b/tests/acceptance/00_basics/macros/if_nested.x.cf new file mode 100644 index 0000000000..14be0ae2b1 --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_nested.x.cf @@ -0,0 +1,45 @@ +###################################################### +# +# Test that nested @if fails +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +@if minimum_version(3.7) +@if minimum_version(3.6) + classes: + "expected" expression => "any"; +@endif +@endif + +@if minimum_version(3.6) + classes: + "expected2" expression => "any"; +@endif + +@if minimum_version(300.700) + classes: + "not_expected" expression => "any"; +@endif +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("expected,expected2", + "not_expected", + $(this.promise_filename)); +} + +@if minimum_version(300.600) + +This text should never be seen, it's completely ignored +@endif diff --git a/tests/acceptance/00_basics/macros/if_on_first_line.cf b/tests/acceptance/00_basics/macros/if_on_first_line.cf new file mode 100644 index 0000000000..a15478e478 --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_on_first_line.cf @@ -0,0 +1,22 @@ +@if minimum_version(300.700) +###################################################### +# +# Test that @if works on the first line in a file +# +##################################################### +@endif + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("any", + "", + $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/macros/if_triple.cf b/tests/acceptance/00_basics/macros/if_triple.cf new file mode 100644 index 0000000000..c25501f442 --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_triple.cf @@ -0,0 +1,45 @@ +###################################################### +# +# Test that @if works with patch level +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + files: + "$(G.testdir)$(const.dirsep)test.cf" + create => "true", + edit_template => "$(this.promise_filename).sub.template", + template_method => "mustache"; +} + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; +} + +bundle agent check +{ + methods: + # Note: dcs_passif_output expects first argument to be regexp. + # To convert Windows-style path (with backslashes) to a regex which will match this path, + # we need to convert all backslashes to double-backslashes. + # In the command below, each backslash is escaped twice: + # once for regex, and once for CFEngine string parser. + "check" usebundle => dcs_passif_output(regex_replace(".*$(G.testdir)$(const.dirsep)test.cf Pass.*", + "\\\\", "\\\\\\\\", "g"), + ".*FAIL.*", + "$(sys.cf_agent) -D AUTO -Kf $(G.testdir)$(const.dirsep)test.cf", + $(this.promise_filename)); +} + +@if minimum_version(300.600.0) + +This text should never be seen, it's completely ignored +@endif diff --git a/tests/acceptance/00_basics/macros/if_triple.cf.sub.template b/tests/acceptance/00_basics/macros/if_triple.cf.sub.template new file mode 100644 index 0000000000..4852269971 --- /dev/null +++ b/tests/acceptance/00_basics/macros/if_triple.cf.sub.template @@ -0,0 +1,45 @@ +###################################################### +# +# Test that @if works with patch level +# +##################################################### + +body common control +{ + bundlesequence => { check }; +} + +bundle common test +{ +@if minimum_version(3.7.100) + classes: + "expected" expression => "any"; +@endif + +@if minimum_version({{vars.sys.cf_version_major}}.{{vars.sys.cf_version_minor}}.0) + classes: + "expected2" expression => "any"; +@endif + +@if minimum_version(2.100.0) + classes: + "expected_2_100" expression => "any"; +@endif + +@if minimum_version({{vars.sys.cf_version_major}}.{{vars.sys.cf_version_minor}}.300) + classes: + "not_expected" expression => "any"; +@endif +} + +bundle agent check +{ + classes: + "ok" expression => "expected.expected2.expected_2_100.!not_expected"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/00_basics/namespaces_can_not_contain_funny_characters.cf b/tests/acceptance/00_basics/namespaces_can_not_contain_funny_characters.cf new file mode 100644 index 0000000000..fc0e97159b --- /dev/null +++ b/tests/acceptance/00_basics/namespaces_can_not_contain_funny_characters.cf @@ -0,0 +1,33 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" + string => "Test that namespaces cannot be defined with + characters that are invalid in bundle names."; + + vars: + "command" string => "$(sys.cf_agent) -KI --define AUTO,DEBUG $(this.promise_filename).sub"; + + methods: + # We check the output of a sub policy that we expect to be syntatically + # invalid. So we pass if the subtest output says syntax error. We fail the + # test if the output of the subtest includes FAIL. We never expect that + # report to print, because the policy should not be syntatically valid and + # should never run. + "check" + usebundle => dcs_passif_output(".*syntax error.*", ".*FAIL", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/00_basics/namespaces_can_not_contain_funny_characters.cf.sub b/tests/acceptance/00_basics/namespaces_can_not_contain_funny_characters.cf.sub new file mode 100644 index 0000000000..1f875da876 --- /dev/null +++ b/tests/acceptance/00_basics/namespaces_can_not_contain_funny_characters.cf.sub @@ -0,0 +1,29 @@ +###################################################### +# +# Issue 375 setup (precursor to actual tickle of bug) +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + reports: + # This should NEVER be reported because we have a body file control that + # contains illegal characters. + "$(this.promise_filename) FAIL"; +} + +body file control +{ + # Variable dereferences should work + namespace => "{}()$:[]"; +} + + diff --git a/tests/acceptance/00_basics/validation/testdir/.gitattributes b/tests/acceptance/00_basics/validation/testdir/.gitattributes new file mode 100644 index 0000000000..fae8897c8e --- /dev/null +++ b/tests/acceptance/00_basics/validation/testdir/.gitattributes @@ -0,0 +1 @@ +* eol=lf diff --git a/tests/acceptance/00_basics/validation/testdir/promises.cf.master b/tests/acceptance/00_basics/validation/testdir/promises.cf.master new file mode 100644 index 0000000000..09cf814c09 --- /dev/null +++ b/tests/acceptance/00_basics/validation/testdir/promises.cf.master @@ -0,0 +1,5 @@ +body common control +{ + inputs => { "x.cf", "y.cf" }; + bundlesequence => { x, y }; +} diff --git a/tests/acceptance/00_basics/validation/testdir/promises.conf b/tests/acceptance/00_basics/validation/testdir/promises.conf new file mode 100644 index 0000000000..09cf814c09 --- /dev/null +++ b/tests/acceptance/00_basics/validation/testdir/promises.conf @@ -0,0 +1,5 @@ +body common control +{ + inputs => { "x.cf", "y.cf" }; + bundlesequence => { x, y }; +} diff --git a/tests/acceptance/00_basics/validation/testdir/promises.dat b/tests/acceptance/00_basics/validation/testdir/promises.dat new file mode 100644 index 0000000000..09cf814c09 --- /dev/null +++ b/tests/acceptance/00_basics/validation/testdir/promises.dat @@ -0,0 +1,5 @@ +body common control +{ + inputs => { "x.cf", "y.cf" }; + bundlesequence => { x, y }; +} diff --git a/tests/acceptance/00_basics/validation/testdir/promises.txt b/tests/acceptance/00_basics/validation/testdir/promises.txt new file mode 100644 index 0000000000..09cf814c09 --- /dev/null +++ b/tests/acceptance/00_basics/validation/testdir/promises.txt @@ -0,0 +1,5 @@ +body common control +{ + inputs => { "x.cf", "y.cf" }; + bundlesequence => { x, y }; +} diff --git a/tests/acceptance/00_basics/validation/testdir/x.cf.master b/tests/acceptance/00_basics/validation/testdir/x.cf.master new file mode 100644 index 0000000000..29dbb03e11 --- /dev/null +++ b/tests/acceptance/00_basics/validation/testdir/x.cf.master @@ -0,0 +1,5 @@ +bundle agent x +{ + reports: + "$(this.bundle): x"; +} diff --git a/tests/acceptance/00_basics/validation/testdir/y.cf.master b/tests/acceptance/00_basics/validation/testdir/y.cf.master new file mode 100644 index 0000000000..7b8438083a --- /dev/null +++ b/tests/acceptance/00_basics/validation/testdir/y.cf.master @@ -0,0 +1,5 @@ +bundle agent y +{ + reports: + "$(this.bundle): y"; +} diff --git a/tests/acceptance/00_basics/validation/validation_tagging_directory.cf b/tests/acceptance/00_basics/validation/validation_tagging_directory.cf new file mode 100644 index 0000000000..f9015d0e48 --- /dev/null +++ b/tests/acceptance/00_basics/validation/validation_tagging_directory.cf @@ -0,0 +1,260 @@ +# Test that cf-promises -T tags a directory correctly + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; + + vars: + "options" string => ifelse("verbose_mode", " -v ", ""); + "dirs" slist => { $(G.testdir), $(sys.masterdir), $(sys.inputdir) }; + "hubfile" slist => { "$(sys.workdir)/policy_server.dat", "$(sys.workdir)/state/am_policy_hub" }; + + methods: + "rm hub" usebundle => dcs_fini($(hubfile)); + + "run" usebundle => test_dorun("testdir", $(G.testdir), "$(sys.cf_promises) -T $(G.testdir) $(options)"); + "run" usebundle => test_dorun("inputdir", $(sys.inputdir), "$(sys.cf_agent) -K $(options)"); + + # make this machine a hub + "mk hub" usebundle => file_make($(hubfile), "$(sys.host)"); + + "run" usebundle => test_dorun("masterdir", $(sys.masterdir), "$(sys.cf_agent) -K $(options)"); +} + +bundle agent test_dorun(name, dir, tagopt) +{ + vars: + "simulated_validated_ok" string => ' +{ + "timestamp": 1234, +} +'; + + "simulated_validated_bad" string => ' +{ + "timestamp": 1234, +} +'; + + methods: + vars: + "masters" slist => { "promises.cf", "x.cf", "y.cf" }; + + methods: + "rsync" + usebundle => dir_sync("$(this.promise_dirname)/testdir", + $(dir)); + + "master2cf" + usebundle => file_copy("$(dir)/$(masters).master", + "$(dir)/$(masters)"); + + "make ignored file 1" + usebundle => file_make("$(dir)/ignoreme", + "fnord $(sys.date)"); + + "make ignored file 2" + usebundle => file_make("$(dir)/ignoredir/2", + "fnord $(sys.date)"); + + "tag1" usebundle => test_tag("1", + $(name), + $(tagopt), + "make new cf_promises_validated and cf_promises_release_id in $(dir)"); + "read1" usebundle => test_read("1", $(name), $(dir)); + + "rm r_id" usebundle => dcs_fini($(test_read.rf)); + "tag2" usebundle => test_tag("2", + $(name), + $(tagopt), + "recreate cf_promises_release_id even if cf_promises_validated is OK"); + "read2" usebundle => test_read("2", $(name), $(dir)); + + "make good validated file" + usebundle => file_make("$(dir)/cf_promises_validated", + $(simulated_validated_ok)); + + "tag3" usebundle => test_tag("3", + $(name), + $(tagopt), + "rewrite good cf_promises_validated and leave cf_promises_release_id alone"); + "read3" usebundle => test_read("3", $(name), $(dir)); + + "make bad validated file" + usebundle => file_make("$(dir)/cf_promises_validated", + $(simulated_validated_bad)); + + "tag4" usebundle => test_tag("4", + $(name), + $(tagopt), + "overwrite bad cf_promises_validated"); + "read4" usebundle => test_read("4", $(name), $(dir)); + + reports: + "$(this.bundle): $(name) $(dir) '$(tagopt)'"; +} + +bundle agent test_tag(vary, topname, tagopt, desc) +{ + vars: + "uid" string => "$(topname)_$(vary)"; + + classes: + "tagged_$(uid)" + expression => returnszero("$(tagopt) -D$(uid)", + "noshell"), + scope => "namespace"; + + reports: + "Case $(uid): $(desc)"; + + DEBUG:: + "Case $(uid): failed to $(tagopt)" + if => "!tagged_$(uid)"; + + "Case $(uid): tagged $(tagopt)" + if => "tagged_$(uid)"; +} + +bundle agent test_read(suffix, topname, dir) +{ + vars: + "vf" string => "$(dir)/cf_promises_validated"; + "rf" string => "$(dir)/cf_promises_release_id"; + "uid" string => "$(topname)_$(suffix)"; + + "v_$(uid)" data => readjson($(vf), 4k), + if => "have_vf_$(uid)"; + + "v_$(uid)_str" string => format("%S", "v_$(uid)"), + handle => "v_$(uid)_str", + if => "have_vf_$(uid)"; + + "r_$(uid)" data => readjson($(rf), 4k), + if => "have_rf_$(uid)"; + + "r_$(uid)_str" string => format("%S", "r_$(uid)"), + handle => "r_$(uid)_str", + if => "have_rf_$(uid)"; + + classes: + "have_vf_$(uid)" expression => fileexists($(vf)), + scope => "namespace"; + + "have_rf_$(uid)" expression => fileexists($(rf)), + scope => "namespace"; + + reports: + DEBUG:: + "Case $(uid): $(vf) = $(v_$(uid)_str)" + depends_on => { "v_$(uid)_str" }, + if => "have_vf_$(uid)"; + + "Case $(uid): Missing validation file $(vf)" + if => "!have_vf_$(uid)"; + + "Case $(uid): $(rf) = $(r_$(uid)_str)" + depends_on => { "r_$(uid)_str" }, + if => "have_rf_$(uid)"; + + "Case $(uid): Missing release ID file $(rf)" + if => "!have_rf_$(uid)"; +} + +bundle agent check +{ + vars: + "expected_checksum" string => "4b974a13b5473cfbf60edfb66b201a364d38960f"; + "tops" slist => { "testdir", "masterdir", "inputdir" }; + "tests" slist => { "1", "2", "3", "4" }; + + classes: + + # exceptions + + # the release ID is not recreated in a normal agent run if the + # promises validated file is OK, even on the hub + "correct_release_masterdir_2" expression => "any"; + + # the release ID is not generated when running on a non-hub + "correct_release_inputdir_$(tests)" expression => "any"; + + # normal testing + "correct_timestamp_$(tops)_1" expression => "any"; + "correct_release_$(tops)_1" expression => strcmp("$(test_read.r_$(tops)_1[releaseId])", + $(expected_checksum)); + + "correct_timestamp_$(tops)_2" expression => "any"; + "correct_release_$(tops)_2" expression => strcmp("$(test_read.r_$(tops)_2[releaseId])", + $(expected_checksum)); + + # we expect the bad timestamp + "correct_timestamp_inputdir_3" expression => strcmp("$(test_read.v_inputdir_3[timestamp])", + "1234"); + # anything except the bad timestamp is OK + "correct_timestamp_masterdir_3" not => strcmp("$(test_read.v_masterdir_3[timestamp])", + "1234"); + "correct_timestamp_testdir_3" not => strcmp("$(test_read.v_testdir_3[timestamp])", + "1234"); + "correct_release_$(tops)_3" expression => strcmp("$(test_read.r_$(tops)_3[releaseId])", + $(expected_checksum)); + + # we expect the bad timestamp + "correct_timestamp_inputdir_4" expression => strcmp("$(test_read.v_inputdir_4[timestamp])", + "1234"); + # anything except the bad timestamp is OK + "correct_timestamp_masterdir_4" not => strcmp("$(test_read.v_masterdir_4[timestamp])", + "1234"); + "correct_timestamp_testdir_4" not => strcmp("$(test_read.v_testdir_4[timestamp])", + "1234"); + "correct_release_$(tops)_4" expression => strcmp("$(test_read.r_$(tops)_4[releaseId])", + $(expected_checksum)); + + "ok" and => { + "tagged_testdir_1", "correct_timestamp_testdir_1", "correct_release_testdir_1", + "tagged_testdir_2", "correct_timestamp_testdir_2", "correct_release_testdir_2", + "tagged_testdir_3", "correct_timestamp_testdir_3", "correct_release_testdir_3", + "tagged_testdir_4", "correct_timestamp_testdir_4", "correct_release_testdir_4", + + "tagged_masterdir_1", "correct_timestamp_masterdir_1", "correct_release_masterdir_1", + "tagged_masterdir_2", "correct_timestamp_masterdir_2", "correct_release_masterdir_2", + "tagged_masterdir_3", "correct_timestamp_masterdir_3", "correct_release_masterdir_3", + "tagged_masterdir_4", "correct_timestamp_masterdir_4", "correct_release_masterdir_4", + + "tagged_inputdir_1", "correct_timestamp_inputdir_1", "correct_release_inputdir_1", + "tagged_inputdir_2", "correct_timestamp_inputdir_2", "correct_release_inputdir_2", + "tagged_inputdir_3", "correct_timestamp_inputdir_3", "correct_release_inputdir_3", + "tagged_inputdir_4", "correct_timestamp_inputdir_4", "correct_release_inputdir_4", + }; + + reports: + DEBUG:: + "Case $(tops)_$(tests): the timestamp was correct or ignored" + if => "correct_timestamp_$(tops)_$(tests)"; + + "Case $(tops)_$(tests): the timestamp was incorrect, actual $(test_read.v_$(tops)_$(tests)[timestamp])" + if => "!correct_timestamp_$(tops)_$(tests)"; + + "Case $(tops)_$(tests): the release ID was correct ($(expected_checksum)) or ignored" + if => "correct_release_$(tops)_$(tests)"; + + "Case $(tops)_$(tests): the release ID was incorrect, expected $(expected_checksum) vs. actual $(test_read.r_$(tops)_$(tests)[releaseId])" + if => "!correct_release_$(tops)_$(tests)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/001.cf b/tests/acceptance/01_vars/01_basic/001.cf new file mode 100644 index 0000000000..fea185a2e8 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/001.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test simple variables +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "ten" int => "10"; + "ten[ten]" int => "11"; + "nine" string => "hello", + policy => "free"; + "nine" int => "9", + policy => "free"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok_10" not => strcmp("$(test.ten)", "$(test.ten[ten])"); + "ok" and => { "ok_10", strcmp("$(test.nine)", "9") }; + + reports: + DEBUG:: + "ten == $(test.ten)"; + "ten[ten] == $(test.ten[ten])"; + "nine == $(test.nine)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/002.x.cf b/tests/acceptance/01_vars/01_basic/002.x.cf new file mode 100644 index 0000000000..71878640a3 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/002.x.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test that a variable cannot be defined twice in the +# same promise. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "twice" + string => "plus ca change..", + int => 42; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "Assignment should fail"; + cfengine_3: + "$(this.promise_filename) Pass"; +} + diff --git a/tests/acceptance/01_vars/01_basic/004.x.cf b/tests/acceptance/01_vars/01_basic/004.x.cf new file mode 100644 index 0000000000..eb72828605 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/004.x.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test simple variables failures +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "ten" real => "1.2.3"; # Not a float +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "The real variable assignment should fail"; + cfengine_3:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/005.x.cf b/tests/acceptance/01_vars/01_basic/005.x.cf new file mode 100644 index 0000000000..416d0e3bd6 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/005.x.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test simple variables failures +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "ten" real => "1.2e.3"; # Not a float +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "The real variable assignment should fail"; + cfengine_3:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/006.x.cf b/tests/acceptance/01_vars/01_basic/006.x.cf new file mode 100644 index 0000000000..4fb9ca23ee --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/006.x.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test simple variables failures +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "ten" real => "."; # Not a float +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "The real variable assignment should fail"; + cfengine_3:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/007.x.cf b/tests/acceptance/01_vars/01_basic/007.x.cf new file mode 100644 index 0000000000..5dacc90dac --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/007.x.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test simple variables failures +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "ten" real => "e2"; # Not a float +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "The real variable assignment should fail"; + cfengine_3:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/008.x.cf b/tests/acceptance/01_vars/01_basic/008.x.cf new file mode 100644 index 0000000000..18fe3857ff --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/008.x.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test simple variables failures +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "ten" real => "1e2345678"; # Exponent too big +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "The real variable assignment should fail"; + cfengine_3:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/009.cf b/tests/acceptance/01_vars/01_basic/009.cf new file mode 100644 index 0000000000..f1a75fcc31 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/009.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test simple variables failures +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "zero_p" real => "0e2345678"; # Exponent too big, but still zero + "zero_n" real => "0e-2345678"; # Exponent too big, but still zero +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + isgreaterthan("$(test.zero_p)", "-0.1"), + islessthan("$(test.zero_p)", "0.1"), + isgreaterthan("$(test.zero_n)", "-0.1"), + islessthan("$(test.zero_n)", "0.1"), + }; + reports: + DEBUG:: + "zero_p == $(test.zero_p)"; + "zero_n == $(test.zero_n)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/010.cf b/tests/acceptance/01_vars/01_basic/010.cf new file mode 100644 index 0000000000..f0a28e636a --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/010.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test nested functions +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "oct2" string => '20'; + "oct4" slist => { '11', '12' }; + "separator" string => ","; + "prefix" string => "10.${oct2}.20"; + "temp_namelist" slist => { " ", @(oct4) }; + "foo" slist => splitstring ( + join("${separator}${prefix}", "temp_namelist"), + "$(separator)", + "999999999" + ); + "nameserv" slist => grep ("^[^\s]+$", join(escape("${separator}${prefix}"), "temp_namelist") ); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/012.cf b/tests/acceptance/01_vars/01_basic/012.cf new file mode 100644 index 0000000000..2dd9ec847b --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/012.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Compilation test of strings that end with a \ - issue 690 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "foo" string => "simple"; + "baz" string => "s\imple"; + "gar" string => "s\\imple"; + "goo" string => "s\\"; + "boo" string => "\\e"; + "zoo" string => "\\"; +} + +bundle agent check +{ + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/013.cf b/tests/acceptance/01_vars/01_basic/013.cf new file mode 100644 index 0000000000..62ce0873f7 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/013.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Test that 'classes' body works on 'vars' promises +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "foo" + string => execresult("/nonexisting", "noshell"), + classes => setclasses("nonexisting_t", "nonexisting_f"); + + "bar" + string => execresult("$(G.true)", "noshell"), + classes => setclasses("true_t", "true_f"); +} + +body classes setclasses(t,f) +{ + promise_repaired => { "$(t)" }; + promise_kept => { "$(t)" }; + repair_failed => { "$(f)" }; + repair_denied => { "$(f)" }; + repair_timeout => { "$(f)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "!nonexisting_f.!nonexisting_t.true_t.!true_f"; + + reports: + DEBUG.nonexisting_t:: + "nonexisting_t"; + DEBUG.nonexisting_f:: + "nonexisting_f"; + DEBUG.true_t:: + "true_t"; + DEBUG.true_f:: + "true_f"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/105.cf b/tests/acceptance/01_vars/01_basic/105.cf new file mode 100644 index 0000000000..7cea48df2e --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/105.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Test Scoping of "this" variables (issue 349) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + # $(this.promiser) is not available in this context + "not_valid" not => strcmp("xyzzy", "$(this.promiser)"); + "valid" expression => strcmp("xyzzy", "$(this.promiser)"); + "any_promiser" and => { "not_valid", "valid" }; + "ok" not => "any_promiser"; + + reports: + DEBUG.not_valid:: + "not_valid IS set (and should not be)"; + DEBUG.!not_valid:: + "not_valid is not set (and should not be)"; + DEBUG.valid:: + "valid IS set (and should not be)"; + DEBUG.!valid:: + "valid is not set (and should not be)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/106.cf b/tests/acceptance/01_vars/01_basic/106.cf new file mode 100644 index 0000000000..7ff48721ef --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/106.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Test arrays with list-specified indices (issue 692) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "useshell"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => regcmp(".*Redefinition.*", "$(test.subout)"); + + reports: + DEBUG:: + "$(test.subout)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/106.cf.sub b/tests/acceptance/01_vars/01_basic/106.cf.sub new file mode 100644 index 0000000000..a0e7427059 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/106.cf.sub @@ -0,0 +1,14 @@ +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ +vars: + "index" slist => { "index" }; + "array[$(index)]" string => "value"; + + "indices" slist => getindices("array"); + "values" slist => getvalues("array"); +} diff --git a/tests/acceptance/01_vars/01_basic/107.cf b/tests/acceptance/01_vars/01_basic/107.cf new file mode 100644 index 0000000000..63768fca74 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/107.cf @@ -0,0 +1,35 @@ +# Test that function call with computed if does not lead to incorrect +# use of values (Mantis #864) (Mantis #1084) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "useshell"); +} + +bundle agent check +{ + classes: + "ok" not => regcmp(".*Unable to parse class expression.*", "$(test.subout)"); + + reports: + DEBUG:: + "$(test.subout)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/107.cf.sub b/tests/acceptance/01_vars/01_basic/107.cf.sub new file mode 100644 index 0000000000..e6ac30a527 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/107.cf.sub @@ -0,0 +1,17 @@ +body common control +{ + bundlesequence => { "main" }; +} + +bundle agent main +{ +methods: + "TEST" usebundle => test("any"); +} + +bundle common test(var) +{ +vars: + "something" string => and("any", "any"), + ifvarclass => canonify("${var}"); +} diff --git a/tests/acceptance/01_vars/01_basic/108.cf b/tests/acceptance/01_vars/01_basic/108.cf new file mode 100644 index 0000000000..5849726eff --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/108.cf @@ -0,0 +1,34 @@ +# Test that variables can be passed across bundles and namespaces + +body common control +{ + inputs => { "../../default.cf.sub", "108.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + methods: + "namespaced" usebundle => ns108:pass($(init.dummy)); +} + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(init.dummy)", "$(ns108:pass.passed_dummy)"); + + reports: + DEBUG:: + "We passed '$(init.dummy)' to ns108:pass() and it recorded '$(ns108:pass.passed_dummy)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/108.cf.sub b/tests/acceptance/01_vars/01_basic/108.cf.sub new file mode 100644 index 0000000000..9b2519a61d --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/108.cf.sub @@ -0,0 +1,15 @@ +body file control +{ + namespace => "ns108"; +} + +bundle agent pass(given_dummy) +{ + vars: + "passed_dummy" string => "$(given_dummy)"; + + reports: + DEBUG:: + "ns108:pass: given_dummy = '$(given_dummy)'"; + "ns108:pass: passed_dummy = '$(passed_dummy)'"; +} diff --git a/tests/acceptance/01_vars/01_basic/109.cf b/tests/acceptance/01_vars/01_basic/109.cf new file mode 100644 index 0000000000..1291c74165 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/109.cf @@ -0,0 +1,34 @@ +# Test that variables can be passed across bundles and namespaces + +body common control +{ + inputs => { "../../default.cf.sub", "109.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + methods: + "namespaced" usebundle => ns109:pass($(init.dummy)); +} + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(init.dummy)", "$(ns109:pass.passed_dummy)"); + + reports: + DEBUG:: + "We passed '$(init.dummy)' to ns109:pass() and it recorded '$(ns109:pass.passed_dummy)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/109.cf.sub b/tests/acceptance/01_vars/01_basic/109.cf.sub new file mode 100644 index 0000000000..6ffb29a463 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/109.cf.sub @@ -0,0 +1,15 @@ +body file control +{ + namespace => "ns109"; +} + +bundle agent pass(given_dummy) +{ + vars: + "passed_dummy" string => "$(given_dummy)"; + + reports: + DEBUG:: + "ns109:pass: given_dummy = '$(given_dummy)'"; + "ns109:pass: passed_dummy = '$(passed_dummy)'"; +} diff --git a/tests/acceptance/01_vars/01_basic/110.cf b/tests/acceptance/01_vars/01_basic/110.cf new file mode 100644 index 0000000000..c1be8dc2fe --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/110.cf @@ -0,0 +1,35 @@ +# Test that variables can be passed across bundles and namespaces + +body common control +{ + inputs => { "../../default.cf.sub", "110.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + methods: + "namespaced" usebundle => ns110:pass($(init.dummy)), + useresult => "nsresult"; +} + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(init.dummy)", "$(test.nsresult[dummy])"); + + reports: + DEBUG:: + "We passed '$(init.dummy)' to ns110:pass() and it returned '$(test.nsresult[dummy])'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/110.cf.sub b/tests/acceptance/01_vars/01_basic/110.cf.sub new file mode 100644 index 0000000000..c4fde5f390 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/110.cf.sub @@ -0,0 +1,16 @@ +body file control +{ + namespace => "ns110"; +} + +bundle agent pass(given_dummy) +{ + vars: + "returned_dummy" string => "$(given_dummy)"; + + reports: + "$(returned_dummy)" bundle_return_value_index => "dummy"; + DEBUG:: + "ns110:pass: given_dummy = '$(given_dummy)'"; + "ns110:pass: returned_dummy = '$(returned_dummy)'"; +} diff --git a/tests/acceptance/01_vars/01_basic/111.cf b/tests/acceptance/01_vars/01_basic/111.cf new file mode 100644 index 0000000000..0169506dc3 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/111.cf @@ -0,0 +1,65 @@ +####################################################### +# +# Check that similar command promises are not skipped +# (cf redmine #2182) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### +body classes if_touched(x) +{ + visited:: + promise_repaired => { "revisited" }; + !visited:: + promise_repaired => { "visited" }; +} + +####################################################### +bundle agent init +{ +} + +####################################################### +bundle agent test +{ + commands: + "$(G.echo) restart" + classes => if_touched("called"); + + methods: + "any" usebundle => call; + +} + +bundle agent call +{ + commands: + "$(G.echo) restart" + classes => if_touched("called"); +} + +####################################################### +bundle agent check +{ + classes: + any:: + "ok" and => { "visited", "revisited" }; + + reports: + DEBUG.visited:: + "visited defined"; + DEBUG.revisited:: + "revisited defined"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/CFE-2191.cf b/tests/acceptance/01_vars/01_basic/CFE-2191.cf new file mode 100644 index 0000000000..3ed5591cbc --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/CFE-2191.cf @@ -0,0 +1,41 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent init +{ + vars: + "suffix" string => "devel"; +} +bundle agent test +{ + meta: + "description" -> { "CFE-2191" } + string => "Test that expansion of a nested variable in another bundle expands to the expected value"; +} + +########################################################### + +bundle agent check +{ + + vars: + "expected" string => "___.___"; + "var_devel" string => "___"; + + methods: + "Pass/Fail" + # First, init.suffix is expanded (to devel) + # Then, $(var_devel) is expanded (to ___) + usebundle => dcs_check_strcmp( "$(expected)", "$(var_$(init.suffix)).___", $(this.promise_filename), "no"); + + reports: + DEBUG|EXTRA:: + "Expect '$(expected)', got '$(var_$(test.suffix)).___'"; + +} diff --git a/tests/acceptance/01_vars/01_basic/CFE-628.cf b/tests/acceptance/01_vars/01_basic/CFE-628.cf new file mode 100644 index 0000000000..c1db73e67a --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/CFE-628.cf @@ -0,0 +1,44 @@ +########################################################### +# +# Test evaluation order expectations +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-628" } + string => "Test that the output is as expected"; + + "test_skip_needs_work" -> { "CFE-628" } + string => "windows", + meta => { "CFE-628" }; + +} + +########################################################### + +bundle agent check +{ + vars: + "cmd" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + + methods: + + "Pass/Fail" + usebundle => dcs_passif_output( + ".*found_the_file.*", # wanted, + ".*didnt_find_the_file.*", # unwanted, + "$(cmd)", # command, + $(this.promise_filename) ); + +} diff --git a/tests/acceptance/01_vars/01_basic/CFE-628.cf.sub b/tests/acceptance/01_vars/01_basic/CFE-628.cf.sub new file mode 100644 index 0000000000..af39da095b --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/CFE-628.cf.sub @@ -0,0 +1,14 @@ +bundle agent main +{ + classes: + "found_the_file" expression => fileexists( $(this.promise_filename) ); + + vars: + found_the_file:: + "myvar" string => execresult("/bin/echo found_the_file", "noshell"); + !found_the_file:: + "myvar" string => execresult("/bin/echo didnt_find_the_file", "noshell"); + + reports: + "$(myvar)"; +} diff --git a/tests/acceptance/01_vars/01_basic/access_classic_array_key_value_using_with.cf b/tests/acceptance/01_vars/01_basic/access_classic_array_key_value_using_with.cf new file mode 100644 index 0000000000..91642295dd --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/access_classic_array_key_value_using_with.cf @@ -0,0 +1,57 @@ +########################################################### +# +# Test the "with" promise attribute +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3223" } + string => "Test that with can be used in the index of a classic array to access the value"; + + "test_soft_fail" + string => "any", + meta => { "CFE-3223" }; + + vars: + "interfaces" slist => { "eth0", "wlan0", "wlan0.1" }; + + "ip[eth0]" string => "1.1.1.1"; + "ip[wlan0]" string => "2.1.1.1"; + "ip[wlan0_1]" string => "3.1.1.1"; + + "with[$(interfaces)]" + string => "$(ip[$(with)])", + with => canonify( $(interfaces) ), + comment => "This is a bug. This classic array is not generated when I try to dereference an array value using with."; + +} + +########################################################### + +bundle agent check +{ + classes: + "_pass" and => { + isvariable( "test.with[eth0]" ), + isvariable( "test.with[wlan0]" ), + isvariable( "test.with[wlan0.1]" )}; + + methods: + "Pass/Fail" usebundle => dcs_passif("_pass", $(this.promise_filename) ); + + reports: + + !_pass:: + 'One or more of "test.with[eth0]", "test.with[wlan0]", or "test.with[wlan0.1]" is not defined'; +} diff --git a/tests/acceptance/01_vars/01_basic/can_not_define_variables_in_remote_bundles.cf b/tests/acceptance/01_vars/01_basic/can_not_define_variables_in_remote_bundles.cf new file mode 100644 index 0000000000..06ed578014 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/can_not_define_variables_in_remote_bundles.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test that it is invalid to define variables in remote bundles +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "check.variable" + string => "is defined from bundle $(this.bundle)", + comment => "This should not be allowed, perhaps the first part + of the variable should be canonified, or it should + be a parser error"; + + "prefix.variable" + string => "value-ok", + comment => "This should be allowed because there is no such + bundle called 'prefix'"; + + "array[with.a.dot]" + string => "value", + comment => "dots should be allowed in array keys"; +} +####################################################### + +bundle agent check +{ + meta: + "description" -> {"CFE-1915"} + string => "Test that it is invalid to define variables in remote bundles"; + + classes: + "variable_defined" expression => isvariable("variable"); + "variable_has_content" expression => regcmp(".*", "$(variable)"); + "prefixed_var_fail" expression => not(strcmp("$(prefix.variable)", "value-ok")); + "array_with_a_dot_fail" expression => not(strcmp("$(test.array[with.a.dot])", "value")); + + "fail" expression => "(variable_defined|variable_has_content|prefixed_var_fail|array_with_a_dot_fail)"; + + reports: + DEBUG:: + "'variable' in bundle '$(this.bundle)' is defined" + if => "variable_defined"; + + "'variable' in bundle '$(this.bundle)' = '$(variable)'" + if => "variable_has_content"; + + "'prefix.variable' = $(prefix.variable)" + if => "prefixed_var_fail"; + + "array[with.a.dot] has the value '$(test.array[with.a.dot])'" + if => "array_with_a_dot_fail"; + + !fail:: + "$(this.promise_filename) Pass"; + + fail:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/duplicate_bundles.cf b/tests/acceptance/01_vars/01_basic/duplicate_bundles.cf new file mode 100644 index 0000000000..8354f83069 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/duplicate_bundles.cf @@ -0,0 +1,38 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + "one" usebundle => gvar( "x", "one" ); + !FAIL:: + "two" usebundle => gvar( "y", "two" ); +} + +bundle agent gvar ( name, value ) +{ + vars: + "${name}" string => "${value}"; +} + +bundle agent check +{ + classes: + "pass_x" expression => strcmp( "one", "${gvar.x}" ); + "pass_y" expression => strcmp( "two", "${gvar.y}" ); + + methods: + "" usebundle => dcs_passif_expected("pass_x,pass_y", + "", + $(this.promise_filename)), + inherit => "true"; + + reports: + EXTRA:: + "gvar.x => ${gvar.x}"; + "gvar.y => ${gvar.y}"; +} diff --git a/tests/acceptance/01_vars/01_basic/empty-splice-does-not-segfault.cf b/tests/acceptance/01_vars/01_basic/empty-splice-does-not-segfault.cf new file mode 100644 index 0000000000..870422043f --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/empty-splice-does-not-segfault.cf @@ -0,0 +1,22 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ +reports: + "empty @{}"; +} + +bundle agent check +{ +reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/01_vars/01_basic/fncall-invalid-type.x.cf b/tests/acceptance/01_vars/01_basic/fncall-invalid-type.x.cf new file mode 100644 index 0000000000..7e4048a1e1 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/fncall-invalid-type.x.cf @@ -0,0 +1,13 @@ +body common control +{ + bundlesequence => { test }; +} + +bundle agent test +{ +vars: + + "mylist" slist => { "a", "b" }; + + "canonified_$(mylist)" slist => canonify("$(mylist)"); +} diff --git a/tests/acceptance/01_vars/01_basic/long_string_from_embedded.cf b/tests/acceptance/01_vars/01_basic/long_string_from_embedded.cf new file mode 100644 index 0000000000..333ac56422 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/long_string_from_embedded.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test that we don't crash when slist has very long elements +# Redmine:3957 (https://cfengine.com/dev/issues/3957) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "keys" slist => { +"ssh-dss YavBsyK3sHDac4Gj4nDbVH9E7OfRPi16DADNOWGKEjWiBB0cCe0UY6xxEgcYuwOOl8HUGqqzLOByvUbo1hENDQldKFt8N7WIb2E9gXFtGm0Sf0NlYARALj1nhLju9hodpGxkr4vKioW1fZQBmBqsI7Ky8ZhzU5p4CAm4uCbxAZuiIhDTAcxj7RlDy3fe9WBw7v0cSIbu8E3zEbPn0VQjTduLCJtFOb5LvUxpxUsHMhHu0xS8DvXIwf7l83cQ0XZtwyIgbF3ZxjIZaPyYhKZRAV1qCze4BlcOerJeVYOiKjpXJlOoBprxy2SfEYVS4Khun0efkmsmBAfllKUUNCqsK9j6oWo7BvNbXqzkl2ULJd6h0LNy9jW8IXnnrfWknLGa7lksVmL3afSAufDAb7yRESaUP5KmcD3ghP3Nvu7fy9hz7nIAPgj2dQOwnFRbkv9Svoi9YmCtvBWYAcwIUVNJBmwXe5j5xJyt5vOyRSI2ooJ9mk69UOCsIM8PEnEXwtHcsg9fDjfu9ChFY2HWZPWeNXH05SATnSyLgclQsZcazhQXPoxZbcqR80mfuRX9ymEzNwZJ1jJUERnj9PZkSInu6mFVRjTaQjg8yvqMGRxWknP8aURCDh5rr4Na23jULVVI66heWOi7nzLqBg5U8GsbLsocM8zBam8bhXfU0KvpBo5wlUtHnDWHoMlS3ktr25ldrp3zLuywhHe5vGUbg2E0gr4xB3oPAyaDa3MGQiVXPfngu5CJhwIXmeN5JtIBA0S46ihg7lQ6IU8kAaRHwhWh3TokoNFOYE8R5i24gpYNF8dgCRGqSZIT5V7wuR3qG1Wau963ILuX8EFOSu7xDc5qnGTem3FwICTpSdnZNqpzRt4Ipqsn4Pa2tFFpHlzDwKFRPS7r8v4QX7acqdTSCUOuZ68GwDRlvHIz5mHUf1XLY3FH2InuWFsonJAZwmEbX5evWXFgyJHFlplQcGJvyNdL0T6oBmbG3ssSMoRnqnsk8XjGWtUyvYoQmOY8HWlMhf4V3v78C4k4TnCbN4LmnDYaDcnXMejrUMYDk6QwW3fdTF5ZyjJMy4ANu2fJfKysZMc1Cc== root@host1", +"ssh-dss a58AZSxQlmcq33EBCW1GuhpkYCgUfr7o5A1arQZ2dr8I4kldCF76mpl6o2CFthZs4YUU8LewbIDDPqjhS2WwafTXeMEvPMtmLufk6E5njNy2WQtAnmGc9R21qIteTgikY9ubRCM3hlhzJ1wGSKCsE5oBHDQC0thk1ljJGHzsEl0AuVClkUq3yz4eWAsiRdbD9QY7ddD6zA61aHomuczKU5F0VvyO8gRQmWV7b6lysoUFcMgCPVf3UaDdg7L7vvBVkQN2vGCh9CCJz5OkBShlZVNaAXd5TkKJNmsTuBDscPyxeCBAk8sai69f1NNtTeAyWTtSu1KiqTDvD9aal93MhpZnPdOZzNr7etK4C7HuPD0uFjdvKZy1H55rpwNHnJ4GojqiNG1VvN5bGLa3sSLiKOngtMBCokdtHpZn2eHD7oLUROTIG3ZXqFGGvfKEP5zlpvJz3392n4PDQ7EKuNFPhyNQpVXEIAQEDcmeWMopVTGezLoFJG01hKMPxs5QWF7qetVLi1pCjmlUpqgE8c81WGxvMe7ooMtQbeVNulX3qBC3rZhYKtk0R5AA8JxmxHSLYlLFbtnR1PA97hnRvnvlfk92i7WL1hjJsMl29LOrubi554Dr9N2uVUrCFcPZMK45PY0TiRH82AKFmkM8mbM0rndJxoJobZsqRAGHVIkcS53hxMT69liRxlCyubwcxgDaqmeQnJU2Ug0YyFs1uxt4NT9laJ0CO2IxhkbmGeDGw1FJqKyc8Haov263cFMcB97I3gyNHccsAynQnxpMS1ltTFXalghuochdue4unbq0Ty2PfS4jPMkavBlMYN8UZdnyZHuUhycwBJri1Grv5kf2SP00P6NQhuB8kwjqoTG8ay5fKWvhDrVetd7tPuj4dMouHuDeaLJInc7Cz0S5tQOuMfRhpPqk1E8A0YnuNCyPDNuW75rkXZkxP9cYSVWeDa1wgOyNLZDTzYts82qiu9kxLbZ184jowJ8rru4UB20JRtoehnD2nGU1NfegD1qOBgQHtDuB18xggfvOgvUNXBrQpICSz0JqszpftAgV5TJq1FjOdjUo1kOCFuqqi6C4S0Siho== root@host2",}; + +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any", + comment => "If we made it to here, we didn't crash"; + + reports: + DEBUG:: + "$(this.bundle): Got key $(test.keys)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/multi_index_arrays_variable_lookup.cf b/tests/acceptance/01_vars/01_basic/multi_index_arrays_variable_lookup.cf new file mode 100644 index 0000000000..de7a914bb0 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/multi_index_arrays_variable_lookup.cf @@ -0,0 +1,52 @@ +####################################################### +# +# Conflation of multi-index entries in arrays +# Redmine#6674: Conflation of multi-index entries in arrays +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "file[22][0]" string => "something"; + "file[2][20]" string => "anything"; + + "a[10]" string => "a"; + "a1[0]" string => "b"; +} + +####################################################### + +bundle agent check +{ + classes: + "fail_1" expression => strcmp("${test.file[22][0]}", "${test.file[2][20]}"); + "fail_2" expression => strcmp("${test.a[10]}", "${test.a1[0]}"); + + "failure" or => { "fail_1", "fail_2" }; + + reports: + failure:: + "$(this.promise_filename) FAIL"; + !failure:: + "$(this.promise_filename) Pass"; + +} + diff --git a/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/1/expected_output.txt b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/1/expected_output.txt new file mode 100644 index 0000000000..c9fdc33817 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/1/expected_output.txt @@ -0,0 +1,3 @@ +Unqualified: The color is #f5821f +Bundle-qualified: The color is #f5821f +Fully-qualified: The color is #f5821f diff --git a/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/1/main.cf b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/1/main.cf new file mode 100644 index 0000000000..f7e36e8ead --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/1/main.cf @@ -0,0 +1,59 @@ +# NOTE: This test is nearly identical to ../2/main.cf, the only difference is a +# single variable definition +body file control +{ + inputs => { + "../../../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent init +{ + commands: + windows:: + "$(G.dos2unix) $(this.promise_dirname)/expected_output.txt" -> { "ENT-10433" }; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-8817" } + string => "Bundle qualified variables should target the promisers namespace. This shows the case where a variable is NOT set in a bundle of the same name in the default namespace."; + +# NOTE THE ABSENCE OF A VARIABLE DEFINITION HERE + + methods: + "Test Reporting Namespaced Variables" + usebundle => example_space:test( $(G.testfile) ); +} + +bundle agent check +{ + methods: + + "Pass/Fail" usebundle => dcs_check_diff("$(this.promise_dirname)/expected_output.txt", + $(G.testfile), + $(this.promise_filename)); +} + +body file control +{ + namespace => "example_space"; +} + +bundle agent test(file) +{ + vars: + "color" string => "#f5821f"; + + reports: + "Unqualified: The color is $(color)" report_to_file => "$(file)"; + "Bundle-qualified: The color is $(test.color)" report_to_file => "$(file)"; + "Fully-qualified: The color is $(example_space:test.color)" report_to_file => "$(file)"; +} diff --git a/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/2/expected_output.txt b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/2/expected_output.txt new file mode 100644 index 0000000000..c9fdc33817 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/2/expected_output.txt @@ -0,0 +1,3 @@ +Unqualified: The color is #f5821f +Bundle-qualified: The color is #f5821f +Fully-qualified: The color is #f5821f diff --git a/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/2/main.cf b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/2/main.cf new file mode 100644 index 0000000000..4dd235b4c9 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/2/main.cf @@ -0,0 +1,60 @@ +# NOTE: This test is nearly identical to ../1/main.cf, the only difference is a +# single variable definition +body file control +{ + inputs => { + "../../../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent init +{ + commands: + windows:: + "$(G.dos2unix) $(this.promise_dirname)/expected_output.txt" -> { "ENT-10433" }; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-8817" } + string => "Bundle qualified variables should target the promisers namespace. This shows the case where a variable is set in a bundle of the same name in the default namespace."; + + vars: + "color" string => "#052569"; # THIS IS THE ONLY DIFFERENCE BETWEEN ../1/main.cf + + methods: + "Test Reporting Namespaced Variables" + usebundle => example_space:test( $(G.testfile) ); +} + +bundle agent check +{ + methods: + + "Pass/Fail" usebundle => dcs_check_diff( "$(this.promise_dirname)/expected_output.txt", + $(G.testfile), + $(this.promise_filename)); +} + +body file control +{ + namespace => "example_space"; +} + +bundle agent test(file) +{ + vars: + "color" string => "#f5821f"; + + reports: + "Unqualified: The color is $(color)" report_to_file => "$(file)"; + "Bundle-qualified: The color is $(test.color)" report_to_file => "$(file)"; + "Fully-qualified: The color is $(example_space:test.color)" report_to_file => "$(file)"; +} diff --git a/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/3/expected_output.txt b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/3/expected_output.txt new file mode 100644 index 0000000000..17f61aa1df --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/3/expected_output.txt @@ -0,0 +1,2 @@ +Bundle-qualified another bundle: The value of my_bundle.My_Variable is GOOD VARIABLE VALUE +Fully-qualified another bundle: The value of example_space:my_bundle.My_Variable GOOD VARIABLE VALUE diff --git a/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/3/main.cf b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/3/main.cf new file mode 100644 index 0000000000..900ffd4966 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/bundle_qualified_refs/3/main.cf @@ -0,0 +1,74 @@ +# NOTE: This test is nearly identical to ../1/main.cf, the only difference is a +# single variable definition + +body file control +{ + inputs => { + "../../../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} +bundle agent my_bundle +{ + vars: + "My_Variable" string => "WRONG VARIABLE VALUE"; +} +bundle agent test +{ + meta: + "description" -> { "ENT-10397" } + string => concat( "The with attribute should be namespace aware.", + "This shows the case where a variable is set in a bundle of a", + "different name." ); + + "test_soft_fail" + string => "any", + meta => { "ENT-10397" }; + + methods: + "Test Reporting Namespaced Variables" + usebundle => example_space:test( $(G.testfile) ); +} + +bundle agent check +{ + methods: + + "Pass/Fail" usebundle => dcs_check_diff( "$(this.promise_dirname)/expected_output.txt", + $(G.testfile), + $(this.promise_filename)); +} + +body file control +{ + namespace => "example_space"; +} + +bundle agent my_bundle +{ + vars: + "My_Variable" string => "GOOD VARIABLE VALUE"; + +} +bundle agent test(file) +{ + methods: + "My Bundle" usebundle => my_bundle; + "write output" usebundle => write_output( $(file) ); +} +bundle agent write_output(file) { + + reports: + "Bundle-qualified another bundle: The value of my_bundle.My_Variable is $(with)" + report_to_file => "$(file)", + with => "$(my_bundle.My_Variable)"; + + "Fully-qualified another bundle: The value of example_space:my_bundle.My_Variable $(with)" + report_to_file => "$(file)", + with => "$(example_space:my_bundle.My_Variable)"; +} diff --git a/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf b/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf new file mode 100644 index 0000000000..fd07452e99 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf @@ -0,0 +1,35 @@ +# Test that variables can be passed across bundles and namespaces + +body common control +{ + inputs => { "../../../default.cf.sub", "namespaced-bundles.cf.sub1", "namespaced-bundles.cf.sub2" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + methods: + "1" usebundle => namespaced_bundles_1:unique; + "2" usebundle => namespaced_bundles_2:unique; + "3" usebundle => namespaced_bundles_1:unique2("a", "b"); + "4" usebundle => namespaced_bundles_2:unique2("one value"); +} + +bundle agent check +{ + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf.sub1 b/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf.sub1 new file mode 100644 index 0000000000..e98c0e9add --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf.sub1 @@ -0,0 +1,18 @@ +body file control +{ + namespace => "namespaced_bundles_1"; +} + +bundle agent unique +{ + reports: + DEBUG:: + "$(this.bundle): run"; +} + +bundle agent unique2(x,y) +{ + reports: + DEBUG:: + "$(this.bundle): run($(x), $(y))"; +} diff --git a/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf.sub2 b/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf.sub2 new file mode 100644 index 0000000000..8afa1de994 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/namespaced-bundles.cf.sub2 @@ -0,0 +1,19 @@ +body file control +{ + namespace => "namespaced_bundles_2"; +} + +bundle agent unique +{ + reports: + DEBUG:: + "$(this.bundle): run"; +} + + +bundle agent unique2(justone) +{ + reports: + DEBUG:: + "$(this.bundle): run($(justone))"; +} diff --git a/tests/acceptance/01_vars/01_basic/namespaces/nested-expansion-including-namespace.cf b/tests/acceptance/01_vars/01_basic/namespaces/nested-expansion-including-namespace.cf new file mode 100644 index 0000000000..26ca2ca07c --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/nested-expansion-including-namespace.cf @@ -0,0 +1,32 @@ +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1583" } + string => "Check that nested expansion with namespaces works"; + +} +bundle agent check +{ + vars: + "data_bundle" string => "default:init"; + + methods: + "Pass/FAIL" usebundle => dcs_check_strcmp( "$($(data_bundle).dummy)","dummy", $(this.promise_filename), "no"); + + reports: + "Nested expansion: '$(data_bundle).dummy' = $($(data_bundle).dummy)"; + "Not nested expansion: '$(data_bundle).dummy' = $(default:init.dummy)"; +} diff --git a/tests/acceptance/01_vars/01_basic/namespaces/nested_expansion_namespaced_variables.cf b/tests/acceptance/01_vars/01_basic/namespaces/nested_expansion_namespaced_variables.cf new file mode 100644 index 0000000000..eea51d6db7 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/nested_expansion_namespaced_variables.cf @@ -0,0 +1,43 @@ +# Test that nested variables including namespaces are expanded correctly + +body common control +{ + inputs => { "../../../default.cf.sub",}; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + meta: + "tags" slist => { "redmine6349" }; + + vars: + "dummy" string => "dummy"; +} + +bundle agent check +{ + + methods: + "Check" usebundle => check2("default:init"); + +} + +bundle agent check2(data_bundle) +{ + classes: + "ok" expression => strcmp("$($(data_bundle).dummy)", "dummy"); + + reports: + DEBUG:: + "Nested: '$(data_bundle).dummy' = $($(data_bundle).dummy)"; + "Not Nested: '$(data_bundle).dummy' = $(default:init.dummy)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + +} diff --git a/tests/acceptance/01_vars/01_basic/nested_parens_var_ref.cf b/tests/acceptance/01_vars/01_basic/nested_parens_var_ref.cf new file mode 100644 index 0000000000..1dc941a439 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/nested_parens_var_ref.cf @@ -0,0 +1,41 @@ +########################################################### +# +# Test that a variable reference with nested parentheses works +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3242" } + string => "Test that a variable reference with nested parentheses works"; + + vars: + "my_array[key(1)]" string => "value"; + "value" string => "$(my_array[key(1)])"; +} + +########################################################### + +bundle agent check +{ + classes: + "ok" and => { isvariable( "test.value" ), + strcmp( "$(test.value)", "value") + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/os_name_human.cf b/tests/acceptance/01_vars/01_basic/os_name_human.cf new file mode 100755 index 0000000000..f4d98afe93 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/os_name_human.cf @@ -0,0 +1,68 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3569" } + string => "Test for expected values of variable sys.os_name_human"; + + # + # Note: + # + # Order is important, the last variable assignment wins. + # E.g. prefer Ubuntu over Debian. + # + # This test should also succeed if none of the os-classes are defined. + # This should be covered in another test. + # + vars: + any:: + "actual" string => "$(sys.os_name_human)"; + "expected" string => "Unknown"; + debian:: + "expected" string => "Debian"; + ubuntu:: + "expected" string => "Ubuntu"; + redhat:: + "expected" string => "RHEL"; + centos:: + "expected" string => "CentOS"; + fedora:: + "expected" string => "Fedora"; + aix:: + "expected" string => "AIX"; + hpux:: + "expected" string => "HP-UX"; + suse:: + "expected" string => "SUSE"; + opensuse:: + "expected" string => "OpenSUSE"; + windows:: + "expected" string => "Windows"; + freebsd:: + "expected" string => "FreeBSD"; + macos:: + "expected" string => "macOS"; + solaris:: + "expected" string => "Solaris"; + amazon_linux:: + "expected" string => "Amazon"; +} + +bundle agent check +{ + classes: + "passed" expression => strcmp("$(test.actual)", "$(test.expected)"); + + reports: + DEBUG:: + "$(this.promise_filename) Expected: $(test.expected)"; + "$(this.promise_filename) Found: $(test.actual)"; + passed:: + "$(this.promise_filename) Pass"; + !passed:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/os_name_human_not_unknown.cf b/tests/acceptance/01_vars/01_basic/os_name_human_not_unknown.cf new file mode 100644 index 0000000000..509ab50953 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/os_name_human_not_unknown.cf @@ -0,0 +1,31 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3569" } + string => "Make sure sys.os_name_human does not resolve to 'Unknown'"; +} + +bundle agent check +{ + classes: + # Check that sys.os_name_human does not resolve to 'Unknown' + "check1" expression => not(strcmp("Unknown", "$(sys.os_name_human)")); + # Check that sys.os_name_human does not resolve to an empty string + "check2" expression => not(strcmp("", "$(sys.os_name_human)")); + # Check that sys.os_name_human does not contain 'os_name_human' + "check3" expression => not(regcmp("os_name_human", "$(sys.os_name_human)")); + "passed" and => { "check1", "check2", "check3" }; + + reports: + DEBUG:: + "sys.os_name_human resolved to '$(sys.os_name_human)'"; + passed:: + "$(this.promise_filename) Pass"; + !passed:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/os_version_major.cf b/tests/acceptance/01_vars/01_basic/os_version_major.cf new file mode 100644 index 0000000000..9e5c9229b1 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/os_version_major.cf @@ -0,0 +1,84 @@ +############################################################## +# +# Test reworked in CFE-3644 to be more dynamic +# +############################################################## + +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + any:: + "decription" -> { "CFE-3569" } + string => "Test for expected values of variable sys.os_version_major"; + + vars: + # Platforms to test + any:: + "platforms" + slist => { "debian", "ubuntu", "redhat", "rhel", "centos", "fedora", + "aix", "hpux", "suse", "opensuse", "opensuse_leap", "sles", + "solaris", "sunos", "windows", "freebsd", "macos" }; + + + # Regex matching current platforms OS-class with version numbers + !solaris&!sunos:: + "class_regex" + string => format("^(%s)_[0-9]+$", join("|", "platforms")); + solaris|sunos:: + "class_regex" + string => format("^(%s)_[0-9]+_[0-9]+$", join("|", "platforms")); + + # Regex to extract major version number from OS-class + # Edge cases: + # - On Solaris/SunOS major version comes second + # E.g. Solaris 11 has class "solaris_5_11" + any:: + "extract_regex" + string => ifelse("solaris|sunos", "^[a-z]+_[0-9]+_([0-9]+$)", + "opensuse_leap", "^[a-z_]+_([0-9]+)$", + "^[a-z]+_([0-9]+)$"); + + # Find OS-class with version numbers using regex + any:: + "os_class" + string => nth(classesmatching("$(class_regex)"), "0"); + + # Get extracted major version number + any:: + "expected" + string => nth("version_number", "1"); + + classes: + any:: + "regextract_success" + expression => regextract("$(extract_regex)", "$(os_class)", "version_number"); +} + +bundle agent check +{ + vars: + any:: + "defined_classes" + slist => classesmatching(".*"); + + classes: + any:: + "passed" + expression => strcmp("$(test.expected)", "$(sys.os_version_major)"); + + reports: + DEBUG:: + "Version number extracted from class: $(test.os_class)"; + "Defined classes: $(defined_classes)"; + "$(this.promise_filename) Expected: $(test.expected)"; + "$(this.promise_filename) Found: $(sys.os_version_major)"; + passed:: + "$(this.promise_filename) Pass"; + !passed:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/parse_long_slists.cf b/tests/acceptance/01_vars/01_basic/parse_long_slists.cf new file mode 100644 index 0000000000..4c403a3217 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/parse_long_slists.cf @@ -0,0 +1,5029 @@ +body common control { + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + # Parser used to abort here with message + # "error: memory exhausted", see Redmine#6672 + vars: + "list" ilist => { + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263, + 264, + 265, + 266, + 267, + 268, + 269, + 270, + 271, + 272, + 273, + 274, + 275, + 276, + 277, + 278, + 279, + 280, + 281, + 282, + 283, + 284, + 285, + 286, + 287, + 288, + 289, + 290, + 291, + 292, + 293, + 294, + 295, + 296, + 297, + 298, + 299, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308, + 309, + 310, + 311, + 312, + 313, + 314, + 315, + 316, + 317, + 318, + 319, + 320, + 321, + 322, + 323, + 324, + 325, + 326, + 327, + 328, + 329, + 330, + 331, + 332, + 333, + 334, + 335, + 336, + 337, + 338, + 339, + 340, + 341, + 342, + 343, + 344, + 345, + 346, + 347, + 348, + 349, + 350, + 351, + 352, + 353, + 354, + 355, + 356, + 357, + 358, + 359, + 360, + 361, + 362, + 363, + 364, + 365, + 366, + 367, + 368, + 369, + 370, + 371, + 372, + 373, + 374, + 375, + 376, + 377, + 378, + 379, + 380, + 381, + 382, + 383, + 384, + 385, + 386, + 387, + 388, + 389, + 390, + 391, + 392, + 393, + 394, + 395, + 396, + 397, + 398, + 399, + 400, + 401, + 402, + 403, + 404, + 405, + 406, + 407, + 408, + 409, + 410, + 411, + 412, + 413, + 414, + 415, + 416, + 417, + 418, + 419, + 420, + 421, + 422, + 423, + 424, + 425, + 426, + 427, + 428, + 429, + 430, + 431, + 432, + 433, + 434, + 435, + 436, + 437, + 438, + 439, + 440, + 441, + 442, + 443, + 444, + 445, + 446, + 447, + 448, + 449, + 450, + 451, + 452, + 453, + 454, + 455, + 456, + 457, + 458, + 459, + 460, + 461, + 462, + 463, + 464, + 465, + 466, + 467, + 468, + 469, + 470, + 471, + 472, + 473, + 474, + 475, + 476, + 477, + 478, + 479, + 480, + 481, + 482, + 483, + 484, + 485, + 486, + 487, + 488, + 489, + 490, + 491, + 492, + 493, + 494, + 495, + 496, + 497, + 498, + 499, + 500, + 501, + 502, + 503, + 504, + 505, + 506, + 507, + 508, + 509, + 510, + 511, + 512, + 513, + 514, + 515, + 516, + 517, + 518, + 519, + 520, + 521, + 522, + 523, + 524, + 525, + 526, + 527, + 528, + 529, + 530, + 531, + 532, + 533, + 534, + 535, + 536, + 537, + 538, + 539, + 540, + 541, + 542, + 543, + 544, + 545, + 546, + 547, + 548, + 549, + 550, + 551, + 552, + 553, + 554, + 555, + 556, + 557, + 558, + 559, + 560, + 561, + 562, + 563, + 564, + 565, + 566, + 567, + 568, + 569, + 570, + 571, + 572, + 573, + 574, + 575, + 576, + 577, + 578, + 579, + 580, + 581, + 582, + 583, + 584, + 585, + 586, + 587, + 588, + 589, + 590, + 591, + 592, + 593, + 594, + 595, + 596, + 597, + 598, + 599, + 600, + 601, + 602, + 603, + 604, + 605, + 606, + 607, + 608, + 609, + 610, + 611, + 612, + 613, + 614, + 615, + 616, + 617, + 618, + 619, + 620, + 621, + 622, + 623, + 624, + 625, + 626, + 627, + 628, + 629, + 630, + 631, + 632, + 633, + 634, + 635, + 636, + 637, + 638, + 639, + 640, + 641, + 642, + 643, + 644, + 645, + 646, + 647, + 648, + 649, + 650, + 651, + 652, + 653, + 654, + 655, + 656, + 657, + 658, + 659, + 660, + 661, + 662, + 663, + 664, + 665, + 666, + 667, + 668, + 669, + 670, + 671, + 672, + 673, + 674, + 675, + 676, + 677, + 678, + 679, + 680, + 681, + 682, + 683, + 684, + 685, + 686, + 687, + 688, + 689, + 690, + 691, + 692, + 693, + 694, + 695, + 696, + 697, + 698, + 699, + 700, + 701, + 702, + 703, + 704, + 705, + 706, + 707, + 708, + 709, + 710, + 711, + 712, + 713, + 714, + 715, + 716, + 717, + 718, + 719, + 720, + 721, + 722, + 723, + 724, + 725, + 726, + 727, + 728, + 729, + 730, + 731, + 732, + 733, + 734, + 735, + 736, + 737, + 738, + 739, + 740, + 741, + 742, + 743, + 744, + 745, + 746, + 747, + 748, + 749, + 750, + 751, + 752, + 753, + 754, + 755, + 756, + 757, + 758, + 759, + 760, + 761, + 762, + 763, + 764, + 765, + 766, + 767, + 768, + 769, + 770, + 771, + 772, + 773, + 774, + 775, + 776, + 777, + 778, + 779, + 780, + 781, + 782, + 783, + 784, + 785, + 786, + 787, + 788, + 789, + 790, + 791, + 792, + 793, + 794, + 795, + 796, + 797, + 798, + 799, + 800, + 801, + 802, + 803, + 804, + 805, + 806, + 807, + 808, + 809, + 810, + 811, + 812, + 813, + 814, + 815, + 816, + 817, + 818, + 819, + 820, + 821, + 822, + 823, + 824, + 825, + 826, + 827, + 828, + 829, + 830, + 831, + 832, + 833, + 834, + 835, + 836, + 837, + 838, + 839, + 840, + 841, + 842, + 843, + 844, + 845, + 846, + 847, + 848, + 849, + 850, + 851, + 852, + 853, + 854, + 855, + 856, + 857, + 858, + 859, + 860, + 861, + 862, + 863, + 864, + 865, + 866, + 867, + 868, + 869, + 870, + 871, + 872, + 873, + 874, + 875, + 876, + 877, + 878, + 879, + 880, + 881, + 882, + 883, + 884, + 885, + 886, + 887, + 888, + 889, + 890, + 891, + 892, + 893, + 894, + 895, + 896, + 897, + 898, + 899, + 900, + 901, + 902, + 903, + 904, + 905, + 906, + 907, + 908, + 909, + 910, + 911, + 912, + 913, + 914, + 915, + 916, + 917, + 918, + 919, + 920, + 921, + 922, + 923, + 924, + 925, + 926, + 927, + 928, + 929, + 930, + 931, + 932, + 933, + 934, + 935, + 936, + 937, + 938, + 939, + 940, + 941, + 942, + 943, + 944, + 945, + 946, + 947, + 948, + 949, + 950, + 951, + 952, + 953, + 954, + 955, + 956, + 957, + 958, + 959, + 960, + 961, + 962, + 963, + 964, + 965, + 966, + 967, + 968, + 969, + 970, + 971, + 972, + 973, + 974, + 975, + 976, + 977, + 978, + 979, + 980, + 981, + 982, + 983, + 984, + 985, + 986, + 987, + 988, + 989, + 990, + 991, + 992, + 993, + 994, + 995, + 996, + 997, + 998, + 999, + 1000, + 1001, + 1002, + 1003, + 1004, + 1005, + 1006, + 1007, + 1008, + 1009, + 1010, + 1011, + 1012, + 1013, + 1014, + 1015, + 1016, + 1017, + 1018, + 1019, + 1020, + 1021, + 1022, + 1023, + 1024, + 1025, + 1026, + 1027, + 1028, + 1029, + 1030, + 1031, + 1032, + 1033, + 1034, + 1035, + 1036, + 1037, + 1038, + 1039, + 1040, + 1041, + 1042, + 1043, + 1044, + 1045, + 1046, + 1047, + 1048, + 1049, + 1050, + 1051, + 1052, + 1053, + 1054, + 1055, + 1056, + 1057, + 1058, + 1059, + 1060, + 1061, + 1062, + 1063, + 1064, + 1065, + 1066, + 1067, + 1068, + 1069, + 1070, + 1071, + 1072, + 1073, + 1074, + 1075, + 1076, + 1077, + 1078, + 1079, + 1080, + 1081, + 1082, + 1083, + 1084, + 1085, + 1086, + 1087, + 1088, + 1089, + 1090, + 1091, + 1092, + 1093, + 1094, + 1095, + 1096, + 1097, + 1098, + 1099, + 1100, + 1101, + 1102, + 1103, + 1104, + 1105, + 1106, + 1107, + 1108, + 1109, + 1110, + 1111, + 1112, + 1113, + 1114, + 1115, + 1116, + 1117, + 1118, + 1119, + 1120, + 1121, + 1122, + 1123, + 1124, + 1125, + 1126, + 1127, + 1128, + 1129, + 1130, + 1131, + 1132, + 1133, + 1134, + 1135, + 1136, + 1137, + 1138, + 1139, + 1140, + 1141, + 1142, + 1143, + 1144, + 1145, + 1146, + 1147, + 1148, + 1149, + 1150, + 1151, + 1152, + 1153, + 1154, + 1155, + 1156, + 1157, + 1158, + 1159, + 1160, + 1161, + 1162, + 1163, + 1164, + 1165, + 1166, + 1167, + 1168, + 1169, + 1170, + 1171, + 1172, + 1173, + 1174, + 1175, + 1176, + 1177, + 1178, + 1179, + 1180, + 1181, + 1182, + 1183, + 1184, + 1185, + 1186, + 1187, + 1188, + 1189, + 1190, + 1191, + 1192, + 1193, + 1194, + 1195, + 1196, + 1197, + 1198, + 1199, + 1200, + 1201, + 1202, + 1203, + 1204, + 1205, + 1206, + 1207, + 1208, + 1209, + 1210, + 1211, + 1212, + 1213, + 1214, + 1215, + 1216, + 1217, + 1218, + 1219, + 1220, + 1221, + 1222, + 1223, + 1224, + 1225, + 1226, + 1227, + 1228, + 1229, + 1230, + 1231, + 1232, + 1233, + 1234, + 1235, + 1236, + 1237, + 1238, + 1239, + 1240, + 1241, + 1242, + 1243, + 1244, + 1245, + 1246, + 1247, + 1248, + 1249, + 1250, + 1251, + 1252, + 1253, + 1254, + 1255, + 1256, + 1257, + 1258, + 1259, + 1260, + 1261, + 1262, + 1263, + 1264, + 1265, + 1266, + 1267, + 1268, + 1269, + 1270, + 1271, + 1272, + 1273, + 1274, + 1275, + 1276, + 1277, + 1278, + 1279, + 1280, + 1281, + 1282, + 1283, + 1284, + 1285, + 1286, + 1287, + 1288, + 1289, + 1290, + 1291, + 1292, + 1293, + 1294, + 1295, + 1296, + 1297, + 1298, + 1299, + 1300, + 1301, + 1302, + 1303, + 1304, + 1305, + 1306, + 1307, + 1308, + 1309, + 1310, + 1311, + 1312, + 1313, + 1314, + 1315, + 1316, + 1317, + 1318, + 1319, + 1320, + 1321, + 1322, + 1323, + 1324, + 1325, + 1326, + 1327, + 1328, + 1329, + 1330, + 1331, + 1332, + 1333, + 1334, + 1335, + 1336, + 1337, + 1338, + 1339, + 1340, + 1341, + 1342, + 1343, + 1344, + 1345, + 1346, + 1347, + 1348, + 1349, + 1350, + 1351, + 1352, + 1353, + 1354, + 1355, + 1356, + 1357, + 1358, + 1359, + 1360, + 1361, + 1362, + 1363, + 1364, + 1365, + 1366, + 1367, + 1368, + 1369, + 1370, + 1371, + 1372, + 1373, + 1374, + 1375, + 1376, + 1377, + 1378, + 1379, + 1380, + 1381, + 1382, + 1383, + 1384, + 1385, + 1386, + 1387, + 1388, + 1389, + 1390, + 1391, + 1392, + 1393, + 1394, + 1395, + 1396, + 1397, + 1398, + 1399, + 1400, + 1401, + 1402, + 1403, + 1404, + 1405, + 1406, + 1407, + 1408, + 1409, + 1410, + 1411, + 1412, + 1413, + 1414, + 1415, + 1416, + 1417, + 1418, + 1419, + 1420, + 1421, + 1422, + 1423, + 1424, + 1425, + 1426, + 1427, + 1428, + 1429, + 1430, + 1431, + 1432, + 1433, + 1434, + 1435, + 1436, + 1437, + 1438, + 1439, + 1440, + 1441, + 1442, + 1443, + 1444, + 1445, + 1446, + 1447, + 1448, + 1449, + 1450, + 1451, + 1452, + 1453, + 1454, + 1455, + 1456, + 1457, + 1458, + 1459, + 1460, + 1461, + 1462, + 1463, + 1464, + 1465, + 1466, + 1467, + 1468, + 1469, + 1470, + 1471, + 1472, + 1473, + 1474, + 1475, + 1476, + 1477, + 1478, + 1479, + 1480, + 1481, + 1482, + 1483, + 1484, + 1485, + 1486, + 1487, + 1488, + 1489, + 1490, + 1491, + 1492, + 1493, + 1494, + 1495, + 1496, + 1497, + 1498, + 1499, + 1500, + 1501, + 1502, + 1503, + 1504, + 1505, + 1506, + 1507, + 1508, + 1509, + 1510, + 1511, + 1512, + 1513, + 1514, + 1515, + 1516, + 1517, + 1518, + 1519, + 1520, + 1521, + 1522, + 1523, + 1524, + 1525, + 1526, + 1527, + 1528, + 1529, + 1530, + 1531, + 1532, + 1533, + 1534, + 1535, + 1536, + 1537, + 1538, + 1539, + 1540, + 1541, + 1542, + 1543, + 1544, + 1545, + 1546, + 1547, + 1548, + 1549, + 1550, + 1551, + 1552, + 1553, + 1554, + 1555, + 1556, + 1557, + 1558, + 1559, + 1560, + 1561, + 1562, + 1563, + 1564, + 1565, + 1566, + 1567, + 1568, + 1569, + 1570, + 1571, + 1572, + 1573, + 1574, + 1575, + 1576, + 1577, + 1578, + 1579, + 1580, + 1581, + 1582, + 1583, + 1584, + 1585, + 1586, + 1587, + 1588, + 1589, + 1590, + 1591, + 1592, + 1593, + 1594, + 1595, + 1596, + 1597, + 1598, + 1599, + 1600, + 1601, + 1602, + 1603, + 1604, + 1605, + 1606, + 1607, + 1608, + 1609, + 1610, + 1611, + 1612, + 1613, + 1614, + 1615, + 1616, + 1617, + 1618, + 1619, + 1620, + 1621, + 1622, + 1623, + 1624, + 1625, + 1626, + 1627, + 1628, + 1629, + 1630, + 1631, + 1632, + 1633, + 1634, + 1635, + 1636, + 1637, + 1638, + 1639, + 1640, + 1641, + 1642, + 1643, + 1644, + 1645, + 1646, + 1647, + 1648, + 1649, + 1650, + 1651, + 1652, + 1653, + 1654, + 1655, + 1656, + 1657, + 1658, + 1659, + 1660, + 1661, + 1662, + 1663, + 1664, + 1665, + 1666, + 1667, + 1668, + 1669, + 1670, + 1671, + 1672, + 1673, + 1674, + 1675, + 1676, + 1677, + 1678, + 1679, + 1680, + 1681, + 1682, + 1683, + 1684, + 1685, + 1686, + 1687, + 1688, + 1689, + 1690, + 1691, + 1692, + 1693, + 1694, + 1695, + 1696, + 1697, + 1698, + 1699, + 1700, + 1701, + 1702, + 1703, + 1704, + 1705, + 1706, + 1707, + 1708, + 1709, + 1710, + 1711, + 1712, + 1713, + 1714, + 1715, + 1716, + 1717, + 1718, + 1719, + 1720, + 1721, + 1722, + 1723, + 1724, + 1725, + 1726, + 1727, + 1728, + 1729, + 1730, + 1731, + 1732, + 1733, + 1734, + 1735, + 1736, + 1737, + 1738, + 1739, + 1740, + 1741, + 1742, + 1743, + 1744, + 1745, + 1746, + 1747, + 1748, + 1749, + 1750, + 1751, + 1752, + 1753, + 1754, + 1755, + 1756, + 1757, + 1758, + 1759, + 1760, + 1761, + 1762, + 1763, + 1764, + 1765, + 1766, + 1767, + 1768, + 1769, + 1770, + 1771, + 1772, + 1773, + 1774, + 1775, + 1776, + 1777, + 1778, + 1779, + 1780, + 1781, + 1782, + 1783, + 1784, + 1785, + 1786, + 1787, + 1788, + 1789, + 1790, + 1791, + 1792, + 1793, + 1794, + 1795, + 1796, + 1797, + 1798, + 1799, + 1800, + 1801, + 1802, + 1803, + 1804, + 1805, + 1806, + 1807, + 1808, + 1809, + 1810, + 1811, + 1812, + 1813, + 1814, + 1815, + 1816, + 1817, + 1818, + 1819, + 1820, + 1821, + 1822, + 1823, + 1824, + 1825, + 1826, + 1827, + 1828, + 1829, + 1830, + 1831, + 1832, + 1833, + 1834, + 1835, + 1836, + 1837, + 1838, + 1839, + 1840, + 1841, + 1842, + 1843, + 1844, + 1845, + 1846, + 1847, + 1848, + 1849, + 1850, + 1851, + 1852, + 1853, + 1854, + 1855, + 1856, + 1857, + 1858, + 1859, + 1860, + 1861, + 1862, + 1863, + 1864, + 1865, + 1866, + 1867, + 1868, + 1869, + 1870, + 1871, + 1872, + 1873, + 1874, + 1875, + 1876, + 1877, + 1878, + 1879, + 1880, + 1881, + 1882, + 1883, + 1884, + 1885, + 1886, + 1887, + 1888, + 1889, + 1890, + 1891, + 1892, + 1893, + 1894, + 1895, + 1896, + 1897, + 1898, + 1899, + 1900, + 1901, + 1902, + 1903, + 1904, + 1905, + 1906, + 1907, + 1908, + 1909, + 1910, + 1911, + 1912, + 1913, + 1914, + 1915, + 1916, + 1917, + 1918, + 1919, + 1920, + 1921, + 1922, + 1923, + 1924, + 1925, + 1926, + 1927, + 1928, + 1929, + 1930, + 1931, + 1932, + 1933, + 1934, + 1935, + 1936, + 1937, + 1938, + 1939, + 1940, + 1941, + 1942, + 1943, + 1944, + 1945, + 1946, + 1947, + 1948, + 1949, + 1950, + 1951, + 1952, + 1953, + 1954, + 1955, + 1956, + 1957, + 1958, + 1959, + 1960, + 1961, + 1962, + 1963, + 1964, + 1965, + 1966, + 1967, + 1968, + 1969, + 1970, + 1971, + 1972, + 1973, + 1974, + 1975, + 1976, + 1977, + 1978, + 1979, + 1980, + 1981, + 1982, + 1983, + 1984, + 1985, + 1986, + 1987, + 1988, + 1989, + 1990, + 1991, + 1992, + 1993, + 1994, + 1995, + 1996, + 1997, + 1998, + 1999, + 2000, + 2001, + 2002, + 2003, + 2004, + 2005, + 2006, + 2007, + 2008, + 2009, + 2010, + 2011, + 2012, + 2013, + 2014, + 2015, + 2016, + 2017, + 2018, + 2019, + 2020, + 2021, + 2022, + 2023, + 2024, + 2025, + 2026, + 2027, + 2028, + 2029, + 2030, + 2031, + 2032, + 2033, + 2034, + 2035, + 2036, + 2037, + 2038, + 2039, + 2040, + 2041, + 2042, + 2043, + 2044, + 2045, + 2046, + 2047, + 2048, + 2049, + 2050, + 2051, + 2052, + 2053, + 2054, + 2055, + 2056, + 2057, + 2058, + 2059, + 2060, + 2061, + 2062, + 2063, + 2064, + 2065, + 2066, + 2067, + 2068, + 2069, + 2070, + 2071, + 2072, + 2073, + 2074, + 2075, + 2076, + 2077, + 2078, + 2079, + 2080, + 2081, + 2082, + 2083, + 2084, + 2085, + 2086, + 2087, + 2088, + 2089, + 2090, + 2091, + 2092, + 2093, + 2094, + 2095, + 2096, + 2097, + 2098, + 2099, + 2100, + 2101, + 2102, + 2103, + 2104, + 2105, + 2106, + 2107, + 2108, + 2109, + 2110, + 2111, + 2112, + 2113, + 2114, + 2115, + 2116, + 2117, + 2118, + 2119, + 2120, + 2121, + 2122, + 2123, + 2124, + 2125, + 2126, + 2127, + 2128, + 2129, + 2130, + 2131, + 2132, + 2133, + 2134, + 2135, + 2136, + 2137, + 2138, + 2139, + 2140, + 2141, + 2142, + 2143, + 2144, + 2145, + 2146, + 2147, + 2148, + 2149, + 2150, + 2151, + 2152, + 2153, + 2154, + 2155, + 2156, + 2157, + 2158, + 2159, + 2160, + 2161, + 2162, + 2163, + 2164, + 2165, + 2166, + 2167, + 2168, + 2169, + 2170, + 2171, + 2172, + 2173, + 2174, + 2175, + 2176, + 2177, + 2178, + 2179, + 2180, + 2181, + 2182, + 2183, + 2184, + 2185, + 2186, + 2187, + 2188, + 2189, + 2190, + 2191, + 2192, + 2193, + 2194, + 2195, + 2196, + 2197, + 2198, + 2199, + 2200, + 2201, + 2202, + 2203, + 2204, + 2205, + 2206, + 2207, + 2208, + 2209, + 2210, + 2211, + 2212, + 2213, + 2214, + 2215, + 2216, + 2217, + 2218, + 2219, + 2220, + 2221, + 2222, + 2223, + 2224, + 2225, + 2226, + 2227, + 2228, + 2229, + 2230, + 2231, + 2232, + 2233, + 2234, + 2235, + 2236, + 2237, + 2238, + 2239, + 2240, + 2241, + 2242, + 2243, + 2244, + 2245, + 2246, + 2247, + 2248, + 2249, + 2250, + 2251, + 2252, + 2253, + 2254, + 2255, + 2256, + 2257, + 2258, + 2259, + 2260, + 2261, + 2262, + 2263, + 2264, + 2265, + 2266, + 2267, + 2268, + 2269, + 2270, + 2271, + 2272, + 2273, + 2274, + 2275, + 2276, + 2277, + 2278, + 2279, + 2280, + 2281, + 2282, + 2283, + 2284, + 2285, + 2286, + 2287, + 2288, + 2289, + 2290, + 2291, + 2292, + 2293, + 2294, + 2295, + 2296, + 2297, + 2298, + 2299, + 2300, + 2301, + 2302, + 2303, + 2304, + 2305, + 2306, + 2307, + 2308, + 2309, + 2310, + 2311, + 2312, + 2313, + 2314, + 2315, + 2316, + 2317, + 2318, + 2319, + 2320, + 2321, + 2322, + 2323, + 2324, + 2325, + 2326, + 2327, + 2328, + 2329, + 2330, + 2331, + 2332, + 2333, + 2334, + 2335, + 2336, + 2337, + 2338, + 2339, + 2340, + 2341, + 2342, + 2343, + 2344, + 2345, + 2346, + 2347, + 2348, + 2349, + 2350, + 2351, + 2352, + 2353, + 2354, + 2355, + 2356, + 2357, + 2358, + 2359, + 2360, + 2361, + 2362, + 2363, + 2364, + 2365, + 2366, + 2367, + 2368, + 2369, + 2370, + 2371, + 2372, + 2373, + 2374, + 2375, + 2376, + 2377, + 2378, + 2379, + 2380, + 2381, + 2382, + 2383, + 2384, + 2385, + 2386, + 2387, + 2388, + 2389, + 2390, + 2391, + 2392, + 2393, + 2394, + 2395, + 2396, + 2397, + 2398, + 2399, + 2400, + 2401, + 2402, + 2403, + 2404, + 2405, + 2406, + 2407, + 2408, + 2409, + 2410, + 2411, + 2412, + 2413, + 2414, + 2415, + 2416, + 2417, + 2418, + 2419, + 2420, + 2421, + 2422, + 2423, + 2424, + 2425, + 2426, + 2427, + 2428, + 2429, + 2430, + 2431, + 2432, + 2433, + 2434, + 2435, + 2436, + 2437, + 2438, + 2439, + 2440, + 2441, + 2442, + 2443, + 2444, + 2445, + 2446, + 2447, + 2448, + 2449, + 2450, + 2451, + 2452, + 2453, + 2454, + 2455, + 2456, + 2457, + 2458, + 2459, + 2460, + 2461, + 2462, + 2463, + 2464, + 2465, + 2466, + 2467, + 2468, + 2469, + 2470, + 2471, + 2472, + 2473, + 2474, + 2475, + 2476, + 2477, + 2478, + 2479, + 2480, + 2481, + 2482, + 2483, + 2484, + 2485, + 2486, + 2487, + 2488, + 2489, + 2490, + 2491, + 2492, + 2493, + 2494, + 2495, + 2496, + 2497, + 2498, + 2499, + 2500, + 2501, + 2502, + 2503, + 2504, + 2505, + 2506, + 2507, + 2508, + 2509, + 2510, + 2511, + 2512, + 2513, + 2514, + 2515, + 2516, + 2517, + 2518, + 2519, + 2520, + 2521, + 2522, + 2523, + 2524, + 2525, + 2526, + 2527, + 2528, + 2529, + 2530, + 2531, + 2532, + 2533, + 2534, + 2535, + 2536, + 2537, + 2538, + 2539, + 2540, + 2541, + 2542, + 2543, + 2544, + 2545, + 2546, + 2547, + 2548, + 2549, + 2550, + 2551, + 2552, + 2553, + 2554, + 2555, + 2556, + 2557, + 2558, + 2559, + 2560, + 2561, + 2562, + 2563, + 2564, + 2565, + 2566, + 2567, + 2568, + 2569, + 2570, + 2571, + 2572, + 2573, + 2574, + 2575, + 2576, + 2577, + 2578, + 2579, + 2580, + 2581, + 2582, + 2583, + 2584, + 2585, + 2586, + 2587, + 2588, + 2589, + 2590, + 2591, + 2592, + 2593, + 2594, + 2595, + 2596, + 2597, + 2598, + 2599, + 2600, + 2601, + 2602, + 2603, + 2604, + 2605, + 2606, + 2607, + 2608, + 2609, + 2610, + 2611, + 2612, + 2613, + 2614, + 2615, + 2616, + 2617, + 2618, + 2619, + 2620, + 2621, + 2622, + 2623, + 2624, + 2625, + 2626, + 2627, + 2628, + 2629, + 2630, + 2631, + 2632, + 2633, + 2634, + 2635, + 2636, + 2637, + 2638, + 2639, + 2640, + 2641, + 2642, + 2643, + 2644, + 2645, + 2646, + 2647, + 2648, + 2649, + 2650, + 2651, + 2652, + 2653, + 2654, + 2655, + 2656, + 2657, + 2658, + 2659, + 2660, + 2661, + 2662, + 2663, + 2664, + 2665, + 2666, + 2667, + 2668, + 2669, + 2670, + 2671, + 2672, + 2673, + 2674, + 2675, + 2676, + 2677, + 2678, + 2679, + 2680, + 2681, + 2682, + 2683, + 2684, + 2685, + 2686, + 2687, + 2688, + 2689, + 2690, + 2691, + 2692, + 2693, + 2694, + 2695, + 2696, + 2697, + 2698, + 2699, + 2700, + 2701, + 2702, + 2703, + 2704, + 2705, + 2706, + 2707, + 2708, + 2709, + 2710, + 2711, + 2712, + 2713, + 2714, + 2715, + 2716, + 2717, + 2718, + 2719, + 2720, + 2721, + 2722, + 2723, + 2724, + 2725, + 2726, + 2727, + 2728, + 2729, + 2730, + 2731, + 2732, + 2733, + 2734, + 2735, + 2736, + 2737, + 2738, + 2739, + 2740, + 2741, + 2742, + 2743, + 2744, + 2745, + 2746, + 2747, + 2748, + 2749, + 2750, + 2751, + 2752, + 2753, + 2754, + 2755, + 2756, + 2757, + 2758, + 2759, + 2760, + 2761, + 2762, + 2763, + 2764, + 2765, + 2766, + 2767, + 2768, + 2769, + 2770, + 2771, + 2772, + 2773, + 2774, + 2775, + 2776, + 2777, + 2778, + 2779, + 2780, + 2781, + 2782, + 2783, + 2784, + 2785, + 2786, + 2787, + 2788, + 2789, + 2790, + 2791, + 2792, + 2793, + 2794, + 2795, + 2796, + 2797, + 2798, + 2799, + 2800, + 2801, + 2802, + 2803, + 2804, + 2805, + 2806, + 2807, + 2808, + 2809, + 2810, + 2811, + 2812, + 2813, + 2814, + 2815, + 2816, + 2817, + 2818, + 2819, + 2820, + 2821, + 2822, + 2823, + 2824, + 2825, + 2826, + 2827, + 2828, + 2829, + 2830, + 2831, + 2832, + 2833, + 2834, + 2835, + 2836, + 2837, + 2838, + 2839, + 2840, + 2841, + 2842, + 2843, + 2844, + 2845, + 2846, + 2847, + 2848, + 2849, + 2850, + 2851, + 2852, + 2853, + 2854, + 2855, + 2856, + 2857, + 2858, + 2859, + 2860, + 2861, + 2862, + 2863, + 2864, + 2865, + 2866, + 2867, + 2868, + 2869, + 2870, + 2871, + 2872, + 2873, + 2874, + 2875, + 2876, + 2877, + 2878, + 2879, + 2880, + 2881, + 2882, + 2883, + 2884, + 2885, + 2886, + 2887, + 2888, + 2889, + 2890, + 2891, + 2892, + 2893, + 2894, + 2895, + 2896, + 2897, + 2898, + 2899, + 2900, + 2901, + 2902, + 2903, + 2904, + 2905, + 2906, + 2907, + 2908, + 2909, + 2910, + 2911, + 2912, + 2913, + 2914, + 2915, + 2916, + 2917, + 2918, + 2919, + 2920, + 2921, + 2922, + 2923, + 2924, + 2925, + 2926, + 2927, + 2928, + 2929, + 2930, + 2931, + 2932, + 2933, + 2934, + 2935, + 2936, + 2937, + 2938, + 2939, + 2940, + 2941, + 2942, + 2943, + 2944, + 2945, + 2946, + 2947, + 2948, + 2949, + 2950, + 2951, + 2952, + 2953, + 2954, + 2955, + 2956, + 2957, + 2958, + 2959, + 2960, + 2961, + 2962, + 2963, + 2964, + 2965, + 2966, + 2967, + 2968, + 2969, + 2970, + 2971, + 2972, + 2973, + 2974, + 2975, + 2976, + 2977, + 2978, + 2979, + 2980, + 2981, + 2982, + 2983, + 2984, + 2985, + 2986, + 2987, + 2988, + 2989, + 2990, + 2991, + 2992, + 2993, + 2994, + 2995, + 2996, + 2997, + 2998, + 2999, + 3000, + 3001, + 3002, + 3003, + 3004, + 3005, + 3006, + 3007, + 3008, + 3009, + 3010, + 3011, + 3012, + 3013, + 3014, + 3015, + 3016, + 3017, + 3018, + 3019, + 3020, + 3021, + 3022, + 3023, + 3024, + 3025, + 3026, + 3027, + 3028, + 3029, + 3030, + 3031, + 3032, + 3033, + 3034, + 3035, + 3036, + 3037, + 3038, + 3039, + 3040, + 3041, + 3042, + 3043, + 3044, + 3045, + 3046, + 3047, + 3048, + 3049, + 3050, + 3051, + 3052, + 3053, + 3054, + 3055, + 3056, + 3057, + 3058, + 3059, + 3060, + 3061, + 3062, + 3063, + 3064, + 3065, + 3066, + 3067, + 3068, + 3069, + 3070, + 3071, + 3072, + 3073, + 3074, + 3075, + 3076, + 3077, + 3078, + 3079, + 3080, + 3081, + 3082, + 3083, + 3084, + 3085, + 3086, + 3087, + 3088, + 3089, + 3090, + 3091, + 3092, + 3093, + 3094, + 3095, + 3096, + 3097, + 3098, + 3099, + 3100, + 3101, + 3102, + 3103, + 3104, + 3105, + 3106, + 3107, + 3108, + 3109, + 3110, + 3111, + 3112, + 3113, + 3114, + 3115, + 3116, + 3117, + 3118, + 3119, + 3120, + 3121, + 3122, + 3123, + 3124, + 3125, + 3126, + 3127, + 3128, + 3129, + 3130, + 3131, + 3132, + 3133, + 3134, + 3135, + 3136, + 3137, + 3138, + 3139, + 3140, + 3141, + 3142, + 3143, + 3144, + 3145, + 3146, + 3147, + 3148, + 3149, + 3150, + 3151, + 3152, + 3153, + 3154, + 3155, + 3156, + 3157, + 3158, + 3159, + 3160, + 3161, + 3162, + 3163, + 3164, + 3165, + 3166, + 3167, + 3168, + 3169, + 3170, + 3171, + 3172, + 3173, + 3174, + 3175, + 3176, + 3177, + 3178, + 3179, + 3180, + 3181, + 3182, + 3183, + 3184, + 3185, + 3186, + 3187, + 3188, + 3189, + 3190, + 3191, + 3192, + 3193, + 3194, + 3195, + 3196, + 3197, + 3198, + 3199, + 3200, + 3201, + 3202, + 3203, + 3204, + 3205, + 3206, + 3207, + 3208, + 3209, + 3210, + 3211, + 3212, + 3213, + 3214, + 3215, + 3216, + 3217, + 3218, + 3219, + 3220, + 3221, + 3222, + 3223, + 3224, + 3225, + 3226, + 3227, + 3228, + 3229, + 3230, + 3231, + 3232, + 3233, + 3234, + 3235, + 3236, + 3237, + 3238, + 3239, + 3240, + 3241, + 3242, + 3243, + 3244, + 3245, + 3246, + 3247, + 3248, + 3249, + 3250, + 3251, + 3252, + 3253, + 3254, + 3255, + 3256, + 3257, + 3258, + 3259, + 3260, + 3261, + 3262, + 3263, + 3264, + 3265, + 3266, + 3267, + 3268, + 3269, + 3270, + 3271, + 3272, + 3273, + 3274, + 3275, + 3276, + 3277, + 3278, + 3279, + 3280, + 3281, + 3282, + 3283, + 3284, + 3285, + 3286, + 3287, + 3288, + 3289, + 3290, + 3291, + 3292, + 3293, + 3294, + 3295, + 3296, + 3297, + 3298, + 3299, + 3300, + 3301, + 3302, + 3303, + 3304, + 3305, + 3306, + 3307, + 3308, + 3309, + 3310, + 3311, + 3312, + 3313, + 3314, + 3315, + 3316, + 3317, + 3318, + 3319, + 3320, + 3321, + 3322, + 3323, + 3324, + 3325, + 3326, + 3327, + 3328, + 3329, + 3330, + 3331, + 3332, + 3333, + 3334, + 3335, + 3336, + 3337, + 3338, + 3339, + 3340, + 3341, + 3342, + 3343, + 3344, + 3345, + 3346, + 3347, + 3348, + 3349, + 3350, + 3351, + 3352, + 3353, + 3354, + 3355, + 3356, + 3357, + 3358, + 3359, + 3360, + 3361, + 3362, + 3363, + 3364, + 3365, + 3366, + 3367, + 3368, + 3369, + 3370, + 3371, + 3372, + 3373, + 3374, + 3375, + 3376, + 3377, + 3378, + 3379, + 3380, + 3381, + 3382, + 3383, + 3384, + 3385, + 3386, + 3387, + 3388, + 3389, + 3390, + 3391, + 3392, + 3393, + 3394, + 3395, + 3396, + 3397, + 3398, + 3399, + 3400, + 3401, + 3402, + 3403, + 3404, + 3405, + 3406, + 3407, + 3408, + 3409, + 3410, + 3411, + 3412, + 3413, + 3414, + 3415, + 3416, + 3417, + 3418, + 3419, + 3420, + 3421, + 3422, + 3423, + 3424, + 3425, + 3426, + 3427, + 3428, + 3429, + 3430, + 3431, + 3432, + 3433, + 3434, + 3435, + 3436, + 3437, + 3438, + 3439, + 3440, + 3441, + 3442, + 3443, + 3444, + 3445, + 3446, + 3447, + 3448, + 3449, + 3450, + 3451, + 3452, + 3453, + 3454, + 3455, + 3456, + 3457, + 3458, + 3459, + 3460, + 3461, + 3462, + 3463, + 3464, + 3465, + 3466, + 3467, + 3468, + 3469, + 3470, + 3471, + 3472, + 3473, + 3474, + 3475, + 3476, + 3477, + 3478, + 3479, + 3480, + 3481, + 3482, + 3483, + 3484, + 3485, + 3486, + 3487, + 3488, + 3489, + 3490, + 3491, + 3492, + 3493, + 3494, + 3495, + 3496, + 3497, + 3498, + 3499, + 3500, + 3501, + 3502, + 3503, + 3504, + 3505, + 3506, + 3507, + 3508, + 3509, + 3510, + 3511, + 3512, + 3513, + 3514, + 3515, + 3516, + 3517, + 3518, + 3519, + 3520, + 3521, + 3522, + 3523, + 3524, + 3525, + 3526, + 3527, + 3528, + 3529, + 3530, + 3531, + 3532, + 3533, + 3534, + 3535, + 3536, + 3537, + 3538, + 3539, + 3540, + 3541, + 3542, + 3543, + 3544, + 3545, + 3546, + 3547, + 3548, + 3549, + 3550, + 3551, + 3552, + 3553, + 3554, + 3555, + 3556, + 3557, + 3558, + 3559, + 3560, + 3561, + 3562, + 3563, + 3564, + 3565, + 3566, + 3567, + 3568, + 3569, + 3570, + 3571, + 3572, + 3573, + 3574, + 3575, + 3576, + 3577, + 3578, + 3579, + 3580, + 3581, + 3582, + 3583, + 3584, + 3585, + 3586, + 3587, + 3588, + 3589, + 3590, + 3591, + 3592, + 3593, + 3594, + 3595, + 3596, + 3597, + 3598, + 3599, + 3600, + 3601, + 3602, + 3603, + 3604, + 3605, + 3606, + 3607, + 3608, + 3609, + 3610, + 3611, + 3612, + 3613, + 3614, + 3615, + 3616, + 3617, + 3618, + 3619, + 3620, + 3621, + 3622, + 3623, + 3624, + 3625, + 3626, + 3627, + 3628, + 3629, + 3630, + 3631, + 3632, + 3633, + 3634, + 3635, + 3636, + 3637, + 3638, + 3639, + 3640, + 3641, + 3642, + 3643, + 3644, + 3645, + 3646, + 3647, + 3648, + 3649, + 3650, + 3651, + 3652, + 3653, + 3654, + 3655, + 3656, + 3657, + 3658, + 3659, + 3660, + 3661, + 3662, + 3663, + 3664, + 3665, + 3666, + 3667, + 3668, + 3669, + 3670, + 3671, + 3672, + 3673, + 3674, + 3675, + 3676, + 3677, + 3678, + 3679, + 3680, + 3681, + 3682, + 3683, + 3684, + 3685, + 3686, + 3687, + 3688, + 3689, + 3690, + 3691, + 3692, + 3693, + 3694, + 3695, + 3696, + 3697, + 3698, + 3699, + 3700, + 3701, + 3702, + 3703, + 3704, + 3705, + 3706, + 3707, + 3708, + 3709, + 3710, + 3711, + 3712, + 3713, + 3714, + 3715, + 3716, + 3717, + 3718, + 3719, + 3720, + 3721, + 3722, + 3723, + 3724, + 3725, + 3726, + 3727, + 3728, + 3729, + 3730, + 3731, + 3732, + 3733, + 3734, + 3735, + 3736, + 3737, + 3738, + 3739, + 3740, + 3741, + 3742, + 3743, + 3744, + 3745, + 3746, + 3747, + 3748, + 3749, + 3750, + 3751, + 3752, + 3753, + 3754, + 3755, + 3756, + 3757, + 3758, + 3759, + 3760, + 3761, + 3762, + 3763, + 3764, + 3765, + 3766, + 3767, + 3768, + 3769, + 3770, + 3771, + 3772, + 3773, + 3774, + 3775, + 3776, + 3777, + 3778, + 3779, + 3780, + 3781, + 3782, + 3783, + 3784, + 3785, + 3786, + 3787, + 3788, + 3789, + 3790, + 3791, + 3792, + 3793, + 3794, + 3795, + 3796, + 3797, + 3798, + 3799, + 3800, + 3801, + 3802, + 3803, + 3804, + 3805, + 3806, + 3807, + 3808, + 3809, + 3810, + 3811, + 3812, + 3813, + 3814, + 3815, + 3816, + 3817, + 3818, + 3819, + 3820, + 3821, + 3822, + 3823, + 3824, + 3825, + 3826, + 3827, + 3828, + 3829, + 3830, + 3831, + 3832, + 3833, + 3834, + 3835, + 3836, + 3837, + 3838, + 3839, + 3840, + 3841, + 3842, + 3843, + 3844, + 3845, + 3846, + 3847, + 3848, + 3849, + 3850, + 3851, + 3852, + 3853, + 3854, + 3855, + 3856, + 3857, + 3858, + 3859, + 3860, + 3861, + 3862, + 3863, + 3864, + 3865, + 3866, + 3867, + 3868, + 3869, + 3870, + 3871, + 3872, + 3873, + 3874, + 3875, + 3876, + 3877, + 3878, + 3879, + 3880, + 3881, + 3882, + 3883, + 3884, + 3885, + 3886, + 3887, + 3888, + 3889, + 3890, + 3891, + 3892, + 3893, + 3894, + 3895, + 3896, + 3897, + 3898, + 3899, + 3900, + 3901, + 3902, + 3903, + 3904, + 3905, + 3906, + 3907, + 3908, + 3909, + 3910, + 3911, + 3912, + 3913, + 3914, + 3915, + 3916, + 3917, + 3918, + 3919, + 3920, + 3921, + 3922, + 3923, + 3924, + 3925, + 3926, + 3927, + 3928, + 3929, + 3930, + 3931, + 3932, + 3933, + 3934, + 3935, + 3936, + 3937, + 3938, + 3939, + 3940, + 3941, + 3942, + 3943, + 3944, + 3945, + 3946, + 3947, + 3948, + 3949, + 3950, + 3951, + 3952, + 3953, + 3954, + 3955, + 3956, + 3957, + 3958, + 3959, + 3960, + 3961, + 3962, + 3963, + 3964, + 3965, + 3966, + 3967, + 3968, + 3969, + 3970, + 3971, + 3972, + 3973, + 3974, + 3975, + 3976, + 3977, + 3978, + 3979, + 3980, + 3981, + 3982, + 3983, + 3984, + 3985, + 3986, + 3987, + 3988, + 3989, + 3990, + 3991, + 3992, + 3993, + 3994, + 3995, + 3996, + 3997, + 3998, + 3999, + 4000, + 4001, + 4002, + 4003, + 4004, + 4005, + 4006, + 4007, + 4008, + 4009, + 4010, + 4011, + 4012, + 4013, + 4014, + 4015, + 4016, + 4017, + 4018, + 4019, + 4020, + 4021, + 4022, + 4023, + 4024, + 4025, + 4026, + 4027, + 4028, + 4029, + 4030, + 4031, + 4032, + 4033, + 4034, + 4035, + 4036, + 4037, + 4038, + 4039, + 4040, + 4041, + 4042, + 4043, + 4044, + 4045, + 4046, + 4047, + 4048, + 4049, + 4050, + 4051, + 4052, + 4053, + 4054, + 4055, + 4056, + 4057, + 4058, + 4059, + 4060, + 4061, + 4062, + 4063, + 4064, + 4065, + 4066, + 4067, + 4068, + 4069, + 4070, + 4071, + 4072, + 4073, + 4074, + 4075, + 4076, + 4077, + 4078, + 4079, + 4080, + 4081, + 4082, + 4083, + 4084, + 4085, + 4086, + 4087, + 4088, + 4089, + 4090, + 4091, + 4092, + 4093, + 4094, + 4095, + 4096, + 4097, + 4098, + 4099, + 4100, + 4101, + 4102, + 4103, + 4104, + 4105, + 4106, + 4107, + 4108, + 4109, + 4110, + 4111, + 4112, + 4113, + 4114, + 4115, + 4116, + 4117, + 4118, + 4119, + 4120, + 4121, + 4122, + 4123, + 4124, + 4125, + 4126, + 4127, + 4128, + 4129, + 4130, + 4131, + 4132, + 4133, + 4134, + 4135, + 4136, + 4137, + 4138, + 4139, + 4140, + 4141, + 4142, + 4143, + 4144, + 4145, + 4146, + 4147, + 4148, + 4149, + 4150, + 4151, + 4152, + 4153, + 4154, + 4155, + 4156, + 4157, + 4158, + 4159, + 4160, + 4161, + 4162, + 4163, + 4164, + 4165, + 4166, + 4167, + 4168, + 4169, + 4170, + 4171, + 4172, + 4173, + 4174, + 4175, + 4176, + 4177, + 4178, + 4179, + 4180, + 4181, + 4182, + 4183, + 4184, + 4185, + 4186, + 4187, + 4188, + 4189, + 4190, + 4191, + 4192, + 4193, + 4194, + 4195, + 4196, + 4197, + 4198, + 4199, + 4200, + 4201, + 4202, + 4203, + 4204, + 4205, + 4206, + 4207, + 4208, + 4209, + 4210, + 4211, + 4212, + 4213, + 4214, + 4215, + 4216, + 4217, + 4218, + 4219, + 4220, + 4221, + 4222, + 4223, + 4224, + 4225, + 4226, + 4227, + 4228, + 4229, + 4230, + 4231, + 4232, + 4233, + 4234, + 4235, + 4236, + 4237, + 4238, + 4239, + 4240, + 4241, + 4242, + 4243, + 4244, + 4245, + 4246, + 4247, + 4248, + 4249, + 4250, + 4251, + 4252, + 4253, + 4254, + 4255, + 4256, + 4257, + 4258, + 4259, + 4260, + 4261, + 4262, + 4263, + 4264, + 4265, + 4266, + 4267, + 4268, + 4269, + 4270, + 4271, + 4272, + 4273, + 4274, + 4275, + 4276, + 4277, + 4278, + 4279, + 4280, + 4281, + 4282, + 4283, + 4284, + 4285, + 4286, + 4287, + 4288, + 4289, + 4290, + 4291, + 4292, + 4293, + 4294, + 4295, + 4296, + 4297, + 4298, + 4299, + 4300, + 4301, + 4302, + 4303, + 4304, + 4305, + 4306, + 4307, + 4308, + 4309, + 4310, + 4311, + 4312, + 4313, + 4314, + 4315, + 4316, + 4317, + 4318, + 4319, + 4320, + 4321, + 4322, + 4323, + 4324, + 4325, + 4326, + 4327, + 4328, + 4329, + 4330, + 4331, + 4332, + 4333, + 4334, + 4335, + 4336, + 4337, + 4338, + 4339, + 4340, + 4341, + 4342, + 4343, + 4344, + 4345, + 4346, + 4347, + 4348, + 4349, + 4350, + 4351, + 4352, + 4353, + 4354, + 4355, + 4356, + 4357, + 4358, + 4359, + 4360, + 4361, + 4362, + 4363, + 4364, + 4365, + 4366, + 4367, + 4368, + 4369, + 4370, + 4371, + 4372, + 4373, + 4374, + 4375, + 4376, + 4377, + 4378, + 4379, + 4380, + 4381, + 4382, + 4383, + 4384, + 4385, + 4386, + 4387, + 4388, + 4389, + 4390, + 4391, + 4392, + 4393, + 4394, + 4395, + 4396, + 4397, + 4398, + 4399, + 4400, + 4401, + 4402, + 4403, + 4404, + 4405, + 4406, + 4407, + 4408, + 4409, + 4410, + 4411, + 4412, + 4413, + 4414, + 4415, + 4416, + 4417, + 4418, + 4419, + 4420, + 4421, + 4422, + 4423, + 4424, + 4425, + 4426, + 4427, + 4428, + 4429, + 4430, + 4431, + 4432, + 4433, + 4434, + 4435, + 4436, + 4437, + 4438, + 4439, + 4440, + 4441, + 4442, + 4443, + 4444, + 4445, + 4446, + 4447, + 4448, + 4449, + 4450, + 4451, + 4452, + 4453, + 4454, + 4455, + 4456, + 4457, + 4458, + 4459, + 4460, + 4461, + 4462, + 4463, + 4464, + 4465, + 4466, + 4467, + 4468, + 4469, + 4470, + 4471, + 4472, + 4473, + 4474, + 4475, + 4476, + 4477, + 4478, + 4479, + 4480, + 4481, + 4482, + 4483, + 4484, + 4485, + 4486, + 4487, + 4488, + 4489, + 4490, + 4491, + 4492, + 4493, + 4494, + 4495, + 4496, + 4497, + 4498, + 4499, + 4500, + 4501, + 4502, + 4503, + 4504, + 4505, + 4506, + 4507, + 4508, + 4509, + 4510, + 4511, + 4512, + 4513, + 4514, + 4515, + 4516, + 4517, + 4518, + 4519, + 4520, + 4521, + 4522, + 4523, + 4524, + 4525, + 4526, + 4527, + 4528, + 4529, + 4530, + 4531, + 4532, + 4533, + 4534, + 4535, + 4536, + 4537, + 4538, + 4539, + 4540, + 4541, + 4542, + 4543, + 4544, + 4545, + 4546, + 4547, + 4548, + 4549, + 4550, + 4551, + 4552, + 4553, + 4554, + 4555, + 4556, + 4557, + 4558, + 4559, + 4560, + 4561, + 4562, + 4563, + 4564, + 4565, + 4566, + 4567, + 4568, + 4569, + 4570, + 4571, + 4572, + 4573, + 4574, + 4575, + 4576, + 4577, + 4578, + 4579, + 4580, + 4581, + 4582, + 4583, + 4584, + 4585, + 4586, + 4587, + 4588, + 4589, + 4590, + 4591, + 4592, + 4593, + 4594, + 4595, + 4596, + 4597, + 4598, + 4599, + 4600, + 4601, + 4602, + 4603, + 4604, + 4605, + 4606, + 4607, + 4608, + 4609, + 4610, + 4611, + 4612, + 4613, + 4614, + 4615, + 4616, + 4617, + 4618, + 4619, + 4620, + 4621, + 4622, + 4623, + 4624, + 4625, + 4626, + 4627, + 4628, + 4629, + 4630, + 4631, + 4632, + 4633, + 4634, + 4635, + 4636, + 4637, + 4638, + 4639, + 4640, + 4641, + 4642, + 4643, + 4644, + 4645, + 4646, + 4647, + 4648, + 4649, + 4650, + 4651, + 4652, + 4653, + 4654, + 4655, + 4656, + 4657, + 4658, + 4659, + 4660, + 4661, + 4662, + 4663, + 4664, + 4665, + 4666, + 4667, + 4668, + 4669, + 4670, + 4671, + 4672, + 4673, + 4674, + 4675, + 4676, + 4677, + 4678, + 4679, + 4680, + 4681, + 4682, + 4683, + 4684, + 4685, + 4686, + 4687, + 4688, + 4689, + 4690, + 4691, + 4692, + 4693, + 4694, + 4695, + 4696, + 4697, + 4698, + 4699, + 4700, + 4701, + 4702, + 4703, + 4704, + 4705, + 4706, + 4707, + 4708, + 4709, + 4710, + 4711, + 4712, + 4713, + 4714, + 4715, + 4716, + 4717, + 4718, + 4719, + 4720, + 4721, + 4722, + 4723, + 4724, + 4725, + 4726, + 4727, + 4728, + 4729, + 4730, + 4731, + 4732, + 4733, + 4734, + 4735, + 4736, + 4737, + 4738, + 4739, + 4740, + 4741, + 4742, + 4743, + 4744, + 4745, + 4746, + 4747, + 4748, + 4749, + 4750, + 4751, + 4752, + 4753, + 4754, + 4755, + 4756, + 4757, + 4758, + 4759, + 4760, + 4761, + 4762, + 4763, + 4764, + 4765, + 4766, + 4767, + 4768, + 4769, + 4770, + 4771, + 4772, + 4773, + 4774, + 4775, + 4776, + 4777, + 4778, + 4779, + 4780, + 4781, + 4782, + 4783, + 4784, + 4785, + 4786, + 4787, + 4788, + 4789, + 4790, + 4791, + 4792, + 4793, + 4794, + 4795, + 4796, + 4797, + 4798, + 4799, + 4800, + 4801, + 4802, + 4803, + 4804, + 4805, + 4806, + 4807, + 4808, + 4809, + 4810, + 4811, + 4812, + 4813, + 4814, + 4815, + 4816, + 4817, + 4818, + 4819, + 4820, + 4821, + 4822, + 4823, + 4824, + 4825, + 4826, + 4827, + 4828, + 4829, + 4830, + 4831, + 4832, + 4833, + 4834, + 4835, + 4836, + 4837, + 4838, + 4839, + 4840, + 4841, + 4842, + 4843, + 4844, + 4845, + 4846, + 4847, + 4848, + 4849, + 4850, + 4851, + 4852, + 4853, + 4854, + 4855, + 4856, + 4857, + 4858, + 4859, + 4860, + 4861, + 4862, + 4863, + 4864, + 4865, + 4866, + 4867, + 4868, + 4869, + 4870, + 4871, + 4872, + 4873, + 4874, + 4875, + 4876, + 4877, + 4878, + 4879, + 4880, + 4881, + 4882, + 4883, + 4884, + 4885, + 4886, + 4887, + 4888, + 4889, + 4890, + 4891, + 4892, + 4893, + 4894, + 4895, + 4896, + 4897, + 4898, + 4899, + 4900, + 4901, + 4902, + 4903, + 4904, + 4905, + 4906, + 4907, + 4908, + 4909, + 4910, + 4911, + 4912, + 4913, + 4914, + 4915, + 4916, + 4917, + 4918, + 4919, + 4920, + 4921, + 4922, + 4923, + 4924, + 4925, + 4926, + 4927, + 4928, + 4929, + 4930, + 4931, + 4932, + 4933, + 4934, + 4935, + 4936, + 4937, + 4938, + 4939, + 4940, + 4941, + 4942, + 4943, + 4944, + 4945, + 4946, + 4947, + 4948, + 4949, + 4950, + 4951, + 4952, + 4953, + 4954, + 4955, + 4956, + 4957, + 4958, + 4959, + 4960, + 4961, + 4962, + 4963, + 4964, + 4965, + 4966, + 4967, + 4968, + 4969, + 4970, + 4971, + 4972, + 4973, + 4974, + 4975, + 4976, + 4977, + 4978, + 4979, + 4980, + 4981, + 4982, + 4983, + 4984, + 4985, + 4986, + 4987, + 4988, + 4989, + 4990, + 4991, + 4992, + 4993, + 4994, + 4995, + }; +} + +bundle agent test +{ + vars: + "list_len" int => length("init.list"); +} + +bundle agent check +{ + classes: + "ok" expression => strcmp("4995", "$(test.list_len)"); + + reports: + DEBUG:: + "List length should be 4995, and is computed to be $(test.list_len)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/parse_number.cf b/tests/acceptance/01_vars/01_basic/parse_number.cf new file mode 100644 index 0000000000..a664a29576 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/parse_number.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Test basic interpretation of digits as numbers. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "digits" string => "42"; + "zero" string => "00000"; + "negate" string => "-137"; + "spacey" string => "17 + "; # Spaces ignored after number + "kilo" string => "42k"; + "kibi" string => "42K"; + "mega" string => "42m"; + "mibi" string => "42M"; + # NB: 42g fails on 32-bit - many places go via int, which overflows (Redmine 4404). + "giga" string => "1g"; + "gibi" string => "1G"; + "pi" string => "3.141592653589793238462643383279502884197169399375105820974"; + "nowt" string => "-000.0000e137"; + "nogiga" string => "0e0G "; # quantifier and spaces + "me2pi" string => "-.5354916555247646e-3M"; # minus exp(two pi) + + "whole" slist => { "digits", "zero", "negate", "spacey", + "kilo", "kibi", "mega", "mibi", "giga", "gibi" }; + "float" slist => { "pi", "nowt", "nogiga", "me2pi" }; + + # Expected answers: + "round[digits]" string => "$(digits)"; + "round[zero]" string => "0"; + "round[negate]" string => "$(negate)"; + "round[spacey]" string => "17"; + "round[kilo]" string => "42000"; + "round[kibi]" string => "43008"; + "round[mega]" string => "42000000"; + "round[mibi]" string => "44040192"; + "round[giga]" string => "1000000000"; + "round[gibi]" string => "1073741824"; + "ratio[$(whole)]" string => "$(round[$(whole)]).000000"; + "ratio[pi]" string => "3.14159265"; + "ratio[nowt]" string => "$(ratio[zero])"; + "ratio[nogiga]" string => "$(ratio[zero])"; + "ratio[me2pi]" string => "535.491656"; +} + +####################################################### + +bundle agent test +{ + vars: + "round[$(init.whole)]" int => "$(init.$(init.whole))"; + "ratio[$(init.whole)]" real => "$(init.$(init.whole))"; + "ratio[$(init.float)]" real => "$(init.$(init.float))"; +} + + +####################################################### + +bundle agent check +{ + vars: + "float" slist => { @(init.whole), @(init.float) }; + + classes: + "whole_$(init.whole)" expression => strcmp("$(test.round[$(init.whole)])", + "$(init.round[$(init.whole)])"); + "float_$(float)" expression => strcmp("$(test.ratio[$(float)])", + "$(init.ratio[$(float)])"); + "$(init.whole)" and => { "whole_$(init.whole)", "float_$(init.whole)" }; + "$(init.float)" and => { "float_$(init.whole)" }; + "iok" and => { @(init.whole) }; + "rok" and => { @(init.float) }; + "ok" and => { "iok", "rok" }; + + reports: + DEBUG.!iok:: + "Read $(init.$(init.whole)) as int $(test.round[$(init.whole)]), expected $(init.round[$(init.whole)])"; + DEBUG.!rok:: + "Read $(init.$(float)) as real $(test.ratio[$(float)]), expected $(init.ratio[$(float)])"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/parse_number_3572.cf b/tests/acceptance/01_vars/01_basic/parse_number_3572.cf new file mode 100644 index 0000000000..6980cb0799 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/parse_number_3572.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Regression test for RedMine #3572. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "digits" string => "42"; +} + +####################################################### + +bundle agent test +{ + vars: + "number" int => "$(init.digits)"; + "floaty" real => "$(init.digits)"; + + # RedMine #3572: this stanza should make no difference, but actually + # caused the numbers to be unexpanded, hence not "42" but literally + # "$(init.digits)", which doesn't parse as a number ! + classes: + "ignored" not => "any"; + # (In fact, even an empty classes stanza provoked the bug - but is + # that even valid ?) +} + + +####################################################### + +bundle agent check +{ + classes: + "iok" expression => strcmp("$(test.number)", "$(init.digits)"); + "rok" expression => strcmp("$(test.floaty)", "42.000000"); + "ok" and => { "iok", "rok" }; + + reports: + DEBUG.!iok:: + "Read $(init.digits) as int $(test.number)"; + DEBUG.!rok:: + "Read $(init.digits) as real $(test.floaty)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/promise_dirname.cf b/tests/acceptance/01_vars/01_basic/promise_dirname.cf new file mode 100644 index 0000000000..72c3cfa486 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/promise_dirname.cf @@ -0,0 +1,38 @@ +# Test $(this.promise_dirname) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + vars: + "expected" string => dirname($(this.promise_filename)); +} + +####################################################### + +bundle agent test +{ +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp($(init.expected), $(this.promise_dirname)); + + reports: + DEBUG:: + "$(this.promise_filename) expected dirname $(init.expected), actual $(this.promise_dirname)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/qualified_array_with_spaced_index.cf b/tests/acceptance/01_vars/01_basic/qualified_array_with_spaced_index.cf new file mode 100644 index 0000000000..8d427e545e --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/qualified_array_with_spaced_index.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test that an array variable will resolve when fully +# qualified and the index contains spaces +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "array[one two three]" string => "snookie"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("snookie", "$(test.array[one two three])"); + + reports: + DEBUG:: + "array: $(test.array[one two])"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/staging/002.x.cf b/tests/acceptance/01_vars/01_basic/staging/002.x.cf new file mode 100644 index 0000000000..6f4d9c45a2 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/002.x.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test simple variables failures +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "ten" int => "10.0"; # Not an integer +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "The int variable assignment should fail"; + cfengine_3:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/staging/003.x.cf b/tests/acceptance/01_vars/01_basic/staging/003.x.cf new file mode 100644 index 0000000000..80af3d24ea --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/003.x.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test simple variables failures +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "ten" int => "12345678901234567890"; # Integer too big +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "The int variable assignment should fail"; + cfengine_3:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/staging/011.cf b/tests/acceptance/01_vars/01_basic/staging/011.cf new file mode 100644 index 0000000000..a6836389d7 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/011.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Test missing variable expansion consistency +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "foo" int => "42"; + "baz[a]" string => "alpha"; + "gar" string => "$(missing)"; + "bletch" string => "$(gar)"; + + classes: + # Known values should test easily + "s_foo" expression => strcmp("$(foo)", "42"); + "s_baz_a" expression => strcmp("$(baz[a])", "alpha"); + # Missing variable names expand to $(varname) in reports and should + # expand to the same value in strcmp... + "s_missing" expression => strcmp("$(missing)", "$(const.dollar)(missing)"); + "s_baz_b" expression => strcmp("$(baz[b])", "$(const.dollar)(baz[b])"); + # We test the rest two ways because we assigned a missing variable to + # another variable - first the literal assigned value and then the + # expanded version testing for missing vars + "s_gar" expression => strcmp("$(gar)", "$(missing)"); + "s_gar2" expression => strcmp("$(gar)", "$(const.dollar)(missing)"); + "s_bletch" expression => strcmp("$(bletch)", "$(missing)"); + "s_bletch2" expression => strcmp("$(bletch)", "$(const.dollar)(missing)"); + + "good" and => { + "s_foo", + "s_baz_a", + "s_gar", + "s_gar2", + "s_bletch", + "s_bletch2", + }; + + "v_missing" expression => isvariable("missing"); + "v_baz" expression => isvariable("baz"); + "v_baz_b" expression => isvariable("baz[b]"); + + "bad" or => { + "v_missing", + "v_baz", + "v_baz_b", + "v_baz_x", + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG.ok:: + "Saw exactly what we expected"; + + DEBUG.!ok:: + "Saw unexpected results!!"; + DEBUG.v_missing:: + "$(const.dollar)missing is a variable, and shouldn't be!"; + DEBUG.v_baz:: + "$(const.dollar)baz is a variable, and shouldn't be!"; + DEBUG.v_baz_b:: + "$(const.dollar)baz[b] is a variable, and shouldn't be!"; + + DEBUG.!s_foo:: + "expected $(const.dollar)foo = '42', saw '$(foo)'"; + DEBUG.!s_baz_a:: + "expected $(const.dollar)baz[a] = 'alpha', saw '$(baz[a])'"; + DEBUG.!(s_gar.s_gar2):: + "expected $(const.dollar)gar = '$(const.dollar)(missing)', saw '$(gar)'"; + DEBUG.!(s_bletch.s_bletch2):: + "expected $(const.dollar)bletch = '$(const.dollar)(missing)', saw '$(bletch)'"; +} + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; + +} diff --git a/tests/acceptance/01_vars/01_basic/staging/025.cf b/tests/acceptance/01_vars/01_basic/staging/025.cf new file mode 100644 index 0000000000..c03d506156 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/025.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Test irange +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + # Normal order + "r1" string => irange(10,20); + "r2" string => irange("10","20"); + "r3" string => irange("1k","2K"); + "r4" string => irange("1m","2M"); + "r5" string => irange("1g","2G"); + "r6" string => irange("-20",10); + + # Reverse order + "x1" string => irange(20,10); + "x2" string => irange("20","10"); + "x3" string => irange("2K","1k"); + "x4" string => irange("2M","1m"); + "x5" string => irange("2G","1g"); + "x6" string => irange(10,"-20"); + + # String equivalent + "s1" string => "10,20"; + "s2" string => "10,20"; + "s3" string => "1000,2048"; + "s4" string => "1000000,2097152"; + "s5" string => "1000000000,2147483648"; + "s6" string => "-20,10"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok1" and => { + strcmp("$(test.r1)", "$(test.x1)"), + strcmp("$(test.r1)", "$(test.s1)"), + }; + "ok2" and => { + strcmp("$(test.r2)", "$(test.x2)"), + strcmp("$(test.r2)", "$(test.s2)"), + }; + "ok3" and => { + strcmp("$(test.r3)", "$(test.x3)"), + strcmp("$(test.r3)", "$(test.s3)"), + }; + "ok4" and => { + strcmp("$(test.r4)", "$(test.x4)"), + strcmp("$(test.r4)", "$(test.s4)"), + }; + "ok5" and => { + strcmp("$(test.r5)", "$(test.x5)"), + strcmp("$(test.r5)", "$(test.s5)"), + }; + "ok6" and => { + strcmp("$(test.r6)", "$(test.x6)"), + strcmp("$(test.r6)", "$(test.s6)"), + }; + + "ok" and => { "ok1", "ok2", "ok3", "ok4", "ok5", "ok6" }; + + reports: + DEBUG.!ok1:: + "Mismatch 1: '$(test.r1)' '$(test.x1)' '$(test.s1)'"; + DEBUG.!ok2:: + "Mismatch 2: '$(test.r2)' '$(test.x2)' '$(test.s2)'"; + DEBUG.!ok3:: + "Mismatch 3: '$(test.r3)' '$(test.x3)' '$(test.s3)'"; + DEBUG.!ok4:: + "Mismatch 4: '$(test.r4)' '$(test.x4)' '$(test.s4)'"; + DEBUG.!ok5:: + "Mismatch 5: '$(test.r5)' '$(test.x5)' '$(test.s5)'"; + DEBUG.!ok6:: + "Mismatch 6: '$(test.r6)' '$(test.x6)' '$(test.s6)'"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; + +} diff --git a/tests/acceptance/01_vars/01_basic/staging/026.cf b/tests/acceptance/01_vars/01_basic/staging/026.cf new file mode 100644 index 0000000000..72f9bf2b23 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/026.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Test rrange +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + # Normal order + "r1" string => rrange(10,20); + "r2" string => rrange("10.1","20.2"); + "r3" string => rrange("1.1k","2.2K"); + "r4" string => rrange("1.1m","2.2M"); + "r5" string => rrange("1.1g","2.2G"); + "r6" string => rrange("-20.2",10); + "r7" string => rrange("10.1%","20.2%"); + + # Reverse order + "x1" string => rrange(20,10); + "x2" string => rrange("20.2","10.1"); + "x3" string => rrange("2.2K","1.1k"); + "x4" string => rrange("2.2M","1.1m"); + "x5" string => rrange("2.2G","1.1g"); + "x6" string => rrange("20.2",10); + "x7" string => rrange("20.2%","10.1%"); + + # String equivalent + "s1" string => "10.000000,20.000000"; + "s2" string => "10.100000,20.200000"; + "s3" string => "1100.000000,2252.800000"; + "s4" string => "1100000.000000,2306867.200000"; + "s5" string => "1100000000.000000,2362232012.800000"; + "s6" string => "-20.200000,10.000000"; + # Unsure that this is correct behavior... and it do this now anyway + "s7" string => "20.200000,10.100000"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok1" and => { + strcmp("$(test.r1)", "$(test.x1)"), + strcmp("$(test.r1)", "$(test.s1)"), + }; + "ok2" and => { + strcmp("$(test.r2)", "$(test.x2)"), + strcmp("$(test.r2)", "$(test.s2)"), + }; + "ok3" and => { + strcmp("$(test.r3)", "$(test.x3)"), + strcmp("$(test.r3)", "$(test.s3)"), + }; + "ok4" and => { + strcmp("$(test.r4)", "$(test.x4)"), + strcmp("$(test.r4)", "$(test.s4)"), + }; + "ok5" and => { + strcmp("$(test.r5)", "$(test.x5)"), + strcmp("$(test.r5)", "$(test.s5)"), + }; + "ok6" and => { + strcmp("$(test.r6)", "$(test.x6)"), + strcmp("$(test.r6)", "$(test.s6)"), + }; + "ok7" and => { + strcmp("$(test.r7)", "$(test.x7)"), + strcmp("$(test.r7)", "$(test.s7)"), + }; + + "ok" and => { "ok1", "ok2", "ok3", "ok4", "ok5", "ok6", "ok7" }; + + reports: + DEBUG.!ok1:: + "Mismatch 1: '$(test.r1)' '$(test.x1)' '$(test.s1)'"; + DEBUG.!ok2:: + "Mismatch 2: '$(test.r2)' '$(test.x2)' '$(test.s2)'"; + DEBUG.!ok3:: + "Mismatch 3: '$(test.r3)' '$(test.x3)' '$(test.s3)'"; + DEBUG.!ok4:: + "Mismatch 4: '$(test.r4)' '$(test.x4)' '$(test.s4)'"; + DEBUG.!ok5:: + "Mismatch 5: '$(test.r5)' '$(test.x5)' '$(test.s5)'"; + DEBUG.!ok6:: + "Mismatch 6: '$(test.r6)' '$(test.x6)' '$(test.s6)'"; + DEBUG.!ok7:: + "Mismatch 7: '$(test.r7)' '$(test.x7)' '$(test.s7)'"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; + +} diff --git a/tests/acceptance/01_vars/01_basic/staging/107.cf b/tests/acceptance/01_vars/01_basic/staging/107.cf new file mode 100644 index 0000000000..29aa3b46cf --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/107.cf @@ -0,0 +1,48 @@ +####################################################### +# +# Test $(this.promise_filename) in body referenced from bundle called from +# another file. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -vKf $(this.promise_filename).sub -DAUTO | grep Q:", "useshell"); +} + +####################################################### + +bundle agent check +{ + classes: + "default" expression => regcmp(".*default\\.cf.*", "$(test.subout)"); + "itself" expression => regcmp(".*107\\.cf.*", "$(test.subout)"); + + "ok" and => { "!default", "itself" }; + + reports: + DEBUG:: + "$(test.subout)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/01_basic/staging/107.cf.ext b/tests/acceptance/01_vars/01_basic/staging/107.cf.ext new file mode 100644 index 0000000000..5e10cd6419 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/107.cf.ext @@ -0,0 +1,14 @@ +body package_method simplepkg(abug) +{ + package_list_command => "/bin/sh -c 'echo $(this.promise_filename)'"; + package_list_update_command => "/bin/sh -c 'echo $(this.promise_filename)'"; + package_list_update_ifelapsed => "0"; + + package_installed_regex => ".*"; + package_list_name_regex => ".*"; + package_list_version_regex => ".*"; + package_list_arch_regex => ".*"; + + package_add_command => "$(G.false)"; + package_delete_command => "$(G.false)"; +} diff --git a/tests/acceptance/01_vars/01_basic/staging/107.cf.sub b/tests/acceptance/01_vars/01_basic/staging/107.cf.sub new file mode 100644 index 0000000000..4f81645c8c --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/107.cf.sub @@ -0,0 +1,36 @@ +# +# Test reading single-line list of packages +# + +body common control +{ + inputs => { "../../default.cf", "107.cf.ext" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; +} + + +bundle agent init +{ +vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ +packages: + "p" + package_version => "", + package_architectures => { "" }, + package_method => simplepkg("abug"), + package_policy => "add", + package_select => "=="; +} + + +bundle agent check +{ +vars: + "dummy" string => "dummy"; +} diff --git a/tests/acceptance/01_vars/01_basic/staging/this_promises_in_command_args.cf b/tests/acceptance/01_vars/01_basic/staging/this_promises_in_command_args.cf new file mode 100644 index 0000000000..8ef0fd79e0 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/staging/this_promises_in_command_args.cf @@ -0,0 +1,32 @@ +####################################################### +# +# Redmine#5959 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + commands: + # should expand to "touch testfile touch testfile.2" + "$(G.touch) $(G.testfile)" + args => "$(this.promiser).2"; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_fileexists("$(G.testfile).2", + $(this.promise_filename)); + +} diff --git a/tests/acceptance/01_vars/01_basic/sys_flavor_on_aix_no_dots.cf b/tests/acceptance/01_vars/01_basic/sys_flavor_on_aix_no_dots.cf new file mode 100644 index 0000000000..b8a648fc44 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/sys_flavor_on_aix_no_dots.cf @@ -0,0 +1,32 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + classes: + "etc_os_release_present" expression => fileexists( '/etc/os-release'); + + meta: + "description" -> { "ENT-3970" } + string => "Test that sys.flavor on aix is in the form aix_digit"; + + "test_skip_needs_work" + string => "!(aix|etc_os_release_present)", + meta => { "ENT-3970"}; + + methods: + + # Probably the common case for this would be $(sys.os)_MAJOR or $(sys.os)_\d+ + aix:: + "Pass/Fail" + usebundle => dcs_check_regcmp( "aix_\d+", $(sys.flavor), $(this.promise_filename), "no" ); + + etc_os_release_present:: + "Pass/Fail" + usebundle => dcs_check_regcmp( "$(sys.os_release[ID])_\d+", $(sys.flavor), $(this.promise_filename), "no" ), + if => fileexists( "/etc/os-release" ); +} diff --git a/tests/acceptance/01_vars/01_basic/sysvars.cf b/tests/acceptance/01_vars/01_basic/sysvars.cf new file mode 100644 index 0000000000..4758b01c3c --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/sysvars.cf @@ -0,0 +1,58 @@ +# Test $(sys.inputdir), $(sys.masterdir), $(sys.libdir), $(sys.bindir), $(sys.failsafe_policy_path), $(sys.update_policy_path), $(sys.local_libdir) +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + vars: + "expected[bindir]" string => "$(sys.workdir)$(const.dirsep)bin"; + "expected[default_policy_path]" string => "$(sys.workdir)$(const.dirsep)inputs$(const.dirsep)promises.cf"; + "expected[failsafe_policy_path]" string => "$(sys.workdir)$(const.dirsep)inputs$(const.dirsep)failsafe.cf"; + "expected[inputdir]" string => "$(sys.workdir)$(const.dirsep)inputs"; + "expected[libdir]" string => "$(sys.workdir)$(const.dirsep)inputs$(const.dirsep)lib"; + "expected[local_libdir]" string => "lib"; + "expected[masterdir]" string => "$(sys.workdir)$(const.dirsep)masterfiles"; + "expected[update_policy_path]" string => "$(sys.workdir)$(const.dirsep)inputs$(const.dirsep)update.cf"; + + # sys.policy_entry variables are the same as this.promise variables in case the cf-agent is with '-f THIS_POLICY_FILENAME' + # except that "/./" can appear in some places in this.promise variables + "expected[policy_entry_dirname]" string => regex_replace("$(this.promise_dirname)", "/\./", "/", ""); + "expected[policy_entry_filename]" string => regex_replace("$(this.promise_filename)", "/\./", "/", ""); + "expected[policy_entry_basename]" string => regex_replace("$(this.promise_filename)", ".*/", "", ""); + + "sysvars" slist => getindices("expected"); +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; +} + +####################################################### + +bundle agent check +{ + classes: + "$(init.sysvars)_ok" expression => strcmp("$(sys.$(init.sysvars))", "$(init.expected[$(init.sysvars)])"); + + "ok" and => { "inputdir_ok", "masterdir_ok", "libdir_ok", "bindir_ok", "default_policy_path_ok", "failsafe_policy_path_ok", "update_policy_path_ok", + "local_libdir_ok", "policy_entry_dirname_ok", "policy_entry_filename_ok", "policy_entry_basename_ok" }; + + reports: + DEBUG:: + "$(init.sysvars) actual='$(sys.$(init.sysvars))', expected '$(init.expected[$(init.sysvars)])'"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/this_promiser_ifvarclass.cf b/tests/acceptance/01_vars/01_basic/this_promiser_ifvarclass.cf new file mode 100644 index 0000000000..3a160f7fe5 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/this_promiser_ifvarclass.cf @@ -0,0 +1,43 @@ +# Test that this.promiser works in ifvarclass +# CFE-2262 (https://northerntech.atlassian.net/browse/CFE-2262) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent test +{ + meta: + "description" -> { "redmine#7880", "CFE-2262" } + string => "Test that it is possible to use this.promiser in ifvarclass.", + meta => { "redmine#7880", "CFE-2262" }; + + files: + # I should be able to use this.promiser to check if the file is a plain + # file + "$(G.testfile)" + delete => tidy, + ifvarclass => isplain( "$(this.promiser)" ); +} + +bundle agent check +{ + classes: + "ok" not => fileexists( "$(G.testfile)" ); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/this_promiser_with_readjson.cf b/tests/acceptance/01_vars/01_basic/this_promiser_with_readjson.cf new file mode 100644 index 0000000000..382b594fca --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/this_promiser_with_readjson.cf @@ -0,0 +1,51 @@ +# Test that this.promiser can be used in a call with readjson +# Redmine:4680 (https://cfengine.com/dev/issues/4680) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "cf_promises_validated_mock_data" + string => '{ + "timestamp": 1393095384 +}'; + + files: + "$(G.testfile).cf_promises_validated" + create => "true", + edit_defaults => empty, + edit_line => insert_lines($(cf_promises_validated_mock_data)), + comment => "we need example data to parse"; +} + +bundle agent test +{ + vars: + "cf_promises_validated" + data => readjson("$(G.testfile).$(this.promiser)", 1K); + + "printable" + string => format("%S", "cf_promises_validated"); + + reports: + DEBUG:: + "$(printable)"; +} + +bundle agent check +{ + classes: + "ok" expression => strcmp("1393095384", $(test.cf_promises_validated[timestamp])); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/this_variables.cf b/tests/acceptance/01_vars/01_basic/this_variables.cf new file mode 100644 index 0000000000..d24a4cebab --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/this_variables.cf @@ -0,0 +1,63 @@ +# This was created because of a bug while working on purging variable table +# copying for CFE-2524. The issue was that variables were not found when +# looked up deep inside the INI_SECTION body. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile).expected" + copy_from => local_cp("$(this.promise_filename).finish"); +} + +bundle agent test +{ + vars: + "var" slist => { "var in test" }; + "var_test" slist => { "var_test in test" }; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_edit_line("testline"); +} + +bundle edit_line test_edit_line(line) +{ + + vars: + "sectionName" string => "test"; + + insert_lines: + "[$(sectionName)] +[end]" + location => start; + + "${line}" + select_region => INI_section(escape("$(sectionName)")); + +} + +bundle agent check +{ + methods: + "check" + usebundle => dcs_if_diff( "$(G.testfile).actual", + "$(G.testfile).expected", + "pass", "_fail"); + + # Fail the test if any of the files fail. + "fail" + usebundle => dcs_fail( $(this.promise_filename) ), + if => "_fail"; + + pass:: + "pass" + usebundle => dcs_pass( $(this.promise_filename) ); +} diff --git a/tests/acceptance/01_vars/01_basic/this_variables.cf.finish b/tests/acceptance/01_vars/01_basic/this_variables.cf.finish new file mode 100644 index 0000000000..904bb6cc72 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/this_variables.cf.finish @@ -0,0 +1,3 @@ +[test] +testline +[end] diff --git a/tests/acceptance/01_vars/01_basic/unresolved_variables.cf b/tests/acceptance/01_vars/01_basic/unresolved_variables.cf new file mode 100644 index 0000000000..803551ddbf --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/unresolved_variables.cf @@ -0,0 +1,34 @@ +# Redmine#4889: avoid unresolved variables in promiser or attributes + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub -DAUTO,DEBUG 2>&1 | $(G.grep) PURPLE_DINOSAUR", "useshell"); +} + +bundle agent check +{ + classes: + "ok" not => regcmp("PURPLE_DINOSAUR", $(test.subout)); + + reports: + ok.EXTRA:: + "The output was OK: $(test.subout)"; + !ok.DEBUG:: + "The output was NOT OK: $(test.subout)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/unresolved_variables.cf.sub b/tests/acceptance/01_vars/01_basic/unresolved_variables.cf.sub new file mode 100644 index 0000000000..a9a5035094 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/unresolved_variables.cf.sub @@ -0,0 +1,77 @@ +# +# Unresolved variables end up in reports and files prommises +# + +body common control +{ + bundlesequence => { run, dmi, etc_nologin }; +} + +bundle agent run +{ + classes: + "dim_found" expression => regcmp("^[1-9][0-9]*$", $(dim)); + + vars: + "data" string => "PURPLE_DINOSAUR: Ubuntu"; + "dim" int => parsestringarray( + "lsb", + $(data), + "\s*#[^\n]*", + "\s*:\s+", + "15", + "4095" + ); + + dim_found:: + "PURPLE_DINOSAUR" string => canonify("$(lsb[PURPLE_DINOSAUR][1])"); + + files: + "$(PURPLE_DINOSAUR)" create => "true"; + "$(sys.workdir)/state/anotherfile" create => $(PURPLE_DINOSAUR); + + reports: + dim_found:: + "$(this.bundle): value = $(PURPLE_DINOSAUR)"; +} + +# inventory DMI decoder, simplified for this test +bundle agent dmi +{ + vars: + "dmivars" slist => { "PURPLE_DINOSAUR" }; + secondpass:: + "dmi[$(dmivars)]" string => execresult("echo Not A Dinosaur", "useshell"); + + classes: + "secondpass" expression => "any"; + + reports: + "$(this.bundle): Obtained qualified '$(dmi.dmi[$(dmivars)])'"; # fails + "$(this.bundle): Obtained unqualified '$(dmi[$(dmivars)])'"; # fails +} + +# the System::nologin sketch, simplified for this test +bundle agent etc_nologin() +{ + vars: + secondpass:: + "PURPLE_DINOSAUR" string => "$(sys.workdir)/state", policy => "free"; + + classes: + "secondpass" expression => "any"; + + vars: + "etc_nologin" string => "$(PURPLE_DINOSAUR)/etc/nologin"; + + files: + "$(etc_nologin)" create => "true"; + + reports: + "$(etc_nologin)"; +} +# THIS LAST BUNDLE IS FAILING! +# TODO implement skipping of promises that have unresolved variables; ways: +# a. scan for dollar-paren and skip if found +# -- this will make it impossible to have any dollar-paren strings +# b. check if the dollar-paren variable is in any vars promise ... diff --git a/tests/acceptance/01_vars/01_basic/uptime_systime_and_sysday.cf b/tests/acceptance/01_vars/01_basic/uptime_systime_and_sysday.cf new file mode 100644 index 0000000000..caccc2b129 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/uptime_systime_and_sysday.cf @@ -0,0 +1,44 @@ +# Test $(sys.uptime), $(sys.systime) and $(sys.sysday) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + vars: + # Quite crude testing, only test that it is a number, and not zero. + "regexp" string => "^[0-9]+$"; + "notregexp" string => "^0$"; +} + +####################################################### + +bundle agent check +{ + classes: + "uptime_ok" expression => regcmp("$(init.regexp)", "$(sys.uptime)"); + "systime_ok" expression => regcmp("$(init.regexp)", "$(sys.systime)"); + "sysday_ok" expression => regcmp("$(init.regexp)", "$(sys.sysday)"); + "uptime_ok_zero" not => regcmp("$(init.notregexp)", "$(sys.uptime)"); + "systime_ok_zero" not => regcmp("$(init.notregexp)", "$(sys.systime)"); + "sysday_ok_zero" not => regcmp("$(init.notregexp)", "$(sys.sysday)"); + + "ok" and => { "uptime_ok", "systime_ok", "sysday_ok", + "uptime_ok_zero", "systime_ok_zero", "sysday_ok_zero" + }; + + reports: + DEBUG:: + "sys.uptime: $(sys.uptime)"; + "sys.systime: $(sys.systime)"; + "sys.sysday: $(sys.sysday)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/var_expansion_in_meta.cf b/tests/acceptance/01_vars/01_basic/var_expansion_in_meta.cf new file mode 100644 index 0000000000..0cad82867c --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/var_expansion_in_meta.cf @@ -0,0 +1,52 @@ +# Test that variables in meta tags are expanded +# Redmine:4885 (https://cfengine.com/dev/issues/4885) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "variables" slist => { "item1", "item2" }; + "variable_tag" string => "mytag"; + + classes: + "$(variables)_class" + expression => "any", + scope => "namespace", + meta => { "$(variable_tag)" }; + +} + +bundle agent test +{ + vars: + "found_classes" + slist => classesmatching(".*", $(init.variable_tag)); + + "joined_found_classes" + string => join(",", found_classes); +} + +bundle agent check +{ + vars: + "expected_joined_found_classes" string => "item1_class,item2_class"; + + classes: + "ok" expression => strcmp($(expected_joined_found_classes), $(test.joined_found_classes)); + + reports: + DEBUG:: + "$(this.promise_filename) Expected: $(expected_joined_found_classes)"; + "$(this.promise_filename) Found: $(test.joined_found_classes)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/01_basic/var_meta_tags_overridden_correctly.cf b/tests/acceptance/01_vars/01_basic/var_meta_tags_overridden_correctly.cf new file mode 100644 index 0000000000..d9d9dcff7c --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/var_meta_tags_overridden_correctly.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} +bundle agent init +{ + + meta: + + "description" -> { "CFE-2601" } + string => "Test that meta tags are correct when a variable is re-defined with new meta tags."; + + vars: + "myvar" + string => "first definition tagged with first", + meta => { "first" }; + + # We expect the last definition to win. + "myvar" + string => "second definition tagged with second", + meta => { "second" }; +} + +bundle agent check +{ + vars: + "vars_tagged_first" slist => variablesmatching(".*", "first"); + "vars_tagged_second" slist => variablesmatching(".*", "second"); + + reports: + DEBUG|EXTRA:: + + "Running CFEngine $(sys.cf_version)"; + "Tagged with first: $(vars_tagged_first)"; + "Tagged with second: $(vars_tagged_second)"; + + "default:init.myvar is NOT tagged with first" + if => none("default:init.myvar", vars_tagged_first); + + "default:init.myvar is only var tagged with second" + if => every("default:init.myvar", vars_tagged_second); + + any:: + "$(this.promise_filename) Pass" + if => and(none("default:init.myvar", vars_tagged_first), every("default:init.myvar", vars_tagged_second)); + + "$(this.promise_filename) FAIL" + if => not(and(none("default:init.myvar", vars_tagged_first), every("default:init.myvar", vars_tagged_second))); +} diff --git a/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_show_vars.cf b/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_show_vars.cf new file mode 100644 index 0000000000..7a2aad82eb --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_show_vars.cf @@ -0,0 +1,22 @@ +# Test $(sys.inputdir), $(sys.masterdir), $(sys.libdir), $(sys.bindir), $(sys.failsafe_policy_path), $(sys.update_policy_path), $(sys.local_libdir) +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent test +{ + meta: + "description" string => "Test that comments on variables are emitted in --show-evaluated-vars output."; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_output(".*test_var.*test_var value.*This is a comment about test_var.*", "", "$(sys.cf_agent) -Kf $(this.promise_filename).sub --show-evaluated-vars", $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_show_vars.cf.sub b/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_show_vars.cf.sub new file mode 100644 index 0000000000..65aa51045a --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_show_vars.cf.sub @@ -0,0 +1,7 @@ +bundle agent main +{ + vars: + "test_var" + string => "test_var value", + comment => "This is a comment about test_var"; +} diff --git a/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_verbose.cf b/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_verbose.cf new file mode 100644 index 0000000000..f2009b7df9 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_verbose.cf @@ -0,0 +1,24 @@ +# Test $(sys.inputdir), $(sys.masterdir), $(sys.libdir), $(sys.bindir), $(sys.failsafe_policy_path), $(sys.update_policy_path), $(sys.local_libdir) +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent test +{ + meta: + "description" + string => "Test that comments on variables are emitted in verbose output.", + meta => { "CFE-2442" }; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_output(".*This is a comment about test_var.*", "", "$(sys.cf_agent) -Kvf $(this.promise_filename).sub", $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_verbose.cf.sub b/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_verbose.cf.sub new file mode 100644 index 0000000000..65aa51045a --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/vars_comments_emitted_in_verbose.cf.sub @@ -0,0 +1,7 @@ +bundle agent main +{ + vars: + "test_var" + string => "test_var value", + comment => "This is a comment about test_var"; +} diff --git a/tests/acceptance/01_vars/01_basic/vars_in_arrays.cf b/tests/acceptance/01_vars/01_basic/vars_in_arrays.cf new file mode 100644 index 0000000000..03c89cbae5 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/vars_in_arrays.cf @@ -0,0 +1,77 @@ + +# +# Parsing paramaterized arrays +# + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "init", "check" }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + + "$(G.testfile).actual" + create => "true", + edit_line => write, + edit_defaults => empty; + + "$(G.testfile).expected" + create => "true", + edit_line => write_control, + edit_defaults => empty; + +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).expected", + "$(G.testfile).actual", + "$(this.promise_filename)"); + +} + +####################################################### + +bundle edit_line write_control +{ + insert_lines: + + "1 +3 +2 +one +two +three +four"; +} + +bundle edit_line write +{ + vars: + + "a[1]" string => "a"; + "a[2]" string => "b"; + "a[3]" string => "c"; + + "alias" string => "a"; + "list" slist => getindices($(alias)); + + "one[$(sys.uqhost)]" slist => { "one", "two" }; + "two" slist => { "three", "four" }; + "oneandtwo" slist => { @(one[$(sys.uqhost)]), @(two) }; + + insert_lines: + + "$(list)"; + "$(oneandtwo)"; +} + diff --git a/tests/acceptance/01_vars/02_functions/001.cf b/tests/acceptance/01_vars/02_functions/001.cf new file mode 100644 index 0000000000..58ddb48c1c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/001.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Test getuid() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; + + + vars: + any:: + "uid_root" int => getuid("root"); + "uid_daemon" int => getuid("daemon"); + !darwin:: + "uid_bin" int => getuid("bin"); + + (linux.!archlinux.!SuSE.!redhat.!gentoo)|solaris|hpux|aix:: + "num_root" int => "0"; + "num_daemon" int => "1"; + "num_bin" int => "2"; + archlinux|SuSE|redhat|gentoo:: + "num_root" int => "0"; + "num_daemon" int => "2"; + "num_bin" int => "1"; + freebsd|openbsd:: + "num_root" int => "0"; + "num_daemon" int => "1"; + "num_bin" int => "3"; + darwin:: + "num_root" int => "0"; + "num_daemon" int => "1"; + + !linux.!solaris.!hpux.!aix.!freebsd.!openbsd.!darwin:: + "num_root" string => "fixme"; + "num_daemon" string => "fixme"; + "num_bin" string => "fixme"; +} + + +####################################################### + +bundle agent check +{ + classes: + darwin:: + "ok_bin" expression => "any"; + !darwin:: + "ok_bin" expression => strcmp("$(test.uid_bin)", "$(test.num_bin)"); + + any:: + "ok" and => { + strcmp("$(test.uid_root)", "$(test.num_root)"), + strcmp("$(test.uid_daemon)", "$(test.num_daemon)"), + "ok_bin" + }; + + reports: + DEBUG:: + "root is UID $(test.uid_root), expected $(test.num_root)"; + "daemon is UID $(test.uid_daemon), expected $(test.num_daemon)"; + "bin is UID $(test.uid_bin), expected $(test.num_bin)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/002.cf b/tests/acceptance/01_vars/02_functions/002.cf new file mode 100644 index 0000000000..22edc1a952 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/002.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Test getgid() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; + + vars: + linux|freebsd|solaris|openbsd|hpux:: + "gid_daemon" int => getgid("daemon"); + "gid_sys" int => getgid("sys"); + darwin:: + "gid_daemon" int => getgid("daemon"); + aix:: + "gid_sys" int => getgid("sys"); + !linux.!freebsd.!solaris.!darwin.!openbsd.!hpux.!aix:: + "gid_daemon" string => "fixme"; + "gid_sys" string => "fixme"; + + linux|solaris|hpux:: + "gid_0" int => getgid("root"); + freebsd|darwin|openbsd:: + "gid_0" int => getgid("wheel"); + aix:: + "gid_0" int => getgid("system"); + !linux.!freebsd.!solaris.!darwin.!openbsd.!hpux.!aix:: + "gid_0" string => "fixme"; + + archlinux|SuSE|redhat|gentoo:: + "num_daemon" int => "2"; + (linux.!archlinux.!SuSE.!redhat.!gentoo)|freebsd|darwin|openbsd:: + "num_daemon" int => "1"; + solaris:: + "num_daemon" int => "12"; + hpux:: + "num_daemon" int => "5"; + !linux.!freebsd.!solaris.!darwin.!openbsd.!hpux.!aix:: + "num_daemon" string => "fixme"; + + linux|freebsd|solaris|openbsd|hpux|aix:: + "num_sys" int => "3"; + !linux.!freebsd.!solaris.!darwin.!openbsd.!hpux.!aix:: + "num_sys" string => "fixme"; +} + +####################################################### + +bundle agent check +{ + classes: + darwin:: + "ok_sys" expression => "any"; + !darwin:: + "ok_sys" expression => strcmp("$(test.gid_sys)", "$(test.num_sys)"); + aix:: + "ok_daemon" expression => "any"; + !aix:: + "ok_daemon" expression => strcmp("$(test.gid_daemon)", "$(test.num_daemon)"); + + any:: + "ok" and => { strcmp("$(test.gid_0)", "0"), "ok_sys", "ok_daemon" }; + + reports: + DEBUG:: + "root/wheel is GID $(test.gid_0), expected 0"; + "daemon is GID $(test.gid_daemon), expected $(test.num_daemon)"; + "sys is GID $(test.gid_sys), expected $(test.num_sys)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/003.cf b/tests/acceptance/01_vars/02_functions/003.cf new file mode 100644 index 0000000000..47cd7bf2e8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/003.cf @@ -0,0 +1,57 @@ +####################################################### +# +# Test countlinesmatching() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "zero_regex" string => "impossible line"; + "one_regex" string => "root:.*"; + "positive_regex" string => ".*:\d+:.*"; + + "zero" int => countlinesmatching("$(zero_regex)", "$(G.etc_passwd)"); + "one" int => countlinesmatching("$(one_regex)", "$(G.etc_passwd)"); + "positive" int => countlinesmatching("$(positive_regex)", "$(G.etc_passwd)"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + strcmp("$(test.zero)", "0"), + strcmp("$(test.one)", "1"), + isgreaterthan("$(test.positive)", "1"), + }; + + reports: + DEBUG:: + "Expected 0 matches to '$(test.zero_regex)', found $(test.zero)"; + "Expected 1 matches to '$(test.one_regex)', found $(test.one)"; + "Expected >1 matches to '$(test.positive_regex)', found $(test.positive)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/004.cf b/tests/acceptance/01_vars/02_functions/004.cf new file mode 100644 index 0000000000..e76eae5b1f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/004.cf @@ -0,0 +1,85 @@ +####################################################### +# +# Test sum() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "six" ilist => { "1", "2", "3" }; + "sum_six" real => sum("six"); + + "minus_six" ilist => { "-1", "-2", "-3" }; + "sum_minus_six" real => sum("minus_six"); + + "zero" ilist => { "-1", "-2", "3" }; + "sum_zero" real => sum("zero"); + + "sixpoint" rlist => { "1.", "2", "3e0" }; + "sum_sixpoint" real => sum("sixpoint"); + + "minus_sixpoint" rlist => { "-1.", "-2", "-3e0" }; + "sum_minus_sixpoint" real => sum("minus_sixpoint"); + + "zeropoint" rlist => { "-1.", "-2", "3e0" }; + "sum_zeropoint" real => sum("zeropoint"); + +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + isgreaterthan("$(test.sum_six)", "5.9999999"), + islessthan("$(test.sum_six)", "6.0000001"), + + islessthan("$(test.sum_minus_six)", "-5.9999999"), + isgreaterthan("$(test.sum_minus_six)", "-6.0000001"), + + isgreaterthan("$(test.sum_zero)", "-.0000001"), + islessthan("$(test.sum_zero)", ".0000001"), + + isgreaterthan("$(test.sum_sixpoint)", "5.9999999"), + islessthan("$(test.sum_sixpoint)", "6.0000001"), + + islessthan("$(test.sum_minus_sixpoint)", "-5.9999999"), + isgreaterthan("$(test.sum_minus_sixpoint)", "-6.0000001"), + + isgreaterthan("$(test.sum_zeropoint)", "-.0000001"), + islessthan("$(test.sum_zeropoint)", ".0000001"), + }; + + reports: + DEBUG:: + "test.sum_six = $(test.sum_six)"; + "test.sum_minus_six = $(test.sum_minus_six)"; + "test.sum_zero = $(test.sum_zero)"; + "test.sum_sixpoint = $(test.sum_sixpoint)"; + "test.sum_minus_sixpoint = $(test.sum_minus_sixpoint)"; + "test.sum_zeropoint = $(test.sum_zeropoint)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/005.cf b/tests/acceptance/01_vars/02_functions/005.cf new file mode 100644 index 0000000000..b76b94bf98 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/005.cf @@ -0,0 +1,85 @@ +####################################################### +# +# Test product() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "six" ilist => { "1", "2", "3" }; + "product_six" real => product("six"); + + "minus_six" ilist => { "-1", "-2", "-3" }; + "product_minus_six" real => product("minus_six"); + + "zero" ilist => { "-1", "0", "3" }; + "product_zero" real => product("zero"); + + "sixpoint" rlist => { "1.", "2", "3" }; + "product_sixpoint" real => product("sixpoint"); + + "minus_sixpoint" rlist => { "-1.", "-2", "-3" }; + "product_minus_sixpoint" real => product("minus_sixpoint"); + + "zeropoint" rlist => { "-1.", "0", "3" }; + "product_zeropoint" real => product("zeropoint"); + +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + isgreaterthan("$(test.product_six)", "5.9999999"), + islessthan("$(test.product_six)", "6.0000001"), + + islessthan("$(test.product_minus_six)", "-5.9999999"), + isgreaterthan("$(test.product_minus_six)", "-6.0000001"), + + isgreaterthan("$(test.product_zero)", "-.0000001"), + islessthan("$(test.product_zero)", ".0000001"), + + isgreaterthan("$(test.product_sixpoint)", "5.9999999"), + islessthan("$(test.product_sixpoint)", "6.0000001"), + + islessthan("$(test.product_minus_sixpoint)", "-5.9999999"), + isgreaterthan("$(test.product_minus_sixpoint)", "-6.0000001"), + + isgreaterthan("$(test.product_zeropoint)", "-.0000001"), + islessthan("$(test.product_zeropoint)", ".0000001"), + }; + + reports: + DEBUG:: + "test.product_six = $(test.product_six)"; + "test.product_minus_six = $(test.product_minus_six)"; + "test.product_zero = $(test.product_zero)"; + "test.product_sixpoint = $(test.product_sixpoint)"; + "test.product_minus_sixpoint = $(test.product_minus_sixpoint)"; + "test.product_zeropoint = $(test.product_zeropoint)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/006.cf b/tests/acceptance/01_vars/02_functions/006.cf new file mode 100644 index 0000000000..69be5b101e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/006.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test diskfree() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "positive_disk" string => "$(G.etc)"; + "p_disk" string => "$(G.etc_passwd)"; + "zero_disk" string => "/lkjqeroiu"; + + "positive" int => diskfree("$(positive_disk)"); + "p" int => diskfree("$(p_disk)"); + "zero" int => diskfree("$(zero_disk)"); + + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine4686" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + strcmp("$(test.zero)", "0"), + isgreaterthan("$(test.p)", "0"), + isgreaterthan("$(test.positive)", "0"), + }; + + reports: + DEBUG:: + "Expected 0 size on $(test.zero_disk), found $(test.zero)"; + "Expected >1 size on $(test.p_disk), found $(test.p)"; + "Expected >1 size on $(test.positive_disk), found $(test.positive)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/007.cf b/tests/acceptance/01_vars/02_functions/007.cf new file mode 100644 index 0000000000..b6f0c762d1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/007.cf @@ -0,0 +1,91 @@ +####################################################### +# +# Test canonify() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "in1" string => "hello"; + "expect1" string => "hello"; + "out1" string => canonify("$(in1)"); + + "in2" string => "hello there"; + "expect2" string => "hello_there"; + "out2" string => canonify("$(in2)"); + + "in3" string => "/etc/passwd"; + "expect3" string => "_etc_passwd"; + "out3" string => canonify("$(in3)"); + + "in4" string => "hello@mumble.com"; + "expect4" string => "hello_mumble_com"; + "out4" string => canonify("$(in4)"); + + "in5" string => "!@#$%^&*()_-+={}[]\:;<>,?"; + "expect5" string => "_________________________"; + "out5" string => canonify("$(in5)"); + + "in6" string => "Eystein Måløy Stenberg"; + "expect6" string => "Eystein_M__l__y_Stenberg"; + "out6" string => canonify("$(in6)"); + + "in7" string => "$(in1) $(in1)"; + "expect7" string => "$(in1)_$(in1)"; + "out7" string => canonify("$(in1) $(in1)"); + + "in8" string => "'\"hello\"'"; + "expect8" string => "__hello__"; + "out8" string => canonify("$(in8)"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + strcmp("$(test.expect1)", "$(test.out1)"), + strcmp("$(test.expect2)", "$(test.out2)"), + strcmp("$(test.expect3)", "$(test.out3)"), + strcmp("$(test.expect4)", "$(test.out4)"), + strcmp("$(test.expect5)", "$(test.out5)"), + strcmp("$(test.expect6)", "$(test.out6)"), + strcmp("$(test.expect7)", "$(test.out7)"), + strcmp("$(test.expect8)", "$(test.out8)"), + }; + + reports: + DEBUG:: + "Expected canonify('$(test.in1)') => $(test.out1) == $(test.expect1)"; + "Expected canonify('$(test.in2)') => $(test.out2) == $(test.expect2)"; + "Expected canonify('$(test.in3)') => $(test.out3) == $(test.expect3)"; + "Expected canonify('$(test.in4)') => $(test.out4) == $(test.expect4)"; + "Expected canonify('$(test.in5)') => $(test.out5) == $(test.expect5)"; + "Expected canonify('$(test.in6)') => $(test.out6) == $(test.expect6)"; + "Expected canonify('$(test.in7)') => $(test.out7) == $(test.expect7)"; + "Expected canonify('$(test.in8)') => $(test.out8) == $(test.expect8)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/008.cf b/tests/acceptance/01_vars/02_functions/008.cf new file mode 100644 index 0000000000..a304093d28 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/008.cf @@ -0,0 +1,95 @@ +# splitstring() + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + vars: + # Basic stuff + "test1" slist => splitstring("abcXdefXghiXjkl", "X", "100"); + + "test1_result" string => join(":", "test1"); + "test1_expected" string => "abc:def:ghi:jkl"; + + # Empty last item + "test2" slist => splitstring("abcX", "X", "100"); + + "test2_result" string => join(":", "test2"); + "test2_expected" string => "abc:"; + + # Empty first item + "test3" slist => splitstring("Xabc", "X", "100"); + + "test3_result" string => join(":", "test3"); + "test3_expected" string => ":abc"; + + # Regex + "test4" slist => splitstring("abc0123def", "[0-9]", "100"); + + "test4_result" string => join(":", "test4"); + "test4_expected" string => "abc::::def"; + + # No matches + "test5" slist => splitstring("abcYdef", "X", "100"); + + "test5_result" string => join(":", "test5"); + "test5_expected" string => "abcYdef"; + + # Empty string + "test6" slist => splitstring("", "X", "100"); + + "test6_result" string => join(":", "test6"); + "test6_expected" string => "$(const.dollar)(test.test6_result)"; + + # Limit + "test7" slist => splitstring("abcXdefXghiXjklXmno", "X", 3); + + "test7_result" string => join(":", "test7"); + "test7_expected" string => "abc:def:ghi"; +} + +bundle agent check +{ + classes: + "ok1" expression => strcmp("$(test.test1_result)", "$(test.test1_expected)"); + "ok2" expression => strcmp("$(test.test2_result)", "$(test.test2_expected)"); + "ok3" expression => strcmp("$(test.test3_result)", "$(test.test3_expected)"); + "ok4" expression => strcmp("$(test.test4_result)", "$(test.test4_expected)"); + "ok5" expression => strcmp("$(test.test5_result)", "$(test.test5_expected)"); + "ok6" expression => strcmp("$(test.test6_result)", "$(test.test6_expected)"); + "ok7" expression => strcmp("$(test.test7_result)", "$(test.test7_expected)"); + + "ok" and => { "ok1", "ok2", "ok3", "ok4", "ok5", "ok6", "ok7" }; + + reports: + DEBUG.!ok1:: + "$(test.test1_result) != $(test.test1_expected)"; + DEBUG.!ok2:: + "$(test.test2_result) != $(test.test2_expected)"; + DEBUG.!ok3:: + "$(test.test3_result) != $(test.test3_expected)"; + DEBUG.!ok4:: + "$(test.test4_result) != $(test.test4_expected)"; + DEBUG.!ok5:: + "$(test.test5_result) != $(test.test5_expected)"; + DEBUG.!ok6:: + "$(test.test6_result) != $(test.test6_expected)"; + DEBUG.!ok7:: + "$(test.test7_result) != $(test.test7_expected)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/010.cf b/tests/acceptance/01_vars/02_functions/010.cf new file mode 100644 index 0000000000..8345ab227d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/010.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test hostsseen() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "hosts" slist => hostsseen("inf", "lastseen", "name"); +} + +####################################################### + +bundle agent check +{ + vars: + "hosts" slist => { @{test.hosts} }; + classes: + "ok" expression => "any"; # XXX # I don't know how to test hostsseen! + + reports: + DEBUG:: + "$(hosts)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/011.cf b/tests/acceptance/01_vars/02_functions/011.cf new file mode 100644 index 0000000000..081bc598e5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/011.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok" and => { "ok_list", + islessthan("$(test.sum)", "123.1"), + isgreaterthan("$(test.sum)", "122.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/012.cf b/tests/acceptance/01_vars/02_functions/012.cf new file mode 100644 index 0000000000..1f955160cd --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/012.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123"; + "456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/013.cf b/tests/acceptance/01_vars/02_functions/013.cf new file mode 100644 index 0000000000..d39f24652e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/013.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/014.cf b/tests/acceptance/01_vars/02_functions/014.cf new file mode 100644 index 0000000000..6134b53b21 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/014.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\.",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/015.cf b/tests/acceptance/01_vars/02_functions/015.cf new file mode 100644 index 0000000000..180695a3a4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/015.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123.456", "$(nums)"); + "ok" and => { "ok_list", "ok123", + islessthan("$(test.sum)", "123.457"), + isgreaterthan("$(test.sum)", "123.455") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/016.cf b/tests/acceptance/01_vars/02_functions/016.cf new file mode 100644 index 0000000000..9910904b18 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/016.cf @@ -0,0 +1,75 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456"; + "789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/017.cf b/tests/acceptance/01_vars/02_functions/017.cf new file mode 100644 index 0000000000..54912582c3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/017.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; + "789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123.456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok789", + islessthan("$(test.sum)", "912.457"), + isgreaterthan("$(test.sum)", "912.455") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/018.cf b/tests/acceptance/01_vars/02_functions/018.cf new file mode 100644 index 0000000000..f49eb7246b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/018.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456 789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/019.cf b/tests/acceptance/01_vars/02_functions/019.cf new file mode 100644 index 0000000000..1bf12b4169 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/019.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readstringlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","X",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123.456", "$(nums)"); + "ok" and => { "ok_list", "ok123", + islessthan("$(test.sum)", "123.457"), + isgreaterthan("$(test.sum)", "123.455") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/020.cf b/tests/acceptance/01_vars/02_functions/020.cf new file mode 100644 index 0000000000..09abae9d63 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/020.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Test readstringlist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456 789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment","\s",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/021.cf b/tests/acceptance/01_vars/02_functions/021.cf new file mode 100644 index 0000000000..90823eacad --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/021.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Test readstringlist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123,,,456,789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/024.cf b/tests/acceptance/01_vars/02_functions/024.cf new file mode 100644 index 0000000000..e0c8ccff8d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/024.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test readintlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok" and => { "ok_list", + islessthan("$(test.sum)", "123.1"), + isgreaterthan("$(test.sum)", "122.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/025.cf b/tests/acceptance/01_vars/02_functions/025.cf new file mode 100644 index 0000000000..e543f38a82 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/025.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readintlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123"; + "456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/026.cf b/tests/acceptance/01_vars/02_functions/026.cf new file mode 100644 index 0000000000..6a7ca5a86d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/026.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test readintlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/027.cf b/tests/acceptance/01_vars/02_functions/027.cf new file mode 100644 index 0000000000..840b1429fa --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/027.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test readintlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\.",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/028.cf b/tests/acceptance/01_vars/02_functions/028.cf new file mode 100644 index 0000000000..c4cf0316fa --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/028.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test readintlist() with reals +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok1" not => strcmp("123.456", "$(nums)"); + "ok2" not => strcmp("123", "$(nums)"); + + "ok" and => { "ok_list", "ok1", "ok2" }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/029.cf b/tests/acceptance/01_vars/02_functions/029.cf new file mode 100644 index 0000000000..6f944c55e2 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/029.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Test readintlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456"; + "789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/030.cf b/tests/acceptance/01_vars/02_functions/030.cf new file mode 100644 index 0000000000..c88560ca99 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/030.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readintlist() with reals +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; + "789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok1" not => strcmp("123.456", "$(nums)"); + "ok2" not => strcmp("123", "$(nums)"); + # One failure and the readintlist aborts parsing the rest + "ok3" not => strcmp("789", "$(nums)"); + + "ok" and => { "ok_list", "ok1", "ok2", "ok3" }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/031.cf b/tests/acceptance/01_vars/02_functions/031.cf new file mode 100644 index 0000000000..ff625ab08b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/031.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readintlist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456 789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/032.cf b/tests/acceptance/01_vars/02_functions/032.cf new file mode 100644 index 0000000000..a2260a518f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/032.cf @@ -0,0 +1,68 @@ +####################################################### +# +# Test readintlist() with reals +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","X",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok1" not => strcmp("123.456", "$(nums)"); + "ok2" not => strcmp("123", "$(nums)"); + "ok" and => { "ok_list", "ok1", "ok2" }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/033.cf b/tests/acceptance/01_vars/02_functions/033.cf new file mode 100644 index 0000000000..e437d2fbe9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/033.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readintlist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456 789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment","\s",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/034.cf b/tests/acceptance/01_vars/02_functions/034.cf new file mode 100644 index 0000000000..9997a643d1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/034.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readintlist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123,,,456,789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/037.cf b/tests/acceptance/01_vars/02_functions/037.cf new file mode 100644 index 0000000000..c57fb0b261 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/037.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok" and => { "ok_list", + islessthan("$(test.sum)", "123.1"), + isgreaterthan("$(test.sum)", "122.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/038.cf b/tests/acceptance/01_vars/02_functions/038.cf new file mode 100644 index 0000000000..4f79176c42 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/038.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123"; + "456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/039.cf b/tests/acceptance/01_vars/02_functions/039.cf new file mode 100644 index 0000000000..3411c0351c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/039.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/040.cf b/tests/acceptance/01_vars/02_functions/040.cf new file mode 100644 index 0000000000..745b846956 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/040.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\.",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", + islessthan("$(test.sum)", "579.1"), + isgreaterthan("$(test.sum)", "578.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/041.cf b/tests/acceptance/01_vars/02_functions/041.cf new file mode 100644 index 0000000000..4ca15aedb3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/041.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123.456", "$(nums)"); + "ok" and => { "ok_list", "ok123", + islessthan("$(test.sum)", "123.457"), + isgreaterthan("$(test.sum)", "123.455") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/042.cf b/tests/acceptance/01_vars/02_functions/042.cf new file mode 100644 index 0000000000..b40488902c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/042.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456"; + "789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/043.cf b/tests/acceptance/01_vars/02_functions/043.cf new file mode 100644 index 0000000000..f719481f76 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/043.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; + "789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123.456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok789", + islessthan("$(test.sum)", "912.457"), + isgreaterthan("$(test.sum)", "912.455") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/044.cf b/tests/acceptance/01_vars/02_functions/044.cf new file mode 100644 index 0000000000..6fa00e9530 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/044.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456 789"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\s+",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/045.cf b/tests/acceptance/01_vars/02_functions/045.cf new file mode 100644 index 0000000000..b9b4ffae7a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/045.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test readreallist() issue 364 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","X",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123.456", "$(nums)"); + "ok" and => { "ok_list", "ok123", + islessthan("$(test.sum)", "123.457"), + isgreaterthan("$(test.sum)", "123.455") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/046.cf b/tests/acceptance/01_vars/02_functions/046.cf new file mode 100644 index 0000000000..d48d497f19 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/046.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readreallist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123 456 789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\s",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/047.cf b/tests/acceptance/01_vars/02_functions/047.cf new file mode 100644 index 0000000000..4e2cd17012 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/047.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readreallist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123,,,456,789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/050.cf b/tests/acceptance/01_vars/02_functions/050.cf new file mode 100644 index 0000000000..7b455b542e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/050.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readreallist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123.456.789"; # Is is a real or a set of ints? +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment","\.",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/051.cf b/tests/acceptance/01_vars/02_functions/051.cf new file mode 100644 index 0000000000..5b72d79cfb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/051.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,0,0,0,0,100); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "100"; + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/052.cf b/tests/acceptance/01_vars/02_functions/052.cf new file mode 100644 index 0000000000..8984aa8044 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/052.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,0,0,0,2,100); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "220"; + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/053.cf b/tests/acceptance/01_vars/02_functions/053.cf new file mode 100644 index 0000000000..45e76bda9a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/053.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,0,1,1,2,100); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "90220"; + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/054.cf b/tests/acceptance/01_vars/02_functions/054.cf new file mode 100644 index 0000000000..b2c805f381 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/054.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,1,0,0,0,100); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "2592100"; # 1 month == 30 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/055.cf b/tests/acceptance/01_vars/02_functions/055.cf new file mode 100644 index 0000000000..08e3533620 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/055.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(1,0,0,0,0,100); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "31536100"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/056.x.cf b/tests/acceptance/01_vars/02_functions/056.x.cf new file mode 100644 index 0000000000..a473e0420a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/056.x.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated("-1",0,0,0,0,100); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "31536100"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "nums: $(time)"; + "sum: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/059.x.cf b/tests/acceptance/01_vars/02_functions/059.x.cf new file mode 100644 index 0000000000..8e5a401387 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/059.x.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,1001,1000,1000,1000,40000); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "2682100000"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/060.x.cf b/tests/acceptance/01_vars/02_functions/060.x.cf new file mode 100644 index 0000000000..b91b35d82b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/060.x.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(1001,1000,1000,1000,1000,40000); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "2682100000"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/061.x.cf b/tests/acceptance/01_vars/02_functions/061.x.cf new file mode 100644 index 0000000000..47baf3086b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/061.x.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,1000,1000,1001,1000,40000); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "2682100000"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/062.x.cf b/tests/acceptance/01_vars/02_functions/062.x.cf new file mode 100644 index 0000000000..b910d84570 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/062.x.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,1000,1000,1000,1001,40000); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "2682100000"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/063.x.cf b/tests/acceptance/01_vars/02_functions/063.x.cf new file mode 100644 index 0000000000..8f9411ac7d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/063.x.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,1000,1000,1000,1000,40001); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "2682100000"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/064.x.cf b/tests/acceptance/01_vars/02_functions/064.x.cf new file mode 100644 index 0000000000..b91b35d82b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/064.x.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(1001,1000,1000,1000,1000,40000); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "2682100000"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/065.cf b/tests/acceptance/01_vars/02_functions/065.cf new file mode 100644 index 0000000000..e245b34d58 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/065.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => now(); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "1289601605"; # About when this test was created + + classes: + "ok" expression => islessthan("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/066.cf b/tests/acceptance/01_vars/02_functions/066.cf new file mode 100644 index 0000000000..8622d149a6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/066.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Test getusers(), arg0 only +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + # All users except root, bin, and daemon + "users" slist => getusers("root,daemon,bin",""); + + files: + "$(G.testfile)" + delete => init_delete; + + reports: + cfengine_3:: + "$(users)" + report_to_file => "$(G.testfile)"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + # Try to delete the lines that shouldn't be there anyway + "$(G.testfile)" + edit_line => test_delete, + classes => full_set; +} + +bundle edit_line test_delete +{ + delete_lines: + "root"; + "daemon"; + "bin"; +} + +body classes full_set +{ + promise_kept => { "pass" }; + promise_repaired => { "fail" }; + repair_failed => { "fail" }; + repair_denied => { "fail" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "pass&!fail"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/067.cf b/tests/acceptance/01_vars/02_functions/067.cf new file mode 100644 index 0000000000..675ea54d47 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/067.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Test getusers() arg1 only +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + # All users except root, bin, and daemon + freebsd|openbsd:: + "users" slist => getusers("","0,1,3"); + !freebsd.!openbsd:: + "users" slist => getusers("","0,1,2"); + + files: + "$(G.testfile)" + delete => init_delete; + + reports: + cfengine_3:: + "$(users)" + report_to_file => "$(G.testfile)"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + # Try to delete the lines that shouldn't be there anyway + "$(G.testfile)" + edit_line => test_delete, + classes => full_set; +} + +bundle edit_line test_delete +{ + delete_lines: + "root"; + "daemon"; + "bin"; +} + +body classes full_set +{ + promise_kept => { "pass" }; + promise_repaired => { "fail" }; + repair_failed => { "fail" }; + repair_denied => { "fail" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "pass&!fail"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/068.cf b/tests/acceptance/01_vars/02_functions/068.cf new file mode 100644 index 0000000000..7383f9993b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/068.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Test getusers() arg0 and arg1 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + # All users except root, bin, and daemon + "users" slist => getusers("root,bin,daemon","0,1,2"); + + files: + "$(G.testfile)" + delete => init_delete; + + reports: + cfengine_3:: + "$(users)" + report_to_file => "$(G.testfile)"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + # Try to delete the lines that shouldn't be there anyway + "$(G.testfile)" + edit_line => test_delete, + classes => full_set; +} + +bundle edit_line test_delete +{ + delete_lines: + "root"; + "daemon"; + "bin"; +} + +body classes full_set +{ + promise_kept => { "pass" }; + promise_repaired => { "fail" }; + repair_failed => { "fail" }; + repair_denied => { "fail" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "pass&!fail"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/069.cf b/tests/acceptance/01_vars/02_functions/069.cf new file mode 100644 index 0000000000..2984947b25 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/069.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Test getusers() arg0 and arg1, but expect a failure +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + # All users except r.*t [illegal!], bin, and daemon + "users" slist => getusers("r.*t,bin","1,5,6,7"); + + files: + "$(G.testfile)" + delete => init_delete; + + reports: + cfengine_3:: + "$(users)" + report_to_file => "$(G.testfile)"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + # Try to delete the lines that shouldn't be there anyway + "$(G.testfile)" + edit_line => test_delete, + classes => full_set; +} + +bundle edit_line test_delete +{ + delete_lines: + "root"; + "daemon"; + "bin"; +} + +body classes full_set +{ + promise_kept => { "fail" }; + promise_repaired => { "pass" }; + repair_failed => { "fail" }; + repair_denied => { "fail" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "pass&!fail"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/071.cf b/tests/acceptance/01_vars/02_functions/071.cf new file mode 100644 index 0000000000..e398b5d90c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/071.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Test filesize(), simple +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "known_val" int => filesize("$(G.testfile)"); + "known_zero" int => filesize("$(G.etc_null)"); + "unknown" int => filesize("/There/Is/nosuch/Name/as-this"); +} + +####################################################### + +bundle agent check +{ + vars: + windows:: + "testfilesize" int => "30"; + !windows:: + "testfilesize" int => "27"; + + classes: + "var" expression => isvariable("test.unknown"); + + "ok" and => { + "!var", + strcmp("$(test.known_val)", "$(testfilesize)"), + strcmp("$(test.known_zero)", "0"), + }; + + reports: + DEBUG:: + "expected filesize($(G.testfile)) to be XXX, saw $(test.known_val)"; + "expected filesize($(G.etc_null)) to be 0, saw $(test.known_zero)"; + "expected filesize('UnknownFile') to be undefined, saw $(test.unknown)"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/080.cf b/tests/acceptance/01_vars/02_functions/080.cf new file mode 100644 index 0000000000..ac10c64bb9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/080.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test getuid() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + # NOTE: We need to make this test work on Windows by finding a good + # registry value to use (and we always expect it to fail on non-Windows + # + # Always execute this - the return value depends on the OS used, but + # we always expect registryvalue() to not crash + "regval" string => registryvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Currentversion", "ProgramFilesDir"); +} + + +####################################################### + +bundle agent check +{ + classes: + windows:: + "ok" and => { + strcmp("$(test.regval)", "C:\Program Files"), + }; + !windows:: + "ok" not => isvariable("test.regval"); + + reports: + DEBUG:: + "Registry value is $(test.regval)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + vars: + "dummy" string => "dummy"; +} diff --git a/tests/acceptance/01_vars/02_functions/201.cf b/tests/acceptance/01_vars/02_functions/201.cf new file mode 100644 index 0000000000..065ec73cf9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/201.cf @@ -0,0 +1,63 @@ +####################################################### +# +# Test getindices(), size 0 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" string => "zero"; # Intentionally not an array + + "keys" slist => getindices("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(keys)"; +} + +bundle edit_line test_insert +{ + vars: + "keys" slist => { @{test.keys} }; + + insert_lines: + "$(keys)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/02_functions/202.cf b/tests/acceptance/01_vars/02_functions/202.cf new file mode 100644 index 0000000000..8920000f4c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/202.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test getindices(), size 1 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "alpha"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[alpha]" string => "zero"; + + "keys" slist => getindices("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(keys)"; +} + +bundle edit_line test_insert +{ + vars: + "keys" slist => { @{test.keys} }; + + insert_lines: + "$(keys)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/02_functions/203.cf b/tests/acceptance/01_vars/02_functions/203.cf new file mode 100644 index 0000000000..2215ff721b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/203.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test getindices(), size 2 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "alpha"; + "beta"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[alpha]" string => "zero"; + "array[beta]" string => "two"; + + "keys" slist => getindices("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(keys)"; +} + +bundle edit_line test_insert +{ + vars: + "keys" slist => { @{test.keys} }; + + insert_lines: + "$(keys)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/02_functions/204.cf b/tests/acceptance/01_vars/02_functions/204.cf new file mode 100644 index 0000000000..d1ed563de5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/204.cf @@ -0,0 +1,78 @@ +####################################################### +# +# Test getindices(), size 5 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "alpha"; + "beta"; + "gamma's"; + "delta-delta:delta"; + "last"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[alpha]" string => "zero"; + "array[beta]" string => "two"; + "array[gamma's]" string => "three's"; + "array[delta-delta:delta]" string => "four-fore:quatre"; + "array[last]" string => "last"; + + "keys" slist => getindices("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(keys)"; +} + +bundle edit_line test_insert +{ + vars: + "keys" slist => { @{test.keys} }; + + insert_lines: + "$(keys)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/02_functions/205.cf b/tests/acceptance/01_vars/02_functions/205.cf new file mode 100644 index 0000000000..b42e163655 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/205.cf @@ -0,0 +1,43 @@ +####################################################### +# +# getindices should not truncate keys +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "longstring" string => "abcdefghijklmnopqrstuvwxyz1234abcdefghijklmnopqrstuvwxyz1234abcdefghijklmnopqrstuvwxyz1234abcdefghijklmnopqrstuvwxyz1234abcdefghijklmnopqrstuvwxyz1234abcdefghijklmnopqrstuvwxyz1234AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJJJJJJJJJJJJJJJJJJJJJJJKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQRSTUVWXYZ"; + "table[$(longstring)]" string => "0050"; + "member" slist => getindices("table"); +} + +####################################################### + +bundle agent test +{ +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp($(init.member), $(init.longstring)); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/211.cf b/tests/acceptance/01_vars/02_functions/211.cf new file mode 100644 index 0000000000..0d9a3baf0d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/211.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Test getvalues(string), should return the string itself +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + "expected" string => "blah"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert("$(init.expected)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" string => "blah"; # Intentionally not an array + + "vals" slist => getvalues("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/02_functions/212.cf b/tests/acceptance/01_vars/02_functions/212.cf new file mode 100644 index 0000000000..41502bae97 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/212.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test getvalues(), size 1 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "zero"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[alpha]" string => "zero"; + + "vals" slist => getvalues("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/02_functions/221.cf b/tests/acceptance/01_vars/02_functions/221.cf new file mode 100644 index 0000000000..5980d0fb01 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/221.cf @@ -0,0 +1,60 @@ +####################################################### +# +# Test grep(), size 0, local slist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" slist => { "One", "Two", "Three", "Four", "Five" }; + + "vals" slist => grep("Z.*", "array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/222.cf b/tests/acceptance/01_vars/02_functions/222.cf new file mode 100644 index 0000000000..c5ddbaf826 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/222.cf @@ -0,0 +1,60 @@ +####################################################### +# +# Test grep(), size 0, global array +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" slist => { "One", "Two", "Three", "Four", "Five" }; + + "vals" slist => grep("Z.*", "test.array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/223.cf b/tests/acceptance/01_vars/02_functions/223.cf new file mode 100644 index 0000000000..23f8d991a5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/223.cf @@ -0,0 +1,76 @@ +####################################################### +# +# Test grep(), size 2 local array +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "Two"; + "Three"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" slist => { "One", "Two", "Three", "Four", "Five" }; + + "vals" slist => grep("T.*", "array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/224.cf b/tests/acceptance/01_vars/02_functions/224.cf new file mode 100644 index 0000000000..09ed34baf8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/224.cf @@ -0,0 +1,76 @@ +####################################################### +# +# Test grep(), size 2, global array +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "Two"; + "Three"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" slist => { "One", "Two", "Three", "Four", "Five" }; + + "vals" slist => grep("T.*","test.array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/225.cf b/tests/acceptance/01_vars/02_functions/225.cf new file mode 100644 index 0000000000..3410e211ef --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/225.cf @@ -0,0 +1,75 @@ +####################################################### +# +# Test grep(), size 1, last element +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "Five"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" slist => { "One", "Two", "Three", "Four", "Five" }; + + "vals" slist => grep(".*v.*", "array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/226.cf b/tests/acceptance/01_vars/02_functions/226.cf new file mode 100644 index 0000000000..8e0e16c6a3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/226.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test grep(), size 0, ".*v" matches nothing (anchored regex) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "zero" string => "zero"; + "one" string => "one"; + + files: + "$(G.testfile).expected" + create => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" slist => { "One", "Two", "Three", "Four", "Five" }; + + "vals" slist => grep(".*v", "array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/227.cf b/tests/acceptance/01_vars/02_functions/227.cf new file mode 100644 index 0000000000..eb130dec01 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/227.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test grep(), size 0, empty pattern should match nothing +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "zero" string => "zero"; + "one" string => "one"; + + files: + "$(G.testfile).expected" + create => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "array" slist => { "One", "Two", "Three", "Four", "Five" }; + + "vals" slist => grep("", "array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/300.cf b/tests/acceptance/01_vars/02_functions/300.cf new file mode 100644 index 0000000000..01116f4b1c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/300.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Test multiline regcmp +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "haystack" string => "foo +bar"; + "needle" string => ".*"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { regcmp("$(test.needle)", "$(test.haystack)") }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/310.cf b/tests/acceptance/01_vars/02_functions/310.cf new file mode 100644 index 0000000000..d7813f768b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/310.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test escape() - basic tests +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "t1" string => escape("foo"); + "e1" string => "foo"; + "t2" string => escape("foo.baz"); + "e2" string => "foo.baz"; + "t3" string => escape("foo[baz]"); + "e3" string => "foo[baz]"; + "t4" string => escape("(fo?o|baz)+x{3}y*"); + "e4" string => "(fo?o|baz)+x{3}y*"; + "t5" string => escape("~`!@#%&_=\"';:,/<>"); + "e5" string => "~`!@#%&_=\"';:,/<>"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + regcmp("$(test.t1)", "$(test.e1)"), + regcmp("$(test.t2)", "$(test.e2)"), + regcmp("$(test.t3)", "$(test.e3)"), + regcmp("$(test.t4)", "$(test.e4)"), + regcmp("$(test.t5)", "$(test.e5)"), + }; + + reports: + DEBUG:: + "Comparing actual vs. expected:"; + "'$(test.t1)' vs. '$(test.e1)'"; + "'$(test.t2)' vs. '$(test.e2)'"; + "'$(test.t3)' vs. '$(test.e3)'"; + "'$(test.t4)' vs. '$(test.e4)'"; + "'$(test.t5)' vs. '$(test.e5)'"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/311.cf b/tests/acceptance/01_vars/02_functions/311.cf new file mode 100644 index 0000000000..1002c916f7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/311.cf @@ -0,0 +1,63 @@ +####################################################### +# +# Test escape() - basic tests inside an slist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "s1" slist => { + "bstr", + escape("foo"), + escape("foo.baz"), + escape("foo[baz]"), + "estr", + }; + "t1" string => join(",","s1"); + "e1" string => "bstr,foo,foo.baz,foo[baz],estr"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + regcmp("$(test.t1)", "$(test.e1)"), + }; + + reports: + DEBUG:: + "'$(test.t1)' comprises $(s1)"; + "Comparing actual vs. expected:"; + "'$(test.t1)' vs. '$(test.e1)'"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/312.cf b/tests/acceptance/01_vars/02_functions/312.cf new file mode 100644 index 0000000000..13e8fa6fd4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/312.cf @@ -0,0 +1,56 @@ +# +# Test escape() - issue 689 +# + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + vars: + "t1" string => escape("1,2"); + "e1" string => "1,2"; + "t2" string => escape("{1,2}"); + "e2" string => "{1,2}"; +} + +bundle agent check +{ + classes: + "ok1" expression => regcmp("$(test.t1)", "$(test.e1)"); + "ok2" expression => regcmp("$(test.t2)", "$(test.e2)"); + "ok" and => { "ok1", "ok2" }; + + reports: + DEBUG:: + "Comparing actual vs. expected:"; + DEBUG.ok1:: + "'$(test.t1)' is the same as '$(test.e1)'"; + DEBUG.!ok1:: + "'$(test.t1)' is NOT the same as '$(test.e1)'"; + DEBUG.ok2:: + "'$(test.t2)' is the same as '$(test.e2)'"; + DEBUG.!ok2:: + "'$(test.t2)' is NOT the same as '$(test.e2)'"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/313.cf b/tests/acceptance/01_vars/02_functions/313.cf new file mode 100644 index 0000000000..d033450bff --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/313.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Test escape() - strings that end with \, issue 690 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "t1" string => escape("\\"); + "e1" string => "\\"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + regcmp("$(test.t1)", "$(test.e1)"), + }; + + reports: + DEBUG:: + "Comparing actual vs. expected:"; + "'$(test.t1)' vs. '$(test.e1)'"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/400.cf b/tests/acceptance/01_vars/02_functions/400.cf new file mode 100644 index 0000000000..65a7a38999 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/400.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test readstringarray(), simple +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "is"), + strcmp("$(test.ary[this][2])", "a"), + strcmp("$(test.ary[this][3])", "test"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[this][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'is', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = 'a', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = 'test', saw '$(test.ary[this][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/401.cf b/tests/acceptance/01_vars/02_functions/401.cf new file mode 100644 index 0000000000..b38fc03185 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/401.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Test readstringarray(), introduce a singleton with no other fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +singleton +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[singleton][0])", "singleton"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[singleton][1]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[singleton][0] = 'singleton', saw '$(test.ary[singleton][0])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/410.cf b/tests/acceptance/01_vars/02_functions/410.cf new file mode 100644 index 0000000000..ec7b97f6df --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/410.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test readstringarray(), add some weird indices, real comments, character limit +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","^#.*",":+",10,14); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "bad" or => { + isvariable("test.ary[he][1]"), + isvariable("test.ary[blank][0]"), + }; + + "ok" and => { + "!bad", + + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[he][0])", "he"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "saw 'bad'-class things"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[he][0] = 'he', saw '$(test.ary[he][0])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/420.cf b/tests/acceptance/01_vars/02_functions/420.cf new file mode 100644 index 0000000000..69ac3a91ac --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/420.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Test parsestringarray(), simple +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","NoComment",":",10,1000); + # ensure that bare words are OK + "ignore" int => parsestringarray("ary", "mydata","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "is"), + strcmp("$(test.ary[this][2])", "a"), + strcmp("$(test.ary[this][3])", "test"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[this][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'is', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = 'a', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = 'test', saw '$(test.ary[this][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/421.cf b/tests/acceptance/01_vars/02_functions/421.cf new file mode 100644 index 0000000000..46146102bd --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/421.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Test parsestringarray(), introduce a singleton with no other fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +singleton +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[singleton][0])", "singleton"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[singleton][1]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[singleton][0] = 'singleton', saw '$(test.ary[singleton][0])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/440.cf b/tests/acceptance/01_vars/02_functions/440.cf new file mode 100644 index 0000000000..4293042d94 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/440.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test readstringarrayidx(), simple +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "this"), + strcmp("$(test.ary[1][1])", "is"), + strcmp("$(test.ary[1][2])", "a"), + strcmp("$(test.ary[1][3])", "test"), + + strcmp("$(test.ary[2][0])", "1"), + strcmp("$(test.ary[2][1])", "2"), + strcmp("$(test.ary[2][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[1][4]"), + isvariable("test.ary[2][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = 'this', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = 'is', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = 'a', saw '$(test.ary[1][2])'"; + "expected test.ary[1][3] = 'test', saw '$(test.ary[1][3])'"; + + "expected test.ary[2][0] = '1', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = '2', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = '3', saw '$(test.ary[2][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/441.cf b/tests/acceptance/01_vars/02_functions/441.cf new file mode 100644 index 0000000000..0d374b76a5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/441.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Test readstringarrayidx(), introduce a singleton with no other fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +singleton +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "singleton"), + + strcmp("$(test.ary[2][0])", "1"), + strcmp("$(test.ary[2][1])", "2"), + strcmp("$(test.ary[2][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[1][1]"), + isvariable("test.ary[2][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = 'singleton', saw '$(test.ary[1][0])'"; + + "expected test.ary[2][0] = '1', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = '2', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = '3', saw '$(test.ary[2][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/442.cf b/tests/acceptance/01_vars/02_functions/442.cf new file mode 100644 index 0000000000..8909b75a6a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/442.cf @@ -0,0 +1,120 @@ +####################################################### +# +# Test readstringarrayidx(), introduce a duplicate key +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +this:too +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "4"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "this"), + strcmp("$(test.ary[1][1])", "is"), + strcmp("$(test.ary[1][2])", "a"), + strcmp("$(test.ary[1][3])", "test"), + + strcmp("$(test.ary[2][0])", "this"), + strcmp("$(test.ary[2][1])", "too"), + + strcmp("$(test.ary[3][0])", "1"), + strcmp("$(test.ary[3][1])", "2"), + strcmp("$(test.ary[3][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[1][4]"), + isvariable("test.ary[2][2]"), + isvariable("test.ary[3][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = 'this', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = 'is', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = 'a', saw '$(test.ary[1][2])'"; + "expected test.ary[1][3] = 'test', saw '$(test.ary[1][3])'"; + + "expected test.ary[2][0] = 'this', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'too', saw '$(test.ary[2][1])'"; + + "expected test.ary[3][0] = '1', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = '2', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '3', saw '$(test.ary[3][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/443.cf b/tests/acceptance/01_vars/02_functions/443.cf new file mode 100644 index 0000000000..86040e9ff3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/443.cf @@ -0,0 +1,124 @@ +####################################################### +# +# Test readstringarrayidx(), introduce a non-parsed comment +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +# Not parsed as a comment +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "4"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "# Not parsed as a comment"), + + strcmp("$(test.ary[2][0])", "this"), + strcmp("$(test.ary[2][1])", "is"), + strcmp("$(test.ary[2][2])", "a"), + strcmp("$(test.ary[2][3])", "test"), + + strcmp("$(test.ary[3][0])", "1"), + strcmp("$(test.ary[3][1])", "2"), + strcmp("$(test.ary[3][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[1][1]"), + isvariable("test.ary[2][4]"), + isvariable("test.ary[3][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'is', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = 'a', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = 'test', saw '$(test.ary[this][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/444.cf b/tests/acceptance/01_vars/02_functions/444.cf new file mode 100644 index 0000000000..5c4c57976f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/444.cf @@ -0,0 +1,159 @@ +####################################################### +# +# Test readstringarrayidx(), add some weird indices (including a duplicate) +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "9"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "# A duplicate follows"), + strcmp("$(test.ary[6][1])", " this line is not always a comment"), + + strcmp("$(test.ary[7][0])", "this"), + strcmp("$(test.ary[7][1])", "also"), + + strcmp("$(test.ary[8][0])", "last"), + strcmp("$(test.ary[8][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = '# A duplicate follows', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = ' this line is not always a comment', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'this', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'also', saw '$(test.ary[7][1])'"; + + "expected test.ary[8][0] = 'last', saw '$(test.ary[8][0])'"; + "expected test.ary[8][1] = 'one', saw '$(test.ary[8][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/445.cf b/tests/acceptance/01_vars/02_functions/445.cf new file mode 100644 index 0000000000..f9b13a6299 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/445.cf @@ -0,0 +1,162 @@ +####################################################### +# +# Test parsestringarray(), weird indices, duplicate and trailing newlines +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one + + +"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "9"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "# A duplicate follows"), + strcmp("$(test.ary[6][1])", " this line is not always a comment"), + + strcmp("$(test.ary[7][0])", "this"), + strcmp("$(test.ary[7][1])", "also"), + + strcmp("$(test.ary[8][0])", "last"), + strcmp("$(test.ary[8][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = '# A duplicate follows', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = ' this line is not always a comment', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'this', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'also', saw '$(test.ary[7][1])'"; + + "expected test.ary[8][0] = 'last', saw '$(test.ary[8][0])'"; + "expected test.ary[8][1] = 'one', saw '$(test.ary[8][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/447.cf b/tests/acceptance/01_vars/02_functions/447.cf new file mode 100644 index 0000000000..3f88cccf08 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/447.cf @@ -0,0 +1,159 @@ +####################################################### +# +# Test parsestringarray(), weird indices, change comment parsing +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","",":",10,1000); + "num" int => "9"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "# A duplicate follows"), + strcmp("$(test.ary[6][1])", " this line is not always a comment"), + + strcmp("$(test.ary[7][0])", "this"), + strcmp("$(test.ary[7][1])", "also"), + + strcmp("$(test.ary[8][0])", "last"), + strcmp("$(test.ary[8][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = '# A duplicate follows', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = ' this line is not always a comment', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'this', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'also', saw '$(test.ary[7][1])'"; + + "expected test.ary[8][0] = 'last', saw '$(test.ary[8][0])'"; + "expected test.ary[8][1] = 'one', saw '$(test.ary[8][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/450.cf b/tests/acceptance/01_vars/02_functions/450.cf new file mode 100644 index 0000000000..38c1d09e1a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/450.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test readstringarrayidx(), add some weird indices, real comments, no empty fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","^#.*",":+",10,14); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "bad" or => { + isvariable("test.ary[2][1]"), + isvariable("test.ary[3][0]"), + }; + + "ok" and => { + "!bad", + + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "he"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "saw 'bad'-class things"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'he', saw '$(test.ary[2][0])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/500.cf b/tests/acceptance/01_vars/02_functions/500.cf new file mode 100644 index 0000000000..b51b94c5c3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/500.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Test readintarray(), simple +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readintarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999:888:777:666 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readintarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[999][0])", "999"), + strcmp("$(test.ary[999][1])", "888"), + strcmp("$(test.ary[999][2])", "777"), + strcmp("$(test.ary[999][3])", "666"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999][0] = '999', saw '$(test.ary[999][0])'"; + "expected test.ary[999][1] = '888', saw '$(test.ary[999][1])'"; + "expected test.ary[999][2] = '777', saw '$(test.ary[999][2])'"; + "expected test.ary[999][3] = '666', saw '$(test.ary[999][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/501.cf b/tests/acceptance/01_vars/02_functions/501.cf new file mode 100644 index 0000000000..4d459c273c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/501.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Test readintarray(), introduce 777 12345 with no other fields +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readintarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +12345 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readintarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[12345][0])", "12345"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[12345][1]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[12345][0] = '12345', saw '$(test.ary[12345][0])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/520.cf b/tests/acceptance/01_vars/02_functions/520.cf new file mode 100644 index 0000000000..89e37ee9de --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/520.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test parseintarray(), simple +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readintarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999:888:777:666 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parseintarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[999][0])", "999"), + strcmp("$(test.ary[999][1])", "888"), + strcmp("$(test.ary[999][2])", "777"), + strcmp("$(test.ary[999][3])", "666"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999][0] = '999', saw '$(test.ary[999][0])'"; + "expected test.ary[999][1] = '888', saw '$(test.ary[999][1])'"; + "expected test.ary[999][2] = '777', saw '$(test.ary[999][2])'"; + "expected test.ary[999][3] = '999', saw '$(test.ary[999][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/521.cf b/tests/acceptance/01_vars/02_functions/521.cf new file mode 100644 index 0000000000..82f8b91ea9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/521.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Test parseintarray(), introduce 777 12345 with no other fields +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readintarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +12345 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parseintarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[12345][0])", "12345"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[12345][1]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[12345][0] = '12345', saw '$(test.ary[12345][0])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/600.cf b/tests/acceptance/01_vars/02_functions/600.cf new file mode 100644 index 0000000000..e9d3d5a823 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/600.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Test readrealarray(), simple +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readrealarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999.9:888:777:666.6 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readrealarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[999.9][0])", "999.9"), + strcmp("$(test.ary[999.9][1])", "888"), + strcmp("$(test.ary[999.9][2])", "777"), + strcmp("$(test.ary[999.9][3])", "666.6"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999.9][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999.9][0] = '999.9', saw '$(test.ary[999.9][0])'"; + "expected test.ary[999.9][1] = '888', saw '$(test.ary[999.9][1])'"; + "expected test.ary[999.9][2] = '777', saw '$(test.ary[999.9][2])'"; + "expected test.ary[999.9][3] = '666.6', saw '$(test.ary[999.9][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/601.cf b/tests/acceptance/01_vars/02_functions/601.cf new file mode 100644 index 0000000000..63f960bca6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/601.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Test readrealarray(), introduce 777 12345 with no other fields +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readrealarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +12345 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readrealarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[12345][0])", "12345"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[12345][1]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[12345][0] = '12345', saw '$(test.ary[12345][0])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/620.cf b/tests/acceptance/01_vars/02_functions/620.cf new file mode 100644 index 0000000000..d207c78b07 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/620.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test parserealarray(), simple +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readrealarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999.9:888:777:666.6 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parserealarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[999.9][0])", "999.9"), + strcmp("$(test.ary[999.9][1])", "888"), + strcmp("$(test.ary[999.9][2])", "777"), + strcmp("$(test.ary[999.9][3])", "666.6"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999.9][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999.9][0] = '999.9', saw '$(test.ary[999.9][0])'"; + "expected test.ary[999.9][1] = '888', saw '$(test.ary[999.9][1])'"; + "expected test.ary[999.9][2] = '777', saw '$(test.ary[999.9][2])'"; + "expected test.ary[999.9][3] = '999.9', saw '$(test.ary[999.9][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/621.cf b/tests/acceptance/01_vars/02_functions/621.cf new file mode 100644 index 0000000000..19fe601bc0 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/621.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Test parserealarray(), introduce 777 12345 with no other fields +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readrealarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +12345 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parserealarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[12345][0])", "12345"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[12345][1]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[12345][0] = '12345', saw '$(test.ary[12345][0])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/700.cf b/tests/acceptance/01_vars/02_functions/700.cf new file mode 100644 index 0000000000..0eef7a109f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/700.cf @@ -0,0 +1,41 @@ +# Test that long bundle names are not cut off (Mantis #975) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent long_long_long_long_long_long_long_long_long_name +{ + vars: + "foo[ok]" string => "abc"; + "bar" slist => getindices("foo"); +} + +bundle agent test +{ + methods: + "l" usebundle => "long_long_long_long_long_long_long_long_long_name"; +} + +bundle agent check +{ + vars: + "res" string => join("", "long_long_long_long_long_long_long_long_long_name.bar"); + + classes: + "ok" expression => strcmp("${res}", "ok"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/701.cf b/tests/acceptance/01_vars/02_functions/701.cf new file mode 100644 index 0000000000..6108c8b73e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/701.cf @@ -0,0 +1,34 @@ +# Test that countclassesmatching works correctly (Mantis #975) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ + classes: + "test_fbeae67f3e347b5e0032302200141131" expression => "any"; + "test_fbeae67f3e347b5e0032302200141131_1" expression => "any"; +} + +bundle agent test +{ + vars: + "num" int => countclassesmatching("test_fbeae67f3e347b5e0032302200141131.*"); +} + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(test.num)", "2"); + + reports: + DEBUG:: + "$(test.num)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/702.cf b/tests/acceptance/01_vars/02_functions/702.cf new file mode 100644 index 0000000000..9034501b02 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/702.cf @@ -0,0 +1,37 @@ +# Test maplist expansion with ${this} syntax (Mantis #1249) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ + vars: + "s" slist => { "suffix" }; +} + +bundle agent test +{ + vars: + "list1" slist => maplist("prefix${this}", "init.s"); + "list2" slist => maplist("prefix$(this)", "init.s"); +} + +bundle agent check +{ + classes: + "ok" and => { reglist("@(test.list1)", "prefixsuffix"), + reglist("@(test.list2)", "prefixsuffix") }; + + reports: + DEBUG:: + "list1 => $(test.list1)"; + "list2 => $(test.list2)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/CFE-2704-0.cf b/tests/acceptance/01_vars/02_functions/CFE-2704-0.cf new file mode 100644 index 0000000000..f64c9dc367 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/CFE-2704-0.cf @@ -0,0 +1,22 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2704" } + string => "Test that attempting to merge incompatible data does not segfault."; + + vars: + "data" data => "{\"env\":\"test\"}"; + "node" data => mergedata("[]", "data[env]"); +} +bundle agent check +{ + methods: + "Pass if we made it this far" + usebundle => dcs_pass( $(this.promise_filename) ); +} diff --git a/tests/acceptance/01_vars/02_functions/CFE-2704-1.cf b/tests/acceptance/01_vars/02_functions/CFE-2704-1.cf new file mode 100644 index 0000000000..d21f4336f7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/CFE-2704-1.cf @@ -0,0 +1,22 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2704" } + string => "Test that attempting to merge incompatible data does not segfault. Variant 1."; + + vars: + "data" data => '{"env":"test"}'; + "node" data => mergedata("[]", "data[env]"); +} +bundle agent check +{ + methods: + "Pass if we made it this far" + usebundle => dcs_pass( $(this.promise_filename) ); +} diff --git a/tests/acceptance/01_vars/02_functions/CFE-954.cf b/tests/acceptance/01_vars/02_functions/CFE-954.cf new file mode 100644 index 0000000000..def229180c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/CFE-954.cf @@ -0,0 +1,107 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} +bundle agent init +{ + files: + + "$(G.testdir)/prod/." + create => "true"; + + "$(G.testdir)/test/." + create => "true"; + + "$(G.testdir)/test/FR1234.gz" + create => "true"; +} +bundle agent test +{ + meta: + "description" -> { "CFE-954" } + string => "Test that using lsdir on a list where nothing matches does not mess up later iterations"; + + vars: + + "directories" slist => {"prod","test"}; + + "files_regex_1_$(directories)" + slist => lsdir( "$(G.testdir)/$(directories)", ".*", "false"), + if => isdir( "$(G.testdir)/$(directories)" ); # Suppress errors about missing directory during pre-eval + + # Expect files_regex_1_prod contains . and .. + # Expect files_regex_1_test contains . .. and FR1234.gz + + "files_regex_2_$(directories)" + slist => lsdir( "$(G.testdir)/$(directories)", "FR.*\.gz", "false"), + if => isdir( "$(G.testdir)/$(directories)" ); # Suppress errors about missing directory during pre-eval + # Expect files_regex_2_prod contains nothing + # Expect files_regex_2_test contains FR1234.gz + + reports: + + DEBUG|EXTRA:: + + "files_regex_1_$(directories) is defined" + if => isvariable( "files_regex_1_$(directories)" ); + "files_regex_1_$(directories) is not defined" + unless => isvariable( "files_regex_1_$(directories)" ); + + "files_regex_2_$(directories) is defined" + if => isvariable( "files_regex_2_$(directories)" ); + "files_regex_2_$(directories) is not defined" + unless => isvariable( "files_regex_2_$(directories)" ); + + "Expect files_regex_1_prod contains . and .."; + "files_regex_1_prod contains $(files_regex_1_prod)"; + "Expect files_regex_1_test contains . .. and FR1234.gz"; + "files_regex_1_test contains $(files_regex_1_test)"; + + "Expect files_regex_2_prod contains nothing"; + "Expect files_regex_2_test contains FR1234.gz"; + "files_regex_2_$(directories) contains $(files_regex_2_$(directories))"; +} + +bundle agent check +{ + vars: + "expect_files_regex_1_prod" slist => { ".", ".." }; + "expect_files_regex_1_test" slist => { ".", "..", "FR1234.gz" }; + + "expect_files_regex_2_prod" slist => { }; + "expect_files_regex_2_test" slist => { "FR1234.gz" }; + + "d_1_prod" slist => difference( "check.expect_files_regex_1_prod", "test.files_regex_1_prod" ); + "d_1_test" slist => difference( "check.expect_files_regex_1_test", "test.files_regex_1_test" ); + + "d_2_prod" slist => difference( "check.expect_files_regex_2_prod", "test.files_regex_2_prod" ); + "d_2_test" slist => difference( "check.expect_files_regex_2_test", "test.files_regex_2_test" ); + + classes: + "pass" and => { + strcmp( "0", length( d_1_prod ) ), + strcmp( "0", length( d_1_test ) ), + strcmp( "0", length( d_2_prod ) ), + strcmp( "0", length( d_2_test ) ) + }; + + reports: + + DEBUG|EXTRA:: + "test.expect_files_regex_2_prod is NOT defined" + if => not(isvariable( "test.expect_files_regex_2_prod" )); + + "test.expect_files_regex_2_prod is defined" + if => isvariable( "test.expect_files_regex_2_prod" ); + "Difference regex 1 prod: $(d_1_prod)"; + "Difference regex 1 test: $(d_1_test)"; + "Difference regex 2 prod: $(d_2_prod)"; + "Difference regex 2 test: $(d_2_test)"; + + pass:: + "$(this.promise_filename) Pass"; + + !pass:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/and.cf b/tests/acceptance/01_vars/02_functions/and.cf new file mode 100644 index 0000000000..9e56882016 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/and.cf @@ -0,0 +1,219 @@ +###################################################### +# +# Test that and() behaves as expected +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3470" } + string => "Test that and() behaves as expected"; + + vars: + "f" # false + string => "(cfengine.(!cfengine))"; + "T" # true, uppercase to be more different visually + string => "(cfengine|(!cfengine))"; + "f_name" + string => "f"; + "T_name" + string => "T"; + "f_name_name" + string => "f_name"; + "T_name_name" + string => "T_name"; + "many_true" + slist => { + "any", + "$(T)", + concat(not(and("$(f)"))), + "(any.cfengine)", + concat(and()), + concat(and(and())), + }; + "many_false" + slist => { + "(!any)", + "$(f)", + concat(and(not("$(T)"))), + "(any.!cfengine)", + concat(not("any")), + concat(not(and())), + }; + "many_both" + slist => { @(many_true), @(many_false) }; + + classes: + + # All elements should be true, fail if one is false: + "ok" + scope => "namespace", + and => { + # Sanity check: + "any", + "cfengine", + + # and() with 0 arguments should default to true: + and(), + + # and() with 1 static string: + and("any"), + and("cfengine"), + and("!(!cfengine)"), + + # and() with 1 string with variable expansion(s): + and("$(T)"), + and("!$(f)"), + and("$(T).any"), + and("$(T).!(!any)"), + and("any.$(T)"), + and("!(!any).$(T)"), + and("any|$(f)"), + and("!(!any)|$(f)"), + and("$(T)|$(f)"), + and("$(f)|$(T)"), + + # and() with slist: + # Careful, if there are expressions in list (using | operator) + # they should be parenthesized for this to work: + and(join(".", many_true)), + and(join("|", many_true)), + and(join("|", many_both)), + and(not(join(".", many_false))), + and(not(join("|", many_false))), + and(not(join(".", many_both))), + + # and() with 1 function call as argument: + and(and("any")), + and(and("cfengine")), + and(not("!cfengine")), + and(not(not("cfengine"))), + and(not("$(f)")), + and(not(not("$(T)"))), + and(strcmp("cfengine", "cfengine")), + and(strcmp("any", not("$(f)"))), + + # and() with 2 arguments: + and("any", "cfengine"), + and("!(!any)", "!(!cfengine)"), + and("$(T)", "$(T)"), + and("$(T)", "!$(f)"), + and("$(T)", not("$(f)")), + and(not("$(f)"), not("$(f)")), + + # and() with 3+ arguments (strings): + and("any", "any", "any"), + and("any", "any", "any", "any"), + and("any", "any", "any", "any", "any"), + and("any", "any", "any", "any", "any", "any"), + and("$(T)", "$(T)", "$(T)"), + and("$(T)", "$(T)", "$(T)", "$(T)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(T)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)"), + + # and() with 3+ function calls: + and(not("!any"), not("!any"), not("!any")), + and(not("!any"), not("!any"), not("!any"), not("!any")), + and(not("!any"), not("!any"), not("!any"), not("!any"), not("!any")), + and(not("!any"), not("!any"), not("!any"), not("!any"), not("!any"), not("!any")), + and(not("$(f)"), not("$(f)"), not("$(f)")), + and(not("$(f)"), not("$(f)"), not("$(f)"), not("$(f)")), + and(not("$(f)"), not("$(f)"), not("$(f)"), not("$(f)"), not("$(f)")), + and(not("$(f)"), not("$(f)"), not("$(f)"), not("$(f)"), not("$(f)"), not("$(f)")), + + # and() with deep nesting: + and(and()), + and(and(and())), + and(and(and(and()))), + and(and(and(and(and())))), + and(and(and(and(and(and()))))), + and(and(and(and(and(and("any")))))), + and(and(and(and(and(and("any", "cfengine")))))), + + # Double expansion: + and("$($(T_name))"), + and("$($(T_name))", "$($(T_name))"), + and("$($(T_name))", "$($(T_name))", "$($(T_name))"), + and("!$($(f_name))", "!$($(f_name))"), + and("!$($(f_name))", "!$($(f_name))", "!$($(f_name))"), + and(not("$($(f_name))"), not("$($(f_name))")), + + # Triple expansion: + and("$($($(T_name_name)))"), + and("$($($(T_name_name)))", "$($($(T_name_name)))"), + and("$($($(T_name_name)))", "$($($(T_name_name)))", "$($($(T_name_name)))"), + and("!$($(f_name_name))", "!$($(f_name_name))"), + and("!$($(f_name_name))", "!$($(f_name_name))", "!$($(f_name_name))"), + and(not("$($(f_name_name))"), not("$($(f_name_name))")), + + # and() should always return any or !any, + # this is important for backwards compatibility: + strcmp(and("any"), "any"), + strcmp(and("!any"), "!any"), + strcmp(and("!cfengine"), "!any"), + strcmp(and("!(cfengine.!cfengine)"), "any"), + strcmp(and("$(T)"), "any"), + strcmp(and("$(f)"), "!any"), + strcmp(and("$(T)", "$(T)"), "any"), + strcmp(and("$(T)", "$(f)"), "!any"), + strcmp(and("$(f)", "$(T)"), "!any"), + strcmp(and("$(f)", "$(f)"), "!any"), + }; + + # Cases where and() should return false (fail if one is true): + "fail_false" + or => { + and("$(f)"), + and("$(T)", "$(f)"), + and("$(T)", "$(T)", "$(f)"), + and("$(T)", "$(T)", "$(T)", "$(f)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(f)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(f)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(f)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(f)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(f)", "$(T)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(f)", "$(T)", "$(T)"), + and("$(T)", "$(T)", "$(T)", "$(T)", "$(f)", "$(T)", "$(T)", "$(T)"), + and("$(T)", "$(T)", "$(T)", "$(f)", "$(T)", "$(T)", "$(T)", "$(T)"), + and("$(T)", "$(T)", "$(f)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)"), + and("$(T)", "$(f)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)"), + and("$(f)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)"), + }; + # Should be skipped because of unresolved variable: + "fail_unresolved" + and => { + "any", + and("$(unresolved_var)"), + }; + # Check that it's really skipped because of unresolved, + # and not that it accidentally becomes false: + "fail_not_of_unresolved" + and => { + "any", + and(not("$(unresolved_var)")), + }; + "fail" + scope => "namespace", + expression => "fail_false|fail_unresolved|fail_not_of_unresolved"; +} + +####################################################### + +bundle agent check +{ + + reports: + ok.(!fail):: + "$(this.promise_filename) Pass"; + (!ok)|fail:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/array_subkey_collision.cf b/tests/acceptance/01_vars/02_functions/array_subkey_collision.cf new file mode 100644 index 0000000000..509bfcdfd7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/array_subkey_collision.cf @@ -0,0 +1,47 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2536" } + string => "Converting arrays with colliding definitions for subkeys should work, somehow."; + + vars: + "array[key1][subkey1]" string => "hey"; + "array[key1]" string => "hou"; + + "array_values" slist => getvalues("array"); + "array_values_str" string => format("%S", array_values); + + "array_data" data => mergedata(array); + "array_data_str" string => format("%S", array_data); +} + +bundle agent check +{ + classes: + # the "more structured structure" should win when converting to JSON + "get_values_ok" expression => strcmp("$(test.array_values_str)", '{ "hey" }'); + "array_data_ok" expression => strcmp("$(test.array_data_str)", '{"key1":{"subkey1":"hey"}}'); + + "ok" and => { "get_values_ok", "array_data_ok" }; + + reports: + "DEBUG":: + "array_values_str: $(test.array_values_str)" + if => "!get_values_ok"; + + "array_data_str: $(test.array_data_str)" + if => "!array_data_ok"; + + "ok":: + "$(this.promise_filename) Pass"; + + "!ok":: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/basename_1.cf b/tests/acceptance/01_vars/02_functions/basename_1.cf new file mode 100644 index 0000000000..b38b3d132f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/basename_1.cf @@ -0,0 +1,152 @@ +####################################################### +# +# Test basename() without suffix +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + # The tests starting with template will be tested with both types of + # directory separators on Windows. + + "template_input[root]" string => "/"; + "template_expected[root]" string => "/"; + + "template_input[simple]" string => "/foo/bar"; + "template_expected[simple]" string => "bar"; + + "template_input[slash]" string => "/foo/bar/"; + "template_expected[slash]" string => "bar"; + + "template_input[sub]" string => "/foo"; + "template_expected[sub]" string => "foo"; + + "template_input[subslash]" string => "/foo/"; + "template_expected[subslash]" string => "foo"; + + "template_input[dot]" string => "/foo/."; + "template_expected[dot]" string => "."; + + "template_input[dot_subslash]" string => "/foo/./"; + "template_expected[dot_subslash]" string => "."; + + "template_input[multi]" string => "/foo/bar/baz"; + "template_expected[multi]" string => "baz"; + + "template_input[multislash]" string => "/foo/bar/baz/"; + "template_expected[multislash]" string => "baz"; + + "template_input[more]" string => "/a/b/c/d/e/f/g/h/i"; + "template_expected[more]" string => "i"; + + "template_input[multislash]" string => "/a///b////c///"; + "template_expected[multislash]" string => "c"; + + # Note: no template, because backslashes will be interpreted as UNC path on Windows. + "input[multislash_start]" string => "//a///b////c///"; + "expected[multislash_start]" string => "c"; + + "template_input[rel_one]" string => "foo"; + "template_expected[rel_one]" string => "foo"; + + "template_input[rel_more]" string => "foo/bar"; + "template_expected[rel_more]" string => "bar"; + + "template_input[rel_one_subslash]" string => "foo/"; + "template_expected[rel_one_subslash]" string => "foo"; + + "template_input[rel_more_subslash]" string => "foo/bar/"; + "template_expected[rel_more_subslash]" string => "bar"; + + "template_input[rel_dot]" string => "foo/."; + "template_expected[rel_dot]" string => "."; + + "template_input[rel_only_dot]" string => "./"; + "template_expected[rel_only_dot]" string => "."; + + "template_input[rel_multislash]" string => "a///b////c///"; + "template_expected[rel_multislash]" string => "c"; + + "template_input[noop]" string => ""; + "template_expected[noop]" string => ""; + + "template_keys" slist => getindices("template_input"); + + windows:: + "template_input[full_root]" string => "C:/"; + "template_expected[full_root]" string => "/"; + + "template_input[full_root_file]" string => "C:/foo"; + "template_expected[full_root_file]" string => "foo"; + + "template_input[full_root_file_subslash]" string => "C:/foo/"; + "template_expected[full_root_file_subslash]" string => "foo"; + + "template_input[full_file]" string => "C:/foo/bar"; + "template_expected[full_file]" string => "bar"; + + "template_input[full_file_subslash]" string => "C:/foo/bar/"; + "template_expected[full_file_subslash]" string => "bar"; + + "template_input[dir]" string => "C:foo"; + "template_expected[dir]" string => "foo"; + + "template_input[two_dirs]" string => "C:foo/bar"; + "template_expected[two_dirs]" string => "bar"; + + "template_input[dir_subslash]" string => "C:foo/"; + "template_expected[dir_subslash]" string => "foo"; + + "template_input[two_dirs_subslash]" string => "C:foo/bar/"; + "template_expected[two_dirs_subslash]" string => "bar"; + + # UNC paths don't have forward slash equivalents. + "input[native_unc_one]" string => "\\\\foo"; + "expected[native_unc_one]" string => "foo"; + + "input[native_unc_two]" string => "\\\\foo\\bar"; + "expected[native_unc_two]" string => "bar"; + + "input[native_unc_three]" string => "\\\\foo\\bar\\charlie\\"; + "expected[native_unc_three]" string => "charlie"; + + any:: + "input[native_$(template_keys)]" string => "$(template_input[$(template_keys)])"; + "expected[native_$(template_keys)]" string => "$(template_expected[$(template_keys)])"; + + "keys" slist => getindices("input"); + + "actual[$(keys)]" string => basename("$(input[$(keys)])"); +} + +####################################################### + +bundle agent check +{ + vars: + "keys" slist => { @(test.keys) }; + + classes: + "failed_cmp_$(keys)" not => strcmp(basename("$(test.input[$(keys)])"), "$(test.expected[$(keys)])"); + "ok" not => classmatch("failed_cmp_.*"); + + reports: + DEBUG:: + "'basename($(test.input[$(keys)]))' = '$(test.actual[$(keys)])' != '$(test.expected[$(keys)])'" + if => "failed_cmp_$(keys)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/basename_2.cf b/tests/acceptance/01_vars/02_functions/basename_2.cf new file mode 100644 index 0000000000..8d3d83666f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/basename_2.cf @@ -0,0 +1,78 @@ +####################################################### +# +# Test basename() with suffix removal +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + # The tests starting with template will be tested with both types of + # directory separators on Windows. + + "template_input[root]" string => "/"; + "template_suffix[root]" string => ""; + "template_expected[root]" string => "/"; + + "template_input[simple]" string => "/foo/bar.c"; + "template_suffix[simple]" string => ".c"; + "template_expected[simple]" string => "bar"; + + "template_input[rel_suffif]" string => "foo.xyz"; + "template_suffix[rel_suffif]" string => ".xyz"; + "template_expected[rel_suffif]" string => "foo"; + + "template_input[no_matching_suffix]" string => "/foo/bar"; + "template_suffix[no_matching_suffix]" string => "baz"; + "template_expected[no_matching_suffix]" string => "bar"; + + "template_input[same_suffix]" string => "foo"; + "template_suffix[same_suffix]" string => "foo"; + "template_expected[same_suffix]" string => "foo"; + + "template_input[sub_same_suffix]" string => "foo/bar"; + "template_suffix[sub_same_suffix]" string => "bar"; + "template_expected[sub_same_suffix]" string => "bar"; + + "template_keys" slist => getindices("template_input"); + + any:: + "input[native_$(template_keys)]" string => "$(template_input[$(template_keys)])"; + "expected[native_$(template_keys)]" string => "$(template_expected[$(template_keys)])"; + "suffix[native_$(template_keys)]" string => "$(template_suffix[$(template_keys)])"; + + "keys" slist => getindices("input"); + + "actual[$(keys)]" string => basename("$(input[$(keys)])", "$(suffix[$suffix])"); +} + +####################################################### + +bundle agent check +{ + vars: + "keys" slist => { @(test.keys) }; + + classes: + "failed_cmp_$(keys)" not => strcmp(basename("$(test.input[$(keys)])", "$(test.suffix[$(keys)])"), "$(test.expected[$(keys)])"); + "ok" not => classmatch("failed_cmp_.*"); + + reports: + DEBUG:: + "'basename($(test.input[$(keys)]), $(test.suffix[$(keys)]))' = '$(test.actual[$(keys)])' != '$(test.expected[$(keys)])'" + if => "failed_cmp_$(keys)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/bundlesmatching.cf b/tests/acceptance/01_vars/02_functions/bundlesmatching.cf new file mode 100644 index 0000000000..7818204f8b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/bundlesmatching.cf @@ -0,0 +1,90 @@ +# Test that bundlesmatching works correctly + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "bundle_runs" slist => bundlesmatching("default:bundle_run.*"); + "bundle_run_123s" slist => bundlesmatching("default:bundle_run_123.*"); + "bundle_123" string => nth(bundle_run_123s, 0); + + "bundle_run_meta_deprecated_unsorted" slist => bundlesmatching("default:bundle_run_meta.*", "deprecated"); + "bundle_run_meta_deprecated" slist => sort(bundle_run_meta_deprecated_unsorted, "lex"); + "bundle_deprecated" string => nth(bundle_run_meta_deprecated, 0); + + "bundle_run_meta_obsoleted_unsorted" slist => bundlesmatching("default:bundle_run_meta.*", "obs.*", "deprecated"); + "bundle_run_meta_obsoleted" slist => sort(bundle_run_meta_obsoleted_unsorted, "lex"); + "bundle_obsoleted" string => nth(bundle_run_meta_obsoleted, 0); + + "count" int => length(bundle_runs); + "123_count" int => length(bundle_run_123s); + "deprecated_count" int => length(bundle_run_meta_deprecated); + "obsoleted_count" int => length(bundle_run_meta_obsoleted); +} + +bundle agent check +{ + classes: + "ok" and => { strcmp("$(test.count)", "6"), + strcmp("$(test.123_count)", "1"), + strcmp("$(test.deprecated_count)", "2"), + strcmp("$(test.bundle_deprecated)", "default:bundle_run_meta_deprecated"), + strcmp("$(test.obsoleted_count)", "1"), + strcmp("$(test.bundle_obsoleted)", "default:bundle_run_meta_deprecated"), + strcmp("$(test.bundle_123)", "default:bundle_run_123_456") }; + + reports: + DEBUG:: + "Bundle runs count $(test.count)"; + "123 count $(test.123_count)"; + "Deprecated count $(test.deprecated_count)"; + "'obs.*' && deprecated count $(test.obsoleted_count)"; + "Found bundles $(test.bundle_runs)"; + "Found 123 bundles $(test.bundle_run_123s)"; + "First 123 bundle is $(test.bundle_123)"; + "Found deprecated bundles $(test.bundle_run_meta_deprecated)"; + "First deprecated bundle is $(test.bundle_deprecated)"; + "Found obsoleted bundles $(test.bundle_run_meta_obsoleted)"; + "First obsoleted bundle is $(test.bundle_obsoleted)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +bundle agent bundle_run_123_456 +{ +} + +bundle agent bundle_run_789_0ab +{ +} + +bundle agent bundle_run_cde_fgh +{ +} + +bundle agent bundle_run_meta_deprecated +{ + meta: + "tags" slist => { "deprecated", "obsoleted" }; +} + +bundle agent bundle_run_meta_string_tags +{ + meta: + "tags" string => "deprecated"; +} + +bundle agent bundle_run_meta_missing_tags +{ +} diff --git a/tests/acceptance/01_vars/02_functions/bundlesmatching_dynamicsequence.cf b/tests/acceptance/01_vars/02_functions/bundlesmatching_dynamicsequence.cf new file mode 100644 index 0000000000..42a4a943a2 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/bundlesmatching_dynamicsequence.cf @@ -0,0 +1,52 @@ +# Test that bundlesmatching works correctly for dynamic sequences + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ +} + +bundle agent test +{ + vars: + "runs" slist => bundlesmatching("default:run.*"); + + methods: + "run them" usebundle => $(runs); +} + +bundle agent check +{ + classes: + "ok" and => { ok1, ok2, ok3 }; + + reports: + DEBUG:: + "Found bundles $(test.runs)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +bundle agent run_123_456 +{ + classes: + "ok1" expression => "any", scope => "namespace"; +} + +bundle agent run_789_0ab +{ + classes: + "ok2" expression => "any", scope => "namespace"; +} + +bundle agent run_cde_fgh +{ + classes: + "ok3" expression => "any", scope => "namespace"; +} diff --git a/tests/acceptance/01_vars/02_functions/bundlestate.cf b/tests/acceptance/01_vars/02_functions/bundlestate.cf new file mode 100644 index 0000000000..d44956c7ca --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/bundlestate.cf @@ -0,0 +1,65 @@ +# Test that the bundlestate() function gives good data + +body common control +{ + inputs => { "../../default.cf.sub", "bundlestate.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "s" string => "Hello!"; + "d" data => parsejson('[4,5,6]'); + "list" slist => { "element1", "element2" }; + "h" data => parsejson('{ "a": "x", "b": "y"}'); + + methods: + "" usebundle => external:init; + "" usebundle => external:dump; +} + +bundle agent test +{ + vars: + "holder" data => bundlestate("init"); + "holder2" data => bundlestate("$(this.namespace):init"); + "holder3" data => bundlestate("nosuchnamespace:init"); + "holder4" data => bundlestate("external:init"); + + "holder_s" string => format("%S", holder); + "holder2_defined" string => ifelse(isvariable(holder2), "we have holder 2 and we shouldn't", ""); + "holder3_defined" string => ifelse(isvariable("holder3"), "we have holder 3 and we shouldn't", ""); + "holder4_s" string => format("%S", holder4); + + "s_s" string => format("%S", "holder[s]"); + "s_d" string => format("%S", "holder[d]"); + "s_list" string => format("%S", "holder[list]"); + "s_h" string => format("%S", "holder[h]"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_strcmp("$(test.s_s) +$(test.s_d) +$(test.s_list) +$(test.s_h) +$(test.holder4_s) +$(external:dump.external_holder_s) +$(external:dump.external_holder2_s) +$(test.holder2_defined) +$(test.holder3_defined)", + '"Hello!" +[4,5,6] +["element1","element2"] +{"a":"x","b":"y"} +{"external_s":"External Hello!"} +$(test.holder_s) +{"external_s":"External Hello!"} + +', + $(this.promise_filename), + "no"); +} diff --git a/tests/acceptance/01_vars/02_functions/bundlestate.cf.sub b/tests/acceptance/01_vars/02_functions/bundlestate.cf.sub new file mode 100644 index 0000000000..17a0d99707 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/bundlestate.cf.sub @@ -0,0 +1,20 @@ +body file control +{ + namespace => "external"; +} + +bundle agent init +{ + vars: + "external_s" string => "External Hello!"; +} + +bundle agent dump +{ + vars: + "external_holder" data => bundlestate("init"); + "external_holder2" data => bundlestate("external:init"); + + "external_holder_s" string => format("%S", external_holder); + "external_holder2_s" string => format("%S", external_holder2); +} diff --git a/tests/acceptance/01_vars/02_functions/cache_name.cf b/tests/acceptance/01_vars/02_functions/cache_name.cf new file mode 100644 index 0000000000..abbace9238 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/cache_name.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test that the function result cache checks function name +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + + +bundle agent init +{ + vars: + "agent_regex" string => ".*cf-agent.*"; +} + +####################################################### + +bundle common test +{ + meta: + "description" -> { "CFE-4244" } + string => "Test that the function result cache checks function name"; + + vars: + "res1" data => findprocesses("${init.agent_regex}"); + + classes: + # must not reuse result from previous line + # is reused, produces a type error + "_pass" expression => processexists("${init.agent_regex}"); +} + + +####################################################### + +bundle agent check +{ + methods: + _pass:: + "pass" usebundle => dcs_pass("$(this.promise_filename)"); + + !_pass:: + "pass" usebundle => dcs_fail("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/canonifyuniquely.cf b/tests/acceptance/01_vars/02_functions/canonifyuniquely.cf new file mode 100644 index 0000000000..d0ec78b10e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/canonifyuniquely.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Test canonifyuniquely() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + + "in1" string => "h?llo"; + "expect1" string => "h_llo_ad3f6fcf7063483c9d23b4b83c0b65df72e9f5cc"; + "out1" string => canonifyuniquely("$(in1)"); + + "in2" string => "h/llo"; + "expect2" string => "h_llo_5d049ef5eaa0c5b4622eac3eee899132731bc034"; + "out2" string => canonifyuniquely("$(in2)"); + + "in3" string => "/etc/passwd"; + "expect3" string => "_etc_passwd_b5cc3f676d00dd320d85ef41a5209fa0a99891ea"; + "out3" string => canonifyuniquely("$(in3)"); + + "in4" string => "hello@mumble.com"; + "expect4" string => "hello_mumble_com_5050b0473d3e79adccfb885b8cbe3f44ed706987"; + "out4" string => canonifyuniquely("$(in4)"); + + "in5" string => "!@#$%^&*()_-+={}[]\:;<>,?"; + "expect5" string => "__________________________55a3d62a602f37917827cbe044fcbb5da58f8df5"; + "out5" string => canonifyuniquely("$(in5)"); + + "in6" string => "Eystein Måløy Stenberg"; + "expect6" string => "Eystein_M__l__y_Stenberg_f1d661c705209075cd873226cb131d2e27374357"; + "out6" string => canonifyuniquely("$(in6)"); + + "in7" string => "$(in1) $(in1)"; + "expect7" string => "h_llo_h_llo_53750186dbccd2f8a512345c4a92ccc34d75267d"; + "out7" string => canonifyuniquely("$(in1) $(in1)"); + + "in8" string => "'\"hello\"'"; + "expect8" string => "__hello___b2a2a0459a623c8e1cae32e88673ba4067106dc9"; + "out8" string => canonifyuniquely("$(in8)"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + strcmp("$(test.expect1)", "$(test.out1)"), + strcmp("$(test.expect2)", "$(test.out2)"), + strcmp("$(test.expect3)", "$(test.out3)"), + strcmp("$(test.expect4)", "$(test.out4)"), + strcmp("$(test.expect5)", "$(test.out5)"), + strcmp("$(test.expect6)", "$(test.out6)"), + strcmp("$(test.expect7)", "$(test.out7)"), + strcmp("$(test.expect8)", "$(test.out8)"), + }; + + reports: + DEBUG:: + "Expected canonifyuniquely('$(test.in1)') => $(test.out1) == $(test.expect1)"; + "Expected canonifyuniquely('$(test.in2)') => $(test.out2) == $(test.expect2)"; + "Expected canonifyuniquely('$(test.in3)') => $(test.out3) == $(test.expect3)"; + "Expected canonifyuniquely('$(test.in4)') => $(test.out4) == $(test.expect4)"; + "Expected canonifyuniquely('$(test.in5)') => $(test.out5) == $(test.expect5)"; + "Expected canonifyuniquely('$(test.in6)') => $(test.out6) == $(test.expect6)"; + "Expected canonifyuniquely('$(test.in7)') => $(test.out7) == $(test.expect7)"; + "Expected canonifyuniquely('$(test.in8)') => $(test.out8) == $(test.expect8)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/cf_version.cf b/tests/acceptance/01_vars/02_functions/cf_version.cf new file mode 100644 index 0000000000..242f2070ad --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/cf_version.cf @@ -0,0 +1,61 @@ +########################################################### +# +# Test cf_version convenience functions +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + vars: + "after_newer" string => "yes", + if => cf_version_after("1.0.0"); + "after_newer_major" string => "yes", + if => cf_version_after("3.12"); + "after_older" string => "no", + if => cf_version_after("999.999.999"); + "at_same" string => "yes", + if => cf_version_at("$(sys.cf_version)"); + "at_major" string => "yes", + if => cf_version_at("3"); + "at_not_same" string => "no", + if => cf_version_at("999.999.999"); + "before_newer" string => "no", + if => cf_version_before("1.0.0"); + "before_older" string => "yes", + if => cf_version_before("999.999.999"); + "before_older_major" string => "yes", + if => cf_version_before("999"); + "between" string => "yes", + if => cf_version_between("1.0", "999.999"); + "not_between" string => "no", + if => cf_version_between("999", "999"); + "also_not_between" string => "no", + if => cf_version_between("1", "1"); + "max_newer" string => "no", + if => cf_version_maximum("1.0.0"); + "max_older" string => "yes", + if => cf_version_maximum("999.999.999"); + "min_newer" string => "yes", + if => cf_version_minimum("1.0.0"); + "min_older" string => "no", + if => cf_version_minimum("999.999.999"); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/cf_version.cf.expected.json b/tests/acceptance/01_vars/02_functions/cf_version.cf.expected.json new file mode 100644 index 0000000000..f19ee2b964 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/cf_version.cf.expected.json @@ -0,0 +1,11 @@ +{ + "after_newer": "yes", + "after_newer_major": "yes", + "at_major": "yes", + "at_same": "yes", + "before_older": "yes", + "before_older_major": "yes", + "between": "yes", + "max_older": "yes", + "min_newer": "yes" +} diff --git a/tests/acceptance/01_vars/02_functions/classesmatching.cf b/tests/acceptance/01_vars/02_functions/classesmatching.cf new file mode 100644 index 0000000000..9f43b510e3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classesmatching.cf @@ -0,0 +1,49 @@ +# Test that classesmatching and countclassesmatching work correctly + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ + classes: + "test_fbeae67f3e347b5e0032302200141131" expression => "any", meta => { "x" }; + "test_fbeae67f3e347b5e0032302200141131_1" expression => "any", meta => { "x" }; + "test_fbeae67f3e347b5e0032302200141131_2" expression => "any", meta => { "y" }; +} + +bundle common test +{ + vars: + "classes" slist => classesmatching("test_fbeae67f3e347b5e0032302200141131.*"); + "x_classes" slist => classesmatching("test_fbeae67f3e347b5e0032302200141131.*", "x"); + "z_classes" slist => classesmatching("test_fbeae67f3e347b5e0032302200141131.*", "z"); + + "ccm_classes" int => countclassesmatching("test_fbeae67f3e347b5e0032302200141131.*"); + "ccm_x_classes" int => countclassesmatching("test_fbeae67f3e347b5e0032302200141131.*", "x"); + "ccm_z_classes" int => countclassesmatching("test_fbeae67f3e347b5e0032302200141131.*", "z"); + + "count" int => length(classes); + "x_count" int => length(x_classes); + "z_count" int => length(z_classes); + + classes: + "have_count_classes" expression => strcmp($(count), 3); + "have_count_x_classes" expression => strcmp($(x_count), 2); + "have_count_z_classes" expression => strcmp($(z_count), 0); + + "have_ccm_classes" expression => strcmp($(ccm_classes), 3); + "have_ccm_x_classes" expression => strcmp($(ccm_x_classes), 2); + "have_ccm_z_classes" expression => strcmp($(ccm_z_classes), 0); +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("have_count_classes,have_count_x_classes,have_count_z_classes,have_ccm_classes,have_ccm_x_classes,have_ccm_z_classes", + "", + $(this.promise_filename)), + inherit => "true"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_1.cf b/tests/acceptance/01_vars/02_functions/classfiltercsv_1.cf new file mode 100644 index 0000000000..0d9ee2f787 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_1.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2768" } + string => "Test that classfiltercsv() works: + - column headings + - simple filtering (no duplicates after filter) + - no sorting"; + +} +bundle agent check +{ + vars: + "data_file" string => "$(this.promise_filename).csv"; + "d" data => classfiltercsv( $(data_file), + "true", # Data file contains column headings + 0); # Column containing class expression to filter with + + classes: + + # Check if Token has the value we expect + "Token_OK" expression => strcmp( "$(d[0][Token])", "net.ipv4.ip_forward" ); + + # Check if Value has the value we expect + "Value_OK" expression => strcmp( "$(d[0][Value])", "ANYVALUE" ); + + # Check if the result contains the number of records we expect. + "Length_OK" expression => strcmp( length( d ), 1 ); + + methods: + + "Pass/FAIL" + usebundle => dcs_passif_expected( 'Token_OK,Value_OK,Length_OK', '', $(this.promise_filename) ), + inherit => "true"; + + reports: + DEBUG|EXTRA:: + "Function returned:$(with)" with => string_mustache( "{{%-top-}}", d ); + + "supercalifragilisticexpialidociousNOTDEFINED is actually defined." + if => "supercalifragilisticexpialidociousNOTDEFINED"; + "supercalifragilisticexpialidociousNOTDEFINED is not defined (as expected)" + unless => "supercalifragilisticexpialidociousNOTDEFINED"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_1.cf.csv b/tests/acceptance/01_vars/02_functions/classfiltercsv_1.cf.csv new file mode 100644 index 0000000000..f5ca635379 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_1.cf.csv @@ -0,0 +1,3 @@ +ClassExpr,Token,Value +any,net.ipv4.ip_forward,ANYVALUE +supercalifragilisticexpialidociousNOTDEFINED,net.ipv4.ip_forward,SHOULD_BE_FILTERED_OUT diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_2.cf b/tests/acceptance/01_vars/02_functions/classfiltercsv_2.cf new file mode 100644 index 0000000000..9b16b1a11a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_2.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2768" } + string => "Test that classfiltercsv() works: + - no column headings + - simple filtering (no duplicates after filter) + - no sorting"; + +} +bundle agent check +{ + vars: + "data_file" string => "$(this.promise_filename).csv"; + "d" data => classfiltercsv( $(data_file), + "false", # Data file contains column headings + 0); # Column containing class expression to filter with + + classes: + + # Check if Token has the value we expect + "Token_OK" expression => strcmp( "$(d[0][0])", "net.ipv4.ip_forward" ); + + # Check if Value has the value we expect + "Value_OK" expression => strcmp( "$(d[0][1])", "ANYVALUE" ); + + # Check if the result contains the number of records we expect. + "Length_OK" expression => strcmp( length( d ), 1 ); + + methods: + + "Pass/FAIL" + usebundle => dcs_passif_expected( 'Token_OK,Value_OK,Length_OK', '', $(this.promise_filename) ), + inherit => "true"; + + reports: + DEBUG|EXTRA:: + "Function returned:$(with)" with => string_mustache( "{{%-top-}}", d ); + + "supercalifragilisticexpialidociousNOTDEFINED is actually defined." + if => "supercalifragilisticexpialidociousNOTDEFINED"; + "supercalifragilisticexpialidociousNOTDEFINED is not defined (as expected)" + unless => "supercalifragilisticexpialidociousNOTDEFINED"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_2.cf.csv b/tests/acceptance/01_vars/02_functions/classfiltercsv_2.cf.csv new file mode 100644 index 0000000000..31a9988ceb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_2.cf.csv @@ -0,0 +1,2 @@ +any,net.ipv4.ip_forward,ANYVALUE +supercalifragilisticexpialidociousNOTDEFINED,net.ipv4.ip_forward,SHOULD_BE_FILTERED_OUT diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_3.cf b/tests/acceptance/01_vars/02_functions/classfiltercsv_3.cf new file mode 100644 index 0000000000..cd02d2e317 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_3.cf @@ -0,0 +1,52 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2768" } + string => "Test that classfiltercsv() works: + - column headings + - simple filtering (no duplicates after filter) + - sorting"; + +} +bundle agent check +{ + vars: + "data_file" string => "$(this.promise_filename).csv"; + "d" data => classfiltercsv( $(data_file), + "true", # Data file contains column headings + 0, # Column containing class expression to filter with + 1); # Column to sort by (NOT IMPLEMENTED AT TIME OF TEST) + + classes: + + # Check if Token has the value we expect + "Token_OK" expression => strcmp( "$(d[0][Token])", "net.ipv4.ip_forward" ); + + # Check if Value has the value we expect + "Value_OK" expression => strcmp( "$(d[0][Value])", "ANYVALUE-sort0" ); + + # Check if the result contains the number of records we expect. + "Length_OK" expression => isgreaterthan( length( d ), 0); + + methods: + + "Pass/FAIL" + usebundle => dcs_passif_expected( 'Token_OK,Value_OK,Length_OK', '', $(this.promise_filename) ), + inherit => "true"; + + reports: + DEBUG|EXTRA:: + "Function returned:$(with)" with => string_mustache( "{{%-top-}}", d ); + + "supercalifragilisticexpialidociousNOTDEFINED is actually defined." + if => "supercalifragilisticexpialidociousNOTDEFINED"; + "supercalifragilisticexpialidociousNOTDEFINED is not defined (as expected)" + unless => "supercalifragilisticexpialidociousNOTDEFINED"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_3.cf.csv b/tests/acceptance/01_vars/02_functions/classfiltercsv_3.cf.csv new file mode 100644 index 0000000000..fe2c749c9f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_3.cf.csv @@ -0,0 +1,6 @@ +ClassExpr,SORT,Token,Value +any,1,net.ipv4.ip_forward,ANYVALUE-sort1 +any,a,net.ipv4.ip_forward,ANYVALUE-sorta +any,A,net.ipv4.ip_forward,ANYVALUE-sortA +supercalifragilisticexpialidociousNOTDEFINED,0,net.ipv4.ip_forward,SHOULD_BE_FILTERED_OUT +any,0,net.ipv4.ip_forward,ANYVALUE-sort0 diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_4.cf b/tests/acceptance/01_vars/02_functions/classfiltercsv_4.cf new file mode 100644 index 0000000000..d40e8910bc --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_4.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2768" } + string => "Test that classfiltercsv() works: + - column headings + - simple filtering (no duplicates after filter) + - no sorting + - 2 invalid rows, one that would be included, and one that would be filtered out are both ignored"; +} +bundle agent check +{ + vars: + "data_file" string => "$(this.promise_filename).csv"; + "d" data => classfiltercsv( $(data_file), + "true", # Data file contains column headings + 0); # Column containing class expression to filter with + + classes: + + # Check if Token has the value we expect + "Token_OK" expression => strcmp( "$(d[0][Token])", "net.ipv4.ip_forward" ); + + # Check if Value has the value we expect + "Value_OK" expression => strcmp( "$(d[0][Value])", "ANYVALUE" ); + + # Check if the result contains the number of records we expect. + "Length_OK" expression => strcmp( length( d ), 1 ); + + methods: + + "Pass/FAIL" + usebundle => dcs_passif_expected( 'Token_OK,Value_OK,Length_OK', '', $(this.promise_filename) ), + inherit => "true"; + + reports: + DEBUG|EXTRA:: + "Function returned:$(with)" with => string_mustache( "{{%-top-}}", d ); + + "supercalifragilisticexpialidociousNOTDEFINED is actually defined." + if => "supercalifragilisticexpialidociousNOTDEFINED"; + "supercalifragilisticexpialidociousNOTDEFINED is not defined (as expected)" + unless => "supercalifragilisticexpialidociousNOTDEFINED"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_4.cf.csv b/tests/acceptance/01_vars/02_functions/classfiltercsv_4.cf.csv new file mode 100644 index 0000000000..1191120d06 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_4.cf.csv @@ -0,0 +1,5 @@ +ClassExpr,Token,Value +any,net.ipv4.ip_forward,ANYVALUE +supercalifragilisticexpialidociousNOTDEFINED,net.ipv4.ip_forward,SHOULD_BE_FILTERED_OUT +supercalifragilisticexpialidociousNOTDEFINED,net.ipv4.ip_forward,SHOULD,BE,FILTERED,OUT +cfengine,net.ipv4.ip_forward,INVALID,SHOULD,BE,FILTERED,OUT diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_5.cf b/tests/acceptance/01_vars/02_functions/classfiltercsv_5.cf new file mode 100644 index 0000000000..df44d26228 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_5.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2768" } + string => "Test that classfiltercsv() works: + - column headings + - simple filtering (no duplicates after filter) + - no sorting + - empty rows"; +} +bundle agent check +{ + vars: + "data_file" string => "$(this.promise_filename).csv"; + "d" data => classfiltercsv( $(data_file), + "true", # Data file contains column headings + 0); # Column containing class expression to filter with + + classes: + + # Check if Token has the value we expect + "Token_OK" expression => strcmp( "$(d[0][Token])", "net.ipv4.ip_forward" ); + + # Check if Value has the value we expect + "Value_OK" expression => strcmp( "$(d[0][Value])", "ANYVALUE" ); + + # Check if the result contains the number of records we expect. + "Length_OK" expression => strcmp( length( d ), 1 ); + + methods: + + "Pass/FAIL" + usebundle => dcs_passif_expected( 'Token_OK,Value_OK,Length_OK', '', $(this.promise_filename) ), + inherit => "true"; + + reports: + DEBUG|EXTRA:: + "Function returned:$(with)" with => string_mustache( "{{%-top-}}", d ); + + "supercalifragilisticexpialidociousNOTDEFINED is actually defined." + if => "supercalifragilisticexpialidociousNOTDEFINED"; + "supercalifragilisticexpialidociousNOTDEFINED is not defined (as expected)" + unless => "supercalifragilisticexpialidociousNOTDEFINED"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfiltercsv_5.cf.csv b/tests/acceptance/01_vars/02_functions/classfiltercsv_5.cf.csv new file mode 100644 index 0000000000..9fdcb03d94 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfiltercsv_5.cf.csv @@ -0,0 +1,7 @@ +ClassExpr,Token,Value + + +any,net.ipv4.ip_forward,ANYVALUE + + +supercalifragilisticexpialidociousNOTDEFINED,net.ipv4.ip_forward,SHOULD_BE_FILTERED_OUT diff --git a/tests/acceptance/01_vars/02_functions/classmatch.cf b/tests/acceptance/01_vars/02_functions/classmatch.cf new file mode 100644 index 0000000000..dc180b403d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classmatch.cf @@ -0,0 +1,34 @@ +# Test that classmatch works correctly + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ + classes: + "test_fbeae67f3e347b5e0032302200141131" expression => "any", meta => { "x" }; + "test_fbeae67f3e347b5e0032302200141131_1" expression => "any", meta => { "x" }; + "test_fbeae67f3e347b5e0032302200141131_2" expression => "any", meta => { "y" }; +} + +bundle common test +{ + classes: + "have_classes" expression => classmatch("test_fbeae67f3e347b5e0032302200141131"); + "have_x_classes" expression => classmatch("test_fbeae67f3e347b5e0032302200141131_1.*", "x"); + "have_z_classes" expression => classmatch("nosuchclass"); + "have_z_classes" expression => classmatch("test_fbeae67f3e347b5e0032302200141131_2", "z"); + "have_z_classes" expression => classmatch("test_fbeae67f3e347b5e0032302200141131_2.*", "z"); +} + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("have_classes,have_x_classes", + "have_z_classes", + $(this.promise_filename)), + inherit => "true"; +} diff --git a/tests/acceptance/01_vars/02_functions/countlinesmatching.cf b/tests/acceptance/01_vars/02_functions/countlinesmatching.cf new file mode 100644 index 0000000000..a4f0b179af --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/countlinesmatching.cf @@ -0,0 +1,44 @@ +# Test that countlinesmatching works correctly + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ +} + +bundle agent test +{ + vars: + # meta comment 1 + # meta comment 2 + # meta comment 3 + # meta comment 4 + "metagrep" int => countlinesmatching(".*# meta comment.*", + $(this.promise_filename)); + "exactgrep" int => countlinesmatching("bundle agent test", + $(this.promise_filename)); + "nullgrep" int => countlinesmatching("blue hippo, purple elephant, yellow submarine", + $(this.promise_filename)); +} + +bundle agent check +{ + classes: + "ok" and => { strcmp("$(test.metagrep)", "5"), # the 4 comments plus the pattern! + strcmp("$(test.exactgrep)", "1"), + strcmp("$(test.nullgrep)", "0") }; + + reports: + DEBUG:: + "Grepping for 'meta comment' found $(test.metagrep) matches, expected 5"; + "Grepping for exactly 'bundle agent test' found $(test.exactgrep) matches, expected 1"; + "Grepping for children's rhyme found $(test.nullgrep) matches, expected 0"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/countlinesmatching_errors_on_absent_file.cf b/tests/acceptance/01_vars/02_functions/countlinesmatching_errors_on_absent_file.cf new file mode 100644 index 0000000000..5fc505da90 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/countlinesmatching_errors_on_absent_file.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test countlinesmatching() errors when file is absent +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3234" } + string => "Test that the agent emits error output when countlinesmatching() is used on a file that is inaccessible."; + + "test_skip_needs_work" + string => "windows", + meta => { "CFE-3234" }; + + vars: + + "subout" string => execresult("$(sys.cf_agent) -Kv --define AUTO -f $(this.promise_dirname)/countlinesmatching_returns_0_on_inaccessable_file.cf 2>&1 | $(G.grep) countlinesmatching", "useshell"); + +} + +####################################################### + +bundle agent check +{ + + vars: + "expression" string => ".* error: File '/asd/fgh/qwertyio0p' could not be read in countlinesmatching.*"; + + classes: + "__pass" expression => regcmp( $(expression), $(test.subout) ); + + methods: + + __pass:: + "Pass" usebundle => dcs_pass( $(this.promise_filename) ); + !__pass:: + "FAIL" usebundle => dcs_pass( $(this.promise_filename) ); +} diff --git a/tests/acceptance/01_vars/02_functions/countlinesmatching_returns_0_on_inaccessable_file.cf b/tests/acceptance/01_vars/02_functions/countlinesmatching_returns_0_on_inaccessable_file.cf new file mode 100644 index 0000000000..ed57633891 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/countlinesmatching_returns_0_on_inaccessable_file.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test countlinesmatching() with some kind of failure expected +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "Mantis 320", "CFE-3234" } + string => "Test that countlinesmatching() returns 0 when the file is inaccessible."; + + vars: + "zero_regex" string => "impossible line"; + + "fail" int => countlinesmatching("$(zero_regex)", "/asd/fgh/qwertyio0p"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + strcmp("$(test.fail)", "0"), + }; + + reports: + DEBUG:: + "Expected 0 matches to '$(test.zero_regex)', found $(test.fail)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/data_expand.cf b/tests/acceptance/01_vars/02_functions/data_expand.cf new file mode 100644 index 0000000000..3d45a407a5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_expand.cf @@ -0,0 +1,33 @@ +########################################################### +# +# Test data_expand() +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + vars: + "x" string => "foo"; + "y" int => "200"; + "load_unexpanded" data => readjson("$(this.promise_filename).json", inf); + "load_expanded" data => data_expand(load_unexpanded); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/data_expand.cf.expected.json b/tests/acceptance/01_vars/02_functions/data_expand.cf.expected.json new file mode 100644 index 0000000000..0c678fcf3b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_expand.cf.expected.json @@ -0,0 +1,14 @@ +{ + "load_expanded": { + "200": "$(nosuchvar)", + "foo": "nothing again", + "plain data": "nothing" + }, + "load_unexpanded": { + "$(x)": "nothing again", + "$(y)": "$(nosuchvar)", + "plain data": "nothing" + }, + "x": "foo", + "y": "200" +} diff --git a/tests/acceptance/01_vars/02_functions/data_expand.cf.json b/tests/acceptance/01_vars/02_functions/data_expand.cf.json new file mode 100644 index 0000000000..50747744be --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_expand.cf.json @@ -0,0 +1,5 @@ +{ + "plain data": "nothing", + "$(x)": "nothing again", + "$(y)": "$(nosuchvar)", +} diff --git a/tests/acceptance/01_vars/02_functions/data_readstringarray.cf b/tests/acceptance/01_vars/02_functions/data_readstringarray.cf new file mode 100644 index 0000000000..03515c0502 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_readstringarray.cf @@ -0,0 +1,60 @@ +# Redmine#2926: test long lines with readstringarrayidx() + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "params" data => data_readstringarray("$(this.promise_filename).txt", + "\s*#[^\n]*", + ";", + 9999, + 99999); + "params_str" string => format("%S", params); + "pk" slist => getindices(params); + + reports: + DEBUG:: + "params: $(params_str)"; +} + +bundle agent check +{ + vars: + "dim" int => length("test.params"); + "length" int => length("test.pk"); + "last" string => nth("test.value", 599); + classes: + "ok1" expression => strcmp($(dim), "3"); + "ok2" expression => strcmp($(dim), $(length)); + "ok3" expression => strcmp("$(test.params[not_working_app_config][0])", "9123"); + "ok" and => { "ok1", "ok2", "ok3" }; + + reports: + DEBUG.ok1:: + "passed1"; + DEBUG.ok2:: + "passed2"; + DEBUG.ok3:: + "passed3"; + DEBUG.!ok1:: + "failed1 $(dim) != 3"; + DEBUG.!ok2:: + "failed2 $(dim) != $(length)"; + DEBUG.!ok3:: + "failed3 $(test.params[not_working_app_config][0]) != 9123"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/data_readstringarray.cf.txt b/tests/acceptance/01_vars/02_functions/data_readstringarray.cf.txt new file mode 100644 index 0000000000..45959f69c3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_readstringarray.cf.txt @@ -0,0 +1,4 @@ +# app_name;instance;mmax;mmin;mperm;zone;tomcat_version;env;setenv;app_properties;newrelic;newrelic_name;newrelic_version;context_static;checks;action;context +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server diff --git a/tests/acceptance/01_vars/02_functions/data_readstringarrayidx.cf b/tests/acceptance/01_vars/02_functions/data_readstringarrayidx.cf new file mode 100644 index 0000000000..1f90dd0e3a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_readstringarrayidx.cf @@ -0,0 +1,74 @@ +# Redmine#2926: test long lines with readstringarrayidx() + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "params" data => data_readstringarrayidx("$(this.promise_filename).txt", + "\s*#[^\n]*", + ";", + 9999, + 99999); + "params_str" string => format("%S", params); + "pk" slist => getindices(params); + + reports: + DEBUG:: + "params: $(params_str)"; +} + +bundle agent check +{ + vars: + "dim" int => length("test.params"); + "length" int => length("test.pk"); + "last" string => nth("test.value", 599); + + "pluck_23" data => mergedata("test.params[22]"); + "pluck_24" data => mergedata("test.params[23]"); + + classes: + "ok1" expression => strcmp($(dim), "24"); + "ok2" expression => strcmp($(dim), $(length)); + "ok3" expression => strcmp("$(test.params[2][0])", "not_working_app_config"); + "ok4" expression => strcmp("$(pluck_24[0])", "LAST"); + "ok5" expression => strcmp("$(pluck_23[0])", "NEXT-TO-LAST"); + "ok" and => { "ok1", "ok2", "ok3", "ok4", "ok5" }; + + reports: + DEBUG.ok1:: + "passed1"; + DEBUG.ok2:: + "passed2"; + DEBUG.ok3:: + "passed3"; + DEBUG.ok4:: + "passed4"; + DEBUG.ok5:: + "passed5"; + DEBUG.!ok1:: + "failed1 $(dim) != 24"; + DEBUG.!ok2:: + "failed2 $(dim) != $(length)"; + DEBUG.!ok3:: + "failed3 $(test.params[2][0]) != not_working_app_config"; + DEBUG.!ok4:: + "failed4 $(pluck_24[0]) != LAST"; + DEBUG.!ok5:: + "failed5 $(pluck_23[0]) != NEXT-TO-LAST"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/data_readstringarrayidx.cf.txt b/tests/acceptance/01_vars/02_functions/data_readstringarrayidx.cf.txt new file mode 100644 index 0000000000..04e4338a7f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_readstringarrayidx.cf.txt @@ -0,0 +1,25 @@ +# app_name;instance;mmax;mmin;mperm;zone;tomcat_version;env;setenv;app_properties;newrelic;newrelic_name;newrelic_version;context_static;checks;action;context +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +NEXT-TO-LAST;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +LAST;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server diff --git a/tests/acceptance/01_vars/02_functions/data_regextract-2.cf b/tests/acceptance/01_vars/02_functions/data_regextract-2.cf new file mode 100644 index 0000000000..42f8152d4d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_regextract-2.cf @@ -0,0 +1,54 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3171" } + string => "test that data_regextract uses a multiline regex"; + + vars: + "file" string => "$(this.promise_filename).readfile"; + + # Note the newline at the end of the file was included in the backreference. + # Per http://www.pcre.org/current/doc/html/pcre2syntax.html#SEC4 that means the regex is being run in “dotall” mode. + # Dotall mode doesn’t necessarily imply multiline mode, but a “$” matching before an internal newline does (per http://www.pcre.org/current/doc/html/pcre2syntax.html#SEC10) + "instance_guid_until_eof" + data => data_regextract( "^guid\s+=\s+(?.*)$", + readfile ( $(file), inf )); + + "instance_guid" + data => data_regextract( "^guid\s+=\s+(?[^\n]*)", + readfile( $(file), inf )); + + "instance_port" + data => data_regextract( "^port\s?+=\s?+(?[^\n]*)", + readfile( $(file), inf )); +} +bundle agent check +{ + classes: + "pass" + and => { + strcmp( "$(test.instance_guid[value])", + "9CB197F0-4569-446A-A987-1DDEC1205F6B"), + strcmp( "$(test.instance_port[value])", + "5308"), + strcmp( "$(test.instance_guid_until_eof[value])", + "9CB197F0-4569-446A-A987-1DDEC1205F6B$(const.n)port=5308"), + }; + + reports: + DEBUG:: + "expect test.instance_guid[value] '$(test.instance_guid[value])' == '9CB197F0-4569-446A-A987-1DDEC1205F6B'"; + "expect test.instance_port[value] '$(test.instance_port[value])' == '5308'"; + "expect test.instance_guid_until_eof[value] '$(test.instance_guid_until_eof[value])' == '9CB197F0-4569-446A-A987-1DDEC1205F6B$(const.n)port=5308'"; + + methods: + "Result" + usebundle => dcs_passif( "pass", $(this.promise_filename) ), + inherit => "true"; +} diff --git a/tests/acceptance/01_vars/02_functions/data_regextract-2.cf.readfile b/tests/acceptance/01_vars/02_functions/data_regextract-2.cf.readfile new file mode 100644 index 0000000000..502c777ba1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_regextract-2.cf.readfile @@ -0,0 +1,3 @@ +[general] +guid = 9CB197F0-4569-446A-A987-1DDEC1205F6B +port=5308 \ No newline at end of file diff --git a/tests/acceptance/01_vars/02_functions/data_regextract.cf b/tests/acceptance/01_vars/02_functions/data_regextract.cf new file mode 100644 index 0000000000..ff7eba0f80 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/data_regextract.cf @@ -0,0 +1,73 @@ +# data_regextract() test + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + vars: + "patterns" data => parsejson('["^(?...)(...)(..)-(?...)-(..).*", "", ".*", "(?...)"]'); + "plength" int => length(patterns); + "pi" slist => expandrange("[0-$(plength)]", 1); # this is one more than the valid indices + + "strings" data => parsejson('[ "abcdef12-345-67andsoon", "", "!!!", "one two three"]'); + "slength" int => length(strings); + "si" slist => expandrange("[0-$(slength)]", 1); # this is one more than the valid indices + + "parsed_$(pi)_$(si)" data => data_regextract(nth(patterns, $(pi)), + nth(strings, $(si))); + "parsed_$(pi)_$(si)_str" string => format("%S", "parsed_$(pi)_$(si)"); + + reports: + EXTRA:: + "$(this.bundle): $(pi) $(si) '$(patterns[$(pi)])' matching '$(strings[$(si)])' => $(parsed_$(pi)_$(si)_str)"; +} + +bundle agent check +{ + vars: + "actual" string => " +0 0 $(test.parsed_0_0_str) + +1 0 $(test.parsed_1_0_str) +1 1 $(test.parsed_1_1_str) +1 2 $(test.parsed_1_2_str) +1 3 $(test.parsed_1_3_str) + +2 0 $(test.parsed_2_0_str) +2 1 $(test.parsed_2_1_str) +2 2 $(test.parsed_2_2_str) +2 3 $(test.parsed_2_3_str) + +3 0 $(test.parsed_3_0_str) +3 2 $(test.parsed_3_2_str) +3 3 $(test.parsed_3_3_str) +"; + + "expected" string => ' +0 0 {"0":"abcdef12-345-67andsoon","2":"def","3":"12","5":"67","name1":"abc","name2":"345"} + +1 0 {"0":""} +1 1 {"0":""} +1 2 {"0":""} +1 3 {"0":""} + +2 0 {"0":"abcdef12-345-67andsoon"} +2 1 {"0":""} +2 2 {"0":"!!!"} +2 3 {"0":"one two three"} + +3 0 {"0":"abc","myword":"abc"} +3 2 {"0":"!!!","myword":"!!!"} +3 3 {"0":"one","myword":"one"} +'; + + methods: + "" usebundle => dcs_check_strcmp($(actual), $(expected), + $(this.promise_filename), + "no"); + +} diff --git a/tests/acceptance/01_vars/02_functions/datastate.cf b/tests/acceptance/01_vars/02_functions/datastate.cf new file mode 100644 index 0000000000..ed4550e464 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/datastate.cf @@ -0,0 +1,63 @@ +# Test that the datastate() function gives good data + +body common control +{ + inputs => { "../../default.cf.sub", "datastate.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + classes: + "a" expression => "any", scope => "namespace"; + "b" expression => "any"; + "c" expression => "!any"; + + vars: + "x" string => "1"; + "y" data => parsejson('{"ykey":["yvalue1", "yvalue2"]}'); + "z" slist => { "z1", "z2", "z3" }; +} + +bundle agent test +{ + vars: + "state" data => datastate(); +} + +bundle agent check +{ + vars: + "init_state_str" string => format("%S", "test.state[vars][init]"); + "ns_state_str" string => format("%S", "test.state[vars][visible_namespace:included]"); + "known_classes" slist => getindices("test.state[classes]"); + "printed_classes" string => format("%S", known_classes); + "init_expected" string => '{"x":"1","y":{"ykey":["yvalue1","yvalue2"]},"z":["z1","z2","z3"]}'; + "ns_expected" string => '{"i":"1","j":"two","k":["k2","everest"],"l":[1,"l","|"]}'; + + classes: + "init_ok" expression => strcmp($(init_state_str), $(init_expected)); + "ns_ok" expression => strcmp($(ns_state_str), $(ns_expected)); + "classes_ok" and => { some("cfengine_3", known_classes), + some("a", known_classes), + some("visible_namespace:foo", known_classes) }; + "ok" and => { "init_ok", "ns_ok", "classes_ok" }; + + reports: + DEBUG.!ns_ok:: + "visible_namespace:included data state is $(ns_state_str)"; + "expected visible_namespace:included state is $(ns_expected)"; + "expected state != visible_namespace:included data state"; + DEBUG.!init_ok:: + "init data state is $(init_state_str)"; + "expected init state is $(init_expected)"; + "expected init state != init data state"; + DEBUG.!classes_ok:: + "expected classes a, visible_namespace:foo, and cfengine_3 were not in $(printed_classes)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/datastate.cf.sub b/tests/acceptance/01_vars/02_functions/datastate.cf.sub new file mode 100644 index 0000000000..39b923689c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/datastate.cf.sub @@ -0,0 +1,17 @@ +body file control +{ + namespace => "visible_namespace"; +} + +bundle common included +{ + classes: + "foo" expression => "any"; + "bar" expression => "any", scope => "bundle"; + + vars: + "i" int => "1"; + "j" string => "two"; + "k" slist => { "k2", "everest" }; + "l" data => parsejson('[1, "l", "|"]'); # Night of the Bad Font +} diff --git a/tests/acceptance/01_vars/02_functions/dirname.cf b/tests/acceptance/01_vars/02_functions/dirname.cf new file mode 100644 index 0000000000..e7bd6dd2e6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/dirname.cf @@ -0,0 +1,160 @@ +####################################################### +# +# Test dirname() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + # The tests starting with template will be tested with both types of + # directory separators on Windows. + + "template_input[root]" string => "/"; + "template_expected[root]" string => "/"; + + "template_input[simple]" string => "/foo/bar"; + "template_expected[simple]" string => "/foo"; + + "template_input[slash]" string => "/foo/bar/"; + "template_expected[slash]" string => "/foo"; + + "template_input[sub]" string => "/foo"; + "template_expected[sub]" string => "/"; + + "template_input[subslash]" string => "/foo/"; + "template_expected[subslash]" string => "/"; + + "template_input[dot]" string => "/foo/."; + "template_expected[dot]" string => "/foo"; + + "template_input[dot_subslash]" string => "/foo/./"; + "template_expected[dot_subslash]" string => "/foo"; + + "template_input[multi]" string => "/foo/bar/baz"; + "template_expected[multi]" string => "/foo/bar"; + + "template_input[multislash]" string => "/foo/bar/baz/"; + "template_expected[multislash]" string => "/foo/bar"; + + "template_input[more]" string => "/a/b/c/d/e/f/g/h/i"; + "template_expected[more]" string => "/a/b/c/d/e/f/g/h"; + + "template_input[multislash]" string => "/a///b////c///"; + "template_expected[multislash]" string => "/a/b"; + + # Note: no template, because backslashes will be interpreted as UNC path on Windows. + "input[multislash_start]" string => "//a///b////c///"; + "expected[multislash_start]" string => "/a/b"; + + "template_input[rel_one]" string => "foo"; + "template_expected[rel_one]" string => "."; + + "template_input[rel_more]" string => "foo/bar"; + "template_expected[rel_more]" string => "foo"; + + "template_input[rel_one_subslash]" string => "foo/"; + "template_expected[rel_one_subslash]" string => "."; + + "template_input[rel_more_subslash]" string => "foo/bar/"; + "template_expected[rel_more_subslash]" string => "foo"; + + "template_input[rel_dot]" string => "foo/."; + "template_expected[rel_dot]" string => "foo"; + + "template_input[rel_only_dot]" string => "./"; + "template_expected[rel_only_dot]" string => "."; + + "template_input[rel_multislash]" string => "a///b////c///"; + "template_expected[rel_multislash]" string => "a/b"; + + "template_input[noop]" string => ""; + "template_expected[noop]" string => ""; + + "template_keys" slist => getindices("template_input"); + + windows:: + "template_input[full_root]" string => "C:/"; + "template_expected[full_root]" string => "C:/"; + + "template_input[full_root_file]" string => "C:/foo"; + "template_expected[full_root_file]" string => "C:/"; + + "template_input[full_root_file_subslash]" string => "C:/foo/"; + "template_expected[full_root_file_subslash]" string => "C:/"; + + "template_input[full_file]" string => "C:/foo/bar"; + "template_expected[full_file]" string => "C:/foo"; + + "template_input[full_file_subslash]" string => "C:/foo/bar/"; + "template_expected[full_file_subslash]" string => "C:/foo"; + + "template_input[dir]" string => "C:foo"; + "template_expected[dir]" string => "C:."; + + "template_input[two_dirs]" string => "C:foo/bar"; + "template_expected[two_dirs]" string => "C:foo"; + + "template_input[dir_subslash]" string => "C:foo/"; + "template_expected[dir_subslash]" string => "C:."; + + "template_input[two_dirs_subslash]" string => "C:foo/bar/"; + "template_expected[two_dirs_subslash]" string => "C:foo"; + + # UNC paths don't have forward slash equivalents. + "input[native_unc_one]" string => "\\\\foo"; + "expected[native_unc_one]" string => "\\\\"; + + "input[native_unc_two]" string => "\\\\foo\\bar"; + "expected[native_unc_two]" string => "\\\\foo"; + + "input[native_unc_three]" string => "\\\\foo\\bar\\charlie\\"; + "expected[native_unc_three]" string => "\\\\foo\\bar"; + + + "input[unix_$(template_keys)]" string => "$(template_input[$(template_keys)])"; + "input[native_$(template_keys)]" string => translatepath("$(template_input[$(template_keys)])"); + "expected[unix_$(template_keys)]" string => "$(template_expected[$(template_keys)])"; + "expected[native_$(template_keys)]" string => translatepath("$(template_expected[$(template_keys)])"); + !windows:: + "input[native_$(template_keys)]" string => "$(template_input[$(template_keys)])"; + "expected[native_$(template_keys)]" string => "$(template_expected[$(template_keys)])"; + + any:: + "keys" slist => getindices("input"); + + "actual[$(keys)]" string => dirname("$(input[$(keys)])"); +} + +####################################################### + +bundle agent check +{ + vars: + "keys" slist => { @(test.keys) }; + + classes: + "failed_cmp_$(keys)" not => strcmp(dirname("$(test.input[$(keys)])"), "$(test.expected[$(keys)])"); + "ok" not => classmatch("failed_cmp_.*"); + + reports: + DEBUG:: + "'dirname($(test.input[$(keys)]))' = '$(test.actual[$(keys)])' != '$(test.expected[$(keys)])'" + if => "failed_cmp_$(keys)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + + diff --git a/tests/acceptance/01_vars/02_functions/eval.cf b/tests/acceptance/01_vars/02_functions/eval.cf new file mode 100644 index 0000000000..debc267781 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/eval.cf @@ -0,0 +1,156 @@ +####################################################### +# +# Test eval() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "values[0]" string => "x"; + "values[1]" string => "+ 200"; + "values[2]" string => "200 + 100"; + "values[3]" string => "200 - 100"; + "values[4]" string => "- - -"; + "values[5]" string => "2 - (3 - 1)"; + "values[6]" string => ""; + "values[7]" string => "3 / 0"; + "values[8]" string => "3^3"; + "values[9]" string => "(-1)^2"; + "values[10]" string => "sin(20)"; + "values[11]" string => "cos(20)"; + "values[12]" string => "asin(0.2)"; + "values[13]" string => "acos(0.2)"; + "values[14]" string => "tan(20)"; + "values[15]" string => "atan(0.2)"; + "values[16]" string => "log(0.2)"; + "values[17]" string => "ln2"; + "values[18]" string => "ln10"; + "values[19]" string => "20 % 3"; + "values[20]" string => "sqrt(0.2)"; + "values[21]" string => "ceil(3.5)"; + "values[22]" string => "floor(3.4)"; + "values[23]" string => "abs(-3.4)"; + "values[24]" string => "-3.4 -3.4"; + "values[25]" string => "-3.400000 -3.400001"; + "values[26]" string => "pi"; + "values[27]" string => "e"; + "values[28]" string => "10 == 10"; + "values[29]" string => "10 == 11"; + "values[30]" string => "3**0"; + "values[31]" string => "step(10)"; + "values[32]" string => "step(-10)"; + "values[33]" string => "100k"; + "values[34]" string => "(200m - 100k) / (2t + 2t)"; + "values[35]" string => "10 > 10"; + "values[36]" string => "10 > 11"; + "values[37]" string => "10 >= 10"; + "values[38]" string => "10 >= 11"; + "values[39]" string => "10 < 10"; + "values[40]" string => "10 < 11"; + "values[41]" string => "10 <= 10"; + "values[42]" string => "10 <= 11"; + "values[43]" -> { "CFE-2762" } string => "1 * .75"; +} + + +####################################################### + +bundle agent test +{ + vars: + "indices" slist => getindices("init.values"); + + # convert the result to lowercase because some platforms use 'inf' + # and others 'Inf' and others... you don't wanna know + "eval[$(indices)]" string => string_downcase(eval("$(init.values[$(indices)])")); +} + + +####################################################### + +bundle agent check +{ + vars: + # note this test will be MUCH more accurate when we have sprintf and rounding + "verify[0]" string => ''; + "verify[1]" string => ''; + "verify[2]" string => '300.000000'; + "verify[3]" string => '100.000000'; + "verify[4]" string => ''; + "verify[5]" string => '0.000000'; + "verify[6]" string => '0.000000'; + "verify[7]" string => 'inf'; + "verify[8]" string => '27.000000'; + "verify[9]" string => '1.000000'; + "verify[10]" string => '0.912945'; + "verify[11]" string => '0.408082'; + "verify[12]" string => '0.201358'; + "verify[13]" string => '1.369438'; + "verify[14]" string => '2.237161'; + "verify[15]" string => '0.197396'; + "verify[16]" string => '-1.609438'; + "verify[17]" string => '0.693147'; + "verify[18]" string => '2.302585'; + "verify[19]" string => '2.000000'; + "verify[20]" string => '0.447214'; + "verify[21]" string => '4.000000'; + "verify[22]" string => '3.000000'; + "verify[23]" string => '3.400000'; + "verify[24]" string => '-6.800000'; + "verify[25]" string => '-6.800001'; + "verify[26]" string => '3.141593'; + "verify[27]" string => '2.718282'; + "verify[28]" string => '1.000000'; + "verify[29]" string => '0.000000'; + "verify[30]" string => '1.000000'; + "verify[31]" string => '1.000000'; + "verify[32]" string => '0.000000'; + "verify[33]" string => '100000.000000'; + "verify[34]" string => '0.000050'; + "verify[35]" string => '0.000000'; + "verify[36]" string => '0.000000'; + "verify[37]" string => '1.000000'; + "verify[38]" string => '0.000000'; + "verify[39]" string => '0.000000'; + "verify[40]" string => '1.000000'; + "verify[41]" string => '1.000000'; + "verify[42]" string => '1.000000'; + "verify[43]" -> { "CFE-2762" } string => '0.750000'; + + "indices" slist => getindices("verify"); + + classes: + "ok_$(indices)" expression => strcmp("$(test.eval[$(indices)])", "$(verify[$(indices)])"); + "not_ok_$(indices)" not => strcmp("$(test.eval[$(indices)])", "$(verify[$(indices)])"); + + "ok" and => { ok_0, ok_1, ok_3, ok_4, ok_5, ok_6, ok_7, ok_8, + ok_9, ok_10, ok_11, ok_13, ok_14, ok_15, ok_16, + ok_17, ok_18, ok_19, ok_20, ok_21, ok_23, ok_24, + ok_25, ok_26, ok_27, ok_28, ok_29, ok_30, ok_31, + ok_32, ok_33, ok_34, ok_35, ok_36, ok_37, ok_38, + ok_39, ok_40, ok_41, ok_42, ok_43 + }; + + reports: + DEBUG:: + "OK math $(indices) eval('$(init.values[$(indices)])') = '$(test.eval[$(indices)])'" + if => "ok_$(indices)"; + + "FAIL math $(indices) eval('$(init.values[$(indices)])') = '$(test.eval[$(indices)])' (expected '$(verify[$(indices)])')" + if => "not_ok_$(indices)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/eval_class.cf b/tests/acceptance/01_vars/02_functions/eval_class.cf new file mode 100644 index 0000000000..6fee459685 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/eval_class.cf @@ -0,0 +1,85 @@ +####################################################### +# +# Test eval() in class context mode +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "values[0]" string => "x"; + "values[1]" string => "+ 200"; + "values[2]" string => "200 + 100"; + "values[3]" string => "200 - 100"; + "values[4]" string => "- - -"; + "values[5]" string => "2 - (3 - 1)"; + "values[6]" string => ""; + "values[7]" string => "3 / 0"; + "values[8]" string => "3^3"; + "values[9]" string => "(-1)^2"; + "values[10]" string => "sin(20)"; + "values[11]" string => "cos(20)"; + "values[12]" string => "asin(0.2)"; + "values[13]" string => "acos(0.2)"; + "values[14]" string => "tan(20)"; + "values[15]" string => "atan(0.2)"; + "values[16]" string => "log(0.2)"; + "values[17]" string => "ln2"; + "values[18]" string => "ln10"; + "values[19]" string => "20 % 3"; + "values[20]" string => "sqrt(0.2)"; + "values[21]" string => "ceil(3.5)"; + "values[22]" string => "floor(3.4)"; + "values[23]" string => "abs(-3.4)"; + "values[24]" string => "-3.4 -3.4"; + "values[25]" string => "-3.400000 -3.400001"; + "values[26]" string => "pi"; + "values[27]" string => "e"; + "values[28]" string => "10 == 10"; + "values[29]" string => "10 == 11"; + "values[30]" string => "3**0"; + "values[31]" string => "step(10)"; + "values[32]" string => "step(-10)"; + "values[33]" string => "100k"; + "values[34]" string => "(200m - 100k) / (2t + 2t)"; + "values[35]" string => "10 > 10"; + "values[36]" string => "10 > 11"; + "values[37]" string => "10 >= 10"; + "values[38]" string => "10 >= 11"; + "values[39]" string => "10 < 10"; + "values[40]" string => "10 < 11"; + "values[41]" string => "10 <= 10"; + "values[42]" string => "10 <= 11"; +} + + +####################################################### + +bundle agent test +{ + classes: + "context_eval_$(indices)" expression => eval("$(init.values[$(indices)])", "class"), + scope => "namespace"; + + vars: + "indices" slist => getindices("init.values"); +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("context_eval_2,context_eval_3,context_eval_7,context_eval_8,context_eval_9,context_eval_10,context_eval_11,context_eval_12,context_eval_13,context_eval_14,context_eval_15,context_eval_16,context_eval_17,context_eval_18,context_eval_19,context_eval_20,context_eval_21,context_eval_22,context_eval_23,context_eval_24,context_eval_25,context_eval_26,context_eval_27,context_eval_28,context_eval_30,context_eval_31,context_eval_33,context_eval_34,context_eval_37,context_eval_40,context_eval_41,context_eval_42", + "context_eval_0,context_eval_1,context_eval_4,context_eval_5,context_eval_6,context_eval_29,context_eval_32,context_eval_35,context_eval_36,context_eval_38,context_eval_39", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/every_some_none.cf b/tests/acceptance/01_vars/02_functions/every_some_none.cf new file mode 100644 index 0000000000..9e75f1c470 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/every_some_none.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Test every(), some(), and none() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + methods: + "pretest"; + "collect"; +} + +bundle common pretest +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; + + "empty" slist => { }; + + "d1" data => parsejson(' +[1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three"]'); + + "d2" data => parsejson(' +{ "one": 1, "two": 2, "three": 3, + "x": "y", "a": "b", "p": "q" }'); + + "dempty" data => parsejson('[]'); + + "collected" slist => classesmatching(".*", "collect"); + "collected_sorted" slist => sort(collected, "lex"); + + classes: + "every1" meta => { "collect" }, expression => every(".*", "test"); + "every2" meta => { "collect" }, expression => every(".", "test"); + + "some1" meta => { "collect" }, expression => some("long string", "test"); + "some2" meta => { "collect" }, expression => some("none", "test"); + + "none1" meta => { "collect" }, expression => none("jebadiah", "test"); + "none2" meta => { "collect" }, expression => none("2", "test"); + + "every_empty" meta => { "collect" }, expression => every(".*", "empty"); + "some_empty" meta => { "collect" }, expression => some(".*", "empty"); + "none_empty" meta => { "collect" }, expression => none(".*", "empty"); + + "everyd11" meta => { "collect" }, expression => every(".*", d1); + "everyd12" meta => { "collect" }, expression => every(".", d1); + + "somed11" meta => { "collect" }, expression => some("long string", d1); + "somed12" meta => { "collect" }, expression => some("none", d1); + + "noned11" meta => { "collect" }, expression => none("jebadiah", d1); + "noned12" meta => { "collect" }, expression => none("2", d1); + + "everyd21" meta => { "collect" }, expression => every(".*", d2); + "everyd22" meta => { "collect" }, expression => every(".", d2); + + "somed21" meta => { "collect" }, expression => some("long string", d2); + "somed22" meta => { "collect" }, expression => some("none", d2); + + "noned21" meta => { "collect" }, expression => none("jebadiah", d2); + "noned22" meta => { "collect" }, expression => none("2", d2); + + "every_dempty" meta => { "collect" }, expression => every(".*", dempty); + "some_dempty" meta => { "collect" }, expression => some(".*", dempty); + "none_dempty" meta => { "collect" }, expression => none(".*", dempty); + + # with inline JSON + "inline_every_empty" meta => { "collect" }, expression => every(".*", '[]'); + "inline_some_data" meta => { "collect" }, expression => every(".*", '[ "foo", "bar" ]'); + "inline_none_data" meta => { "collect" }, expression => none("jebadiah", '[ "foo", "bar" ]'); + +} + +bundle common collect +{ + vars: + "collected" slist => { @(pretest.collected_sorted) }; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(collect, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/every_some_none.cf.expected.json b/tests/acceptance/01_vars/02_functions/every_some_none.cf.expected.json new file mode 100644 index 0000000000..8628e79bfb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/every_some_none.cf.expected.json @@ -0,0 +1,17 @@ +{ + "collected": [ + "every1", + "everyd11", + "everyd21", + "everyd22", + "inline_none_data", + "inline_some_data", + "none1", + "none_dempty", + "none_empty", + "noned11", + "noned21", + "some1", + "somed11" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/execresult.cf b/tests/acceptance/01_vars/02_functions/execresult.cf new file mode 100644 index 0000000000..d4a64917fd --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/execresult.cf @@ -0,0 +1,233 @@ +####################################################### +# +# Test returnszero() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "cwd" string => dirname("$(this.promise_filename)"); + + windows:: + "zero_rel" string => "type $(cwd)$(const.dirsep)text.txt"; + "one_rel" string => "type $(cwd)$(const.dirsep)text.txt.doesnotexist"; + "nxe_rel" string => "nosuchprogramexists"; + "zero_abs" string => "c:\windows\system32\cmd.exe /C type $(cwd)$(const.dirsep)text.txt"; + "one_abs" string => "c:\windows\system32\cmd.exe /C type $(cwd)$(const.dirsep)text.txt.doesnotexist"; + "nxe_abs" string => "c:\xbin\nosuchprogramexists"; + "zero_powershell" string => "Get-Content $(cwd)$(const.dirsep)text.txt"; + "one_powershell" string => "Get-Content $(cwd)$(const.dirsep)text.txt.doesnotexist"; + "nxe_powershell" string => "nosuchprogramexists"; + !windows:: + "zero_rel" string => "cat $(cwd)$(const.dirsep)text.txt"; + "one_rel" string => "cat $(cwd)$(const.dirsep)text.txt.doesnotexist"; + "nxe_rel" string => "nosuchprogramexists"; + "zero_abs" string => "/bin/cat $(cwd)$(const.dirsep)text.txt"; + "one_abs" string => "/bin/cat $(cwd)$(const.dirsep)text.txt.doesnotexist"; + "nxe_abs" string => "/xbin/nosuchprogramexists"; + + classes: + # Test whether we extracted the expected string. + "zero_noshell_rel" expression => regcmp(".*Succeeded.*", execresult("$(zero_rel)", "noshell")); + "zero_useshell_rel" expression => regcmp(".*Succeeded.*", execresult("$(zero_rel)", "useshell")); + "zero_noshell_abs" expression => regcmp(".*Succeeded.*", execresult("$(zero_abs)", "noshell")); + "zero_useshell_abs" expression => regcmp(".*Succeeded.*", execresult("$(zero_abs)", "useshell")); + + "one_noshell_rel" expression => regcmp(".*Succeeded.*", execresult("$(one_rel)", "noshell")); + "one_useshell_rel" expression => regcmp(".*Succeeded.*", execresult("$(one_rel)", "useshell")); + "one_noshell_abs" expression => regcmp(".*Succeeded.*", execresult("$(one_abs)", "noshell")); + "one_useshell_abs" expression => regcmp(".*Succeeded.*", execresult("$(one_abs)", "useshell")); + + "nxe_noshell_rel" expression => regcmp(".*Succeeded.*", execresult("$(nxe_rel)", "noshell")); + "nxe_useshell_rel" expression => regcmp(".*Succeeded.*", execresult("$(nxe_rel)", "useshell")); + "nxe_noshell_abs" expression => regcmp(".*Succeeded.*", execresult("$(nxe_abs)", "noshell")); + "nxe_useshell_abs" expression => regcmp(".*Succeeded.*", execresult("$(nxe_abs)", "useshell")); + + # Test whether any assignment happens at all. + "does_assign_zero_noshell_rel" expression => regcmp(".*", execresult("$(zero_rel)", "noshell")); + "does_assign_zero_useshell_rel" expression => regcmp(".*", execresult("$(zero_rel)", "useshell")); + "does_assign_zero_noshell_abs" expression => regcmp(".*", execresult("$(zero_abs)", "noshell")); + "does_assign_zero_useshell_abs" expression => regcmp(".*", execresult("$(zero_abs)", "useshell")); + + "does_assign_one_noshell_rel" expression => regcmp(".*", execresult("$(one_rel)", "noshell")); + "does_assign_one_useshell_rel" expression => regcmp(".*", execresult("$(one_rel)", "useshell")); + "does_assign_one_noshell_abs" expression => regcmp(".*", execresult("$(one_abs)", "noshell")); + "does_assign_one_useshell_abs" expression => regcmp(".*", execresult("$(one_abs)", "useshell")); + + "does_assign_nxe_noshell_rel" expression => regcmp(".*", execresult("$(nxe_rel)", "noshell")); + "does_assign_nxe_useshell_rel" expression => regcmp(".*", execresult("$(nxe_rel)", "useshell")); + "does_assign_nxe_noshell_abs" expression => regcmp(".*", execresult("$(nxe_abs)", "noshell")); + "does_assign_nxe_useshell_abs" expression => regcmp(".*", execresult("$(nxe_abs)", "useshell")); + + windows:: + # Test whether we extracted the expected string. + "zero_powershell" expression => regcmp(".*Succeeded.*", execresult("$(zero_powershell)", "powershell")); + "one_powershell" expression => regcmp(".*Succeeded.*", execresult("$(one_powershell)", "powershell")); + "nxe_powershell" expression => regcmp(".*Succeeded.*", execresult("$(nxe_powershell)", "powershell")); + + # Test whether any assignment happens at all. + "does_assign_zero_powershell" expression => regcmp(".*", execresult("$(zero_powershell)", "powershell")); + "does_assign_one_powershell" expression => regcmp(".*", execresult("$(one_powershell)", "powershell")); + "does_assign_nxe_powershell" expression => regcmp(".*", execresult("$(nxe_powershell)", "powershell")); + + any:: + "ok_common" and => { + "!zero_noshell_rel", "zero_useshell_rel", "zero_noshell_abs", "zero_useshell_abs", + "!one_noshell_rel", "!one_useshell_rel", "!one_noshell_abs", "!one_useshell_abs", + "!nxe_noshell_rel", "!nxe_useshell_rel", "!nxe_noshell_abs", "!nxe_useshell_abs", + "!does_assign_zero_noshell_rel", "does_assign_zero_useshell_rel", "does_assign_zero_noshell_abs", "does_assign_zero_useshell_abs", + "!does_assign_one_noshell_rel", "does_assign_one_useshell_rel", "does_assign_one_noshell_abs", "does_assign_one_useshell_abs", + "!does_assign_nxe_noshell_rel", "does_assign_nxe_useshell_rel", "!does_assign_nxe_noshell_abs", "!does_assign_nxe_useshell_abs", + }; + windows:: + "ok_windows" and => { + "zero_powershell", "!one_powershell", "!nxe_powershell", + "does_assign_zero_powershell", "does_assign_one_powershell", "does_assign_nxe_powershell", + }; + "ok" and => { + "ok_common", "ok_windows", + }; + + !windows:: + "ok" and => { "ok_common" }; + + reports: + DEBUG.zero_noshell_rel:: + "'$(zero_rel)' executes command successfully with noshell"; + + DEBUG.!zero_useshell_rel:: + "'$(zero_rel)' executes command unsuccessfully with useshell"; + + + DEBUG.one_noshell_rel:: + "'$(one_rel)' executes command successfully with noshell"; + + DEBUG.one_useshell_rel:: + "'$(one_rel)' executes command successfully with useshell"; + + + DEBUG.nxe_noshell_rel:: + "'$(nxe_rel)' executes command successfully with noshell"; + + DEBUG.nxe_useshell_rel:: + "'$(nxe_rel)' executes command successfully with useshell"; + + DEBUG.!zero_noshell_abs:: + "'$(zero_abs)' executes command unsuccessfully with noshell"; + + DEBUG.!zero_useshell_abs:: + "'$(zero_abs)' executes command unsuccessfully with useshell"; + + + DEBUG.one_noshell_abs:: + "'$(one_abs)' executes command successfully with noshell"; + + DEBUG.one_useshell_abs:: + "'$(one_abs)' executes command successfully with useshell"; + + + DEBUG.nxe_noshell_abs:: + "'$(nxe_abs)' executes command successfully with noshell"; + + DEBUG.nxe_useshell_abs:: + "'$(nxe_abs)' executes command successfully with useshell"; + + + DEBUG.does_assign_zero_noshell_rel:: + "'$(zero_rel)' executes command with noshell"; + + DEBUG.!does_assign_zero_useshell_rel:: + "'$(zero_rel)' does not execute command with useshell"; + + + DEBUG.does_assign_one_noshell_rel:: + "'$(one_rel)' executes command with noshell"; + + DEBUG.!does_assign_one_useshell_rel:: + "'$(one_rel)' does not execute command with useshell"; + + + DEBUG.does_assign_nxe_noshell_rel:: + "'$(nxe_rel)' executes command with noshell"; + + DEBUG.!does_assign_nxe_useshell_rel:: + "'$(nxe_rel)' does not execute command with useshell"; + + DEBUG.!does_assign_zero_noshell_abs:: + "'$(zero_abs)' does not execute command with noshell"; + + DEBUG.!does_assign_zero_useshell_abs:: + "'$(zero_abs)' does not execute command with useshell"; + + + DEBUG.!does_assign_one_noshell_abs:: + "'$(one_abs)' does not execute command with noshell"; + + DEBUG.!does_assign_one_useshell_abs:: + "'$(one_abs)' does not execute command with useshell"; + + + DEBUG.does_assign_nxe_noshell_abs:: + "'$(nxe_abs)' executes command with noshell"; + + DEBUG.does_assign_nxe_useshell_abs:: + "'$(nxe_abs)' executes command with useshell"; + + windows.DEBUG.!zero_powershell:: + "'$(zero_powershell)' executes command unsuccessfully with powershell"; + + windows.DEBUG.one_powershell:: + "'$(one_powershell)' executes command successfully with powershell"; + + windows.DEBUG.nxe_powershell:: + "'$(nxe_powershell)' executes command successfully with powershell"; + + windows.DEBUG.!does_assign_zero_powershell:: + "'$(zero_powershell)' does not execute command with powershell"; + + windows.DEBUG.!does_assign_one_powershell:: + "'$(one_powershell)' does not execute command with powershell"; + + windows.DEBUG.!does_assign_nxe_powershell:: + "'$(nxe_powershell)' does not execute command with powershell"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/execresult_4k.cf b/tests/acceptance/01_vars/02_functions/execresult_4k.cf new file mode 100644 index 0000000000..f6fc2bc5b7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/execresult_4k.cf @@ -0,0 +1,38 @@ +####################################################### +# +# Test returnszero() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + any:: + "cwd" string => dirname("$(this.promise_filename)"); + "cat_4k" string => "$(G.cat) $(cwd)$(const.dirsep)text_xform.cf.txt"; + "result" string => execresult("$(cat_4k)", "useshell"); + + classes: + any:: + "ok" expression => regcmp(".*3456789abcdefghij$", "$(result)"); + + reports: + DEBUG:: + "$(result)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + +} diff --git a/tests/acceptance/01_vars/02_functions/execresult_action_immediate.cf b/tests/acceptance/01_vars/02_functions/execresult_action_immediate.cf new file mode 100644 index 0000000000..91a4301b44 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/execresult_action_immediate.cf @@ -0,0 +1,60 @@ +# Test that execresult is called multiple times with the same command if +# 'ifelapsed => "0"' is used. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check_date(x) +{ + vars: + "reports_file" string => "$(G.testdir)/execresult_action_immediate_report"; + + "date" string => execresult("date; sleep 1", "useshell"), + action => immediate; + + reports: + "Date for ${x}: ${date}" + report_to_file => "$(reports_file)"; +} + +bundle agent test +{ + meta: + "description" -> {"ENT-7478"} + string => "If 'ifelapsed => 0' is used, execresult() should run the given command every time"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + methods: + "foo" usebundle => check_date("foo"); + "bar" usebundle => check_date("bar"); +} + + +bundle agent check +{ + vars: + "content" + slist => readstringlist( "$(check_date.reports_file)", + "", + '$(const.n)', + inf, + inf); + "count" + int => length( content ); + + reports: + DEBUG|EXTRA:: + "$(check_date.reports_file) contents:"; + "$(content)"; + + any:: + # Pass if there are 6 lines, 3 for each check_date bundle actuation, + # indicating that execresult is being called once for each pass. + "$(this.promise_filename) Pass" if => strcmp( $(count), 6 ); + "$(this.promise_filename) FAIL" unless => strcmp( $(count), 6 ); +} diff --git a/tests/acceptance/01_vars/02_functions/execresult_multiples.cf b/tests/acceptance/01_vars/02_functions/execresult_multiples.cf new file mode 100644 index 0000000000..e8ab5e6214 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/execresult_multiples.cf @@ -0,0 +1,49 @@ +############################################################################## +# +# Redmine #2981: execresult and returnszero should not be run so much +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + +} + + +bundle agent test +{ +vars: + "subout" string => execresult("$(sys.cf_agent) -Kv -f $(this.promise_filename).sub | $(G.egrep) 'execresult|returnszero'", "useshell"); +} + + +bundle agent check +{ +# If the output contains CLASSONCE or RETONCE more than once, we fail +classes: + "ok1" not => regcmp(".*ran.*CLASSONCE.*ran.*CLASSONCE.*", "$(test.subout)"); + "ok2" not => regcmp(".*ran.*RETONCE.*ran.*RETONCE.*", "$(test.subout)"); + + "ok" and => { "ok1", "ok2" }; + +reports: + DEBUG:: + "agent output: $(test.subout)"; + + ok1.ok2:: + "The duplicate output in cf-agent didn't happen"; + !(ok1.ok2):: + "The duplicate output in cf-agent happened"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/execresult_multiples.cf.sub b/tests/acceptance/01_vars/02_functions/execresult_multiples.cf.sub new file mode 100644 index 0000000000..292fd1539e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/execresult_multiples.cf.sub @@ -0,0 +1,20 @@ +############################################################################## +# +# Redmine #2981: execresult and returnszero should not be run so much +# +############################################################################## + + +body common control +{ + bundlesequence => {"example"}; +} + +bundle agent example +{ + classes: + "y" expression => returnszero("/bin/echo CLASSONCE", "noshell"); + + vars: + "x" string => execresult("/bin/echo RETONCE", "noshell"); +} diff --git a/tests/acceptance/01_vars/02_functions/execresult_select.cf b/tests/acceptance/01_vars/02_functions/execresult_select.cf new file mode 100644 index 0000000000..4f3edda5a2 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/execresult_select.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Test that you can select stderr/stdout in execresult +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3108" } + string => "Test that you can select stderr/stdout in execresult"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "result_with_stdout_stderr" + string => execresult("$(G.echo) stdout; $(G.echo) stderr >&2", "useshell", "both"); + + "result_with_stdout" + string => execresult("$(G.echo) stdout; $(G.echo) stderr >&2", "useshell", "stdout"); + + "result_with_stderr" + string => execresult("$(G.echo) stdout; $(G.echo) stderr >&2", "useshell", "stderr"); + + classes: + "pass_stdout_stderr" + scope => "namespace", + expression => strcmp( "stdout$(const.n)stderr", $(result_with_stdout_stderr) ); + + "pass_stdout" + scope => "namespace", + expression => strcmp( "stdout", $(result_with_stdout) ); + + "pass_stderr" + scope => "namespace", + expression => strcmp( "stderr", $(result_with_stderr) ); + + methods: + "Pass/Fail" + usebundle => dcs_passif("pass_stdout_stderr.pass_stdout.pass_stderr", $(this.promise_filename)); +} + +bundle agent __main__ +{ + methods: "test"; +} diff --git a/tests/acceptance/01_vars/02_functions/execresult_stderr.cf b/tests/acceptance/01_vars/02_functions/execresult_stderr.cf new file mode 100644 index 0000000000..d006607268 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/execresult_stderr.cf @@ -0,0 +1,56 @@ +####################################################### +# +# Test execresult() captures stdout and stderr +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3103" } + string => "Test that execresult captures both stdout and stderr"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "result_with_stdout_stderr" + string => execresult("$(G.echo) stdout; $(G.echo) stderr >&2", "useshell"); + + "result_with_stdout" + string => execresult("$(G.echo) stdout", "useshell"); + + "result_with_stderr" + string => execresult("$(G.echo) stderr >&2", "useshell"); + + classes: + "pass_stdout_stderr" + scope => "namespace", + expression => strcmp( "stdout$(const.n)stderr", $(result_with_stdout_stderr) ); + + "pass_stdout" + scope => "namespace", + expression => strcmp( "stdout", $(result_with_stdout) ); + + "pass_stderr" + scope => "namespace", + expression => strcmp( "stderr", $(result_with_stderr) ); + + methods: + "Pass/Fail" + usebundle => dcs_passif("pass_stdout_stderr.pass_stdout.pass_stderr", $(this.promise_filename)); + +} + +bundle agent __main__ +{ + methods: "test"; +} diff --git a/tests/acceptance/01_vars/02_functions/filesexist-can-detect-files-containing-hash-character.cf b/tests/acceptance/01_vars/02_functions/filesexist-can-detect-files-containing-hash-character.cf new file mode 100644 index 0000000000..3eeff0f87f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/filesexist-can-detect-files-containing-hash-character.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Test that filesexist() can check for presence of files containing hashes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "test_files" slist => { "example##4567", "example##123" }; + + files: + "$(G.testdir)/$(test_files)" + create => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-2744" } + string => "Test that filesexist can verify files containing hashes exist"; + + vars: + "found_files" slist => findfiles( "$(G.testdir)/example##*"); + + classes: + # This should always be true. + # We check that the files that we found exist. + + "found_expected_files_with_filesexist" + expression => filesexist( "@(found_files)" ), + scope => "namespace"; + +} +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "found_expected_files_with_filesexist"; + + reports: + DEBUG|EXTRA:: + "Test files: $(init.test_files)"; + + "Found files: $(test.found_files)"; + + "filesexist() cannot validate files containing '#', not working as expected" + if => not( "ok" ); + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/filesexist-is-collecting.cf b/tests/acceptance/01_vars/02_functions/filesexist-is-collecting.cf new file mode 100644 index 0000000000..b954d9db46 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/filesexist-is-collecting.cf @@ -0,0 +1,80 @@ +####################################################### +# +# Test that filesexist() can check for presence of files containing hashes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "test_files" slist => { "example4567", "example123" }; + + files: + "$(G.testdir)/$(test_files)" + create => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-2744" } + string => "Test that filesexist works as a collecting function"; + + classes: + "filesexist_slist_identifier_ok" + expression => filesexist( "init.test_files" ), + scope => "namespace"; + + "filesexist_slist_ref_ok" + expression => filesexist( @(init.test_files) ), + scope => "namespace"; + + "filesexist_findfiles_ok" + expression => filesexist( findfiles( "$(G.testdir)/example*" ) ), + scope => "namespace"; + + "filesexist_json_array_ok" + expression => filesexist( '[ "example4567", "example123" ]' ), + scope => "namespace"; +} +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "filesexist_slist_identifier_ok", "filesexist_slist_ref_ok", + "filesexist_findfiles_ok", "filesexist_json_array_ok" }; + + reports: + DEBUG|EXTRA:: + "Test files: $(init.test_files)"; + + "filesexist() doesn't work with an slist identifier" + if => not( "filesexist_slist_identifier_ok" ); + + "filesexist() doesn't work with an slist reference" + if => not( "filesexist_slist_ref_ok" ); + + "filesexist() doesn't work properly with a nested findfiles() call" + if => not( "filesexist_findfiles_ok" ); + + "filesexist() doesn't work with a JSON array" + if => not( "filesexist_json_array_ok" ); + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/filestat.cf b/tests/acceptance/01_vars/02_functions/filestat.cf new file mode 100644 index 0000000000..d15b21c625 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/filestat.cf @@ -0,0 +1,118 @@ +####################################################### +# +# Test filestat() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + perms => init_m(600), + edit_defaults => init_empty, + edit_line => init_fill_in; + + reports: + DEBUG:: + "Created $(G.testfile)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; + edit_backup => "false"; +} + +body perms init_m(mode) +{ + mode => "$(mode)"; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "012345789"; + "112345789"; + "212345789"; + "312345789"; + "4"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + + +####################################################### + +bundle agent test +{ + vars: + "fields" slist => splitstring("size,gid,uid,ino,nlink,ctime,atime,mtime,mode,modeoct,permstr,permoct,type,devno,dev_minor,dev_major,basename,dirname,linktarget,linktarget_shallow", ",", 999); + + "stat[$(fields)]" string => filestat($(G.testfile), $(fields)); +} + + +####################################################### + +bundle agent check +{ + vars: + "expected[type]" string => "regular file"; + "expected[nlink]" string => "1"; + "expected[dirname]" string => dirname($(G.testfile)); + "expected[basename]" string => lastnode($(G.testfile), escape("$(const.dirsep)")); + "expected[linktarget]" string => $(G.testfile); + "expected[linktarget_shallow]" string => $(G.testfile); + + windows:: + "expected[size]" string => "47"; + "expected[mode]" string => "33206"; # 100666 + "expected[permoct]" string => "666"; + "expected[modeoct]" string => "100666"; + "expected[permstr]" string => "-rw-rw-rw-"; + !windows:: + "expected[size]" string => "42"; + "expected[mode]" string => "33152"; # 100600 + "expected[permoct]" string => "600"; + "expected[modeoct]" string => "100600"; + "expected[permstr]" string => "-rw-------"; + + any:: + "expects" slist => getindices("expected"); + + "fields" slist => getindices("test.stat"); + + "joint_condition" string => join(".", "expects"); + classes: + "$(expects)" expression => strcmp("$(test.stat[$(expects)])", "$(expected[$(expects)])"); + "ok" expression => "$(joint_condition)"; + + reports: + DEBUG:: + "got $(G.testfile) field $(fields)=$(test.stat[$(fields)])"; + + "got $(G.testfile) field $(expects)=$(test.stat[$(expects)]) matches expected" + if => "$(expects)"; + + "got $(G.testfile) field $(expects)=$(test.stat[$(expects)]) did NOT match expected $(expected[$(expects)])" + if => "!$(expects)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/filestat_linktarget.cf b/tests/acceptance/01_vars/02_functions/filestat_linktarget.cf new file mode 100644 index 0000000000..5e22d8db44 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/filestat_linktarget.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Test filestat() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + commands: + "$(G.ln) -fs $(G.testfile) $(G.testfile)"; + + reports: + DEBUG:: + "Created $(G.testfile) linked to itself"; +} + + +####################################################### + +bundle agent test +{ + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine4608" }; + vars: + "fields" slist => splitstring("linktarget,linktarget_shallow", ",", 999); + + "stat[$(fields)]" string => filestat($(G.testfile), $(fields)); +} + + +####################################################### + +bundle agent check +{ + vars: + # Note that on W32 the link target is the file itself + "expected[linktarget]" string => $(G.testfile); + "expected[linktarget_shallow]" string => $(G.testfile); + + "expects" slist => getindices("expected"); + + "fields" slist => getindices("test.stat"); + + "joint_condition" string => join(".", "expects"); + classes: + "$(expects)" expression => strcmp("$(test.stat[$(expects)])", "$(expected[$(expects)])"); + "ok" expression => "$(joint_condition)"; + + reports: + DEBUG:: + "got $(G.testfile) field $(fields)=$(test.stat[$(fields)])"; + + "got $(G.testfile) field $(expects)=$(test.stat[$(expects)]) matches expected" + if => "$(expects)"; + + "got $(G.testfile) field $(expects)=$(test.stat[$(expects)]) did NOT match expected $(expected[$(expects)])" + if => "!$(expects)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/filestat_linktarget_relative_symlink.cf b/tests/acceptance/01_vars/02_functions/filestat_linktarget_relative_symlink.cf new file mode 100644 index 0000000000..8bea1d739a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/filestat_linktarget_relative_symlink.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Test filestat(linktarget) follows relative symlinks - redmine#7404 +# Also filestat(linktarget_shallow) should output the first relative symlink itself +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + + # Create chain of links first_link -> second_link -> final_target + + commands: + "$(G.touch) $(G.testdir)/final_target"; + "$(G.ln) -s final_target $(G.testdir)/second_link"; + "$(G.ln) -s second_link $(G.testdir)/first_link"; + + reports: + DEBUG:: + "Init: created chain of links first_link -> second_link -> final_target"; +} + + +####################################################### + +bundle agent test +{ + meta: + # windows don't support symlinks + "test_skip_unsupported" string => "windows"; + + vars: + "fields" slist => splitstring("linktarget,linktarget_shallow", ",", 999); + + "stat[$(fields)]" string => filestat("$(G.testdir)/first_link", $(fields)); +} + + +####################################################### + +bundle agent check +{ + vars: + "expected[linktarget_shallow]" string => "second_link"; + "expected[linktarget]" string => "$(G.testdir)/final_target"; + + classes: + + "test1_ok" expression => strcmp("$(test.stat[linktarget])", + "$(expected[linktarget])"); + "test2_ok" expression => strcmp("$(test.stat[linktarget_shallow])", + "$(expected[linktarget_shallow])"); + "ok" expression => "test1_ok.test2_ok"; + + reports: + DEBUG:: + "linktarget : expected '$(expected[linktarget])' returned '$(test.stat[linktarget])'"; + "linktarget_shallow: expected '$(expected[linktarget_shallow])' returned '$(test.stat[linktarget_shallow])'"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/filestat_xattr.cf b/tests/acceptance/01_vars/02_functions/filestat_xattr.cf new file mode 100644 index 0000000000..8004ce1a87 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/filestat_xattr.cf @@ -0,0 +1,57 @@ +####################################################### +# +# Redmine#4079: Test filestat() with xattr +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common xattr_support +{ + vars: + "xattr" string => "/usr/bin/xattr"; + + classes: + "has_xattr" and => { fileexists($(xattr)) }; +} + +####################################################### + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "!has_xattr"; + + methods: + "" usebundle => file_make($(G.testfile), ""); +} + + +####################################################### + +bundle agent test +{ + commands: + "$(init.xattr) -w foo bar $(G.testfile)"; +} + + +####################################################### + +bundle agent check +{ + vars: + "attributes" string => filestat($(G.testfile), "xattr"); + + methods: + "" usebundle => dcs_check_strcmp($(attributes), "foo=bar", + $(this.promise_filename), + "no"); +} diff --git a/tests/acceptance/01_vars/02_functions/filter.cf b/tests/acceptance/01_vars/02_functions/filter.cf new file mode 100644 index 0000000000..541f14a536 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/filter.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test filter() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + vars: + "tests" slist => { "fgrep09", "exact1", "exactdot", "regexdot", "invert", "max2", "max0", "grep09" }; + "lists" slist => { "s1", "d1", "d2", "dempty" }; + + "s1" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "one", "two", "three", + }; + + "d1" data => parsejson(' +[1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three"]'); + + "d2" data => parsejson(' +{ "one": 1, "two": 2, "three": 3, + "x": "y", "a": "b", "p": "q" }'); + + "dempty" data => parsejson('[]'); + + "$(lists)_fgrep09" slist => filter("[0-9]", $(lists), "true", "false", 999); + "$(lists)_exact1" slist => filter("one", $(lists), "false", "false", 999); + "$(lists)_exactdot" slist => filter(".", $(lists), "false", "false", 999); + "$(lists)_regexdot" slist => filter(".", $(lists), "true", "false", 999); + "$(lists)_invert" slist => filter("[0-9]", $(lists), "true", "true", 999); + "$(lists)_max2" slist => filter(".*", $(lists), "true", "false", 2); + "$(lists)_max0" slist => filter(".*", $(lists), "true", "false", 0); + "$(lists)_grep09" slist => grep("[0-9]", $(lists)); + + # with inline JSON + "inline_fgrep09" slist => filter("[0-9]", '[1,2,3]', "true", "false", 999); + "inline_exact1" slist => filter("one", '[1,2,3]', "false", "false", 999); + "inline_exactdot" slist => filter(".", '[1,2,3]', "false", "false", 999); + "inline_regexdot" slist => filter(".", '[1,2,3]', "true", "false", 999); + "inline_invert" slist => filter("[0-9]", '[1,2,3]', "true", "true", 999); + "inline_max2" slist => filter(".*", '[1,2,3]', "true", "false", 2); + "inline_max0" slist => filter(".*", '[1,2,3]', "true", "false", 0); + "inline_grep09" slist => grep("[0-9]", '[1,2,3]'); + +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/filter.cf.expected.json b/tests/acceptance/01_vars/02_functions/filter.cf.expected.json new file mode 100644 index 0000000000..fcb80b98a1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/filter.cf.expected.json @@ -0,0 +1,186 @@ +{ + "d1": [ + 1, + 2, + 3, + "one", + "two", + "three", + "long string", + "four", + "fix", + "six", + "one", + "two", + "three" + ], + "d1_exact1": [ + "one", + "one" + ], + "d1_exactdot": [], + "d1_fgrep09": [ + "1", + "2", + "3" + ], + "d1_grep09": [ + "1", + "2", + "3" + ], + "d1_invert": [ + "one", + "two", + "three", + "long string", + "four", + "fix", + "six", + "one", + "two", + "three" + ], + "d1_max0": [], + "d1_max2": [ + "1", + "2" + ], + "d1_regexdot": [ + "1", + "2", + "3" + ], + "d2": { + "a": "b", + "one": 1, + "p": "q", + "three": 3, + "two": 2, + "x": "y" + }, + "d2_exact1": [], + "d2_exactdot": [], + "d2_fgrep09": [ + "1", + "2", + "3" + ], + "d2_grep09": [ + "1", + "2", + "3" + ], + "d2_invert": [ + "y", + "b", + "q" + ], + "d2_max0": [], + "d2_max2": [ + "1", + "2" + ], + "d2_regexdot": [ + "1", + "2", + "3", + "y", + "b", + "q" + ], + "dempty": [], + "dempty_exact1": [], + "dempty_exactdot": [], + "dempty_fgrep09": [], + "dempty_grep09": [], + "dempty_invert": [], + "dempty_max0": [], + "dempty_max2": [], + "dempty_regexdot": [], + "inline_exact1": [], + "inline_exactdot": [], + "inline_fgrep09": [ + "1", + "2", + "3" + ], + "inline_grep09": [ + "1", + "2", + "3" + ], + "inline_invert": [], + "inline_max0": [], + "inline_max2": [ + "1", + "2" + ], + "inline_regexdot": [ + "1", + "2", + "3" + ], + "lists": [ + "s1", + "d1", + "d2", + "dempty" + ], + "s1": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "one", + "two", + "three" + ], + "s1_exact1": [ + "one", + "one" + ], + "s1_exactdot": [], + "s1_fgrep09": [ + "1", + "2", + "3" + ], + "s1_grep09": [ + "1", + "2", + "3" + ], + "s1_invert": [ + "one", + "two", + "three", + "long string", + "one", + "two", + "three" + ], + "s1_max0": [], + "s1_max2": [ + "1", + "2" + ], + "s1_regexdot": [ + "1", + "2", + "3" + ], + "tests": [ + "fgrep09", + "exact1", + "exactdot", + "regexdot", + "invert", + "max2", + "max0", + "grep09" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/findfiles-GLOB_BRACE.cf b/tests/acceptance/01_vars/02_functions/findfiles-GLOB_BRACE.cf new file mode 100644 index 0000000000..72123d650b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/findfiles-GLOB_BRACE.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Test GLOB_BRACE with findfiles() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common findfiles +{ + vars: + "names" slist => { "a", "bc", "d/e/f", "g/h/i/j", "klm/nop/qrs" }; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/$(findfiles.names)" + create => "true"; + + reports: + DEBUG:: + "Created $(G.testdir)/$(findfiles.names)"; +} + + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3292" } + string => "Test that findfiles() works as expected."; + + vars: + "patterns[GLOB_BRACE]" string => "$(G.testdir)/{a,bc}"; + + "pnames" slist => getindices("patterns"); + + "found[$(pnames)]" slist => findfiles("$(patterns[$(pnames)])"); + "found_string[$(pnames)]" string => join(",", "found[$(pnames)]"); + + + reports: + DEBUG:: + "findfiles pattern $(pnames) '$(patterns[$(pnames)])' => '$(found_string[$(pnames)])'"; +} + + +####################################################### + +bundle agent check +{ + vars: + "expected[GLOB_BRACE]" string => "$(G.testdir)$(const.dirsep)a,$(G.testdir)$(const.dirsep)bc"; + + "expects" slist => getindices("expected"); + + "fstring" slist => getindices("test.found_string"); + + "joint_condition" string => join(".", "expects"); + + classes: + "$(expects)" expression => strcmp("$(test.found_string[$(expects)])", "$(expected[$(expects)])"); + "ok" expression => "$(joint_condition)"; + + reports: + DEBUG:: + "pattern $(expects) matches as expected: '$(expected[$(expects)])'" + if => "$(expects)"; + + "pattern $(expects) does NOT match expected: '$(test.found_string[$(expects)])' != '$(expected[$(expects)])'" + if => "!$(expects)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/findfiles.cf b/tests/acceptance/01_vars/02_functions/findfiles.cf new file mode 100644 index 0000000000..978e2ea591 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/findfiles.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Test findfiles() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common findfiles +{ + vars: + # * in filenames not allowed on win + windows:: + "names" slist => { "a", "bc", "d/e/f", "g/h/i/j", "klm/nop/qrs" }; + !windows:: + "names" slist => { "a", "bc", "d/e/f", "g/h/i/j", "klm/nop/qrs", "tu/*" }; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/$(findfiles.names)" + create => "true"; + + reports: + DEBUG:: + "Created $(G.testdir)/$(findfiles.names)"; +} + + +####################################################### + +bundle agent test +{ + vars: + "patterns[a]" string => "$(G.testdir)/?"; + "patterns[b]" string => "$(G.testdir)/*"; + "patterns[c]" string => "$(G.testdir)/?/*"; + "patterns[d]" string => "$(G.testdir)/[ab]*"; + "patterns[e]" string => "$(G.testdir)/nosuch/*"; + "patterns[relative_path_1]" string => "./*"; + "patterns[relative_path_2]" string => "**"; + "patterns[relative_path_3]" string => "../**"; + !windows:: + # First of all '*' is an illegal filename on Windows. Also you cannot + # escape wildcards with backslash when it is a file separator. + "patterns[f]" string => "$(G.testdir)/tu/\\*"; + any:: + "patterns[g]" string => "$(G.testdir)/*/**"; + "patterns[h]" string => "$(G.testdir)/**/j"; + + "pnames" slist => getindices("patterns"); + + "found[$(pnames)]" slist => findfiles("$(patterns[$(pnames)])"); + "found_string[$(pnames)]" string => join(",", "found[$(pnames)]"); + + + reports: + DEBUG:: + "findfiles pattern $(pnames) '$(patterns[$(pnames)])' => '$(found_string[$(pnames)])'"; +} + + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expected[a]" string => "$(G.testdir)/a,$(G.testdir)/d,$(G.testdir)/g"; + "expected[b]" string => "$(G.testdir)/a,$(G.testdir)/bc,$(G.testdir)/d,$(G.testdir)/g,$(G.testdir)/klm,$(G.testdir)/tu"; + + "expected[c]" string => "$(G.testdir)/d/e,$(G.testdir)/g/h"; + "expected[d]" string => "$(G.testdir)/a,$(G.testdir)/bc"; + "expected[e]" string => ""; + + "expected[f]" string => "$(G.testdir)/tu/*"; + "expected[g]" string => "$(G.testdir)/a,$(G.testdir)/bc,$(G.testdir)/d,$(G.testdir)/g,$(G.testdir)/klm,$(G.testdir)/tu,$(G.testdir)/d/e,$(G.testdir)/g/h,$(G.testdir)/klm/nop,$(G.testdir)/tu/*,$(G.testdir)/d/e/f,$(G.testdir)/g/h/i,$(G.testdir)/klm/nop/qrs,$(G.testdir)/g/h/i/j"; + "expected[h]" string => "$(G.testdir)/g/h/i/j"; + windows:: + "expected[a]" string => "$(G.testdir)\\a,$(G.testdir)\\d,$(G.testdir)\\g"; + "expected[b]" string => "$(G.testdir)\\a,$(G.testdir)\\bc,$(G.testdir)\\d,$(G.testdir)\\g,$(G.testdir)\\klm"; + + "expected[c]" string => "$(G.testdir)\\d\\e,$(G.testdir)\\g\\h"; + "expected[d]" string => "$(G.testdir)\\a,$(G.testdir)\\bc"; + "expected[e]" string => ""; + + "expected[g]" string => "$(G.testdir)\\a,$(G.testdir)\\bc,$(G.testdir)\\d,$(G.testdir)\\g,$(G.testdir)\\klm,$(G.testdir)\\d\\e,$(G.testdir)\\g\\h,$(G.testdir)\\klm\\nop,$(G.testdir)\\d\\e\\f,$(G.testdir)\\g\\h\\i,$(G.testdir)\\klm\\nop\\qrs,$(G.testdir)\\g\\h\\i\\j"; + "expected[h]" string => "$(G.testdir)\\g\\h\\i\\j"; + any:: + # relative paths are skipped, thus return empty list + "expected[relative_path_1]" string => ""; + "expected[relative_path_2]" string => ""; + "expected[relative_path_3]" string => ""; + + "expects" slist => getindices("expected"); + + "fstring" slist => getindices("test.found_string"); + + "joint_condition" string => join(".", "expects"); + + classes: + "$(expects)" expression => strcmp("$(test.found_string[$(expects)])", "$(expected[$(expects)])"); + "ok" expression => "$(joint_condition)"; + + reports: + DEBUG:: + "pattern $(expects) matches as expected: '$(expected[$(expects)])'" + if => "$(expects)"; + + "pattern $(expects) does NOT match expected: '$(test.found_string[$(expects)])' != '$(expected[$(expects)])'" + if => "!$(expects)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/findfiles_glob_bracket.cf b/tests/acceptance/01_vars/02_functions/findfiles_glob_bracket.cf new file mode 100644 index 0000000000..79647c829e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/findfiles_glob_bracket.cf @@ -0,0 +1,114 @@ +####################################################### +# +# Test that square bracket works in glob patterns +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "init", "test", "check" }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "filenames" + slist => { "foo", "bar", "baz" }; + + files: + "$(G.testdir)/$(filenames)" + create => "true"; + + reports: + DEBUG:: + "Created $(G.testdir)/$(filenames)"; +} + + +####################################################### + +bundle agent test +{ + meta: + "description" + string => "Test that square bracket works in glob patterns."; + + vars: + "patterns[a]" + string => "$(G.testdir)/[f]oo"; + "patterns[b]" + string => "$(G.testdir)/b[a]r"; + "patterns[c]" + string => "$(G.testdir)/ba[z]"; + "patterns[d]" + string => "$(G.testdir)/[a-z][a-z][a-z]"; + "patterns[e]" + string => "$(G.testdir)/ba[rz]"; + "patterns[f]" + string => "$(G.testdir)/[fb][oa][orz]"; + + "pnames" + slist => getindices("patterns"); + "found[$(pnames)]" + slist => findfiles("$(patterns[$(pnames)])"); + "found_string[$(pnames)]" + string => join(",", "found[$(pnames)]"); + + reports: + DEBUG:: + "findfiles pattern $(pnames) '$(patterns[$(pnames)])' => '$(found_string[$(pnames)])'"; +} + + +####################################################### + +bundle agent check +{ + meta: + "test_skip_needs_work" string => "windows", + meta => { "ENT-11176" }; + + vars: + "expected[a]" + string => "$(G.testdir)$(const.dirsep)foo"; + "expected[b]" + string => "$(G.testdir)$(const.dirsep)bar"; + "expected[c]" + string => "$(G.testdir)$(const.dirsep)baz"; + "expected[d]" + string => "$(G.testdir)$(const.dirsep)bar,$(G.testdir)$(const.dirsep)baz,$(G.testdir)$(const.dirsep)foo"; + "expected[e]" + string => "$(G.testdir)$(const.dirsep)bar,$(G.testdir)$(const.dirsep)baz"; + "expected[f]" + string => "$(G.testdir)$(const.dirsep)bar,$(G.testdir)$(const.dirsep)baz,$(G.testdir)$(const.dirsep)foo"; + + "expects" + slist => getindices("expected"); + "fstring" + slist => getindices("test.found_string"); + "joint_condition" + string => join(".", "expects"); + + classes: + "$(expects)" + expression => strcmp("$(test.found_string[$(expects)])", "$(expected[$(expects)])"); + "ok" + expression => "$(joint_condition)"; + + reports: + DEBUG:: + "pattern $(expects) matches as expected: '$(expected[$(expects)])'" + if => "$(expects)"; + + "pattern $(expects) does NOT match expected: '$(test.found_string[$(expects)])' != '$(expected[$(expects)])'" + if => "!$(expects)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/findfiles_up.cf b/tests/acceptance/01_vars/02_functions/findfiles_up.cf new file mode 100755 index 0000000000..f05fcc72cc --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/findfiles_up.cf @@ -0,0 +1,177 @@ +body common control +{ + bundlesequence => { "init", "test", "check" }; + version => "1.0"; +} + +bundle common G { + vars: + "testdir" + string => concat( + getenv("TEMP", "65535"), + "$(const.dirsep)TESTDIR.cfengine" + ); +} + +bundle agent init +{ + vars: + "files" + slist => { + "core/.gitignore", + "core/.git/config", + "core/libpromises/cf3parse.y", + "core/libpromises/cf3lex.l", + "core/libntech/.gitignore", + "core/libntech/.git/config", + "core/libntech/libutils/string.h", + "core/libntech/libutils/string.c" + }; + + files: + "$(G.testdir)/$(files)" + create => "true"; + + reports: + DEBUG:: + "Created $(G.testdir)/$(files)"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3577" } + string => "Test for expected results from policy function findfiles_up"; + + vars: + "t1" + data => findfiles_up("$(G.testdir)/core/libntech/libutils/", ".gitignore", "inf"); + "t2" + data => findfiles_up("$(G.testdir)/core/libntech/libutils/", "string.?"); + "t3" + data => findfiles_up("$(G.testdir)/core/libntech/libutils/", ".git/"); + "t4" + data => findfiles_up("$(G.testdir)/core/libntech/libutils/", ".git/", "1"); + "t5" + data => findfiles_up("$(G.testdir)/core/libntech/libutils/", ".git/config"); + "t6" + data => findfiles_up("$(G.testdir)/core/libntech/libutils/", "*/cf?{lex,parse}.[ly]"); +} + +bundle agent check +{ + classes: + windows:: + "c1" + expression => and( + strcmp("$(G.testdir)\\core\\libntech\\.gitignore", "$(test.t1[0])"), + strcmp("$(G.testdir)\\core\\.gitignore", "$(test.t1[1])") + ); + "c2" + expression => and( + strcmp("$(G.testdir)\\core\\libntech\\libutils\\string.c", "$(test.t2[0])"), + strcmp("$(G.testdir)\\core\\libntech\\libutils\\string.h", "$(test.t2[1])") + ); + "c3" + expression => and( + strcmp("$(G.testdir)\\core\\libntech\\.git", "$(test.t3[0])"), + strcmp("$(G.testdir)\\core\\.git", "$(test.t3[1])") + ); + "c4" + expression => and( + strcmp("$(G.testdir)\\core\\libntech\\.git", "$(test.t4[0])"), + not(isvariable("test.t4[1]")) + ); + "c5" + expression => and( + strcmp("$(G.testdir)\\core\\libntech\\.git\\config", "$(test.t5[0])"), + strcmp("$(G.testdir)\\core\\.git\\config", "$(test.t5[1])") + ); + "c6" + expression => and( + strcmp("$(G.testdir)\\core\\libpromises\\cf3lex.l", "$(test.t6[0])"), + strcmp("$(G.testdir)\\core\\libpromises\\cf3parse.y", "$(test.t6[1])") + ); + + !windows:: + "c1" + expression => and( + strcmp("$(G.testdir)/core/libntech/.gitignore", "$(test.t1[0])"), + strcmp("$(G.testdir)/core/.gitignore", "$(test.t1[1])") + ); + "c2" + expression => and( + strcmp("$(G.testdir)/core/libntech/libutils/string.c", "$(test.t2[0])"), + strcmp("$(G.testdir)/core/libntech/libutils/string.h", "$(test.t2[1])") + ); + "c3" + expression => and( + strcmp("$(G.testdir)/core/libntech/.git", "$(test.t3[0])"), + strcmp("$(G.testdir)/core/.git", "$(test.t3[1])") + ); + "c4" + expression => and( + strcmp("$(G.testdir)/core/libntech/.git", "$(test.t4[0])"), + not(isvariable("test.t4[1]")) + ); + "c5" + expression => and( + strcmp("$(G.testdir)/core/libntech/.git/config", "$(test.t5[0])"), + strcmp("$(G.testdir)/core/.git/config", "$(test.t5[1])") + ); + "c6" + expression => and( + strcmp("$(G.testdir)/core/libpromises/cf3lex.l", "$(test.t6[0])"), + strcmp("$(G.testdir)/core/libpromises/cf3parse.y", "$(test.t6[1])") + ); + + any:: + "ok" + expression => and("c1", "c2", "c3", "c4", "c5", "c6"); + + reports: + DEBUG.windows.!c1:: + "$(const.dollar)(test.t1[0]): Expected '$(G.testdir)\\core\\libntech\\.gitignore', found '$(test.t1[0])'"; + "$(const.dollar)(test.t1[1]): Expected '$(G.testdir)\\core\\.gitignore', found '$(test.t1[1])'"; + DEBUG.windows.!c2:: + "$(const.dollar)(test.t2[0]): Expected '$(G.testdir)\\core\\libntech\\libutils\\string.c', found '$(test.t2[0])'"; + "$(const.dollar)(test.t2[1]): Expected '$(G.testdir)\\core\\libntech\\libutils\\string.h', found '$(test.t2[1])'"; + DEBUG.windows.!c3:: + "$(const.dollar)(test.t3[0]): Expected '$(G.testdir)\\core\\libntech\\.git', found '$(test.t3[0])'"; + "$(const.dollar)(test.t3[1]): Expected '$(G.testdir)\\core\\.git', found '$(test.t3[1])'"; + DEBUG.windows.!c4:: + "$(const.dollar)(test.t4[0]): Expected '$(G.testdir)\\core\\libntech\\.git', found '$(test.t4[0])'"; + "$(const.dollar)(test.t4[1]): Should not exist, $(with). Expanded value: '$(test.t4[1])'" + with => ifelse(isvariable("test.t4[1]"), "but does exist", "and does not exist"); + DEBUG.windows.!c5:: + "$(const.dollar)(test.t5[0]): Expected '$(G.testdir)\\core\\libntech\\.git\\config', found '$(test.t5[0])'"; + "$(const.dollar)(test.t5[1]): Expected '$(G.testdir)\\core\\.git\\config', found '$(test.t5[1])'"; + DEBUG.windows.!c6:: + "$(const.dollar)(test.t6[0]): Expected '$(G.testdir)\\core\\libpromises\\cf3lex.l', found '$(test.t6[0])'"; + "$(const.dollar)(test.t6[1]): Expected '$(G.testdir)\\core\\libpromises\\cf3parse.y', found '$(test.t6[1])'"; + + DEBUG.!windows.!c1:: + "$(const.dollar)(test.t1[0]): Expected '$(G.testdir)/core/libntech/.gitignore', found '$(test.t1[0])'"; + "$(const.dollar)(test.t1[1]): Expected '$(G.testdir)/core/.gitignore', found '$(test.t1[1])'"; + DEBUG.!windows.!c2:: + "$(const.dollar)(test.t2[0]): Expected '$(G.testdir)/core/libntech/libutils/string.c', found '$(test.t2[0])'"; + "$(const.dollar)(test.t2[1]): Expected '$(G.testdir)/core/libntech/libutils/string.h', found '$(test.t2[1])'"; + DEBUG.!windows.!c3:: + "$(const.dollar)(test.t3[0]): Expected '$(G.testdir)/core/libntech/.git', found '$(test.t3[0])'"; + "$(const.dollar)(test.t3[1]): Expected '$(G.testdir)/core/.git', found '$(test.t3[1])'"; + DEBUG.!windows.!c4:: + "$(const.dollar)(test.t4[0]): Expected '$(G.testdir)/core/libntech/.git', found '$(test.t4[0])'"; + "$(const.dollar)(test.t4[1]): Should not exist, $(with). Expanded value: '$(test.t4[1])'" + with => ifelse(isvariable("test.t4[1]"), "but does exist", "and does not exist"); + DEBUG.!windows.!c5:: + "$(const.dollar)(test.t5[0]): Expected '$(G.testdir)/core/libntech/.git/config', found '$(test.t5[0])'"; + "$(const.dollar)(test.t5[1]): Expected '$(G.testdir)/core/.git/config', found '$(test.t5[1])'"; + DEBUG.!windows.!c6:: + "$(const.dollar)(test.t6[0]): Expected '$(G.testdir)/core/libpromises/cf3lex.l', found '$(test.t6[0])'"; + "$(const.dollar)(test.t6[1]): Expected '$(G.testdir)/core/libpromises/cf3parse.y', found '$(test.t6[1])'"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/findfiles_up.cf.expected.json b/tests/acceptance/01_vars/02_functions/findfiles_up.cf.expected.json new file mode 100755 index 0000000000..be29153fb4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/findfiles_up.cf.expected.json @@ -0,0 +1,12 @@ +{ + "test[0]": "/a, /file_1.png, /file_1.txt", + "test[1]": "/a/b/c/d/e/f/., /a/b/c/d/e/., /a/b/c/d/., /a/b/c/., /a/b/., /a/., /.", + "test[2]": "/file_1.txt", + "test[3]": "/a/file_2.txt", + "test[4]": "/a/b/c/d/e/f/file_3.txt", + "test[5]": "/a/b/c/d/e/f/file_3.txt, /a/b/c/d/file_3.txt", + "test[6]": "/file_1.png, /file_1.txt", + "test[7]": "/a/b/c/d/e/f/file_3.txt, /a/b/c/d/file_3.txt, /a/b/file_3.txt, /a/file_2.txt, /file_1.txt", + "test[8]": "/a/b/c/d/file_3.txt", + "test[9]": "/a/b/c/d/file_3.txt" +} diff --git a/tests/acceptance/01_vars/02_functions/fold.cf b/tests/acceptance/01_vars/02_functions/fold.cf new file mode 100644 index 0000000000..9c2deefeb9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/fold.cf @@ -0,0 +1,208 @@ +####################################################### +# +# Test folding functions: length(), max(), min(), mean(), range(), and variance() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "a" slist => { "b", "c", "a" }; + "b" slist => { "100", "9", "10" }; + "c" slist => { }; + "d" slist => { "", "a", "", "b" }; + "e" slist => { "a", "1", "b" }; + "f" rlist => { "100", "200", "300" }; + "g" rlist => { "1.11", "-2.22", "-3.33" }; + "h" ilist => { "-10", "0", "200" }; + "i" data => parsejson('[ 1, 2, 3000 ]'); + "j" data => parsejson('[ 1, 2, [ 3, 4, 5 ], null, true, false ]'); + "k" data => parsejson('{}'); + "l" data => parsejson('{ "a": 100, "b": 200, "c": null}'); + + "lists" slist => { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" }; + "bad_lists" slist => { "c", "k" }; + "good_lists" slist => difference("lists", "bad_lists"); + "joined_$(lists)" string => format("%S", $(lists)); + + "length_$(lists)" int => length($(lists)); + "lexmin_$(lists)" string => min($(lists), "lex"); + "realmin_$(lists)" string => min($(lists), "real"); + "lexmax_$(lists)" string => max($(lists), "lex"); + "realmax_$(lists)" string => max($(lists), "real"); + "variance_$(lists)" string => format("%.2f", variance($(lists))); + "stddev_$(lists)" string => format("%.2f", eval("sqrt($(variance_$(lists)))", "math", "infix")); + "mean_$(lists)" string => format("%.2f", mean($(lists))); + + reports: + DEBUG:: + "List $(lists): $(joined_$(lists)) had length $(length_$(lists))"; + "List $(lists): $(joined_$(lists)) had variance $(variance_$(lists))"; + "List $(lists): $(joined_$(lists)) had standard deviation $(stddev_$(lists))"; + "List $(lists): $(joined_$(lists)) had mean $(mean_$(lists))"; + "List $(lists): $(joined_$(lists)) had lex min $(lexmin_$(lists))"; + "List $(lists): $(joined_$(lists)) had lex max $(lexmax_$(lists))"; + "List $(lists): $(joined_$(lists)) had real min $(realmin_$(lists))"; + "List $(lists): $(joined_$(lists)) had real max $(realmax_$(lists))"; +} + + +####################################################### + +bundle agent check +{ + vars: + "lists" slist => { @(test.lists) }; + "good_lists" slist => { @(test.good_lists) }; + "methods" slist => { @(test.methods) }; + "measurements" slist => { "length", "mean", "variance", "stddev", "lexmin", "lexmax", "realmin", "realmax" }; + + "expected[a][length]" int => "3"; + "expected[b][length]" int => "3"; + "expected[c][length]" int => "0"; + "expected[d][length]" int => "4"; + "expected[e][length]" int => "3"; + "expected[f][length]" int => "3"; + "expected[g][length]" int => "3"; + "expected[h][length]" int => "3"; + "expected[i][length]" int => "3"; + "expected[j][length]" int => "6"; + "expected[k][length]" int => "0"; + "expected[l][length]" int => "3"; + + "expected[a][variance]" string => "0.00"; + "expected[b][variance]" string => "2730.33"; + "expected[c][variance]" string => "-1"; + "expected[d][variance]" string => "0.00"; + "expected[e][variance]" string => "0.33"; + "expected[f][variance]" string => "10000.00"; + "expected[g][variance]" string => "5.34"; + "expected[h][variance]" string => "14033.33"; + "expected[i][variance]" string => "2997001.00"; + "expected[j][variance]" string => "0.92"; + "expected[k][variance]" string => "0"; + "expected[l][variance]" string => "5000.00"; + + "expected[a][stddev]" string => "0.00"; + "expected[b][stddev]" string => "52.25"; + "expected[c][stddev]" string => "-1"; + "expected[d][stddev]" string => "0.00"; + "expected[e][stddev]" string => "0.57"; + "expected[f][stddev]" string => "100.00"; + "expected[g][stddev]" string => "2.31"; + "expected[h][stddev]" string => "118.46"; + "expected[i][stddev]" string => "1731.18"; + "expected[j][stddev]" string => "0.96"; + "expected[k][stddev]" string => "0"; + "expected[l][stddev]" string => "70.71"; + + "expected[a][mean]" string => "0.00"; + "expected[b][mean]" string => "39.67"; + "expected[c][mean]" string => "-1"; + "expected[d][mean]" string => "0.00"; + "expected[e][mean]" string => "0.33"; + "expected[f][mean]" string => "200.00"; + "expected[g][mean]" string => "-1.48"; + "expected[h][mean]" string => "63.33"; + "expected[i][mean]" string => "1001.00"; + "expected[j][mean]" string => "0.75"; + "expected[k][mean]" string => "0"; + "expected[l][mean]" string => "150.00"; + + "expected[a][lexmin]" string => "a"; + "expected[b][lexmin]" string => "10"; + "expected[c][lexmin]" string => "-1"; + "expected[d][lexmin]" string => ""; + "expected[e][lexmin]" string => "1"; + "expected[f][lexmin]" string => "100"; + "expected[g][lexmin]" string => "-2.22"; + "expected[h][lexmin]" string => "-10"; + "expected[i][lexmin]" string => "1"; + "expected[j][lexmin]" string => "1"; + "expected[k][lexmin]" string => ""; + "expected[l][lexmin]" string => "100"; + + "expected[a][realmin]" string => "a"; + "expected[b][realmin]" string => "9"; + "expected[c][realmin]" string => "-1"; + "expected[d][realmin]" string => ""; + "expected[e][realmin]" string => "a"; + "expected[f][realmin]" string => "100"; + "expected[g][realmin]" string => "-3.33"; + "expected[h][realmin]" string => "-10"; + "expected[i][realmin]" string => "1"; + "expected[j][realmin]" string => "false"; + "expected[k][realmin]" string => ""; + "expected[l][realmin]" string => "100"; + + "expected[a][lexmax]" string => "c"; + "expected[b][lexmax]" string => "9"; + "expected[c][lexmax]" string => "-1"; + "expected[d][lexmax]" string => "b"; + "expected[e][lexmax]" string => "b"; + "expected[f][lexmax]" string => "300"; + "expected[g][lexmax]" string => "1.11"; + "expected[h][lexmax]" string => "200"; + "expected[i][lexmax]" string => "3000"; + "expected[j][lexmax]" string => "true"; + "expected[k][lexmax]" string => ""; + "expected[l][lexmax]" string => "200"; + + "expected[a][realmax]" string => "c"; + "expected[b][realmax]" string => "100"; + "expected[c][realmax]" string => "-1"; + "expected[d][realmax]" string => "b"; + "expected[e][realmax]" string => "1"; + "expected[f][realmax]" string => "300"; + "expected[g][realmax]" string => "1.11"; + "expected[h][realmax]" string => "200"; + "expected[i][realmax]" string => "3000"; + "expected[j][realmax]" string => "2"; + "expected[k][realmax]" string => ""; + "expected[l][realmax]" string => "200"; + + classes: + "ok_$(measurements)_$(lists)" expression => strcmp("$(expected[$(lists)][$(measurements)])", + "$(test.$(measurements)_$(lists))"); + + "no_$(measurements)_$(lists)" not => isvariable("test.$(measurements)_$(lists)"); + + "ok" and => { + "ok_length_a", "ok_length_b", "ok_length_c", "ok_length_d", "ok_length_e", "ok_length_f", "ok_length_g", "ok_length_h", "ok_length_i", "ok_length_j", "ok_length_k", "ok_length_l", + "ok_mean_a", "ok_mean_b", "no_mean_c", "ok_mean_d", "ok_mean_e", "ok_mean_f", "ok_mean_g", "ok_mean_h", "ok_mean_i", "ok_mean_j", "no_mean_k", "ok_mean_l", + "ok_variance_a", "ok_variance_b", "no_variance_c", "ok_variance_d", "ok_variance_e", "ok_variance_f", "ok_variance_g", "ok_variance_h", "ok_variance_i", "ok_variance_j", "no_variance_k", "ok_variance_l", + "ok_stddev_a", "ok_stddev_b", "no_stddev_c", "ok_stddev_d", "ok_stddev_e", "ok_stddev_f", "ok_stddev_g", "ok_stddev_h", "ok_stddev_i", "ok_stddev_j", "no_stddev_k", "ok_stddev_l", + "ok_realmax_a", "ok_realmax_b", "no_realmax_c", "ok_realmax_d", "ok_realmax_e", "ok_realmax_f", "ok_realmax_g", "ok_realmax_h", "ok_realmax_i", "ok_realmax_j", "no_realmax_k", "ok_realmax_l", + "ok_realmin_a", "ok_realmin_b", "no_realmin_c", "ok_realmin_d", "ok_realmin_e", "ok_realmin_f", "ok_realmin_g", "ok_realmin_h", "ok_realmin_i", "ok_realmin_j", "no_realmin_k", "ok_realmin_l", + "ok_lexmax_a", "ok_lexmax_b", "no_lexmax_c", "ok_lexmax_d", "ok_lexmax_e", "ok_lexmax_f", "ok_lexmax_g", "ok_lexmax_h", "ok_lexmax_i", "ok_lexmax_j", "no_lexmax_k", "ok_lexmax_l", + "ok_lexmin_a", "ok_lexmin_b", "no_lexmin_c", "ok_lexmin_d", "ok_lexmin_e", "ok_lexmin_f", "ok_lexmin_g", "ok_lexmin_h", "ok_lexmin_i", "ok_lexmin_j", "no_lexmin_k", "ok_lexmin_l", + }; + + reports: + DEBUG:: + "$(good_lists) $(measurements) check expected '$(expected[$(good_lists)][$(measurements)])' <> actual '$(test.$(measurements)_$(good_lists))'" + if => "!ok_$(measurements)_$(good_lists)"; + + "good list $(good_lists) had no $(measurements): '$(test.$(measurements)_$(good_lists))' was not expanded" + if => "no_$(measurements)_$(good_lists)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/format.cf b/tests/acceptance/01_vars/02_functions/format.cf new file mode 100644 index 0000000000..ef86c440a2 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/format.cf @@ -0,0 +1,118 @@ +####################################################### +# +# Test formatint(), formatreal(), and formatstring() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_defaults => empty, + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "key='c32' value='(unhandled format)'"; + "key='c107' value='(unhandled format)'"; + "key='int100' value='100'"; + "key='string100' value='100'"; + "key='int0100' value='0100'"; + "key='string0100' value='0100'"; + "key='real200' value='200.0'"; + "key='string200' value='200.0'"; + "key='real0200_00' value='0200.00'"; + "key='string0200_00' value='0200.00'"; + "key='stringabc' value='abc'"; + "key='string6abc' value=' abc'"; + "key='string-6abc' value='abc '"; + "key='2strings-6abc-6def' value='abc def '"; + "key='S_empty_list' value='{ }'"; + "key='badend' value='ends badly '"; + "key='escape' value='% should be a single percent'"; + "key='S_123_list' value='{ \"one\", \"two\", \"\\\"three\\\"\" }'"; + "key='S_container_1' value='[null]'"; + "key='S_container_2' value='[{\"x\":123},\"yz\"]'"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[c32]" string => format("%c", 32); + "array[c107]" string => format("%c", 107); + "array[int100]" string => format("%d", 100); + "array[string100]" string => format("%d", "100"); + "array[int0100]" string => format("%04d", 100); + "array[string0100]" string => format("%04d", "100"); + "array[real200]" string => format("%.1f", 200); + "array[string200]" string => format("%.1f", "200.00"); + "array[real0200_00]" string => format("%07.2f", 200); + "array[string0200_00]" string => format("%07.2f", "200.00"); + "array[stringabc]" string => format("%s", "abc"); + "array[string6abc]" string => format("%6s", "abc"); + "array[string-6abc]" string => format("%-6s", "abc"); + "array[2strings-6abc-6def]" string => format("%-6s%-6s", "abc", "def"); + "array[badend]" string => format("ends badly %"); + "array[escape]" string => format("%% should be a single percent"); + + # %S specifier + "mydata" string => 'simplest data'; + "emptylist" slist => {}; + "list123" slist => { "one", "two", '"three"'}; + "mycontainer1" data => parsejson("[ null ]"); + "mycontainer2" data => parsejson('[ { "x": 123 }, "yz" ]'); + + "array[S_string]" string => format("prefix %S suffix", "simple data"); + "array[S_var]" string => format("%S", $(mydata)); + "array[S_empty_list]" string => format("%S", emptylist); + "array[S_123_list]" string => format("%S", list123); + "array[S_container_1]" string => format("%S", mycontainer1); + "array[S_container_2]" string => format("%S", mycontainer2); + + "formatted" slist => maparray("key='$(this.k)' value='$(this.v)'", "array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(formatted)"; +} + +bundle edit_line test_insert +{ + vars: + "formatted" slist => { @{test.formatted} }; + + insert_lines: + "$(formatted)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/format_edge_case.cf b/tests/acceptance/01_vars/02_functions/format_edge_case.cf new file mode 100644 index 0000000000..818ae3168f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/format_edge_case.cf @@ -0,0 +1,28 @@ +# Test bug fix +# Former bug truncates strings greater than 4096 bytes +# There is no reason for format() to truncate strings + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +########################################################## + +bundle agent test { + vars: + "str" string => format('%s', 'Hello, everyone! This is the LONGEST TEXT EVER! I was inspired by the various other longest texts ever on the internet, and I wanted to make my own. So here it is! This is going to be a WORLD RECORD! This is actually my third attempt at doing this. The first time, I didnt save it. The second time, the Neocities editor crashed. Now Im writing this in Notepad, then copying it into the Neocities editor instead of typing it directly in the Neocities editor to avoid crashing. It sucks that my past two attempts are gone now. Those actually got pretty long. Not the longest, but still pretty long. I hope this one wont get lost somehow. Anyways, lets talk about WAFFLES! I like waffles. Waffles are cool. Waffles is a funny word. Theres a Teen Titans Go episode called Waffles where the word Waffles is said a hundred-something times. Its pretty annoying. Theres also a Teen Titans Go episode about Pig Latin. Dont know what Pig Latin is? Its a language where you take all the consonants before the first vowel, move them to the end, and add -ay to the end. If the word begins with a vowel, you just add -way to the end. For example, Waffles becomes Afflesway. Ive been speaking Pig Latin fluently since the fourth grade, so it surprised me when I saw the episode for the first time. I speak Pig Latin with my sister sometimes. Its pretty fun. I like speaking it in public so that everyone around us gets confused. Thats never actually happened before, but if it ever does, twill be pretty funny. By the way, twill is a word I invented recently, and its a contraction of it will. I really hope it gains popularity in the near future, because twill is WAY more fun than saying itll. Itll is too boring. Nobody likes boring. This is nowhere near being the longest text ever, but eventually it will be! I might still be writing this a decade later, who knows? But right now, its not very long. But Ill just keep writing until it is the longest! Have you ever heard the song Dau Dau by Awesome Scampis? Its an amazing song. Look it up on YouTube! I play that song all the time around my sister! It drives her crazy, and I love it. Another way I like driving my sister crazy is by speaking my own made up language to her. She hates the languages I make! The only language that we both speak besides English is Pig Latin. I think you already knew that. Whatever. I think Im gonna go for now. Bye! Hi, Im back now. Im gonna contribute more to this soon-to-be giant wall of text. I just realised I have a giant stuffed frog on my bed. I forgot his name. Im pretty sure it was something stupid though. I think it was FROG in Morse Code or something. Morse Code is cool. I know a bit of it, but Im not very good at it. Im also not very good at French. I barely know anything in French, and my pronunciation probably sucks. But Im learning it, at least. Im also learning Esperanto. Its this language that was made up by some guy a long time ago to be the universal language. A lot of people speak it. I am such a language nerd. Half of this text is probably gonna be about languages. But hey, as long as its long! Ha, get it? As LONG as its LONG? Im so funny, right? No, Im not. I should probably get some sleep. Goodnight! Hello, Im back again. I basically have only two interests nowadays: languages and furries. What? Oh, sorry, I thought you knew I was a furry. Haha, oops. Anyway, yeah, Im a furry, but since Im a young furry, I cant really do as much as I would like to do in the fandom. When Im older, I would like to have a fursuit, go to furry conventions, all that stuff. But for now I can only dream of that. Sorry you had to deal with me talking about furries, but Im honestly very desperate for this to be the longest text ever. Last night I was watching nothing but fursuit unboxings. I think I need help. This one time, me and my mom were going to go to a furry Christmas party, but we didnt end up going because of the fact that there was alcohol on the premises, and that she didnt wanna have to be a mom dragging her son through a crowd of furries. Both of those reasons were understandable. Okay, hopefully I wont have to talk about furries anymore. I dont care if youre a furry reading this right now, I just dont wanna have to torture everyone else.'); + "list" slist => { $(str) }; + "list_str" string => format('%S', "list"); + "container" data => parsejson(' ["$(str)"] '); + "container_str" string => format('%S', "container"); +} + +bundle agent check { + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/format_edge_case.cf.expected.json b/tests/acceptance/01_vars/02_functions/format_edge_case.cf.expected.json new file mode 100644 index 0000000000..85f6b5b648 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/format_edge_case.cf.expected.json @@ -0,0 +1,11 @@ +{ + "container": [ + "Hello, everyone! This is the LONGEST TEXT EVER! I was inspired by the various other longest texts ever on the internet, and I wanted to make my own. So here it is! This is going to be a WORLD RECORD! This is actually my third attempt at doing this. The first time, I didnt save it. The second time, the Neocities editor crashed. Now Im writing this in Notepad, then copying it into the Neocities editor instead of typing it directly in the Neocities editor to avoid crashing. It sucks that my past two attempts are gone now. Those actually got pretty long. Not the longest, but still pretty long. I hope this one wont get lost somehow. Anyways, lets talk about WAFFLES! I like waffles. Waffles are cool. Waffles is a funny word. Theres a Teen Titans Go episode called Waffles where the word Waffles is said a hundred-something times. Its pretty annoying. Theres also a Teen Titans Go episode about Pig Latin. Dont know what Pig Latin is? Its a language where you take all the consonants before the first vowel, move them to the end, and add -ay to the end. If the word begins with a vowel, you just add -way to the end. For example, Waffles becomes Afflesway. Ive been speaking Pig Latin fluently since the fourth grade, so it surprised me when I saw the episode for the first time. I speak Pig Latin with my sister sometimes. Its pretty fun. I like speaking it in public so that everyone around us gets confused. Thats never actually happened before, but if it ever does, twill be pretty funny. By the way, twill is a word I invented recently, and its a contraction of it will. I really hope it gains popularity in the near future, because twill is WAY more fun than saying itll. Itll is too boring. Nobody likes boring. This is nowhere near being the longest text ever, but eventually it will be! I might still be writing this a decade later, who knows? But right now, its not very long. But Ill just keep writing until it is the longest! Have you ever heard the song Dau Dau by Awesome Scampis? Its an amazing song. Look it up on YouTube! I play that song all the time around my sister! It drives her crazy, and I love it. Another way I like driving my sister crazy is by speaking my own made up language to her. She hates the languages I make! The only language that we both speak besides English is Pig Latin. I think you already knew that. Whatever. I think Im gonna go for now. Bye! Hi, Im back now. Im gonna contribute more to this soon-to-be giant wall of text. I just realised I have a giant stuffed frog on my bed. I forgot his name. Im pretty sure it was something stupid though. I think it was FROG in Morse Code or something. Morse Code is cool. I know a bit of it, but Im not very good at it. Im also not very good at French. I barely know anything in French, and my pronunciation probably sucks. But Im learning it, at least. Im also learning Esperanto. Its this language that was made up by some guy a long time ago to be the universal language. A lot of people speak it. I am such a language nerd. Half of this text is probably gonna be about languages. But hey, as long as its long! Ha, get it? As LONG as its LONG? Im so funny, right? No, Im not. I should probably get some sleep. Goodnight! Hello, Im back again. I basically have only two interests nowadays: languages and furries. What? Oh, sorry, I thought you knew I was a furry. Haha, oops. Anyway, yeah, Im a furry, but since Im a young furry, I cant really do as much as I would like to do in the fandom. When Im older, I would like to have a fursuit, go to furry conventions, all that stuff. But for now I can only dream of that. Sorry you had to deal with me talking about furries, but Im honestly very desperate for this to be the longest text ever. Last night I was watching nothing but fursuit unboxings. I think I need help. This one time, me and my mom were going to go to a furry Christmas party, but we didnt end up going because of the fact that there was alcohol on the premises, and that she didnt wanna have to be a mom dragging her son through a crowd of furries. Both of those reasons were understandable. Okay, hopefully I wont have to talk about furries anymore. I dont care if youre a furry reading this right now, I just dont wanna have to torture everyone else." + ], + "container_str": "[\"Hello, everyone! This is the LONGEST TEXT EVER! I was inspired by the various other longest texts ever on the internet, and I wanted to make my own. So here it is! This is going to be a WORLD RECORD! This is actually my third attempt at doing this. The first time, I didnt save it. The second time, the Neocities editor crashed. Now Im writing this in Notepad, then copying it into the Neocities editor instead of typing it directly in the Neocities editor to avoid crashing. It sucks that my past two attempts are gone now. Those actually got pretty long. Not the longest, but still pretty long. I hope this one wont get lost somehow. Anyways, lets talk about WAFFLES! I like waffles. Waffles are cool. Waffles is a funny word. Theres a Teen Titans Go episode called Waffles where the word Waffles is said a hundred-something times. Its pretty annoying. Theres also a Teen Titans Go episode about Pig Latin. Dont know what Pig Latin is? Its a language where you take all the consonants before the first vowel, move them to the end, and add -ay to the end. If the word begins with a vowel, you just add -way to the end. For example, Waffles becomes Afflesway. Ive been speaking Pig Latin fluently since the fourth grade, so it surprised me when I saw the episode for the first time. I speak Pig Latin with my sister sometimes. Its pretty fun. I like speaking it in public so that everyone around us gets confused. Thats never actually happened before, but if it ever does, twill be pretty funny. By the way, twill is a word I invented recently, and its a contraction of it will. I really hope it gains popularity in the near future, because twill is WAY more fun than saying itll. Itll is too boring. Nobody likes boring. This is nowhere near being the longest text ever, but eventually it will be! I might still be writing this a decade later, who knows? But right now, its not very long. But Ill just keep writing until it is the longest! Have you ever heard the song Dau Dau by Awesome Scampis? Its an amazing song. Look it up on YouTube! I play that song all the time around my sister! It drives her crazy, and I love it. Another way I like driving my sister crazy is by speaking my own made up language to her. She hates the languages I make! The only language that we both speak besides English is Pig Latin. I think you already knew that. Whatever. I think Im gonna go for now. Bye! Hi, Im back now. Im gonna contribute more to this soon-to-be giant wall of text. I just realised I have a giant stuffed frog on my bed. I forgot his name. Im pretty sure it was something stupid though. I think it was FROG in Morse Code or something. Morse Code is cool. I know a bit of it, but Im not very good at it. Im also not very good at French. I barely know anything in French, and my pronunciation probably sucks. But Im learning it, at least. Im also learning Esperanto. Its this language that was made up by some guy a long time ago to be the universal language. A lot of people speak it. I am such a language nerd. Half of this text is probably gonna be about languages. But hey, as long as its long! Ha, get it? As LONG as its LONG? Im so funny, right? No, Im not. I should probably get some sleep. Goodnight! Hello, Im back again. I basically have only two interests nowadays: languages and furries. What? Oh, sorry, I thought you knew I was a furry. Haha, oops. Anyway, yeah, Im a furry, but since Im a young furry, I cant really do as much as I would like to do in the fandom. When Im older, I would like to have a fursuit, go to furry conventions, all that stuff. But for now I can only dream of that. Sorry you had to deal with me talking about furries, but Im honestly very desperate for this to be the longest text ever. Last night I was watching nothing but fursuit unboxings. I think I need help. This one time, me and my mom were going to go to a furry Christmas party, but we didnt end up going because of the fact that there was alcohol on the premises, and that she didnt wanna have to be a mom dragging her son through a crowd of furries. Both of those reasons were understandable. Okay, hopefully I wont have to talk about furries anymore. I dont care if youre a furry reading this right now, I just dont wanna have to torture everyone else.\"]", + "list": [ + "Hello, everyone! This is the LONGEST TEXT EVER! I was inspired by the various other longest texts ever on the internet, and I wanted to make my own. So here it is! This is going to be a WORLD RECORD! This is actually my third attempt at doing this. The first time, I didnt save it. The second time, the Neocities editor crashed. Now Im writing this in Notepad, then copying it into the Neocities editor instead of typing it directly in the Neocities editor to avoid crashing. It sucks that my past two attempts are gone now. Those actually got pretty long. Not the longest, but still pretty long. I hope this one wont get lost somehow. Anyways, lets talk about WAFFLES! I like waffles. Waffles are cool. Waffles is a funny word. Theres a Teen Titans Go episode called Waffles where the word Waffles is said a hundred-something times. Its pretty annoying. Theres also a Teen Titans Go episode about Pig Latin. Dont know what Pig Latin is? Its a language where you take all the consonants before the first vowel, move them to the end, and add -ay to the end. If the word begins with a vowel, you just add -way to the end. For example, Waffles becomes Afflesway. Ive been speaking Pig Latin fluently since the fourth grade, so it surprised me when I saw the episode for the first time. I speak Pig Latin with my sister sometimes. Its pretty fun. I like speaking it in public so that everyone around us gets confused. Thats never actually happened before, but if it ever does, twill be pretty funny. By the way, twill is a word I invented recently, and its a contraction of it will. I really hope it gains popularity in the near future, because twill is WAY more fun than saying itll. Itll is too boring. Nobody likes boring. This is nowhere near being the longest text ever, but eventually it will be! I might still be writing this a decade later, who knows? But right now, its not very long. But Ill just keep writing until it is the longest! Have you ever heard the song Dau Dau by Awesome Scampis? Its an amazing song. Look it up on YouTube! I play that song all the time around my sister! It drives her crazy, and I love it. Another way I like driving my sister crazy is by speaking my own made up language to her. She hates the languages I make! The only language that we both speak besides English is Pig Latin. I think you already knew that. Whatever. I think Im gonna go for now. Bye! Hi, Im back now. Im gonna contribute more to this soon-to-be giant wall of text. I just realised I have a giant stuffed frog on my bed. I forgot his name. Im pretty sure it was something stupid though. I think it was FROG in Morse Code or something. Morse Code is cool. I know a bit of it, but Im not very good at it. Im also not very good at French. I barely know anything in French, and my pronunciation probably sucks. But Im learning it, at least. Im also learning Esperanto. Its this language that was made up by some guy a long time ago to be the universal language. A lot of people speak it. I am such a language nerd. Half of this text is probably gonna be about languages. But hey, as long as its long! Ha, get it? As LONG as its LONG? Im so funny, right? No, Im not. I should probably get some sleep. Goodnight! Hello, Im back again. I basically have only two interests nowadays: languages and furries. What? Oh, sorry, I thought you knew I was a furry. Haha, oops. Anyway, yeah, Im a furry, but since Im a young furry, I cant really do as much as I would like to do in the fandom. When Im older, I would like to have a fursuit, go to furry conventions, all that stuff. But for now I can only dream of that. Sorry you had to deal with me talking about furries, but Im honestly very desperate for this to be the longest text ever. Last night I was watching nothing but fursuit unboxings. I think I need help. This one time, me and my mom were going to go to a furry Christmas party, but we didnt end up going because of the fact that there was alcohol on the premises, and that she didnt wanna have to be a mom dragging her son through a crowd of furries. Both of those reasons were understandable. Okay, hopefully I wont have to talk about furries anymore. I dont care if youre a furry reading this right now, I just dont wanna have to torture everyone else." + ], + "list_str": "{ \"Hello, everyone! This is the LONGEST TEXT EVER! I was inspired by the various other longest texts ever on the internet, and I wanted to make my own. So here it is! This is going to be a WORLD RECORD! This is actually my third attempt at doing this. The first time, I didnt save it. The second time, the Neocities editor crashed. Now Im writing this in Notepad, then copying it into the Neocities editor instead of typing it directly in the Neocities editor to avoid crashing. It sucks that my past two attempts are gone now. Those actually got pretty long. Not the longest, but still pretty long. I hope this one wont get lost somehow. Anyways, lets talk about WAFFLES! I like waffles. Waffles are cool. Waffles is a funny word. Theres a Teen Titans Go episode called Waffles where the word Waffles is said a hundred-something times. Its pretty annoying. Theres also a Teen Titans Go episode about Pig Latin. Dont know what Pig Latin is? Its a language where you take all the consonants before the first vowel, move them to the end, and add -ay to the end. If the word begins with a vowel, you just add -way to the end. For example, Waffles becomes Afflesway. Ive been speaking Pig Latin fluently since the fourth grade, so it surprised me when I saw the episode for the first time. I speak Pig Latin with my sister sometimes. Its pretty fun. I like speaking it in public so that everyone around us gets confused. Thats never actually happened before, but if it ever does, twill be pretty funny. By the way, twill is a word I invented recently, and its a contraction of it will. I really hope it gains popularity in the near future, because twill is WAY more fun than saying itll. Itll is too boring. Nobody likes boring. This is nowhere near being the longest text ever, but eventually it will be! I might still be writing this a decade later, who knows? But right now, its not very long. But Ill just keep writing until it is the longest! Have you ever heard the song Dau Dau by Awesome Scampis? Its an amazing song. Look it up on YouTube! I play that song all the time around my sister! It drives her crazy, and I love it. Another way I like driving my sister crazy is by speaking my own made up language to her. She hates the languages I make! The only language that we both speak besides English is Pig Latin. I think you already knew that. Whatever. I think Im gonna go for now. Bye! Hi, Im back now. Im gonna contribute more to this soon-to-be giant wall of text. I just realised I have a giant stuffed frog on my bed. I forgot his name. Im pretty sure it was something stupid though. I think it was FROG in Morse Code or something. Morse Code is cool. I know a bit of it, but Im not very good at it. Im also not very good at French. I barely know anything in French, and my pronunciation probably sucks. But Im learning it, at least. Im also learning Esperanto. Its this language that was made up by some guy a long time ago to be the universal language. A lot of people speak it. I am such a language nerd. Half of this text is probably gonna be about languages. But hey, as long as its long! Ha, get it? As LONG as its LONG? Im so funny, right? No, Im not. I should probably get some sleep. Goodnight! Hello, Im back again. I basically have only two interests nowadays: languages and furries. What? Oh, sorry, I thought you knew I was a furry. Haha, oops. Anyway, yeah, Im a furry, but since Im a young furry, I cant really do as much as I would like to do in the fandom. When Im older, I would like to have a fursuit, go to furry conventions, all that stuff. But for now I can only dream of that. Sorry you had to deal with me talking about furries, but Im honestly very desperate for this to be the longest text ever. Last night I was watching nothing but fursuit unboxings. I think I need help. This one time, me and my mom were going to go to a furry Christmas party, but we didnt end up going because of the fact that there was alcohol on the premises, and that she didnt wanna have to be a mom dragging her son through a crowd of furries. Both of those reasons were understandable. Okay, hopefully I wont have to talk about furries anymore. I dont care if youre a furry reading this right now, I just dont wanna have to torture everyone else.\" }", + "str": "Hello, everyone! This is the LONGEST TEXT EVER! I was inspired by the various other longest texts ever on the internet, and I wanted to make my own. So here it is! This is going to be a WORLD RECORD! This is actually my third attempt at doing this. The first time, I didnt save it. The second time, the Neocities editor crashed. Now Im writing this in Notepad, then copying it into the Neocities editor instead of typing it directly in the Neocities editor to avoid crashing. It sucks that my past two attempts are gone now. Those actually got pretty long. Not the longest, but still pretty long. I hope this one wont get lost somehow. Anyways, lets talk about WAFFLES! I like waffles. Waffles are cool. Waffles is a funny word. Theres a Teen Titans Go episode called Waffles where the word Waffles is said a hundred-something times. Its pretty annoying. Theres also a Teen Titans Go episode about Pig Latin. Dont know what Pig Latin is? Its a language where you take all the consonants before the first vowel, move them to the end, and add -ay to the end. If the word begins with a vowel, you just add -way to the end. For example, Waffles becomes Afflesway. Ive been speaking Pig Latin fluently since the fourth grade, so it surprised me when I saw the episode for the first time. I speak Pig Latin with my sister sometimes. Its pretty fun. I like speaking it in public so that everyone around us gets confused. Thats never actually happened before, but if it ever does, twill be pretty funny. By the way, twill is a word I invented recently, and its a contraction of it will. I really hope it gains popularity in the near future, because twill is WAY more fun than saying itll. Itll is too boring. Nobody likes boring. This is nowhere near being the longest text ever, but eventually it will be! I might still be writing this a decade later, who knows? But right now, its not very long. But Ill just keep writing until it is the longest! Have you ever heard the song Dau Dau by Awesome Scampis? Its an amazing song. Look it up on YouTube! I play that song all the time around my sister! It drives her crazy, and I love it. Another way I like driving my sister crazy is by speaking my own made up language to her. She hates the languages I make! The only language that we both speak besides English is Pig Latin. I think you already knew that. Whatever. I think Im gonna go for now. Bye! Hi, Im back now. Im gonna contribute more to this soon-to-be giant wall of text. I just realised I have a giant stuffed frog on my bed. I forgot his name. Im pretty sure it was something stupid though. I think it was FROG in Morse Code or something. Morse Code is cool. I know a bit of it, but Im not very good at it. Im also not very good at French. I barely know anything in French, and my pronunciation probably sucks. But Im learning it, at least. Im also learning Esperanto. Its this language that was made up by some guy a long time ago to be the universal language. A lot of people speak it. I am such a language nerd. Half of this text is probably gonna be about languages. But hey, as long as its long! Ha, get it? As LONG as its LONG? Im so funny, right? No, Im not. I should probably get some sleep. Goodnight! Hello, Im back again. I basically have only two interests nowadays: languages and furries. What? Oh, sorry, I thought you knew I was a furry. Haha, oops. Anyway, yeah, Im a furry, but since Im a young furry, I cant really do as much as I would like to do in the fandom. When Im older, I would like to have a fursuit, go to furry conventions, all that stuff. But for now I can only dream of that. Sorry you had to deal with me talking about furries, but Im honestly very desperate for this to be the longest text ever. Last night I was watching nothing but fursuit unboxings. I think I need help. This one time, me and my mom were going to go to a furry Christmas party, but we didnt end up going because of the fact that there was alcohol on the premises, and that she didnt wanna have to be a mom dragging her son through a crowd of furries. Both of those reasons were understandable. Okay, hopefully I wont have to talk about furries anymore. I dont care if youre a furry reading this right now, I just dont wanna have to torture everyone else." +} diff --git a/tests/acceptance/01_vars/02_functions/function_with_skipped_promise.cf b/tests/acceptance/01_vars/02_functions/function_with_skipped_promise.cf new file mode 100644 index 0000000000..f2ec7feb55 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/function_with_skipped_promise.cf @@ -0,0 +1,43 @@ +# Make sure function is (not) evaluated in a promise (not) skipped via if +# Redmine 6577 + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testdir)/." create => "true"; +} + +bundle agent test +{ + vars: + "files" slist => { "want_file", "dont_want_file" }; + + classes: + "want_file" expression => "any"; + + "test" expression => returnszero("$(G.touch) $(G.testdir)/$(files)", "noshell"), + if => "$(files)"; +} + +bundle agent check +{ + classes: + "$(test.files)_exists" expression => fileexists("$(G.testdir)/$(test.files)"); + + "ok" expression => "want_file_exists.!dont_want_file_exists"; + + reports: + DEBUG.!ok:: + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/getfields.cf b/tests/acceptance/01_vars/02_functions/getfields.cf new file mode 100644 index 0000000000..8ebb521f5e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getfields.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Test getfields() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; + + reports: + DEBUG:: + "Created $(G.testfile)"; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "one:data1"; + "two:data2"; + "three:data3"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + + +####################################################### + +bundle agent test +{ + vars: + "num_matching" int => getfields("t.*", "$(G.testfile)", ":", "fields"); +} + + +####################################################### + +bundle agent check +{ + classes: + any:: + "ok" and => { + strcmp("$(test.fields[1])", "two"), + strcmp("$(test.fields[2])", "data2"), + strcmp("$(test.num_matching)", "2") + }; + + reports: + DEBUG:: + "got field $(test.fields[1])"; + "got field $(test.fields[2])"; + "num_matching is $(test.num_matching)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/getindices.cf b/tests/acceptance/01_vars/02_functions/getindices.cf new file mode 100644 index 0000000000..f682f5d1f8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Test that getindices on an array variable will resolve to 2 levels +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +# Do not change the name of this bundle or of variables inside this bundle. The +# original bug CFE-2397 depended on a certain order in the variable table which +# was triggered by these names. +bundle common b +{ + vars: + "d[k]" string => "val1"; + "d[f][f1]" string => "val2"; + "d[s][s1]" string => "val3"; + + "d_keys" slist => getindices("d"); + "d_keys_f" slist => getindices("d[f]"); + "d_keys_s" slist => getindices("d[s]"); + + "c[$(d_keys)]" string => "$(d[$(d_keys)])"; + "c[f][$(d_keys_f)]" string => "$(d[f][$(d_keys_f)])"; + "c[s][$(d_keys_s)]" string => "$(d[s][$(d_keys_s)])"; + + "c_keys" slist => getindices("c"); + "c_keys_f" slist => getindices("c[f]"); + "c_keys_s" slist => getindices("c[s]"); +} + +bundle agent test +{ + vars: + "user[name]" string => "zamboni"; + "user[fullname][first]" string => "Diego"; + "user[fullname][last]" string => "Zamboni"; + "user[dirs]" slist => { "/home/zamboni", + "/tmp/zamboni", + "/export/home/zamboni" }; + + "fields" slist => getindices("user"); + "userfields" slist => getindices("user[fullname]"); + "inline_fields" slist => getindices('{ "foo": 1, "bar": 2 }'); + "inline_numfields" slist => getindices('[ "foo", 1, "bar", 2 ]'); + "inline_function" slist => getindices(parsejson('{ "a": "b", "c": "d" }')); + "c_keys" slist => { @(b.c_keys) }; + "c_keys_f" slist => { @(b.c_keys_f) }; + "c_keys_s" slist => { @(b.c_keys_s) }; +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/getindices.cf.expected.json b/tests/acceptance/01_vars/02_functions/getindices.cf.expected.json new file mode 100644 index 0000000000..9ce6a07105 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices.cf.expected.json @@ -0,0 +1,44 @@ +{ + "c_keys": [ + "s", + "f", + "k" + ], + "c_keys_f": [ + "f1" + ], + "c_keys_s": [ + "s1" + ], + "fields": [ + "fullname", + "dirs", + "name" + ], + "inline_fields": [ + "foo", + "bar" + ], + "inline_function": [ + "a", + "c" + ], + "inline_numfields": [ + "0", + "1", + "2", + "3" + ], + "user[dirs]": [ + "/home/zamboni", + "/tmp/zamboni", + "/export/home/zamboni" + ], + "user[fullname][first]": "Diego", + "user[fullname][last]": "Zamboni", + "user[name]": "zamboni", + "userfields": [ + "first", + "last" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_and_getvalues_return_same_with_empty_datacontainer.cf b/tests/acceptance/01_vars/02_functions/getindices_and_getvalues_return_same_with_empty_datacontainer.cf new file mode 100644 index 0000000000..e1fe3fba06 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_and_getvalues_return_same_with_empty_datacontainer.cf @@ -0,0 +1,43 @@ +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + meta: + "description" string => "Test that getindices() and getvalues() return the same data when run against an empty data container"; + + "test_soft_fail" + string => "cfengine_3_9_1", + meta => { "CFE-2479" }, + comment => "This regression was found in 3.9.1 from 3.7.4. Soft failing + for already released version where tests will fail."; + + vars: + # Because def.does_not_exist does in fact not exist, we expect this will + # produce an empty data container. + "data" data => mergedata("def.does_not_exist"); + + "a" slist => getindices(data); + "b" slist => getvalues(data); + + reports: + DEBUG:: + "a is variable" + if => isvariable(a); + "b is variable" + if => isvariable(b); + + "getindices and getvalues do not behave the same when run against an empty data container." + if => not(and(isvariable(a), isvariable(b))); + + any:: + "CFEngine version: $(sys.cf_version)"; + + "$(this.promise_filename) Pass" + if => and(isvariable(a), isvariable(b)); + + "$(this.promise_filename) FAIL" + if => not(and(isvariable(a), isvariable(b))); +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_returns_empty_list_if_array_index_has_no_key_values.cf b/tests/acceptance/01_vars/02_functions/getindices_returns_empty_list_if_array_index_has_no_key_values.cf new file mode 100644 index 0000000000..5b5dba80a1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_returns_empty_list_if_array_index_has_no_key_values.cf @@ -0,0 +1,65 @@ +####################################################### +# +# Test that getindices() returns an empty list if used on +# an array index that has no key values. +# +####################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "data[foo]" slist => { "alpha", "bravo" }; + "data[bar]" string => "zulu"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "!any", + meta => { "redmine7116" }; + + vars: + "values_data" slist => getindices("init.data[bar]"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected_elements" slist => { }; + + "diff1" slist => difference( "test.values_data", expected_elements ); + "diff2" slist => difference( expected_elements, "test.values_data"); + + "len_values_data" int => length( "test.values_data" ); + "len_diff1" int => length(diff1); + "len_diff2" int => length(diff2); + + classes: + "ok" expression => strcmp( $(len_diff1), $(len_diff2) ); + + reports: + DEBUG:: + "DEBUG: data value: '$(test.values_data)'"; + "DEBUG: expected value: '$(expected_elements)'"; + "DEBUG: length of test.values_data: '$(len_values_data)'"; + "DEBUG: found '$(diff1)' in test.values_data, but not in expected_elements"; + "DEBUG: found '$(diff2)' in expected_elements, but not in test.values_data"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_returns_empty_list_if_datacontainer_index_has_no_key_values.cf b/tests/acceptance/01_vars/02_functions/getindices_returns_empty_list_if_datacontainer_index_has_no_key_values.cf new file mode 100644 index 0000000000..8c560d6ccf --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_returns_empty_list_if_datacontainer_index_has_no_key_values.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test that getindices() returns an empy list if used +# on a datacontainer index that has no key values. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "data" data => parsejson('{ + "foo": [ + "alpha", + "bravo" + ], + "bar": "zulu" +}'); +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "!any", + meta => { "redmine7116" }; + + vars: + "values_data" slist => getindices("init.data[bar]"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected_elements" slist => { }; + + "diff1" slist => difference( "test.values_data", expected_elements ); + "diff2" slist => difference( expected_elements, "test.values_data"); + + "len_values_data" int => length( "test.values_data" ); + "len_diff1" int => length(diff1); + "len_diff2" int => length(diff2); + + classes: + "ok" expression => strcmp( $(len_diff1), $(len_diff2) ); + + reports: + DEBUG:: + "DEBUG: data value: '$(test.values_data)'"; + "DEBUG: expected value: '$(expected_elements)'"; + "DEBUG: length of test.values_data: '$(len_values_data)'"; + "DEBUG: found '$(diff1)' in test.values_data, but not in expected_elements"; + "DEBUG: found '$(diff2)' in expected_elements, but not in test.values_data"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_array.cf b/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_array.cf new file mode 100644 index 0000000000..27342861ab --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_array.cf @@ -0,0 +1,54 @@ +####################################################### +# +# Test that getindices() returns the expected list from +# an array +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "data[foo]" slist => { "alpha", "bravo" }; + "data[bar]" string => "zulu"; + "data[bar][one]" string => "1"; + "data[bar][two]" string => "2"; + + "data[zebra][gazelle]" slist => { "alpha", "bravo" }; + "data[zebra][lion]" string => "zulu"; + "data[zebra][lion][first]" string => "1st"; + "data[zebra][lion][second]" string => "2nd"; + + "data[one][two][zero]" slist => { "alpha", "bravo" }; + "data[one][two][three]" string => "zulu"; + "data[one][two][three][a]" string => "c"; + "data[one][two][three][b]" string => "d"; +} + +####################################################### + +bundle agent test +{ + vars: + "values_data_1" slist => getindices("init.data[bar]"); + "values_data_2" slist => getindices("init.data[zebra][lion]"); + "values_data_3" slist => getindices("init.data[one][two][three]"); +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_array.cf.expected.json b/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_array.cf.expected.json new file mode 100644 index 0000000000..b24c6db624 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_array.cf.expected.json @@ -0,0 +1,14 @@ +{ + "values_data_1": [ + "two", + "one" + ], + "values_data_2": [ + "first", + "second" + ], + "values_data_3": [ + "a", + "b" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_datacontainer.cf b/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_datacontainer.cf new file mode 100644 index 0000000000..6fc8f93606 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_datacontainer.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test that getindices() returns the expected list from +# a data container +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "data" data => parsejson('{ + "foo": [ + "alpha", + "bravo" + ], + "bar": { "one": "1" "two": "2" } +}'); +} + +####################################################### + +bundle agent test +{ + vars: + # expected: one, two + "values_data" slist => getindices("init.data[bar]"); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_datacontainer.cf.expected.json b/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_datacontainer.cf.expected.json new file mode 100644 index 0000000000..59023314fe --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_returns_expected_list_from_datacontainer.cf.expected.json @@ -0,0 +1,6 @@ +{ + "values_data": [ + "one", + "two" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_size2_with_expansion.cf b/tests/acceptance/01_vars/02_functions/getindices_size2_with_expansion.cf new file mode 100644 index 0000000000..71d6f5441b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_size2_with_expansion.cf @@ -0,0 +1,83 @@ +####################################################### +# +# Test getindices(), size 2 with variable expansion +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "the fini$end"; + "the end$(const.dollar)fini"; + "XXX dummy XXX"; + "YYY $(init.dummy) YYY"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[the fini$end]" string => + "additional line +again"; + "array[the end$(const.dollar)fini]" string => "one$(const.n)extra line"; + "array[XXX dummy XXX]" string => "XXX dummy XXX"; + "array[YYY $(init.dummy) YYY]" string => "YYY $(init.dummy) YYY"; + + "keys" slist => getindices("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(keys)"; +} + +bundle edit_line test_insert +{ + vars: + "keys" slist => { @{test.keys} }; + + insert_lines: + "$(keys)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/getindices_with_multi_index_arrays.cf b/tests/acceptance/01_vars/02_functions/getindices_with_multi_index_arrays.cf new file mode 100644 index 0000000000..dea05a86ae --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getindices_with_multi_index_arrays.cf @@ -0,0 +1,62 @@ +########################################################### +# +# Test that multiple getindices only get the specified keys +# Redmine#6779 +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +########################################################### + +bundle agent init +{ +} + +########################################################### + +bundle agent test +{ + vars: + "bundle_config[dir][dir_test1]" string => "dir_test1"; + "bundle_config[dir][dir_test2]" string => "dir_test2"; + "bundle_config[lnk][lnk_test1]" string => "lnk_test1"; + "bundle_config[lnk][lnk_test2]" string => "lnk_test2"; + + "dir_keys" slist => getindices("bundle_config[dir]"); + "lnk_keys" slist => getindices("bundle_config[lnk]"); + + "dir_keys_sorted" slist => sort("dir_keys", "lex"); + "lnk_keys_sorted" slist => sort("lnk_keys", "lex"); + + "dir_keys_str" string => join(",", "dir_keys_sorted"); + "lnk_keys_str" string => join(",", "lnk_keys_sorted"); +} + +########################################################### + +bundle agent check +{ + vars: + "expected_dir_keys" string => "dir_test1,dir_test2"; + "expected_lnk_keys" string => "lnk_test1,lnk_test2"; + + classes: + "ok_dir_keys" expression => strcmp($(expected_dir_keys), $(test.dir_keys_str)); + "ok_lnk_keys" expression => strcmp($(expected_lnk_keys), $(test.lnk_keys_str)); + "ok" and => { "ok_dir_keys", "ok_lnk_keys" }; + + reports: + DEBUG:: + "dir_keys = '$(test.dir_keys_str)', expected = '$(expected_dir_keys)'"; + "lnk_keys = '$(test.lnk_keys_str)', expected = '$(expected_lnk_keys)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/gettags.cf b/tests/acceptance/01_vars/02_functions/gettags.cf new file mode 100644 index 0000000000..c7eeb3546e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/gettags.cf @@ -0,0 +1,49 @@ +# Test that getvariabletags and getclasstags work correctly + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ + classes: + "myclass" expression => "any", meta => { "mytag1" }; + "myotherclass" expression => "any", meta => { "mytag5", "mytag51" }; + "myplainclass" expression => "any"; + "keyclass" expression => "any", meta => { "foo=1", "mytag51", "foo", "foo=2" }; + "keyclass2" expression => "any", meta => { "foo" }; + + vars: + "tests" slist => { "1", "2", "3", "4", "5" }; + "myvar" string => "123", meta => { "mytag3" }; + "myothervar" string => "123"; + "keyvar" int => "1", meta => { "foo=1", "mytag51", "foo", "foo=2" }; + "keyvar2" real => "20", meta => { "foo" }; +} + +bundle agent test +{ + vars: + "tags1" slist => getclassmetatags("myclass"); + "tags2" slist => getclassmetatags("myplainclass"); + "tags3" slist => getvariablemetatags("init.myvar"); + "tags4" slist => getvariablemetatags("init.myothervar"); + "tags5" slist => getclassmetatags("myotherclass"); + "tags6" slist => getclassmetatags("nosuchclass"); + "tags7" slist => getvariablemetatags("nosuchvariable"); + "tags8" slist => getclassmetatags("keyclass", "foo"); + "tags9" slist => getclassmetatags("keyclass2", "foo"); + "tagsa" slist => getvariablemetatags("init.keyvar", "foo"); + "tagsb" slist => getvariablemetatags("init.keyvar2", "foo"); +} + + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/gettags.cf.expected.json b/tests/acceptance/01_vars/02_functions/gettags.cf.expected.json new file mode 100644 index 0000000000..3ef94daff9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/gettags.cf.expected.json @@ -0,0 +1,31 @@ +{ + "tags1": [ + "source=promise", + "mytag1" + ], + "tags2": [ + "source=promise" + ], + "tags3": [ + "source=promise", + "mytag3" + ], + "tags4": [ + "source=promise" + ], + "tags5": [ + "source=promise", + "mytag5", + "mytag51" + ], + "tags8": [ + "1", + "2" + ], + "tags9": [], + "tagsa": [ + "1", + "2" + ], + "tagsb": [] +} diff --git a/tests/acceptance/01_vars/02_functions/getuserinfo.cf b/tests/acceptance/01_vars/02_functions/getuserinfo.cf new file mode 100644 index 0000000000..829610105e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getuserinfo.cf @@ -0,0 +1,31 @@ +####################################################### +# +# Test 'getuserinfo' function +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + vars: + # this is pretty much all we can test across platforms + "info_root" string => nth(getuserinfo("root"), "username"); + "info_0" string => nth(getuserinfo(0), "uid"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/getuserinfo.cf.expected.json b/tests/acceptance/01_vars/02_functions/getuserinfo.cf.expected.json new file mode 100644 index 0000000000..252e46f8c9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getuserinfo.cf.expected.json @@ -0,0 +1,4 @@ +{ + "info_0": "0", + "info_root": "root" +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues.cf b/tests/acceptance/01_vars/02_functions/getvalues.cf new file mode 100644 index 0000000000..9087b7fbea --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Test getvalues() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "user[name]" string => "zamboni"; + "user[fullname][first]" string => "Diego"; + "user[fullname][last]" string => "Zamboni"; + "user[dirs]" slist => { "/home/zamboni", + "/tmp/zamboni", + "/export/home/zamboni" }; + + "values" slist => getvalues("user"); + "uservalues" slist => getvalues("user[fullname]"); + "inline_values_map" slist => getvalues('{ "foo": 1, "bar": 2 }'); + "inline_values_array" slist => getvalues('[ "foo", 1, "bar", 2 ]'); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues.cf.expected.json b/tests/acceptance/01_vars/02_functions/getvalues.cf.expected.json new file mode 100644 index 0000000000..34f77644ac --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues.cf.expected.json @@ -0,0 +1,32 @@ +{ + "inline_values_array": [ + "foo", + "1", + "bar", + "2" + ], + "inline_values_map": [ + "1", + "2" + ], + "user[dirs]": [ + "/home/zamboni", + "/tmp/zamboni", + "/export/home/zamboni" + ], + "user[fullname][first]": "Diego", + "user[fullname][last]": "Zamboni", + "user[name]": "zamboni", + "uservalues": [ + "Diego", + "Zamboni" + ], + "values": [ + "Diego", + "Zamboni", + "/home/zamboni", + "/tmp/zamboni", + "/export/home/zamboni", + "zamboni" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_array_returns_first_index_values.cf b/tests/acceptance/01_vars/02_functions/getvalues_array_returns_first_index_values.cf new file mode 100644 index 0000000000..d64eb20d1c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_array_returns_first_index_values.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test getvalues() +# +####################################################### + +# If we run getvalues on a classic array index that contains a string +# we should end up with a list made up of that single value + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + + vars: + "array[string]" string => "scrumdiddlyumptious"; + "array[slist]" slist => { "1", "2" }; + "array[idx][2]" slist => { "3", "4" }; # DO not expect to get these values + "values" slist => getvalues("array"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected" slist => { "scrumdiddlyumptious", 1, 2 }; + "diff" slist => difference( expected, "test.values" ); + + classes: + "_pass" expression => strcmp( length( diff ), 0 ); + + methods: + + _pass:: + "pass" usebundle => dcs_pass("$(this.promise_filename)"); + + !_pass:: + "pass" usebundle => dcs_fail("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_array_slist.cf b/tests/acceptance/01_vars/02_functions/getvalues_array_slist.cf new file mode 100644 index 0000000000..4ff46972c6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_array_slist.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Test getvalues() +# +####################################################### + +# If we run getvalues on a classic array index that contains an slist we should +# end up with a copy of that slist. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[slist]" slist => { "scrumdiddlyumptious" }; + "values" slist => getvalues("array[slist]"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected" slist => { "scrumdiddlyumptious" }; + "diff" slist => difference( expected, "test.values" ); + + classes: + "_pass" expression => strcmp( length( diff ), 0 ); + + methods: + + _pass:: + "pass" usebundle => dcs_pass("$(this.promise_filename)"); + + !_pass:: + "pass" usebundle => dcs_fail("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_array_string.cf b/tests/acceptance/01_vars/02_functions/getvalues_array_string.cf new file mode 100644 index 0000000000..417ca4fc79 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_array_string.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Test getvalues() +# +####################################################### + +# If we run getvalues on a classic array index that contains a string +# we should end up with a list made up of that single value + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[string]" string => "scrumdiddlyumptious"; + "values" slist => getvalues("array[string]"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected" slist => { "scrumdiddlyumptious" }; + "diff" slist => difference( expected, "test.values" ); + + classes: + "_pass" expression => strcmp( length( diff ), 0 ); + + methods: + + _pass:: + "pass" usebundle => dcs_pass("$(this.promise_filename)"); + + !_pass:: + "pass" usebundle => dcs_fail("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_containers.cf b/tests/acceptance/01_vars/02_functions/getvalues_containers.cf new file mode 100644 index 0000000000..c47506e79d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_containers.cf @@ -0,0 +1,32 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "arr" data => '["a", [], { "x": 1 }, 2, 5.30, true]'; +} + +bundle agent test +{ + vars: + "arr_v" slist => getvalues("init.arr"); + +reports: + DEBUG:: + "arr_v $(arr_v)"; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_containers.cf.expected.json b/tests/acceptance/01_vars/02_functions/getvalues_containers.cf.expected.json new file mode 100644 index 0000000000..150f2006ef --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_containers.cf.expected.json @@ -0,0 +1,9 @@ +{ + "arr_v": [ + "a", + "1", + "2", + "5.30", + "true" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_flattens-list-elements.cf b/tests/acceptance/01_vars/02_functions/getvalues_flattens-list-elements.cf new file mode 100644 index 0000000000..1e115ff5fb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_flattens-list-elements.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test getvalues(), to be sure that If the array contains list elements on the +# right hand side then all of the list elements are flattened into a single list +# to make the return value a list. (Bug #1271) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "results" slist => { "one", "two", "red", "blue" }; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "$(init.results)"; +} + +####################################################### + +bundle agent test +{ + vars: + "sea[fish]" slist => { "one", "two" }; + "sea[shark]" slist => { "red", "blue" }; + + secondpass:: + "vals" slist => getvalues("sea"); + + classes: + "secondpass" expression => "any"; + + files: + secondpass:: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_plain_slist.cf b/tests/acceptance/01_vars/02_functions/getvalues_plain_slist.cf new file mode 100644 index 0000000000..ce47c25d3e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_plain_slist.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Test getvalues() +# +####################################################### + +# If we run getvalues on a plan slist, we should end up with a copy of that +# list. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "slist" slist => { "scrumdiddlyumptious" }; + "values" slist => getvalues("slist"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected" slist => { "scrumdiddlyumptious" }; + "diff" slist => difference( expected, "test.values" ); + + classes: + "_pass" expression => strcmp( length( diff ), 0 ); + + methods: + + _pass:: + "pass" usebundle => dcs_pass("$(this.promise_filename)"); + + !_pass:: + "pass" usebundle => dcs_fail("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_plain_string.cf b/tests/acceptance/01_vars/02_functions/getvalues_plain_string.cf new file mode 100644 index 0000000000..4cdc122cbf --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_plain_string.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Test getvalues() +# +####################################################### + +# If we run getvalues on a plan string, we should end up with a list of one +# element being that string value. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "string" string => "scrumdiddlyumptious"; + "values" slist => getvalues("string"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected" slist => { "scrumdiddlyumptious" }; + "diff" slist => difference( expected, "test.values" ); + + classes: + "_pass" expression => strcmp( length( diff ), 0 ); + + methods: + + _pass:: + "pass" usebundle => dcs_pass("$(this.promise_filename)"); + + !_pass:: + "pass" usebundle => dcs_fail("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_array_index_merging_strings.cf b/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_array_index_merging_strings.cf new file mode 100644 index 0000000000..9fc60cfed4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_array_index_merging_strings.cf @@ -0,0 +1,64 @@ +####################################################### +# +# Test getvalues() returns all values for a given array +# merging strings +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "data[foo]" slist => { "alpha", "bravo" }; + "data[bar]" string => "zulu"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "!any", + meta => { "redmine7116" }; + + vars: + "values_data" slist => getvalues("init.data"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected_elements" slist => { "alpha", "bravo", "zulu" }; + + "diff1" slist => difference( "test.values_data", expected_elements ); + "diff2" slist => difference( expected_elements, "test.values_data"); + + "len_diff1" int => length(diff1); + "len_diff2" int => length(diff2); + + classes: + "ok" expression => strcmp( $(len_diff1), $(len_diff2) ); + + reports: + DEBUG:: + "DEBUG: data value: '$(test.values_data)'"; + "DEBUG: expected value: '$(expected_elements)'"; + "DEBUG: found '$(diff1)' in test.values_data, but not in expected_elements"; + "DEBUG: found '$(diff2)' in expected_elements, but not in test.values_data"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_datacontainer_index_merging_strings.cf b/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_datacontainer_index_merging_strings.cf new file mode 100644 index 0000000000..25822c688d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_datacontainer_index_merging_strings.cf @@ -0,0 +1,54 @@ +####################################################### +# +# Test getvalues() returns all values for a given +# datacontainer index merging strings. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "data" data => ' +{ + "foo": [ "alpha", "bravo" ], + "bar": "zulu" +}'; + "data2" data => ' +{ + "foo": [ null, "bravo" ], + "zed": { "quu": "something else" }, + "bar": null, + "bar2": 1233333 +}'; + + "data3" data => '[ "foo", null, "bravo", { "bar": "barista" } ]'; +} + +####################################################### + +bundle agent test +{ + vars: + "values_data" slist => getvalues("init.data"); + "values_data2" slist => getvalues("init.data2"); + "values_data3" slist => getvalues("init.data3"); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_datacontainer_index_merging_strings.cf.expected.json b/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_datacontainer_index_merging_strings.cf.expected.json new file mode 100644 index 0000000000..3036aa6ee7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_returns_all_values_for_given_datacontainer_index_merging_strings.cf.expected.json @@ -0,0 +1,17 @@ +{ + "values_data": [ + "alpha", + "bravo", + "zulu" + ], + "values_data2": [ + "bravo", + "something else", + "1233333" + ], + "values_data3": [ + "foo", + "bravo", + "barista" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_returns_slist_for_array_scalar.cf b/tests/acceptance/01_vars/02_functions/getvalues_returns_slist_for_array_scalar.cf new file mode 100644 index 0000000000..14b82b7d16 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_returns_slist_for_array_scalar.cf @@ -0,0 +1,63 @@ +####################################################### +# +# Test getvalues() returns an slist when used against +# an array that has a scalar. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "data[foo]" string => "bar"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "!any", + meta => { "redmine7116" }; + + vars: + "values_data" slist => getvalues("init.data"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected_elements" slist => { "bar" }; + + "diff1" slist => difference( "test.values_data", expected_elements ); + "diff2" slist => difference( expected_elements, "test.values_data"); + + "len_diff1" int => length(diff1); + "len_diff2" int => length(diff2); + + classes: + "ok" expression => strcmp( $(len_diff1), $(len_diff2) ); + + reports: + DEBUG:: + "DEBUG: data value: '$(test.values_data)'"; + "DEBUG: expected value: '$(expected_elements)'"; + "DEBUG: found '$(diff1)' in test.values_data, but not in expected_elements"; + "DEBUG: found '$(diff2)' in expected_elements, but not in test.values_data"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/getvalues_returns_slist_for_datacontainer_scalar.cf b/tests/acceptance/01_vars/02_functions/getvalues_returns_slist_for_datacontainer_scalar.cf new file mode 100644 index 0000000000..20971c0d4a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getvalues_returns_slist_for_datacontainer_scalar.cf @@ -0,0 +1,63 @@ +####################################################### +# +# Test getvalues() returns an slist for a datacontainer +# scalar +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "data" data => parsejson('{"foo": "bar"}'); +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "!any", + meta => { "redmine7116" }; + + vars: + "values_data" slist => getvalues("init.data"); +} + +####################################################### + +bundle agent check +{ + vars: + "expected_elements" slist => { "bar" }; + + "diff1" slist => difference( "test.values_data", expected_elements ); + "diff2" slist => difference( expected_elements, "test.values_data"); + + "len_diff1" int => length(diff1); + "len_diff2" int => length(diff2); + + classes: + "ok" expression => strcmp( $(len_diff1), $(len_diff2) ); + + reports: + DEBUG:: + "DEBUG: data value: '$(test.values_data)'"; + "DEBUG: expected value: '$(expected_elements)'"; + "DEBUG: found '$(diff1)' in test.values_data, but not in expected_elements"; + "DEBUG: found '$(diff2)' in expected_elements, but not in test.values_data"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/hash.cf b/tests/acceptance/01_vars/02_functions/hash.cf new file mode 100644 index 0000000000..24cebb60a9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/hash.cf @@ -0,0 +1,182 @@ +####################################################### +# +# Test hash() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "base_null" string => ""; + "base_easy" string => "test"; + "base_hard" string => 'This is a test of +the hash function!";'; + + "strings" slist => { "null", "easy", "hard" }; + + "$(strings)" string => "$(base_$(strings)) +"; + + methods: + "make $(strings)" usebundle => file_make("$(G.testfile).$(strings).txt", "$(base_$(strings))"); +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; + + vars: + "algos" slist => { "md5", "sha1", "sha256", "sha384", "sha512" }; + + # XXX # What do we do about testing the crypt hash-type? + # + "null_$(algos)" string => hash("$(init.null)", $(algos)); + + # Cfengine does not expand most \ characters, so use Perl :-) + "easy_$(algos)" string => hash("$(init.easy)", $(algos)); + + "hard_$(algos)" string => hash("$(init.hard)", $(algos)); +} + +####################################################### + +bundle agent check +{ + vars: + "null_md5" string => "68b329da9893e34099c7d8ad5cb9c940"; + "null_sha1" string => "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc"; + "null_sha256" string => "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"; + "null_sha384" string => "ec664e889ed6c1b2763cacf7899d95b7f347373eb982e523419feea3aa362d891b3bf025f292267a5854049091789c3e"; + "null_sha512" string => "be688838ca8686e5c90689bf2ab585cef1137c999b48c70b92f67a5c34dc15697b5d11c982ed6d71be1e1e7f7b4e0733884aa97c3f7a339a8ed03577cf74be09"; + + "easy_md5" string => "d8e8fca2dc0f896fd7cb4cb0031ba249"; + "easy_sha1" string => "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83"; + "easy_sha256" string => "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2"; + "easy_sha384" string => "109bb6b5b6d5547c1ce03c7a8bd7d8f80c1cb0957f50c4f7fda04692079917e4f9cad52b878f3d8234e1a170b154b72d"; + "easy_sha512" string => "0e3e75234abc68f4378a86b3f4b32a198ba301845b0cd6e50106e874345700cc6663a86c1ea125dc5e92be17c98f9a0f85ca9d5f595db2012f7cc3571945c123"; + + "hard_md5" string => "e80a705b338f8bd4e2525a2cec846233"; + "hard_sha1" string => "305636f0563c179c71955612bff716648d2aa9bf"; + "hard_sha256" string => "fbfa93af3ddc81df9f812a6d2eff1a40d53160692c81f9f8cfffa9b4562a1749"; + "hard_sha384" string => "712843c71d5ca0b6a2b663251b8b5d08abe5fb55adc4445e6fdf759e5429a98f2c762a0f82612d4e42c4703474b58eb5"; + "hard_sha512" string => "c9d45d2669a941d4e5014ac291fd0cac0c7717604476416cad118898f238a2887057269e70ac5cad972cf5b8cbb0673e985285b0cc69810d4fee507b8b89067c"; + + "algos" slist => { @(test.algos) }; + "strings" slist => { @(init.strings) }; + + classes: + "ok_$(strings)_$(algos)" expression => strcmp("$(test.$(strings)_$(algos))", "$($(strings)_$(algos))"); + "not_ok_$(strings)_$(algos)" not => strcmp("$(test.$(strings)_$(algos))", "$($(strings)_$(algos))"); + + "ok_hmatch_$(strings)_$(algos)" expression => hashmatch("$(G.testfile).$(strings).txt", $(algos), "$($(strings)_$(algos))"); + "not_ok_hmatch_$(strings)_$(algos)" not => hashmatch("$(G.testfile).$(strings).txt", $(algos), "$($(strings)_$(algos))"); + + "ok_filehash_$(strings)_$(algos)" expression => strcmp(file_hash("$(G.testfile).$(strings).txt", $(algos)), "$($(strings)_$(algos))"); + "not_ok_filehash_$(strings)_$(algos)" not => strcmp(file_hash("$(G.testfile).$(strings).txt", $(algos)), "$($(strings)_$(algos))"); + + "ok_null" and => { + "ok_null_md5", + "ok_null_sha1", + "ok_null_sha256", + "ok_null_sha384", + "ok_null_sha512", + }; + + "ok_easy" and => { + "ok_easy_md5", + "ok_easy_sha1", + "ok_easy_sha256", + "ok_easy_sha384", + "ok_easy_sha512", + }; + + "ok_hard" and => { + "ok_hard_md5", + "ok_hard_sha1", + "ok_hard_sha256", + "ok_hard_sha384", + "ok_hard_sha512", + }; + + "ok_hmatch_null" and => { + "ok_hmatch_null_md5", + "ok_hmatch_null_sha1", + "ok_hmatch_null_sha256", + "ok_hmatch_null_sha384", + "ok_hmatch_null_sha512", + }; + + "ok_hmatch_easy" and => { + "ok_hmatch_easy_md5", + "ok_hmatch_easy_sha1", + "ok_hmatch_easy_sha256", + "ok_hmatch_easy_sha384", + "ok_hmatch_easy_sha512", + }; + + "ok_hmatch_hard" and => { + "ok_hmatch_hard_md5", + "ok_hmatch_hard_sha1", + "ok_hmatch_hard_sha256", + "ok_hmatch_hard_sha384", + "ok_hmatch_hard_sha512", + }; + + "ok_filehash_null" and => { + "ok_filehash_null_md5", + "ok_filehash_null_sha1", + "ok_filehash_null_sha256", + "ok_filehash_null_sha384", + "ok_filehash_null_sha512", + }; + + "ok_filehash_easy" and => { + "ok_filehash_easy_md5", + "ok_filehash_easy_sha1", + "ok_filehash_easy_sha256", + "ok_filehash_easy_sha384", + "ok_filehash_easy_sha512", + }; + + "ok_filehash_hard" and => { + "ok_filehash_hard_md5", + "ok_filehash_hard_sha1", + "ok_filehash_hard_sha256", + "ok_filehash_hard_sha384", + "ok_filehash_hard_sha512", + }; + + methods: + "" usebundle => dcs_passif_expected("ok_null,ok_easy,ok_hard,ok_filehash_null,ok_filehash_easy,ok_filehash_hard,ok_hmatch_null,ok_hmatch_easy,ok_hmatch_hard", + "", + $(this.promise_filename)), + inherit => "true"; + + reports: + DEBUG:: + "$(strings) / $(algos) failed, $(test.$(strings)_$(algos)) != $($(strings)_$(algos)) (original = $(init.$(strings)))" + if => "not_ok_$(strings)_$(algos)"; + + "$(strings) / $(algos) hashmatch failed $($(strings)_$(algos)) against $(G.testfile).$(strings).txt" + if => "not_ok_hmatch_$(strings)_$(algos)"; + + "$(strings) / $(algos) file_hash failed $($(strings)_$(algos)) against $(G.testfile).$(strings).txt" + if => "not_ok_filehash_$(strings)_$(algos)"; + + EXTRA:: + "$(strings) / $(algos) is OK" if => "ok_$(strings)_$(algos)"; + "$(strings) / $(algos) hashmatch is OK" if => "ok_hmatch_$(strings)_$(algos)"; + "$(strings) / $(algos) file_hash is OK" if => "ok_filehash_$(strings)_$(algos)"; +} diff --git a/tests/acceptance/01_vars/02_functions/hash_to_int.cf b/tests/acceptance/01_vars/02_functions/hash_to_int.cf new file mode 100644 index 0000000000..2568440811 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/hash_to_int.cf @@ -0,0 +1,60 @@ +####################################################### +# +# Test hash_to_int() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "a_9" int => hash_to_int(9,10,"string"); + "b_9" int => hash_to_int(9,9,"doesn't"); + "c_9" int => hash_to_int(10,9,"matter"); + "empty_27" int => hash_to_int(0,100,""); + "empty_5327" int => hash_to_int(100,10000,""); + "empty_n2" int => hash_to_int("-2","-1",""); + "short_9872" int => hash_to_int("-10000","10001","shortstring"); + "long_n893" int => + hash_to_int("-1000","1001", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\\!\"#$%&/()=?.:,;-_"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok_a" expression => strcmp("$(test.a_9)", "9"); + "ok_b" expression => strcmp("$(test.b_9)", "9"); + "ok_c" expression => strcmp("$(test.c_9)", "9"); + "ok_d" expression => strcmp("$(test.empty_27)", "27"); + "ok_e" expression => strcmp("$(test.empty_5327)", "5327"); + "ok_f" expression => strcmp("$(test.empty_n2)", "-2"); + "ok_g" expression => strcmp("$(test.long_n893)", "-893"); + "ok_h" expression => strcmp("$(test.short_9872)", "9872"); + "ok" and => {ok_a, ok_b, ok_c, ok_d, ok_e, ok_f, ok_g, ok_h}; + + reports: + DEBUG:: + "a_9: $(test.a_9)"; + "b_9: $(test.b_9)"; + "c_9: $(test.c_9)"; + "empty_27: $(test.empty_27)"; + "empty_5327: $(test.empty_5327)"; + "empty_n2: $(test.empty_n2)"; + "long_n893: $(test.long_n893)"; + "short_9872: $(test.short_9872)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/ifelse.cf b/tests/acceptance/01_vars/02_functions/ifelse.cf new file mode 100644 index 0000000000..e33851efc3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/ifelse.cf @@ -0,0 +1,58 @@ +####################################################### +# +# Test ifelse() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common init +{ + classes: + "myclass" expression => "any"; +} + +####################################################### + +bundle agent test +{ + classes: + "myclass2" expression => "any"; + vars: + "one" string => ifelse(1); + "single" string => ifelse("single string parameter"); + "hardclass" string => ifelse("cfengine", "hardclass OK", "hardclass broken"); + "empty" string => ifelse("cfengine", "", "hardclass broken"); + "empty2" string => ifelse("!cfengine", "", "hardclass expected"); + "five" string => ifelse("this is not true", "5 parameters broken", + "this is also not true", "5 parameters broken 2", + "5 parameters OK"); + "unresolved1" string => ifelse( isvariable( "missing" ), "$(missing)", "expected fallback 1"); + "unresolved2" string => ifelse( isvariable( "missing" ), "$($(missing))", "expected fallback 2"); + + any:: + "mystring" string => ifelse("any","pass","!any","$(foo)","bar"); + + myclass2:: + "expression" string => ifelse("myclass.myclass2", "bundle class OK", "bundle class broken"); + + reports: + "ifelse result: $(mystring)"; +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/ifelse.cf.expected.json b/tests/acceptance/01_vars/02_functions/ifelse.cf.expected.json new file mode 100644 index 0000000000..0689e517ef --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/ifelse.cf.expected.json @@ -0,0 +1,11 @@ +{ + "empty": "", + "empty2": "hardclass expected", + "expression": "bundle class OK", + "five": "5 parameters OK", + "hardclass": "hardclass OK", + "one": "1", + "single": "single string parameter", + "unresolved1": "expected fallback 1", + "unresolved2": "expected fallback 2" +} diff --git a/tests/acceptance/01_vars/02_functions/ifelse_isvariable-ENT-4653.cf b/tests/acceptance/01_vars/02_functions/ifelse_isvariable-ENT-4653.cf new file mode 100644 index 0000000000..294d77d8a8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/ifelse_isvariable-ENT-4653.cf @@ -0,0 +1,35 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-4653" } + string => "Test that ifelse can use the result of isvariable with 3 arguments even when they contain variables that don't resolve"; + + vars: + "lookup" string => "THIS_IS_NOT_A_DEFINED_VARIABLE"; + + "test" string => + ifelse( isvariable( "$(lookup)" ), + "$($(lookup))", + "FALLBACK"); +} + +bundle agent check +{ + + reports: + '$(this.promise_filename) Pass' + if => strcmp( "FALLBACK", $(test.test) ) ; + + '$(this.promise_filename) FAIL' + if => not( isvariable( "test.test" ) ); + + '$(this.promise_filename) FAIL' + if => not( strcmp( "FALLBACK", $(test.test) ) ); +} diff --git a/tests/acceptance/01_vars/02_functions/ifelse_isvariable.cf b/tests/acceptance/01_vars/02_functions/ifelse_isvariable.cf new file mode 100644 index 0000000000..f1eddae20f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/ifelse_isvariable.cf @@ -0,0 +1,41 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "passwd_file" + string => "/tmp/custom_passwd"; +} + +bundle agent test +{ + meta: + "description" + string => "Test that ifelse can use the result of isvariable as a class identifer"; + + vars: + # Since init.passwd_file is defined, I expect the value to be + # the value of init.passwd_file + "use_passwd_file" + string => ifelse( isvariable("init.passwd_file"), $(init.passwd_file), + "/etc/passwd"); + + # Since init.shadow_file is not defined, I expect that the value + # will be "/etc/shadow" + "use_shadow_file" + string => ifelse( isvariable("init.shadow_file"), $(init.shadow_file), + "/etc/shadow"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/ifelse_isvariable.cf.expected.json b/tests/acceptance/01_vars/02_functions/ifelse_isvariable.cf.expected.json new file mode 100644 index 0000000000..4ad51c2c3c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/ifelse_isvariable.cf.expected.json @@ -0,0 +1,4 @@ +{ + "use_passwd_file": "/tmp/custom_passwd", + "use_shadow_file": "/etc/shadow" +} diff --git a/tests/acceptance/01_vars/02_functions/ifelse_undefined.cf b/tests/acceptance/01_vars/02_functions/ifelse_undefined.cf new file mode 100644 index 0000000000..96126464ec --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/ifelse_undefined.cf @@ -0,0 +1,37 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-4653" } + string => "Test that ifelse works with undefined variables in the second or third arguments"; + + vars: + "test_one" string => ifelse( "no_such_class", "$(no_such_var)", "test_one_expected_value" ); + "test_two" string => ifelse( "any", "test_two_expected_value", "$(no_such_var)" ); +} + +bundle agent check +{ + + reports: + '$(this.promise_filename) Pass' + if => and( + strcmp( "test_one_expected_value", $(test.test_one) ), + strcmp( "test_two_expected_value", $(test.test_two) ) ); + + '$(this.promise_filename) FAIL' + if => or( + not( isvariable( "test.test_one" ) ), + not( isvariable( "test.test_two" ) ) ); + + '$(this.propmise_filename) FAIL' + if => or( + not(strcmp( "test_one_expected_value", $(test.test_one) ) ), + not(strcmp( "test_two_expected_value", $(test.test_two) ) ) ); +} diff --git a/tests/acceptance/01_vars/02_functions/inline_json.cf b/tests/acceptance/01_vars/02_functions/inline_json.cf new file mode 100644 index 0000000000..0d27f9e642 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/inline_json.cf @@ -0,0 +1,67 @@ +########################################################### +# +# Test inline JSON expansion +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + vars: + "z" string => "100"; + "foo" string => "bar"; + + # test mergedata() as well + "var" data => mergedata('[]', '["x"]'); + + # test mapdata() as well + "var2" data => mapdata("none", '$(this.v)', '["x"]'); + + # the basic building block + "var3" data => data_expand('["linux"]'); + + # variable references with a bundle + "var4" data => data_expand('[test.var3]'); + "var5" data => data_expand('[test.var3,]'); + + # make sure bareword keys are not expanded + "var6" data => data_expand('[foo]'); + "var7" data => data_expand('{foo:var3}'); + + # variable references without a bundle + "var8" data => data_expand('[var3]'); + "var9" data => data_expand('{ "x": var3 }'); + + # regular lookup with bundle name into a map value + "varA" data => data_expand('{ "fullx": test.var3 }'); + + # try to trigger off-by-one errors + "varB" data => data_expand('[z]'); + + # intentionally broken to try to trigger off-by-one errors + "varC" data => data_expand('[z'); + + # inline array lookup + "varD" data => data_expand('[var3[0]]'); + "varE" data => data_expand('[varA[fullx]]'); + "varF" data => data_expand('[var9[x]]'); + +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/inline_json.cf.expected.json b/tests/acceptance/01_vars/02_functions/inline_json.cf.expected.json new file mode 100644 index 0000000000..09a4b16a3f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/inline_json.cf.expected.json @@ -0,0 +1,62 @@ +{ + "foo": "bar", + "var": [ + "x" + ], + "var2": [ + "x" + ], + "var3": [ + "linux" + ], + "var4": [ + [ + "linux" + ] + ], + "var5": [ + [ + "linux" + ] + ], + "var6": [ + "bar" + ], + "var7": { + "foo": [ + "linux" + ] + }, + "var8": [ + [ + "linux" + ] + ], + "var9": { + "x": [ + "linux" + ] + }, + "varA": { + "fullx": [ + "linux" + ] + }, + "varB": [ + "100" + ], + "varD": [ + "linux" + ], + "varE": [ + [ + "linux" + ] + ], + "varF": [ + [ + "linux" + ] + ], + "z": "100" +} diff --git a/tests/acceptance/01_vars/02_functions/int.cf b/tests/acceptance/01_vars/02_functions/int.cf new file mode 100644 index 0000000000..887060ef22 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/int.cf @@ -0,0 +1,82 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ + vars: + "expect" data => '{ + "pos_int": { + "in": "12", + "out": "12" + }, + "pos_int_k": { + "in": "21k", + "out": "21000" + }, + "neg_int": { + "in": "-34", + "out": "-34" + }, + "neg_int_k": { + "in": "-42k", + "out": "-42000" + }, + "pos_real": { + "in": "56.789", + "out": "56" + }, + "pos_real_k": { + "in": "65.987k", + "out": "65987" + }, + "neg_real": { + "in": "-78.789", + "out": "-78" + }, + "neg_real_k": { + "in": "-87.987k", + "out": "-87987" + }, + }'; + + "cases" slist => getindices( @{expect} ); + "all_ok_classes" slist => maplist( "ok_${this}", @{cases} ); +} + +bundle common test +{ + meta: + "description" -> { "CFE-3616" } + string => "Test whether the int() policy function properly returns integer from string"; + + classes: + "ok_${init.cases}" + expression => strcmp( int( "${init.expect[${init.cases}][in]}" ), + "${init.expect[${init.cases}][out]}" + ); +} + +bundle agent check +{ + vars: + "difference" + slist => difference( @{init.all_ok_classes}, + classesmatching( "ok_.*" ) + ); + + classes: + "ok" + expression => strcmp( "0", length( @{difference} ) ); + + methods: + "Pass/FAIL" + usebundle => dcs_passif( "ok", ${this.promise_filename} ), + inherit => "true"; + + reports: + !ok.DEBUG:: + "Ok classes not matching: ${with}" with => join(", ", @{difference}); +} diff --git a/tests/acceptance/01_vars/02_functions/isreadable.cf b/tests/acceptance/01_vars/02_functions/isreadable.cf new file mode 100644 index 0000000000..a26260413e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/isreadable.cf @@ -0,0 +1,117 @@ +############################################################################## +# +# Test policy function isreadable +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +############################################################################## + +bundle agent init +{ + files: + # Has content + "$(G.testfile)_1" + content => "Hello CFEngine!"; + # No content + "$(G.testfile)_2" + content => ""; + # Does not exists + "$(G.testfile)_3" + delete => tidy; + # Not a regular file + "$(G.testfile)_4/." + create => "true"; +} + +############################################################################## + +bundle agent test +{ + meta: + "description" -> { "ENT-9380" } + string => "Test policy function filereadable"; + + "test_soft_fail" -> { "ENT-9930" } + string => "hpux|aix", + meta => { "ENT-9930" }; +} + +############################################################################## + +bundle agent check +{ + classes: + # Possibly block for 3 seconds (default) + "test_1" # Has content + expression => isreadable("$(G.testfile)_1"); + "test_2" # No content + expression => isreadable("$(G.testfile)_2"); + "test_3" # Does not exist + expression => not(isreadable("$(G.testfile)_3")); + "test_4" # Not a regular file + expression => not(isreadable("$(G.testfile)_4")); + + # Possibly block forever + "test_5" # Has content + expression => isreadable("$(G.testfile)_1", 0); + "test_6" # No content + expression => isreadable("$(G.testfile)_2", 0); + "test_7" # Does not exist + expression => not(isreadable("$(G.testfile)_3", 0)); + "test_8" # Not a regular file + expression => not(isreadable("$(G.testfile)_4", 0)); + + # Possibly block for 5 seconds + "test_9" # Has content + expression => isreadable("$(G.testfile)_1", 5); + "test_10" # No content + expression => isreadable("$(G.testfile)_2", 5); + "test_11" # Does not exists + expression => not(isreadable("$(G.testfile)_3", 5)); + "test_12" # Not a regular file + expression => not(isreadable("$(G.testfile)_4", 5)); + "ok" + expression => and("test_1", "test_2", "test_3", "test_4", + "test_5", "test_6", "test_7", "test_8", + "test_9", "test_10", "test_11", "test_12"); + + reports: + DEBUG.!test_1:: + "Expected 'test_1' to be defined, but 'test_1' was not defined"; + DEBUG.!test_2:: + "Expected 'test_2' to be defined, but 'test_2' was not defined"; + DEBUG.!test_3:: + "Expected 'test_3' to be defined, but 'test_3' was not defined"; + DEBUG.!test_4:: + "Expected 'test_4' to be defined, but 'test_4' was not defined"; + + DEBUG.!test_5:: + "Expected 'test_5' to be defined, but 'test_5' was not defined"; + DEBUG.!test_6:: + "Expected 'test_6' to be defined, but 'test_6' was not defined"; + DEBUG.!test_7:: + "Expected 'test_7' to be defined, but 'test_7' was not defined"; + DEBUG.!test_8:: + "Expected 'test_8' to be defined, but 'test_8' was not defined"; + + DEBUG.!test_9:: + "Expected 'test_9' to be defined, but 'test_9' was not defined"; + DEBUG.!test_10:: + "Expected 'test_10' to be defined, but 'test_10' was not defined"; + DEBUG.!test_11:: + "Expected 'test_11' to be defined, but 'test_11' was not defined"; + DEBUG.!test_12:: + "Expected 'test_12' to be defined, but 'test_12' was not defined"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/iterating_variablesmatching_results.cf b/tests/acceptance/01_vars/02_functions/iterating_variablesmatching_results.cf new file mode 100644 index 0000000000..5f88162cfa --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/iterating_variablesmatching_results.cf @@ -0,0 +1,23 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test { + meta: + "description" -> { "CFE-2593" } + string => "Test that the agent won't crash trying to iterate over variablesmatching() results"; +} +bundle agent check { + vars: + "some_var" + string => "some value"; + + "make_cfe_crash" + slist => variablesmatching("$(this.namespace):$(this.bundle)\..*"); + + reports: + "DEBUG: '$(make_cfe_crash)' = '$($(make_cfe_crash))'"; + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/01_vars/02_functions/join.cf b/tests/acceptance/01_vars/02_functions/join.cf new file mode 100644 index 0000000000..964fcee246 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/join.cf @@ -0,0 +1,81 @@ +####################################################### +# +# test join() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "a" slist => { "b", "c", "a" }; + "b" slist => { "100", "9", "10" }; + "c" slist => { }; + "d" slist => { "", "a", "", "b" }; + "e" slist => { "a", "1", "b" }; + "f" rlist => { "100", "200", "300" }; + "g" rlist => { "1.11", "-2.22", "-3.33" }; + "h" ilist => { "-10", "0", "200" }; + "i" data => parsejson('[ 1, 2, "", 3000, "" ]'); + "j" data => parsejson('[ 1, 2, [ 3, 4, 5 ], null, true, false ]'); + "k" data => parsejson('{}'); + "l" data => parsejson('{ "a": 100, "b": 200, "c": null}'); + "m" slist => { "cf_null" }; + "n" slist => { "a", "b", "c", "cf_null" }; + "o" slist => { @(a), @(c) }; + "p" slist => { @(c), @(m) }; + "q" slist => { ":", ":" }; + "r" slist => { ":", @(c) }; + "s" slist => { ":", @(c), @(m) }; + "t" slist => { @(n), @(m), @(n), ":" }; + + "lists" slist => { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t" }; +} + +####################################################### + +bundle agent test +{ + # "expected_a" string => "b:c:a"; + # "expected_b" string => "100:9:10"; + # "expected_c" string => ""; + # "expected_d" string => ":a::b"; + # "expected_e" string => "a:1:b"; + # "expected_f" string => "100:200:300"; + # "expected_g" string => "1.11:-2.22:-3.33"; + # "expected_h" string => "-10:0:200"; + # "expected_i" string => "1:2::3000:"; + # "expected_j" string => "1:2:true:false"; + # "expected_k" string => ""; + # "expected_l" string => "100:200"; + # "expected_m" string => "cf_null"; + # "expected_n" string => "a:b:c:cf_null"; + # "expected_o" string => "b:c:a"; + # "expected_p" string => "cf_null"; + # "expected_q" string => ":::"; + # "expected_r" string => ":"; + # "expected_s" string => "::cf_null"; + # "expected_t" string => "a:b:c:cf_null:cf_null:a:b:c:cf_null::"; + vars: + "lists" slist => { @(init.lists) }; + "join_$(lists)" string => join(":", "init.$(lists)"); +} + + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/join.cf.expected.json b/tests/acceptance/01_vars/02_functions/join.cf.expected.json new file mode 100644 index 0000000000..4e206fe349 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/join.cf.expected.json @@ -0,0 +1,44 @@ +{ + "join_a": "b:c:a", + "join_b": "100:9:10", + "join_c": "", + "join_d": ":a::b", + "join_e": "a:1:b", + "join_f": "100:200:300", + "join_g": "1.11:-2.22:-3.33", + "join_h": "-10:0:200", + "join_i": "1:2::3000:", + "join_j": "1:2:true:false", + "join_k": "", + "join_l": "100:200", + "join_m": "cf_null", + "join_n": "a:b:c:cf_null", + "join_o": "b:c:a", + "join_p": "cf_null", + "join_q": ":::", + "join_r": ":", + "join_s": "::cf_null", + "join_t": "a:b:c:cf_null:cf_null:a:b:c:cf_null::", + "lists": [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/join_mapped_list.cf b/tests/acceptance/01_vars/02_functions/join_mapped_list.cf new file mode 100644 index 0000000000..b362f8cccc --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/join_mapped_list.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Redmine #2614: join should iterate over all elements of a mapped list +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "elements" slist => { "a", "b", "c" }; + "expected" string => "a_x:b_x:c_x"; +} + +####################################################### + +bundle agent test +{ + vars: + "map" slist => maplist("$(this)_x", "init.elements"); + "join" string => join(":","map"); +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp($(init.expected), $(test.join)); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/length.cf b/tests/acceptance/01_vars/02_functions/length.cf new file mode 100644 index 0000000000..7c50f21627 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/length.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Test 'length' function +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "normal_list" slist => { "b", "c", "a" }; # 3 + "empty_list" slist => { }; # 0 + "normal_object" data => parsejson('{ "a": 1, "b": 2 }'); # 2 + "empty_object" data => parsejson('{}'); # 0 + + "normal_list_len" int => length(normal_list); + "empty_list_len" int => length(empty_list); + "normal_object_len" int => length(normal_object); + "empty_object_len" int => length(empty_object); + + "inline_object_len" int => length('{ "a": 1, "b": 2 }'); # 2 + "inline_array_len" int => length('[ "a", 1, "b", 2 ]'); # 4 +} + + +####################################################### + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/length.cf.expected.json b/tests/acceptance/01_vars/02_functions/length.cf.expected.json new file mode 100644 index 0000000000..ab7e74b870 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/length.cf.expected.json @@ -0,0 +1,20 @@ +{ + "empty_list": [], + "empty_list_len": "0", + "empty_object": { + }, + "empty_object_len": "0", + "inline_array_len": "4", + "inline_object_len": "2", + "normal_list": [ + "b", + "c", + "a" + ], + "normal_list_len": "3", + "normal_object": { + "a": 1, + "b": 2 + }, + "normal_object_len": "2" +} diff --git a/tests/acceptance/01_vars/02_functions/long_string_from_module.cf b/tests/acceptance/01_vars/02_functions/long_string_from_module.cf new file mode 100644 index 0000000000..66b1f6d72a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/long_string_from_module.cf @@ -0,0 +1,57 @@ +####################################################### +# +# Test that we don't crash when reading in large strings from a module +# Redmine:3957 (https://cfengine.com/dev/issues/3957) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).module" + create => "true", + perms => m("700"), + edit_defaults => empty, + edit_line => seed_module; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.testfile).module" + module => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any", + comment => "If we made it to here, we didn't crash"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + + +bundle edit_line seed_module +{ + insert_lines: + "#!/bin/bash"; + "echo '@keys= { \"ssh-dss YavBsyK3sHDac4Gj4nDbVH9E7OfRPi16DADNOWGKEjWiBB0cCe0UY6xxEgcYuwOOl8HUGqqzLOByvUbo1hENDQldKFt8N7WIb2E9gXFtGm0Sf0NlYARALj1nhLju9hodpGxkr4vKioW1fZQBmBqsI7Ky8ZhzU5p4CAm4uCbxAZuiIhDTAcxj7RlDy3fe9WBw7v0cSIbu8E3zEbPn0VQjTduLCJtFOb5LvUxpxUsHMhHu0xS8DvXIwf7l83cQ0XZtwyIgbF3ZxjIZaPyYhKZRAV1qCze4BlcOerJeVYOiKjpXJlOoBprxy2SfEYVS4Khun0efkmsmBAfllKUUNCqsK9j6oWo7BvNbXqzkl2ULJd6h0LNy9jW8IXnnrfWknLGa7lksVmL3afSAufDAb7yRESaUP5KmcD3ghP3Nvu7fy9hz7nIAPgj2dQOwnFRbkv9Svoi9YmCtvBWYAcwIUVNJBmwXe5j5xJyt5vOyRSI2ooJ9mk69UOCsIM8PEnEXwtHcsg9fDjfu9ChFY2HWZPWeNXH05SATnSyLgclQsZcazhQXPoxZbcqR80mfuRX9ymEzNwZJ1jJUERnj9PZkSInu6mFVRjTaQjg8yvqMGRxWknP8aURCDh5rr4Na23jULVVI66heWOi7nzLqBg5U8GsbLsocM8zBam8bhXfU0KvpBo5wlUtHnDWHoMlS3ktr25ldrp3zLuywhHe5vGUbg2E0gr4xB3oPAyaDa3MGQiVXPfngu5CJhwIXmeN5JtIBA0S46ihg7lQ6IU8kAaRHwhWh3TokoNFOYE8R5i24gpYNF8dgCRGqSZIT5V7wuR3qG1Wau963ILuX8EFOSu7xDc5qnGTem3FwICTpSdnZNqpzRt4Ipqsn4Pa2tFFpHlzDwKFRPS7r8v4QX7acqdTSCUOuZ68GwDRlvHIz5mHUf1XLY3FH2InuWFsonJAZwmEbX5evWXFgyJHFlplQcGJvyNdL0T6oBmbG3ssSMoRnqnsk8XjGWtUyvYoQmOY8HWlMhf4V3v78C4k4TnCbN4LmnDYaDcnXMejrUMYDk6QwW3fdTF5ZyjJMy4ANu2fJfKysZMc1Cc== root@host1\", \"ssh-dss a58AZSxQlmcq33EBCW1GuhpkYCgUfr7o5A1arQZ2dr8I4kldCF76mpl6o2CFthZs4YUU8LewbIDDPqjhS2WwafTXeMEvPMtmLufk6E5njNy2WQtAnmGc9R21qIteTgikY9ubRCM3hlhzJ1wGSKCsE5oBHDQC0thk1ljJGHzsEl0AuVClkUq3yz4eWAsiRdbD9QY7ddD6zA61aHomuczKU5F0VvyO8gRQmWV7b6lysoUFcMgCPVf3UaDdg7L7vvBVkQN2vGCh9CCJz5OkBShlZVNaAXd5TkKJNmsTuBDscPyxeCBAk8sai69f1NNtTeAyWTtSu1KiqTDvD9aal93MhpZnPdOZzNr7etK4C7HuPD0uFjdvKZy1H55rpwNHnJ4GojqiNG1VvN5bGLa3sSLiKOngtMBCokdtHpZn2eHD7oLUROTIG3ZXqFGGvfKEP5zlpvJz3392n4PDQ7EKuNFPhyNQpVXEIAQEDcmeWMopVTGezLoFJG01hKMPxs5QWF7qetVLi1pCjmlUpqgE8c81WGxvMe7ooMtQbeVNulX3qBC3rZhYKtk0R5AA8JxmxHSLYlLFbtnR1PA97hnRvnvlfk92i7WL1hjJsMl29LOrubi554Dr9N2uVUrCFcPZMK45PY0TiRH82AKFmkM8mbM0rndJxoJobZsqRAGHVIkcS53hxMT69liRxlCyubwcxgDaqmeQnJU2Ug0YyFs1uxt4NT9laJ0CO2IxhkbmGeDGw1FJqKyc8Haov263cFMcB97I3gyNHccsAynQnxpMS1ltTFXalghuochdue4unbq0Ty2PfS4jPMkavBlMYN8UZdnyZHuUhycwBJri1Grv5kf2SP00P6NQhuB8kwjqoTG8ay5fKWvhDrVetd7tPuj4dMouHuDeaLJInc7Cz0S5tQOuMfRhpPqk1E8A0YnuNCyPDNuW75rkXZkxP9cYSVWeDa1wgOyNLZDTzYts82qiu9kxLbZ184jowJ8rru4UB20JRtoehnD2nGU1NfegD1qOBgQHtDuB18xggfvOgvUNXBrQpICSz0JqszpftAgV5TJq1FjOdjUo1kOCFuqqi6C4S0Siho== root@host2\" }'"; +} diff --git a/tests/acceptance/01_vars/02_functions/long_string_from_text_file.cf b/tests/acceptance/01_vars/02_functions/long_string_from_text_file.cf new file mode 100644 index 0000000000..1d68b99942 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/long_string_from_text_file.cf @@ -0,0 +1,56 @@ +####################################################### +# +# Test that we don't crash when reading in large strings from a file +# Redmine:3957 (https://cfengine.com/dev/issues/3957) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).data" + create => "true", + edit_defaults => empty, + edit_line => seed_data; +} + +####################################################### + +bundle agent test +{ + vars: + "keyfile" string => "$(G.testfile).data"; + "keys" slist => readstringlist("$(keyfile)", "#[^\n]*", "\n", 10, 3072); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any", + comment => "If we made it to here, we didn't crash"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + + +bundle edit_line seed_data +{ + insert_lines: + "ssh-dss YavBsyK3sHDac4Gj4nDbVH9E7OfRPi16DADNOWGKEjWiBB0cCe0UY6xxEgcYuwOOl8HUGqqzLOByvUbo1hENDQldKFt8N7WIb2E9gXFtGm0Sf0NlYARALj1nhLju9hodpGxkr4vKioW1fZQBmBqsI7Ky8ZhzU5p4CAm4uCbxAZuiIhDTAcxj7RlDy3fe9WBw7v0cSIbu8E3zEbPn0VQjTduLCJtFOb5LvUxpxUsHMhHu0xS8DvXIwf7l83cQ0XZtwyIgbF3ZxjIZaPyYhKZRAV1qCze4BlcOerJeVYOiKjpXJlOoBprxy2SfEYVS4Khun0efkmsmBAfllKUUNCqsK9j6oWo7BvNbXqzkl2ULJd6h0LNy9jW8IXnnrfWknLGa7lksVmL3afSAufDAb7yRESaUP5KmcD3ghP3Nvu7fy9hz7nIAPgj2dQOwnFRbkv9Svoi9YmCtvBWYAcwIUVNJBmwXe5j5xJyt5vOyRSI2ooJ9mk69UOCsIM8PEnEXwtHcsg9fDjfu9ChFY2HWZPWeNXH05SATnSyLgclQsZcazhQXPoxZbcqR80mfuRX9ymEzNwZJ1jJUERnj9PZkSInu6mFVRjTaQjg8yvqMGRxWknP8aURCDh5rr4Na23jULVVI66heWOi7nzLqBg5U8GsbLsocM8zBam8bhXfU0KvpBo5wlUtHnDWHoMlS3ktr25ldrp3zLuywhHe5vGUbg2E0gr4xB3oPAyaDa3MGQiVXPfngu5CJhwIXmeN5JtIBA0S46ihg7lQ6IU8kAaRHwhWh3TokoNFOYE8R5i24gpYNF8dgCRGqSZIT5V7wuR3qG1Wau963ILuX8EFOSu7xDc5qnGTem3FwICTpSdnZNqpzRt4Ipqsn4Pa2tFFpHlzDwKFRPS7r8v4QX7acqdTSCUOuZ68GwDRlvHIz5mHUf1XLY3FH2InuWFsonJAZwmEbX5evWXFgyJHFlplQcGJvyNdL0T6oBmbG3ssSMoRnqnsk8XjGWtUyvYoQmOY8HWlMhf4V3v78C4k4TnCbN4LmnDYaDcnXMejrUMYDk6QwW3fdTF5ZyjJMy4ANu2fJfKysZMc1Cc== root@host1"; + "ssh-dss a58AZSxQlmcq33EBCW1GuhpkYCgUfr7o5A1arQZ2dr8I4kldCF76mpl6o2CFthZs4YUU8LewbIDDPqjhS2WwafTXeMEvPMtmLufk6E5njNy2WQtAnmGc9R21qIteTgikY9ubRCM3hlhzJ1wGSKCsE5oBHDQC0thk1ljJGHzsEl0AuVClkUq3yz4eWAsiRdbD9QY7ddD6zA61aHomuczKU5F0VvyO8gRQmWV7b6lysoUFcMgCPVf3UaDdg7L7vvBVkQN2vGCh9CCJz5OkBShlZVNaAXd5TkKJNmsTuBDscPyxeCBAk8sai69f1NNtTeAyWTtSu1KiqTDvD9aal93MhpZnPdOZzNr7etK4C7HuPD0uFjdvKZy1H55rpwNHnJ4GojqiNG1VvN5bGLa3sSLiKOngtMBCokdtHpZn2eHD7oLUROTIG3ZXqFGGvfKEP5zlpvJz3392n4PDQ7EKuNFPhyNQpVXEIAQEDcmeWMopVTGezLoFJG01hKMPxs5QWF7qetVLi1pCjmlUpqgE8c81WGxvMe7ooMtQbeVNulX3qBC3rZhYKtk0R5AA8JxmxHSLYlLFbtnR1PA97hnRvnvlfk92i7WL1hjJsMl29LOrubi554Dr9N2uVUrCFcPZMK45PY0TiRH82AKFmkM8mbM0rndJxoJobZsqRAGHVIkcS53hxMT69liRxlCyubwcxgDaqmeQnJU2Ug0YyFs1uxt4NT9laJ0CO2IxhkbmGeDGw1FJqKyc8Haov263cFMcB97I3gyNHccsAynQnxpMS1ltTFXalghuochdue4unbq0Ty2PfS4jPMkavBlMYN8UZdnyZHuUhycwBJri1Grv5kf2SP00P6NQhuB8kwjqoTG8ay5fKWvhDrVetd7tPuj4dMouHuDeaLJInc7Cz0S5tQOuMfRhpPqk1E8A0YnuNCyPDNuW75rkXZkxP9cYSVWeDa1wgOyNLZDTzYts82qiu9kxLbZ184jowJ8rru4UB20JRtoehnD2nGU1NfegD1qOBgQHtDuB18xggfvOgvUNXBrQpICSz0JqszpftAgV5TJq1FjOdjUo1kOCFuqqi6C4S0Siho== root@host2"; +} diff --git a/tests/acceptance/01_vars/02_functions/lsdir.cf b/tests/acceptance/01_vars/02_functions/lsdir.cf new file mode 100644 index 0000000000..90ddc7436b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/lsdir.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Test lsdir() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common files +{ + vars: + # * in filenames not allowed on win + windows:: + "names" slist => { "a", "bc", "d/e/f", "g/h/i/j", "klm/nop/qrs" }; + !windows:: + "names" slist => { "a", "bc", "d/e/f", "g/h/i/j", "klm/nop/qrs", "tu/*" }; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/$(files.names)" + create => "true"; + + reports: + DEBUG:: + "Created $(G.testdir)/$(files.names)"; +} + + +####################################################### + +bundle agent test +{ + vars: + "patterns[a]" string => "$(G.testdir)"; + "patterns[b]" string => "$(G.testdir)/b"; # no such directory + + "pnames" slist => getindices("patterns"); + + "found_unsorted[$(pnames)]" slist => lsdir("$(patterns[$(pnames)])", "[^.].*", "true"); + "found[$(pnames)]" slist => sort("found_unsorted[$(pnames)]", "lex"); + "found_string[$(pnames)]" string => join(",", "found[$(pnames)]"); + + reports: + DEBUG:: + "found pattern $(pnames) '$(patterns[$(pnames)])' => '$(found_string[$(pnames)])'"; +} + + +####################################################### + +bundle agent check +{ + vars: + windows:: + "expected[a]" string => "$(G.testdir)$(const.dirsep)a,$(G.testdir)$(const.dirsep)bc,$(G.testdir)$(const.dirsep)d,$(G.testdir)$(const.dirsep)g,$(G.testdir)$(const.dirsep)klm"; + !windows:: + "expected[a]" string => "$(G.testdir)/a,$(G.testdir)/bc,$(G.testdir)/d,$(G.testdir)/g,$(G.testdir)/klm,$(G.testdir)/tu"; + + any:: + "expected[b]" string => ""; + + "expects" slist => getindices("expected"); + + "fstring" slist => getindices("test.found_string"); + + "joint_condition" string => join(".", "expects"); + + classes: + "$(expects)" expression => strcmp("$(test.found_string[$(expects)])", "$(expected[$(expects)])"); + "ok" expression => "$(joint_condition)"; + + reports: + DEBUG:: + "pattern $(expects) matches as expected: '$(expected[$(expects)])'" + if => "$(expects)"; + + "pattern $(expects) does NOT match expected: '$(test.found_string[$(expects)])' != '$(expected[$(expects)])'" + if => "!$(expects)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/maparray.cf b/tests/acceptance/01_vars/02_functions/maparray.cf new file mode 100644 index 0000000000..0a8e99a5b6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/maparray.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test maparray() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "load1" data => parsejson('[ 1, 2, 3]'); + "load2" slist => { "eleme\"nt1", "element2", "element3" }; + "load3" data => parsejson('{ "x\\"x": "y\\"y" }'); + "load4" data => parsejson('[]'); + "load5[mykey]" slist => { "myvalue" }; + "load5[anotherkey]" string => "anothervalue"; + "load5[lastkey!]" slist => { "o\"ne", "two", "three" }; + "load6" data => parsejson('{ "a": { "b": "c" } }'); + + "static[x]" string => "xvalue"; + "static[x\"x]" string => "xxvalue"; + "static[0]" string => "0value"; + "static[1]" string => "1value"; + "static[2]" string => "2value"; + "static[lastkey!]" string => "lastvalue"; + "static[anotherkey]" string => "anothervalue"; + "static[mykey]" string => "myvalue"; + "static[a]" string => "avalue"; + + "spec1" string => "key = $(this.k)"; + "spec2" string => "key = $(this.k), value = ${this.v}"; + "spec3" string => "key = $(this.k), key2 = $(this.k[1]), value = ${this.v}"; + "spec4" string => "xvalue should be $(static[$(this.k)])"; + + "jsonspec1" string => '{ "key": "$(this.k)", "value": "$(this.v)"'; + "jsonspec2" string => '{ "key": "$(this.k)", "value": "$(this.v)" }'; + "jsonspec3" string => '[ "$(this.k)", "$(this.v)" ]'; + "jsonspec4" string => '{ "key": "$(this.k)", "key2": "$(this.k[1])", "value": "$(this.v)" }'; + + "X" slist => { "1", "2", "3", "4", "5", "6" }; + "Y" slist => { "1", "2", "3", "4" }; + + "maparray_$(X)_$(Y)" slist => sort(maparray("$(spec$(Y))", "load$(X)")); + "bad2" slist => maparray("", missingvar); + + "justastring" string => "me"; + "bad4" slist => maparray("", justastring); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/maparray.cf.expected.json b/tests/acceptance/01_vars/02_functions/maparray.cf.expected.json new file mode 100644 index 0000000000..4beadbc9e5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/maparray.cf.expected.json @@ -0,0 +1,154 @@ +{ + "X": [ + "1", + "2", + "3", + "4", + "5", + "6" + ], + "Y": [ + "1", + "2", + "3", + "4" + ], + "jsonspec1": "{ \"key\": \"$(this.k)\", \"value\": \"$(this.v)\"", + "jsonspec2": "{ \"key\": \"$(this.k)\", \"value\": \"$(this.v)\" }", + "jsonspec3": "[ \"$(this.k)\", \"$(this.v)\" ]", + "jsonspec4": "{ \"key\": \"$(this.k)\", \"key2\": \"$(this.k[1])\", \"value\": \"$(this.v)\" }", + "justastring": "me", + "load1": [ + 1, + 2, + 3 + ], + "load2": [ + "eleme\"nt1", + "element2", + "element3" + ], + "load3": { + "x"x": "y\"y" + }, + "load4": [], + "load5[anotherkey]": "anothervalue", + "load5[lastkey!]": [ + "o\"ne", + "two", + "three" + ], + "load5[mykey]": [ + "myvalue" + ], + "load6": { + "a": { + "b": "c" + } + }, + "maparray_1_1": [ + "key = 0", + "key = 1", + "key = 2" + ], + "maparray_1_2": [ + "key = 0, value = 1", + "key = 1, value = 2", + "key = 2, value = 3" + ], + "maparray_1_3": [ + "key = 0, key2 = $(this.k[1]), value = 1", + "key = 1, key2 = $(this.k[1]), value = 2", + "key = 2, key2 = $(this.k[1]), value = 3" + ], + "maparray_1_4": [ + "xvalue should be 0value", + "xvalue should be 1value", + "xvalue should be 2value" + ], + "maparray_2_1": [ + "key = 0", + "key = 1", + "key = 2" + ], + "maparray_2_2": [ + "key = 0, value = eleme\"nt1", + "key = 1, value = element2", + "key = 2, value = element3" + ], + "maparray_2_3": [ + "key = 0, key2 = $(this.k[1]), value = eleme\"nt1", + "key = 1, key2 = $(this.k[1]), value = element2", + "key = 2, key2 = $(this.k[1]), value = element3" + ], + "maparray_2_4": [ + "xvalue should be 0value", + "xvalue should be 1value", + "xvalue should be 2value" + ], + "maparray_3_1": [ + "key = x\"x" + ], + "maparray_3_2": [ + "key = x\"x, value = y\"y" + ], + "maparray_3_3": [ + "key = x\"x, key2 = $(this.k[1]), value = y\"y" + ], + "maparray_3_4": [ + "xvalue should be xxvalue" + ], + "maparray_4_1": [], + "maparray_4_2": [], + "maparray_4_3": [], + "maparray_4_4": [], + "maparray_5_1": [ + "key = anotherkey", + "key = lastkey!", + "key = mykey" + ], + "maparray_5_2": [ + "key = anotherkey, value = anothervalue", + "key = lastkey!, value = o\"ne", + "key = lastkey!, value = three", + "key = lastkey!, value = two", + "key = mykey, value = myvalue" + ], + "maparray_5_3": [ + "key = anotherkey, key2 = $(this.k[1]), value = anothervalue", + "key = lastkey!, key2 = $(this.k[1]), value = o\"ne", + "key = lastkey!, key2 = $(this.k[1]), value = three", + "key = lastkey!, key2 = $(this.k[1]), value = two", + "key = mykey, key2 = $(this.k[1]), value = myvalue" + ], + "maparray_5_4": [ + "xvalue should be anothervalue", + "xvalue should be lastvalue", + "xvalue should be myvalue" + ], + "maparray_6_1": [ + "key = a" + ], + "maparray_6_2": [ + "key = a, value = c" + ], + "maparray_6_3": [ + "key = a, key2 = b, value = c" + ], + "maparray_6_4": [ + "xvalue should be avalue" + ], + "spec1": "key = $(this.k)", + "spec2": "key = $(this.k), value = ${this.v}", + "spec3": "key = $(this.k), key2 = $(this.k[1]), value = ${this.v}", + "spec4": "xvalue should be $(static[$(this.k)])", + "static[0]": "0value", + "static[1]": "1value", + "static[2]": "2value", + "static[a]": "avalue", + "static[anotherkey]": "anothervalue", + "static[lastkey!]": "lastvalue", + "static[mykey]": "myvalue", + "static[x"x]": "xxvalue", + "static[x]": "xvalue" +} diff --git a/tests/acceptance/01_vars/02_functions/maparray_mixed.cf b/tests/acceptance/01_vars/02_functions/maparray_mixed.cf new file mode 100644 index 0000000000..56b62152bd --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/maparray_mixed.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Test maparray() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" string => "Test that maparray can expand variables with this and without this at the same time."; + + "test_soft_fail" string => "any", meta => { "CFE-2590" }; + + vars: + + "ntp_servers" string => ifelse( + "cfengine", ' + { "kali01": + { "address" : "172.27.82.22", + "key" : "232", + "trustedkey" : [ "232", "242", "252" ], + "serverkey" : "213" + }, + "kali02": + { "address" : "172.27.82.23", + "key" : "232", + "trustedkey" : [ "232", "242", "252" ], + "serverkey" : "213" + }, + "kali03": + { "address" : "172.27.82.24", + "key" : "232", + "trustedkey" : [ "232", "242", "252" ], + "serverkey" : "213" + } + }', + "EOP", ' + { "nexus": + { "address" : "134.152.192.11", + "key" : "", + "trustedkey" : [ "" ], + "serverkey" : "" + } + }', + "undefined"); + + "ntp" data => parsejson($(ntp_servers)); + "i" slist => getindices("ntp"); + + "key[$(i)]" string => ifelse(isgreaterthan( string_length("$(ntp[$(i)][key])"), "0" ), "key", + " "); + + # In 3.9.2 $(key[$(i)]) is expanded in the maparray used below. In 3.10 it + # is not expanded and the literal is left in each entry of the resulting + # slist. + + "commands" slist => maparray("server $(ntp[$(this.k)][address]) iburst $(key[$(i)]) $(ntp[$(this.k)][key]) # $(this.k)", "ntp"); + + + files: + "$(G.testfile).actual" + create => "true", + edit_template => "$(this.promise_dirname)/multiline_json.mustache", + template_method => "mustache", + template_data => bundlestate("test"); + + reports: + DEBUG:: + "$(commands)"; +} + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(this.promise_filename).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/maparray_mixed.cf.expected b/tests/acceptance/01_vars/02_functions/maparray_mixed.cf.expected new file mode 100644 index 0000000000..e3b021ca4d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/maparray_mixed.cf.expected @@ -0,0 +1,48 @@ +{ + "commands": [ + "server 172.27.82.22 iburst key 232 # kali01", + "server 172.27.82.23 iburst key 232 # kali02", + "server 172.27.82.24 iburst key 232 # kali03" + ], + "i": [ + "kali01", + "kali02", + "kali03" + ], + "key[kali01]": "key", + "key[kali02]": "key", + "key[kali03]": "key", + "ntp": { + "kali01": { + "address": "172.27.82.22", + "key": "232", + "serverkey": "213", + "trustedkey": [ + "232", + "242", + "252" + ] + }, + "kali02": { + "address": "172.27.82.23", + "key": "232", + "serverkey": "213", + "trustedkey": [ + "232", + "242", + "252" + ] + }, + "kali03": { + "address": "172.27.82.24", + "key": "232", + "serverkey": "213", + "trustedkey": [ + "232", + "242", + "252" + ] + } + }, + "ntp_servers": "\n { \"kali01\":\n { \"address\" : \"172.27.82.22\",\n \"key\" : \"232\",\n \"trustedkey\" : [ \"232\", \"242\", \"252\" ],\n \"serverkey\" : \"213\"\n },\n \"kali02\":\n { \"address\" : \"172.27.82.23\",\n \"key\" : \"232\",\n \"trustedkey\" : [ \"232\", \"242\", \"252\" ],\n \"serverkey\" : \"213\"\n },\n \"kali03\":\n { \"address\" : \"172.27.82.24\",\n \"key\" : \"232\",\n \"trustedkey\" : [ \"232\", \"242\", \"252\" ],\n \"serverkey\" : \"213\"\n }\n }" +} diff --git a/tests/acceptance/01_vars/02_functions/maparray_multi_index.cf b/tests/acceptance/01_vars/02_functions/maparray_multi_index.cf new file mode 100644 index 0000000000..a0feb0165d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/maparray_multi_index.cf @@ -0,0 +1,55 @@ +########################################################### +# +# Test maparray for multi-index arrays +# Redmine#6033 +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +########################################################### + +bundle agent init +{ +} + +########################################################### + +bundle agent test +{ + vars: + "bundles[x][y][z1]" string => "xyz1"; + "bundles[x][y][z23]" slist => { "xyz2", "xyz3" }; + + "bundles[zculib][mypaths]" slist => { "pathsa.cf", "pathsb.cf" }; + "bundles[zculib][myservices]" slist => { "myservices.cf" }; + + #make sure that below variables will be not mapped + "bundles[zcuinventory][zcuinventory]" slist => { "inv_zcuinventory.cf" }; + "bundles[zcuinventory][inventory_fibrechannel]" slist => { "inv_fibrechannel.cf" }; + "bundles[zcuinventory][inventory_virtualization]" slist => { "inv_virtualization.cf" }; + "bundles[services][afs]" slist => { "afs.cf", "afsadm.cf" }; + "bundles[services][base]" slist => { "base.cf", "base2.cf" }; + + "bundles[onelevel1]" slist => { "onelevel1_avalue", "onelevel1_bvalue" }; + "bundles[onelevel2]" string => "onelevel2value"; + + "inputs" slist => maparray("zculib/$(this.v)","bundles[zculib]"); + "merged_bundles" data => mergedata(bundles); + "merged_zculib" data => mergedata("bundles[zculib]"); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/maparray_multi_index.cf.expected.json b/tests/acceptance/01_vars/02_functions/maparray_multi_index.cf.expected.json new file mode 100644 index 0000000000..6f6a2b20a9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/maparray_multi_index.cf.expected.json @@ -0,0 +1,96 @@ +{ + "bundles[onelevel1]": [ + "onelevel1_avalue", + "onelevel1_bvalue" + ], + "bundles[onelevel2]": "onelevel2value", + "bundles[services][afs]": [ + "afs.cf", + "afsadm.cf" + ], + "bundles[services][base]": [ + "base.cf", + "base2.cf" + ], + "bundles[x][y][z1]": "xyz1", + "bundles[x][y][z23]": [ + "xyz2", + "xyz3" + ], + "bundles[zcuinventory][inventory_fibrechannel]": [ + "inv_fibrechannel.cf" + ], + "bundles[zcuinventory][inventory_virtualization]": [ + "inv_virtualization.cf" + ], + "bundles[zcuinventory][zcuinventory]": [ + "inv_zcuinventory.cf" + ], + "bundles[zculib][mypaths]": [ + "pathsa.cf", + "pathsb.cf" + ], + "bundles[zculib][myservices]": [ + "myservices.cf" + ], + "inputs": [ + "zculib/pathsa.cf", + "zculib/pathsb.cf", + "zculib/myservices.cf" + ], + "merged_bundles": { + "onelevel1": [ + "onelevel1_avalue", + "onelevel1_bvalue" + ], + "onelevel2": "onelevel2value", + "services": { + "afs": [ + "afs.cf", + "afsadm.cf" + ], + "base": [ + "base.cf", + "base2.cf" + ] + }, + "x": { + "y": { + "z1": "xyz1", + "z23": [ + "xyz2", + "xyz3" + ] + } + }, + "zcuinventory": { + "inventory_fibrechannel": [ + "inv_fibrechannel.cf" + ], + "inventory_virtualization": [ + "inv_virtualization.cf" + ], + "zcuinventory": [ + "inv_zcuinventory.cf" + ] + }, + "zculib": { + "mypaths": [ + "pathsa.cf", + "pathsb.cf" + ], + "myservices": [ + "myservices.cf" + ] + } + }, + "merged_zculib": { + "mypaths": [ + "pathsa.cf", + "pathsb.cf" + ], + "myservices": [ + "myservices.cf" + ] + } +} diff --git a/tests/acceptance/01_vars/02_functions/mapdata.cf b/tests/acceptance/01_vars/02_functions/mapdata.cf new file mode 100644 index 0000000000..d3ffa334d4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mapdata.cf @@ -0,0 +1,335 @@ +####################################################### +# +# Test mapdata() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "load1" data => parsejson('[ 1, 2, 3]'); + "load2" slist => { "eleme\"nt1", "element2", "element3" }; + "load3" data => parsejson('{ "x\\"x": "y\\"y" }'); + "load4" data => parsejson('[]'); + "load5[mykey]" slist => { "myvalue" }; + "load5[anotherkey]" string => "anothervalue"; + "load5[lastkey!]" slist => { "o\"ne", "two", "three" }; + "load6" data => parsejson('{ "a": { "b": "c" } }'); + + "static[x]" string => "xvalue"; + "static[x\"x]" string => "xxvalue"; + "static[0]" string => "0value"; + "static[1]" string => "1value"; + "static[2]" string => "2value"; + "static[lastkey!]" string => "lastvalue"; + "static[anotherkey]" string => "anothervalue"; + "static[mykey]" string => "myvalue"; + "static[a]" string => "avalue"; + + "spec1" string => "key = $(this.k)"; + "spec2" string => "key = $(this.k), value = ${this.v}"; + "spec3" string => "key = $(this.k), key2 = $(this.k[1]), value = ${this.v}"; + "spec4" string => "xvalue should be $(static[$(this.k)])"; + + "jsonspec1" string => '{ "key": "$(this.k)", "value": "$(this.v)"'; + "jsonspec2" string => '{ "key": "$(this.k)", "value": "$(this.v)" }'; + "jsonspec3" string => '[ "$(this.k)", "$(this.v)" ]'; + "jsonspec4" string => '{ "key": "$(this.k)", "key2": "$(this.k[1])", "value": "$(this.v)" }'; + + "X" slist => { "1", "2", "3", "4", "5", "6" }; + "Y" slist => { "1", "2", "3", "4" }; + + "mapdata_none_$(X)_$(Y)" data => mapdata("none", "$(spec$(Y))", "load$(X)"); + "mapdata_none_str_$(X)_$(Y)" string => format("%S", "mapdata_none_$(X)_$(Y)"); + + "mapdata_canonify_$(X)_$(Y)" data => mapdata("canonify", "$(spec$(Y))", "load$(X)"); + "mapdata_canonify_str_$(X)_$(Y)" string => format("%S", "mapdata_canonify_$(X)_$(Y)"); + + "mapdata_canonify_eval_$(X)_$(Y)" data => mapdata("canonify", concat("$(spec$(Y))"), "load$(X)"); + "mapdata_canonify_eval_str_$(X)_$(Y)" string => format("%S", "mapdata_canonify_eval_$(X)_$(Y)"); + + "mapdata_json_$(X)_$(Y)" data => mapdata("json", "$(jsonspec$(Y))", "load$(X)"); + "mapdata_json_str_$(X)_$(Y)" string => format("%S", "mapdata_json_$(X)_$(Y)"); + + "bad1" data => mapdata("json", "", missingvar); + + "justastring" string => "me"; + "bad3" data => mapdata("json", "", justastring); + + classes: + 'mapdata_none_1_1_ok' and => { regcmp('.*"key = 0".*', '$(mapdata_none_str_1_1)'), + regcmp('.*"key = 1".*', '$(mapdata_none_str_1_1)'), + regcmp('.*"key = 2".*', '$(mapdata_none_str_1_1)') }; + 'mapdata_none_1_2_ok' and => { regcmp('.*"key = 0, value = 1".*', '$(mapdata_none_str_1_2)'), + regcmp('.*"key = 1, value = 2".*', '$(mapdata_none_str_1_2)'), + regcmp('.*"key = 2, value = 3".*', '$(mapdata_none_str_1_2)') }; + 'mapdata_none_1_3_ok' and => { regcmp('.*"key = 0, key2 = \$\(this.k\[1\]\), value = 1".*', '$(mapdata_none_str_1_3)'), + regcmp('.*"key = 1, key2 = \$\(this.k\[1\]\), value = 2".*', '$(mapdata_none_str_1_3)'), + regcmp('.*"key = 2, key2 = \$\(this.k\[1\]\), value = 3".*', '$(mapdata_none_str_1_3)') }; + 'mapdata_none_1_4_ok' and => { regcmp('.*"xvalue should be 0value".*', '$(mapdata_none_str_1_4)'), + regcmp('.*"xvalue should be 1value".*', '$(mapdata_none_str_1_4)'), + regcmp('.*"xvalue should be 2value".*', '$(mapdata_none_str_1_4)') }; + 'mapdata_none_2_1_ok' and => { regcmp('.*"key = 0".*', '$(mapdata_none_str_2_1)'), + regcmp('.*"key = 1".*', '$(mapdata_none_str_2_1)'), + regcmp('.*"key = 2".*', '$(mapdata_none_str_2_1)') }; + 'mapdata_none_2_2_ok' and => { regcmp('.*"key = 0, value = eleme\\\\"nt1".*', '$(mapdata_none_str_2_2)'), + regcmp('.*"key = 1, value = element2".*', '$(mapdata_none_str_2_2)'), + regcmp('.*"key = 2, value = element3".*', '$(mapdata_none_str_2_2)') }; + 'mapdata_none_2_3_ok' and => { regcmp('.*"key = 0, key2 = \$\(this.k\[1\]\), value = eleme\\\\"nt1".*', '$(mapdata_none_str_2_3)'), + regcmp('.*"key = 1, key2 = \$\(this.k\[1\]\), value = element2".*', '$(mapdata_none_str_2_3)'), + regcmp('.*"key = 2, key2 = \$\(this.k\[1\]\), value = element3".*', '$(mapdata_none_str_2_3)') }; + 'mapdata_none_2_4_ok' and => { regcmp('.*"xvalue should be 0value".*', '$(mapdata_none_str_2_4)'), + regcmp('.*"xvalue should be 1value".*', '$(mapdata_none_str_2_4)'), + regcmp('.*"xvalue should be 2value".*', '$(mapdata_none_str_2_4)') }; + 'mapdata_none_3_1_ok' and => { regcmp('.*"key = x\\\\"x".*', '$(mapdata_none_str_3_1)') }; + 'mapdata_none_3_2_ok' and => { regcmp('.*"key = x\\\\"x, value = y\\\\"y".*', '$(mapdata_none_str_3_2)') }; + 'mapdata_none_3_3_ok' and => { regcmp('.*"key = x\\\\"x, key2 = \$\(this.k\[1\]\), value = y\\\\"y".*', '$(mapdata_none_str_3_3)') }; + 'mapdata_none_3_4_ok' and => { regcmp('.*"xvalue should be xxvalue".*', '$(mapdata_none_str_3_4)') }; + 'mapdata_none_4_1_ok' expression => strcmp('$(mapdata_none_str_4_1)', '[]'); + 'mapdata_none_4_2_ok' expression => strcmp('$(mapdata_none_str_4_1)', '[]'); + 'mapdata_none_4_3_ok' expression => strcmp('$(mapdata_none_str_4_1)', '[]'); + 'mapdata_none_4_4_ok' expression => strcmp('$(mapdata_none_str_4_1)', '[]'); + 'mapdata_none_5_1_ok' and => { regcmp('.*"key = mykey".*', '$(mapdata_none_str_5_1)'), + regcmp('.*"key = anotherkey".*', '$(mapdata_none_str_5_1)'), + regcmp('.*"key = lastkey!".*', '$(mapdata_none_str_5_1)') }; + 'mapdata_none_5_2_ok' and => { regcmp('.*"key = mykey, value = myvalue".*', '$(mapdata_none_str_5_2)'), + regcmp('.*"key = anotherkey, value = anothervalue".*', '$(mapdata_none_str_5_2)'), + regcmp('.*"key = lastkey!, value = o\\\\"ne".*', '$(mapdata_none_str_5_2)'), + regcmp('.*"key = lastkey!, value = two".*', '$(mapdata_none_str_5_2)'), + regcmp('.*"key = lastkey!, value = three".*', '$(mapdata_none_str_5_2)') }; + 'mapdata_none_5_3_ok' and => { regcmp('.*"key = mykey, key2 = \$\(this.k\[1\]\), value = myvalue".*', '$(mapdata_none_str_5_3)'), + regcmp('.*"key = anotherkey, key2 = \$\(this.k\[1\]\), value = anothervalue".*', '$(mapdata_none_str_5_3)'), + regcmp('.*"key = lastkey!, key2 = \$\(this.k\[1\]\), value = o\\\\"ne".*', '$(mapdata_none_str_5_3)'), + regcmp('.*"key = lastkey!, key2 = \$\(this.k\[1\]\), value = two".*', '$(mapdata_none_str_5_3)'), + regcmp('.*"key = lastkey!, key2 = \$\(this.k\[1\]\), value = three".*', '$(mapdata_none_str_5_3)') }; + 'mapdata_none_5_4_ok' and => { regcmp('.*"xvalue should be myvalue".*', '$(mapdata_none_str_5_4)'), + regcmp('.*"xvalue should be anothervalue".*', '$(mapdata_none_str_5_4)'), + regcmp('.*"xvalue should be lastvalue".*', '$(mapdata_none_str_5_4)') }; + 'mapdata_none_6_1_ok' and => { regcmp('.*"key = a".*', '$(mapdata_none_str_6_1)') }; + 'mapdata_none_6_2_ok' and => { regcmp('.*"key = a, value = c".*', '$(mapdata_none_str_6_2)') }; + 'mapdata_none_6_3_ok' and => { regcmp('.*"key = a, key2 = b, value = c".*', '$(mapdata_none_str_6_3)') }; + 'mapdata_none_6_4_ok' and => { regcmp('.*"xvalue should be avalue".*', '$(mapdata_none_str_6_4)') }; + + 'mapdata_canonify_1_1_ok' and => { regcmp('.*"key___0".*', '$(mapdata_canonify_str_1_1)'), + regcmp('.*"key___1".*', '$(mapdata_canonify_str_1_1)'), + regcmp('.*"key___2".*', '$(mapdata_canonify_str_1_1)') }; + 'mapdata_canonify_1_2_ok' and => { regcmp('.*"key___0__value___1".*', '$(mapdata_canonify_str_1_2)'), + regcmp('.*"key___1__value___2".*', '$(mapdata_canonify_str_1_2)'), + regcmp('.*"key___2__value___3".*', '$(mapdata_canonify_str_1_2)') }; + 'mapdata_canonify_1_3_ok' and => { regcmp('.*"key___0__key2_____this_k_1____value___1".*', '$(mapdata_canonify_str_1_3)'), + regcmp('.*"key___1__key2_____this_k_1____value___2".*', '$(mapdata_canonify_str_1_3)'), + regcmp('.*"key___2__key2_____this_k_1____value___3".*', '$(mapdata_canonify_str_1_3)') }; + 'mapdata_canonify_1_4_ok' and => { regcmp('.*"xvalue_should_be_0value".*', '$(mapdata_canonify_str_1_4)'), + regcmp('.*"xvalue_should_be_1value".*', '$(mapdata_canonify_str_1_4)'), + regcmp('.*"xvalue_should_be_2value".*', '$(mapdata_canonify_str_1_4)') }; + 'mapdata_canonify_2_1_ok' and => { regcmp('.*"key___0".*', '$(mapdata_canonify_str_2_1)'), + regcmp('.*"key___1".*', '$(mapdata_canonify_str_2_1)'), + regcmp('.*"key___2".*', '$(mapdata_canonify_str_2_1)') }; + 'mapdata_canonify_2_2_ok' and => { regcmp('.*"key___0__value___eleme_nt1".*', '$(mapdata_canonify_str_2_2)'), + regcmp('.*"key___1__value___element2".*', '$(mapdata_canonify_str_2_2)'), + regcmp('.*"key___2__value___element3".*', '$(mapdata_canonify_str_2_2)') }; + 'mapdata_canonify_2_3_ok' and => { regcmp('.*"key___0__key2_____this_k_1____value___eleme_nt1".*', '$(mapdata_canonify_str_2_3)'), + regcmp('.*"key___1__key2_____this_k_1____value___element2".*', '$(mapdata_canonify_str_2_3)'), + regcmp('.*"key___2__key2_____this_k_1____value___element3".*', '$(mapdata_canonify_str_2_3)') }; + 'mapdata_canonify_2_4_ok' and => { regcmp('.*"xvalue_should_be_0value".*', '$(mapdata_canonify_str_2_4)'), + regcmp('.*"xvalue_should_be_1value".*', '$(mapdata_canonify_str_2_4)'), + regcmp('.*"xvalue_should_be_2value".*', '$(mapdata_canonify_str_2_4)') }; + 'mapdata_canonify_3_1_ok' and => { regcmp('.*"key___x_x".*', '$(mapdata_canonify_str_3_1)') }; + 'mapdata_canonify_3_2_ok' and => { regcmp('.*"key___x_x__value___y_y".*', '$(mapdata_canonify_str_3_2)') }; + 'mapdata_canonify_3_3_ok' and => { regcmp('.*"key___x_x__key2_____this_k_1____value___y_y".*', '$(mapdata_canonify_str_3_3)') }; + 'mapdata_canonify_3_4_ok' and => { regcmp('.*"xvalue_should_be_xxvalue".*', '$(mapdata_canonify_str_3_4)') }; + 'mapdata_canonify_4_1_ok' expression => strcmp('$(mapdata_canonify_str_4_1)', '[]'); + 'mapdata_canonify_4_2_ok' expression => strcmp('$(mapdata_canonify_str_4_1)', '[]'); + 'mapdata_canonify_4_3_ok' expression => strcmp('$(mapdata_canonify_str_4_1)', '[]'); + 'mapdata_canonify_4_4_ok' expression => strcmp('$(mapdata_canonify_str_4_1)', '[]'); + 'mapdata_canonify_5_1_ok' and => { regcmp('.*"key___mykey".*', '$(mapdata_canonify_str_5_1)'), + regcmp('.*"key___anotherkey".*', '$(mapdata_canonify_str_5_1)'), + regcmp('.*"key___lastkey_".*', '$(mapdata_canonify_str_5_1)') }; + 'mapdata_canonify_5_2_ok' and => { regcmp('.*"key___mykey__value___myvalue".*', '$(mapdata_canonify_str_5_2)'), + regcmp('.*"key___anotherkey__value___anothervalue".*', '$(mapdata_canonify_str_5_2)'), + regcmp('.*"key___lastkey___value___o_ne".*', '$(mapdata_canonify_str_5_2)'), + regcmp('.*"key___lastkey___value___two".*', '$(mapdata_canonify_str_5_2)'), + regcmp('.*"key___lastkey___value___three".*', '$(mapdata_canonify_str_5_2)') }; + 'mapdata_canonify_5_3_ok' and => { regcmp('.*"key___mykey__key2_____this_k_1____value___myvalue".*', '$(mapdata_canonify_str_5_3)'), + regcmp('.*"key___anotherkey__key2_____this_k_1____value___anothervalue".*', '$(mapdata_canonify_str_5_3)'), + regcmp('.*"key___lastkey___key2_____this_k_1____value___o_ne".*', '$(mapdata_canonify_str_5_3)'), + regcmp('.*"key___lastkey___key2_____this_k_1____value___two".*', '$(mapdata_canonify_str_5_3)'), + regcmp('.*"key___lastkey___key2_____this_k_1____value___three".*', '$(mapdata_canonify_str_5_3)') }; + 'mapdata_canonify_5_4_ok' and => { regcmp('.*"xvalue_should_be_myvalue".*', '$(mapdata_canonify_str_5_4)'), + regcmp('.*"xvalue_should_be_anothervalue".*', '$(mapdata_canonify_str_5_4)'), + regcmp('.*"xvalue_should_be_lastvalue".*', '$(mapdata_canonify_str_5_4)') }; + 'mapdata_canonify_6_1_ok' and => { regcmp('.*"key___a".*', '$(mapdata_canonify_str_6_1)') }; + 'mapdata_canonify_6_2_ok' and => { regcmp('.*"key___a__value___c".*', '$(mapdata_canonify_str_6_2)') }; + 'mapdata_canonify_6_3_ok' and => { regcmp('.*"key___a__key2___b__value___c".*', '$(mapdata_canonify_str_6_3)') }; + 'mapdata_canonify_6_4_ok' and => { regcmp('.*"xvalue_should_be_avalue".*', '$(mapdata_canonify_str_6_4)') }; + + 'mapdata_canonify_eval_1_1_ok' and => { regcmp('.*"key___0".*', '$(mapdata_canonify_eval_str_1_1)'), + regcmp('.*"key___1".*', '$(mapdata_canonify_eval_str_1_1)'), + regcmp('.*"key___2".*', '$(mapdata_canonify_eval_str_1_1)') }; + 'mapdata_canonify_eval_1_2_ok' and => { regcmp('.*"key___0__value___1".*', '$(mapdata_canonify_eval_str_1_2)'), + regcmp('.*"key___1__value___2".*', '$(mapdata_canonify_eval_str_1_2)'), + regcmp('.*"key___2__value___3".*', '$(mapdata_canonify_eval_str_1_2)') }; + 'mapdata_canonify_eval_1_3_ok' and => { regcmp('.*"key___0__key2_____this_k_1____value___1".*', '$(mapdata_canonify_eval_str_1_3)'), + regcmp('.*"key___1__key2_____this_k_1____value___2".*', '$(mapdata_canonify_eval_str_1_3)'), + regcmp('.*"key___2__key2_____this_k_1____value___3".*', '$(mapdata_canonify_eval_str_1_3)') }; + 'mapdata_canonify_eval_1_4_ok' and => { regcmp('.*"xvalue_should_be_0value".*', '$(mapdata_canonify_eval_str_1_4)'), + regcmp('.*"xvalue_should_be_1value".*', '$(mapdata_canonify_eval_str_1_4)'), + regcmp('.*"xvalue_should_be_2value".*', '$(mapdata_canonify_eval_str_1_4)') }; + 'mapdata_canonify_eval_2_1_ok' and => { regcmp('.*"key___0".*', '$(mapdata_canonify_eval_str_2_1)'), + regcmp('.*"key___1".*', '$(mapdata_canonify_eval_str_2_1)'), + regcmp('.*"key___2".*', '$(mapdata_canonify_eval_str_2_1)') }; + 'mapdata_canonify_eval_2_2_ok' and => { regcmp('.*"key___0__value___eleme_nt1".*', '$(mapdata_canonify_eval_str_2_2)'), + regcmp('.*"key___1__value___element2".*', '$(mapdata_canonify_eval_str_2_2)'), + regcmp('.*"key___2__value___element3".*', '$(mapdata_canonify_eval_str_2_2)') }; + 'mapdata_canonify_eval_2_3_ok' and => { regcmp('.*"key___0__key2_____this_k_1____value___eleme_nt1".*', '$(mapdata_canonify_eval_str_2_3)'), + regcmp('.*"key___1__key2_____this_k_1____value___element2".*', '$(mapdata_canonify_eval_str_2_3)'), + regcmp('.*"key___2__key2_____this_k_1____value___element3".*', '$(mapdata_canonify_eval_str_2_3)') }; + 'mapdata_canonify_eval_2_4_ok' and => { regcmp('.*"xvalue_should_be_0value".*', '$(mapdata_canonify_eval_str_2_4)'), + regcmp('.*"xvalue_should_be_1value".*', '$(mapdata_canonify_eval_str_2_4)'), + regcmp('.*"xvalue_should_be_2value".*', '$(mapdata_canonify_eval_str_2_4)') }; + 'mapdata_canonify_eval_3_1_ok' and => { regcmp('.*"key___x_x".*', '$(mapdata_canonify_eval_str_3_1)') }; + 'mapdata_canonify_eval_3_2_ok' and => { regcmp('.*"key___x_x__value___y_y".*', '$(mapdata_canonify_eval_str_3_2)') }; + 'mapdata_canonify_eval_3_3_ok' and => { regcmp('.*"key___x_x__key2_____this_k_1____value___y_y".*', '$(mapdata_canonify_eval_str_3_3)') }; + 'mapdata_canonify_eval_3_4_ok' and => { regcmp('.*"xvalue_should_be_xxvalue".*', '$(mapdata_canonify_eval_str_3_4)') }; + 'mapdata_canonify_eval_4_1_ok' expression => strcmp('$(mapdata_canonify_eval_str_4_1)', '[]'); + 'mapdata_canonify_eval_4_2_ok' expression => strcmp('$(mapdata_canonify_eval_str_4_1)', '[]'); + 'mapdata_canonify_eval_4_3_ok' expression => strcmp('$(mapdata_canonify_eval_str_4_1)', '[]'); + 'mapdata_canonify_eval_4_4_ok' expression => strcmp('$(mapdata_canonify_eval_str_4_1)', '[]'); + 'mapdata_canonify_eval_5_1_ok' and => { regcmp('.*"key___mykey".*', '$(mapdata_canonify_eval_str_5_1)'), + regcmp('.*"key___anotherkey".*', '$(mapdata_canonify_eval_str_5_1)'), + regcmp('.*"key___lastkey_".*', '$(mapdata_canonify_eval_str_5_1)') }; + 'mapdata_canonify_eval_5_2_ok' and => { regcmp('.*"key___mykey__value___myvalue".*', '$(mapdata_canonify_eval_str_5_2)'), + regcmp('.*"key___anotherkey__value___anothervalue".*', '$(mapdata_canonify_eval_str_5_2)'), + regcmp('.*"key___lastkey___value___o_ne".*', '$(mapdata_canonify_eval_str_5_2)'), + regcmp('.*"key___lastkey___value___two".*', '$(mapdata_canonify_eval_str_5_2)'), + regcmp('.*"key___lastkey___value___three".*', '$(mapdata_canonify_eval_str_5_2)') }; + 'mapdata_canonify_eval_5_3_ok' and => { regcmp('.*"key___mykey__key2_____this_k_1____value___myvalue".*', '$(mapdata_canonify_eval_str_5_3)'), + regcmp('.*"key___anotherkey__key2_____this_k_1____value___anothervalue".*', + '$(mapdata_canonify_eval_str_5_3)'), + regcmp('.*"key___lastkey___key2_____this_k_1____value___o_ne".*', '$(mapdata_canonify_eval_str_5_3)'), + regcmp('.*"key___lastkey___key2_____this_k_1____value___two".*', '$(mapdata_canonify_eval_str_5_3)'), + regcmp('.*"key___lastkey___key2_____this_k_1____value___three".*', '$(mapdata_canonify_eval_str_5_3)') }; + 'mapdata_canonify_eval_5_4_ok' and => { regcmp('.*"xvalue_should_be_myvalue".*', '$(mapdata_canonify_eval_str_5_4)'), + regcmp('.*"xvalue_should_be_anothervalue".*', '$(mapdata_canonify_eval_str_5_4)'), + regcmp('.*"xvalue_should_be_lastvalue".*', '$(mapdata_canonify_eval_str_5_4)') }; + 'mapdata_canonify_eval_6_1_ok' and => { regcmp('.*"key___a".*', '$(mapdata_canonify_eval_str_6_1)') }; + 'mapdata_canonify_eval_6_2_ok' and => { regcmp('.*"key___a__value___c".*', '$(mapdata_canonify_eval_str_6_2)') }; + 'mapdata_canonify_eval_6_3_ok' and => { regcmp('.*"key___a__key2___b__value___c".*', '$(mapdata_canonify_eval_str_6_3)') }; + 'mapdata_canonify_eval_6_4_ok' and => { regcmp('.*"xvalue_should_be_avalue".*', '$(mapdata_canonify_eval_str_6_4)') }; + + 'mapdata_json_1_1_ok' expression => strcmp('$(mapdata_json_str_1_1)', '[]'); + 'mapdata_json_1_2_ok' and => { regcmp('.*\{"key":"0","value":"1"\}.*', '$(mapdata_json_str_1_2)'), + regcmp('.*\{"key":"1","value":"2"\}.*', '$(mapdata_json_str_1_2)'), + regcmp('.*\{"key":"2","value":"3"\}.*', '$(mapdata_json_str_1_2)') }; + 'mapdata_json_1_3_ok' and => { regcmp('.*\["0","1"\].*', '$(mapdata_json_str_1_3)'), + regcmp('.*\["1","2"\].*', '$(mapdata_json_str_1_3)'), + regcmp('.*\["2","3"\].*', '$(mapdata_json_str_1_3)') }; + 'mapdata_json_1_4_ok' and => { regcmp('.*\{"key":"0","key2":"\$\(this.k\[1\]\)","value":"1"\}.*', '$(mapdata_json_str_1_4)'), + regcmp('.*\{"key":"1","key2":"\$\(this.k\[1\]\)","value":"2"\}.*', '$(mapdata_json_str_1_4)'), + regcmp('.*\{"key":"2","key2":"\$\(this.k\[1\]\)","value":"3"\}.*', '$(mapdata_json_str_1_4)') }; + 'mapdata_json_2_1_ok' expression => strcmp('$(mapdata_json_str_2_1)', '[]'); + 'mapdata_json_2_2_ok' and => { regcmp('.*\{"key":"0","value":"eleme\\\\"nt1"\}.*', '$(mapdata_json_str_2_2)'), + regcmp('.*\{"key":"1","value":"element2"\}.*', '$(mapdata_json_str_2_2)'), + regcmp('.*\{"key":"2","value":"element3"\}.*', '$(mapdata_json_str_2_2)') }; + 'mapdata_json_2_3_ok' and => { regcmp('.*\["0","eleme\\\\"nt1"\].*', '$(mapdata_json_str_2_3)'), + regcmp('.*\["1","element2"\].*', '$(mapdata_json_str_2_3)'), + regcmp('.*\["2","element3"\].*', '$(mapdata_json_str_2_3)') }; + 'mapdata_json_2_4_ok' and => { regcmp('.*\{"key":"0","key2":"\$\(this.k\[1\]\)","value":"eleme\\\\"nt1"\}.*', '$(mapdata_json_str_2_4)'), + regcmp('.*\{"key":"1","key2":"\$\(this.k\[1\]\)","value":"element2"\}.*', '$(mapdata_json_str_2_4)'), + regcmp('.*\{"key":"2","key2":"\$\(this.k\[1\]\)","value":"element3"\}.*', '$(mapdata_json_str_2_4)') }; + 'mapdata_json_3_1_ok' expression => strcmp('$(mapdata_json_str_3_1)', '[]'); + 'mapdata_json_3_2_ok' and => { regcmp('.*\{"key":"x\\\\"x","value":"y\\\\"y"\}.*', '$(mapdata_json_str_3_2)') }; + 'mapdata_json_3_3_ok' and => { regcmp('.*\["x\\\\"x","y\\\\"y"\].*', '$(mapdata_json_str_3_3)') }; + 'mapdata_json_3_4_ok' and => { regcmp('.*\{"key":"x\\\\"x","key2":"\$\(this.k\[1\]\)","value":"y\\\\"y"\}.*', '$(mapdata_json_str_3_4)') }; + 'mapdata_json_4_1_ok' expression => strcmp('$(mapdata_json_str_4_1)', '[]'); + 'mapdata_json_4_2_ok' expression => strcmp('$(mapdata_json_str_4_1)', '[]'); + 'mapdata_json_4_3_ok' expression => strcmp('$(mapdata_json_str_4_1)', '[]'); + 'mapdata_json_4_4_ok' expression => strcmp('$(mapdata_json_str_4_1)', '[]'); + 'mapdata_json_5_1_ok' expression => strcmp('$(mapdata_json_str_5_1)', '[]'); + 'mapdata_json_5_2_ok' and => { regcmp('.*\{"key":"mykey","value":"myvalue"\}.*', '$(mapdata_json_str_5_2)'), + regcmp('.*\{"key":"anotherkey","value":"anothervalue"\}.*', '$(mapdata_json_str_5_2)'), + regcmp('.*\{"key":"lastkey!","value":"o\\\\"ne"\}.*', '$(mapdata_json_str_5_2)'), + regcmp('.*\{"key":"lastkey!","value":"two"\}.*', '$(mapdata_json_str_5_2)'), + regcmp('.*\{"key":"lastkey!","value":"three"\}.*', '$(mapdata_json_str_5_2)') }; + 'mapdata_json_5_3_ok' and => { regcmp('.*\["mykey","myvalue"\].*', '$(mapdata_json_str_5_3)'), + regcmp('.*\["anotherkey","anothervalue"\].*', '$(mapdata_json_str_5_3)'), + regcmp('.*\["lastkey!","o\\\\"ne"\].*', '$(mapdata_json_str_5_3)'), + regcmp('.*\["lastkey!","two"\].*', '$(mapdata_json_str_5_3)'), + regcmp('.*\["lastkey!","three"\].*', '$(mapdata_json_str_5_3)') }; + 'mapdata_json_5_4_ok' and => { regcmp('.*\{"key":"mykey","key2":"\$\(this.k\[1\]\)","value":"myvalue"\}.*', '$(mapdata_json_str_5_4)'), + regcmp('.*\{"key":"anotherkey","key2":"\$\(this.k\[1\]\)","value":"anothervalue"\}.*', '$(mapdata_json_str_5_4)'), + regcmp('.*\{"key":"lastkey!","key2":"\$\(this.k\[1\]\)","value":"o\\\\"ne"\}.*', '$(mapdata_json_str_5_4)'), + regcmp('.*\{"key":"lastkey!","key2":"\$\(this.k\[1\]\)","value":"two"\}.*', '$(mapdata_json_str_5_4)'), + regcmp('.*\{"key":"lastkey!","key2":"\$\(this.k\[1\]\)","value":"three"\}.*', '$(mapdata_json_str_5_4)') }; + 'mapdata_json_6_1_ok' expression => strcmp('$(mapdata_json_str_6_1)', '[]'); + 'mapdata_json_6_2_ok' and => { regcmp('.*\{"key":"a","value":"c"\}.*', '$(mapdata_json_str_6_2)') }; + 'mapdata_json_6_3_ok' and => { regcmp('.*\["a","c"\].*', '$(mapdata_json_str_6_3)') }; + 'mapdata_json_6_4_ok' and => { regcmp('.*\{"key":"a","key2":"b","value":"c"\}.*', '$(mapdata_json_str_6_4)') }; + + "mapdata_none_ok" and => { "mapdata_none_1_1_ok", "mapdata_none_1_2_ok", "mapdata_none_1_3_ok", "mapdata_none_1_4_ok", + "mapdata_none_2_1_ok", "mapdata_none_2_2_ok", "mapdata_none_2_3_ok", "mapdata_none_2_4_ok", + "mapdata_none_3_1_ok", "mapdata_none_3_2_ok", "mapdata_none_3_3_ok", "mapdata_none_3_4_ok", + "mapdata_none_4_1_ok", "mapdata_none_4_2_ok", "mapdata_none_4_3_ok", "mapdata_none_4_4_ok", + "mapdata_none_5_1_ok", "mapdata_none_5_2_ok", "mapdata_none_5_3_ok", "mapdata_none_5_4_ok", + "mapdata_none_6_1_ok", "mapdata_none_6_2_ok", "mapdata_none_6_3_ok", "mapdata_none_6_4_ok" }; + + "mapdata_canonify_ok" and => { "mapdata_canonify_1_1_ok", "mapdata_canonify_1_2_ok", "mapdata_canonify_1_3_ok", + "mapdata_canonify_1_4_ok", "mapdata_canonify_2_1_ok", "mapdata_canonify_2_2_ok", + "mapdata_canonify_2_3_ok", "mapdata_canonify_2_4_ok", "mapdata_canonify_3_1_ok", + "mapdata_canonify_3_2_ok", "mapdata_canonify_3_3_ok", "mapdata_canonify_3_4_ok", + "mapdata_canonify_4_1_ok", "mapdata_canonify_4_2_ok", "mapdata_canonify_4_3_ok", + "mapdata_canonify_4_4_ok", "mapdata_canonify_5_1_ok", "mapdata_canonify_5_2_ok", + "mapdata_canonify_5_3_ok", "mapdata_canonify_5_4_ok", "mapdata_canonify_6_1_ok", + "mapdata_canonify_6_2_ok", "mapdata_canonify_6_3_ok", "mapdata_canonify_6_4_ok" }; + + "mapdata_canonify_eval_ok" and => { "mapdata_canonify_eval_1_1_ok", "mapdata_canonify_eval_1_2_ok", "mapdata_canonify_eval_1_3_ok", + "mapdata_canonify_eval_1_4_ok", "mapdata_canonify_eval_2_1_ok", "mapdata_canonify_eval_2_2_ok", + "mapdata_canonify_eval_2_3_ok", "mapdata_canonify_eval_2_4_ok", "mapdata_canonify_eval_3_1_ok", + "mapdata_canonify_eval_3_2_ok", "mapdata_canonify_eval_3_3_ok", "mapdata_canonify_eval_3_4_ok", + "mapdata_canonify_eval_4_1_ok", "mapdata_canonify_eval_4_2_ok", "mapdata_canonify_eval_4_3_ok", + "mapdata_canonify_eval_4_4_ok", "mapdata_canonify_eval_5_1_ok", "mapdata_canonify_eval_5_2_ok", + "mapdata_canonify_eval_5_3_ok", "mapdata_canonify_eval_5_4_ok", "mapdata_canonify_eval_6_1_ok", + "mapdata_canonify_eval_6_2_ok", "mapdata_canonify_eval_6_3_ok", "mapdata_canonify_eval_6_4_ok" }; + + "mapdata_json_ok" and => { "mapdata_json_1_1_ok", "mapdata_json_1_2_ok", "mapdata_json_1_3_ok", "mapdata_json_1_4_ok", + "mapdata_json_2_1_ok", "mapdata_json_2_2_ok", "mapdata_json_2_3_ok", "mapdata_json_2_4_ok", + "mapdata_json_3_1_ok", "mapdata_json_3_2_ok", "mapdata_json_3_3_ok", "mapdata_json_3_4_ok", + "mapdata_json_4_1_ok", "mapdata_json_4_2_ok", "mapdata_json_4_3_ok", "mapdata_json_4_4_ok", + "mapdata_json_5_1_ok", "mapdata_json_5_2_ok", "mapdata_json_5_3_ok", "mapdata_json_5_4_ok", + "mapdata_json_6_1_ok", "mapdata_json_6_2_ok", "mapdata_json_6_3_ok", "mapdata_json_6_4_ok" }; + + + "ok" and => { "mapdata_none_ok", "mapdata_canonify_ok", "mapdata_canonify_eval_ok", "mapdata_json_ok" }, + scope => "namespace"; +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "mapdata_none*:"; + "mapdata_none_str_$(test.X)_$(test.Y): '$(test.mapdata_none_str_$(test.X)_$(test.Y))'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/mapdata.cf.expected.json b/tests/acceptance/01_vars/02_functions/mapdata.cf.expected.json new file mode 100644 index 0000000000..d57613305c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mapdata.cf.expected.json @@ -0,0 +1,546 @@ +{ + "X": [ + "1", + "2", + "3", + "4", + "5", + "6" + ], + "Y": [ + "1", + "2", + "3", + "4" + ], + "jsonspec1": "{ \"key\": \"$(this.k)\", \"value\": \"$(this.v)\"", + "jsonspec2": "{ \"key\": \"$(this.k)\", \"value\": \"$(this.v)\" }", + "jsonspec3": "[ \"$(this.k)\", \"$(this.v)\" ]", + "jsonspec4": "{ \"key\": \"$(this.k)\", \"key2\": \"$(this.k[1])\", \"value\": \"$(this.v)\" }", + "justastring": "me", + "load1": [ + 1, + 2, + 3 + ], + "load2": [ + "eleme\"nt1", + "element2", + "element3" + ], + "load3": { + "x"x": "y\"y" + }, + "load4": [], + "load5[anotherkey]": "anothervalue", + "load5[lastkey!]": [ + "o\"ne", + "two", + "three" + ], + "load5[mykey]": [ + "myvalue" + ], + "load6": { + "a": { + "b": "c" + } + }, + "mapdata_canonify_1_1": [ + "key___0", + "key___1", + "key___2" + ], + "mapdata_canonify_1_2": [ + "key___0__value___1", + "key___1__value___2", + "key___2__value___3" + ], + "mapdata_canonify_1_3": [ + "key___0__key2_____this_k_1____value___1", + "key___1__key2_____this_k_1____value___2", + "key___2__key2_____this_k_1____value___3" + ], + "mapdata_canonify_1_4": [ + "xvalue_should_be_0value", + "xvalue_should_be_1value", + "xvalue_should_be_2value" + ], + "mapdata_canonify_2_1": [ + "key___0", + "key___1", + "key___2" + ], + "mapdata_canonify_2_2": [ + "key___0__value___eleme_nt1", + "key___1__value___element2", + "key___2__value___element3" + ], + "mapdata_canonify_2_3": [ + "key___0__key2_____this_k_1____value___eleme_nt1", + "key___1__key2_____this_k_1____value___element2", + "key___2__key2_____this_k_1____value___element3" + ], + "mapdata_canonify_2_4": [ + "xvalue_should_be_0value", + "xvalue_should_be_1value", + "xvalue_should_be_2value" + ], + "mapdata_canonify_3_1": [ + "key___x_x" + ], + "mapdata_canonify_3_2": [ + "key___x_x__value___y_y" + ], + "mapdata_canonify_3_3": [ + "key___x_x__key2_____this_k_1____value___y_y" + ], + "mapdata_canonify_3_4": [ + "xvalue_should_be_xxvalue" + ], + "mapdata_canonify_4_1": [], + "mapdata_canonify_4_2": [], + "mapdata_canonify_4_3": [], + "mapdata_canonify_4_4": [], + "mapdata_canonify_5_1": [ + "key___anotherkey", + "key___lastkey_", + "key___mykey" + ], + "mapdata_canonify_5_2": [ + "key___anotherkey__value___anothervalue", + "key___lastkey___value___o_ne", + "key___lastkey___value___two", + "key___lastkey___value___three", + "key___mykey__value___myvalue" + ], + "mapdata_canonify_5_3": [ + "key___anotherkey__key2_____this_k_1____value___anothervalue", + "key___lastkey___key2_____this_k_1____value___o_ne", + "key___lastkey___key2_____this_k_1____value___two", + "key___lastkey___key2_____this_k_1____value___three", + "key___mykey__key2_____this_k_1____value___myvalue" + ], + "mapdata_canonify_5_4": [ + "xvalue_should_be_anothervalue", + "xvalue_should_be_lastvalue", + "xvalue_should_be_myvalue" + ], + "mapdata_canonify_6_1": [ + "key___a" + ], + "mapdata_canonify_6_2": [ + "key___a__value___c" + ], + "mapdata_canonify_6_3": [ + "key___a__key2___b__value___c" + ], + "mapdata_canonify_6_4": [ + "xvalue_should_be_avalue" + ], + "mapdata_canonify_eval_1_1": [ + "key___0", + "key___1", + "key___2" + ], + "mapdata_canonify_eval_1_2": [ + "key___0__value___1", + "key___1__value___2", + "key___2__value___3" + ], + "mapdata_canonify_eval_1_3": [ + "key___0__key2_____this_k_1____value___1", + "key___1__key2_____this_k_1____value___2", + "key___2__key2_____this_k_1____value___3" + ], + "mapdata_canonify_eval_1_4": [ + "xvalue_should_be_0value", + "xvalue_should_be_1value", + "xvalue_should_be_2value" + ], + "mapdata_canonify_eval_2_1": [ + "key___0", + "key___1", + "key___2" + ], + "mapdata_canonify_eval_2_2": [ + "key___0__value___eleme_nt1", + "key___1__value___element2", + "key___2__value___element3" + ], + "mapdata_canonify_eval_2_3": [ + "key___0__key2_____this_k_1____value___eleme_nt1", + "key___1__key2_____this_k_1____value___element2", + "key___2__key2_____this_k_1____value___element3" + ], + "mapdata_canonify_eval_2_4": [ + "xvalue_should_be_0value", + "xvalue_should_be_1value", + "xvalue_should_be_2value" + ], + "mapdata_canonify_eval_3_1": [ + "key___x_x" + ], + "mapdata_canonify_eval_3_2": [ + "key___x_x__value___y_y" + ], + "mapdata_canonify_eval_3_3": [ + "key___x_x__key2_____this_k_1____value___y_y" + ], + "mapdata_canonify_eval_3_4": [ + "xvalue_should_be_xxvalue" + ], + "mapdata_canonify_eval_4_1": [], + "mapdata_canonify_eval_4_2": [], + "mapdata_canonify_eval_4_3": [], + "mapdata_canonify_eval_4_4": [], + "mapdata_canonify_eval_5_1": [ + "key___anotherkey", + "key___lastkey_", + "key___mykey" + ], + "mapdata_canonify_eval_5_2": [ + "key___anotherkey__value___anothervalue", + "key___lastkey___value___o_ne", + "key___lastkey___value___two", + "key___lastkey___value___three", + "key___mykey__value___myvalue" + ], + "mapdata_canonify_eval_5_3": [ + "key___anotherkey__key2_____this_k_1____value___anothervalue", + "key___lastkey___key2_____this_k_1____value___o_ne", + "key___lastkey___key2_____this_k_1____value___two", + "key___lastkey___key2_____this_k_1____value___three", + "key___mykey__key2_____this_k_1____value___myvalue" + ], + "mapdata_canonify_eval_5_4": [ + "xvalue_should_be_anothervalue", + "xvalue_should_be_lastvalue", + "xvalue_should_be_myvalue" + ], + "mapdata_canonify_eval_6_1": [ + "key___a" + ], + "mapdata_canonify_eval_6_2": [ + "key___a__value___c" + ], + "mapdata_canonify_eval_6_3": [ + "key___a__key2___b__value___c" + ], + "mapdata_canonify_eval_6_4": [ + "xvalue_should_be_avalue" + ], + "mapdata_json_1_1": [], + "mapdata_json_1_2": [ + { + "key": "0", + "value": "1" + }, + { + "key": "1", + "value": "2" + }, + { + "key": "2", + "value": "3" + } + ], + "mapdata_json_1_3": [ + [ + "0", + "1" + ], + [ + "1", + "2" + ], + [ + "2", + "3" + ] + ], + "mapdata_json_1_4": [ + { + "key": "0", + "key2": "$(this.k[1])", + "value": "1" + }, + { + "key": "1", + "key2": "$(this.k[1])", + "value": "2" + }, + { + "key": "2", + "key2": "$(this.k[1])", + "value": "3" + } + ], + "mapdata_json_2_1": [], + "mapdata_json_2_2": [ + { + "key": "0", + "value": "eleme\"nt1" + }, + { + "key": "1", + "value": "element2" + }, + { + "key": "2", + "value": "element3" + } + ], + "mapdata_json_2_3": [ + [ + "0", + "eleme\"nt1" + ], + [ + "1", + "element2" + ], + [ + "2", + "element3" + ] + ], + "mapdata_json_2_4": [ + { + "key": "0", + "key2": "$(this.k[1])", + "value": "eleme\"nt1" + }, + { + "key": "1", + "key2": "$(this.k[1])", + "value": "element2" + }, + { + "key": "2", + "key2": "$(this.k[1])", + "value": "element3" + } + ], + "mapdata_json_3_1": [], + "mapdata_json_3_2": [ + { + "key": "x\"x", + "value": "y\"y" + } + ], + "mapdata_json_3_3": [ + [ + "x\"x", + "y\"y" + ] + ], + "mapdata_json_3_4": [ + { + "key": "x\"x", + "key2": "$(this.k[1])", + "value": "y\"y" + } + ], + "mapdata_json_4_1": [], + "mapdata_json_4_2": [], + "mapdata_json_4_3": [], + "mapdata_json_4_4": [], + "mapdata_json_5_1": [], + "mapdata_json_5_2": [ + { + "key": "anotherkey", + "value": "anothervalue" + }, + { + "key": "lastkey!", + "value": "o\"ne" + }, + { + "key": "lastkey!", + "value": "two" + }, + { + "key": "lastkey!", + "value": "three" + }, + { + "key": "mykey", + "value": "myvalue" + } + ], + "mapdata_json_5_3": [ + [ + "anotherkey", + "anothervalue" + ], + [ + "lastkey!", + "o\"ne" + ], + [ + "lastkey!", + "two" + ], + [ + "lastkey!", + "three" + ], + [ + "mykey", + "myvalue" + ] + ], + "mapdata_json_5_4": [ + { + "key": "anotherkey", + "key2": "$(this.k[1])", + "value": "anothervalue" + }, + { + "key": "lastkey!", + "key2": "$(this.k[1])", + "value": "o\"ne" + }, + { + "key": "lastkey!", + "key2": "$(this.k[1])", + "value": "two" + }, + { + "key": "lastkey!", + "key2": "$(this.k[1])", + "value": "three" + }, + { + "key": "mykey", + "key2": "$(this.k[1])", + "value": "myvalue" + } + ], + "mapdata_json_6_1": [], + "mapdata_json_6_2": [ + { + "key": "a", + "value": "c" + } + ], + "mapdata_json_6_3": [ + [ + "a", + "c" + ] + ], + "mapdata_json_6_4": [ + { + "key": "a", + "key2": "b", + "value": "c" + } + ], + "mapdata_none_1_1": [ + "key = 0", + "key = 1", + "key = 2" + ], + "mapdata_none_1_2": [ + "key = 0, value = 1", + "key = 1, value = 2", + "key = 2, value = 3" + ], + "mapdata_none_1_3": [ + "key = 0, key2 = $(this.k[1]), value = 1", + "key = 1, key2 = $(this.k[1]), value = 2", + "key = 2, key2 = $(this.k[1]), value = 3" + ], + "mapdata_none_1_4": [ + "xvalue should be 0value", + "xvalue should be 1value", + "xvalue should be 2value" + ], + "mapdata_none_2_1": [ + "key = 0", + "key = 1", + "key = 2" + ], + "mapdata_none_2_2": [ + "key = 0, value = eleme\"nt1", + "key = 1, value = element2", + "key = 2, value = element3" + ], + "mapdata_none_2_3": [ + "key = 0, key2 = $(this.k[1]), value = eleme\"nt1", + "key = 1, key2 = $(this.k[1]), value = element2", + "key = 2, key2 = $(this.k[1]), value = element3" + ], + "mapdata_none_2_4": [ + "xvalue should be 0value", + "xvalue should be 1value", + "xvalue should be 2value" + ], + "mapdata_none_3_1": [ + "key = x\"x" + ], + "mapdata_none_3_2": [ + "key = x\"x, value = y\"y" + ], + "mapdata_none_3_3": [ + "key = x\"x, key2 = $(this.k[1]), value = y\"y" + ], + "mapdata_none_3_4": [ + "xvalue should be xxvalue" + ], + "mapdata_none_4_1": [], + "mapdata_none_4_2": [], + "mapdata_none_4_3": [], + "mapdata_none_4_4": [], + "mapdata_none_5_1": [ + "key = anotherkey", + "key = lastkey!", + "key = mykey" + ], + "mapdata_none_5_2": [ + "key = anotherkey, value = anothervalue", + "key = lastkey!, value = o\"ne", + "key = lastkey!, value = two", + "key = lastkey!, value = three", + "key = mykey, value = myvalue" + ], + "mapdata_none_5_3": [ + "key = anotherkey, key2 = $(this.k[1]), value = anothervalue", + "key = lastkey!, key2 = $(this.k[1]), value = o\"ne", + "key = lastkey!, key2 = $(this.k[1]), value = two", + "key = lastkey!, key2 = $(this.k[1]), value = three", + "key = mykey, key2 = $(this.k[1]), value = myvalue" + ], + "mapdata_none_5_4": [ + "xvalue should be anothervalue", + "xvalue should be lastvalue", + "xvalue should be myvalue" + ], + "mapdata_none_6_1": [ + "key = a" + ], + "mapdata_none_6_2": [ + "key = a, value = c" + ], + "mapdata_none_6_3": [ + "key = a, key2 = b, value = c" + ], + "mapdata_none_6_4": [ + "xvalue should be avalue" + ], + "spec1": "key = $(this.k)", + "spec2": "key = $(this.k), value = ${this.v}", + "spec3": "key = $(this.k), key2 = $(this.k[1]), value = ${this.v}", + "spec4": "xvalue should be $(static[$(this.k)])", + "static[0]": "0value", + "static[1]": "1value", + "static[2]": "2value", + "static[a]": "avalue", + "static[anotherkey]": "anothervalue", + "static[lastkey!]": "lastvalue", + "static[mykey]": "myvalue", + "static[x"x]": "xxvalue", + "static[x]": "xvalue" +} diff --git a/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cat_consume_input.sh b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cat_consume_input.sh new file mode 100755 index 0000000000..b0c2e498c7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cat_consume_input.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Works like "cat ", except that it will consume everything on stdin +# before outputting the file. + +cat > /dev/null +cat "$@" diff --git a/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf new file mode 100644 index 0000000000..75bc258f51 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf @@ -0,0 +1,37 @@ +####################################################### +# +# Test mapdata('json_pipe') +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + # Skip on Windows because we use shell script below. + "test_skip_needs_work" string => "windows"; + + vars: + "items" data => '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}'; + "items_passthrough" data => mapdata("json_pipe", "$(G.cat)", items); + "items_jqinput" data => mapdata("json_pipe", "$(this.promise_dirname)/mapdata_json_pipe.cat_consume_input.sh $(this.promise_filename).jqinput.json", items); + "items_input" data => mapdata("json_pipe", "$(this.promise_dirname)/mapdata_json_pipe.cat_consume_input.sh $(this.promise_filename).input.json", items); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.expected.json b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.expected.json new file mode 100644 index 0000000000..7eebde0ed7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.expected.json @@ -0,0 +1,44 @@ +{ + "items": { + "titles": [ + "JQ Primer", + "More JQ" + ], + "user": "stedolan" + }, + "items_input": [ + { + "a": 100, + "b": true + }, + { + "a": 100, + "b": true + }, + { + "a": 100, + "b": true + }, + { + } + ], + "items_jqinput": [ + { + "title": "JQ Primer", + "user": "stedolan" + }, + { + "title": "More JQ", + "user": "stedolan" + } + ], + "items_passthrough": [ + { + "titles": [ + "JQ Primer", + "More JQ" + ], + "user": "stedolan" + } + ] +} diff --git a/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.input.json b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.input.json new file mode 100644 index 0000000000..6436aee33d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.input.json @@ -0,0 +1,11 @@ +{ "a": 100, "b": true } + +{ "a": 100, + "b": true } +{ + "a": 100, + "b": + true +} + +{} diff --git a/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.jqinput.json b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.jqinput.json new file mode 100644 index 0000000000..37222a8843 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mapdata_json_pipe.cf.jqinput.json @@ -0,0 +1,8 @@ +{ + "user": "stedolan", + "title": "JQ Primer" +} +{ + "user": "stedolan", + "title": "More JQ" +} diff --git a/tests/acceptance/01_vars/02_functions/maplist.cf b/tests/acceptance/01_vars/02_functions/maplist.cf new file mode 100644 index 0000000000..202f94f2d8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/maplist.cf @@ -0,0 +1,38 @@ +####################################################### +# +# Test maplist() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "testlist" slist => { "zero", "two", "three's", "four-fore:quatre", "last" }; + "empty" slist => { }; + + "mapped_testlist" slist => maplist("value=$(this)", "testlist"); + "mapped_empty" slist => maplist("value=$(this)", empty); + "mapped_inline" slist => maplist("value=$(this)", '["a", "b", "c"]'); + "mapped_function_empty" slist => maplist(canonify($(this)), empty); + "mapped_function1" slist => maplist(format("%10.10s", $(this)), testlist); + "mapped_function2" slist => maplist(canonify($(this)), '["a or b", "b and c", "c+d", "e"]'); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/maplist.cf.expected.json b/tests/acceptance/01_vars/02_functions/maplist.cf.expected.json new file mode 100644 index 0000000000..f2147ab5b8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/maplist.cf.expected.json @@ -0,0 +1,37 @@ +{ + "empty": [], + "mapped_empty": [], + "mapped_function1": [ + " zero", + " two", + " three's", + "four-fore:", + " last" + ], + "mapped_function2": [ + "a_or_b", + "b_and_c", + "c_d", + "e" + ], + "mapped_function_empty": [], + "mapped_inline": [ + "value=a", + "value=b", + "value=c" + ], + "mapped_testlist": [ + "value=zero", + "value=two", + "value=three's", + "value=four-fore:quatre", + "value=last" + ], + "testlist": [ + "zero", + "two", + "three's", + "four-fore:quatre", + "last" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-0.cf b/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-0.cf new file mode 100644 index 0000000000..1a99d9c4b6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-0.cf @@ -0,0 +1,98 @@ +# Test that merged data does not inherit variables from calling bundle + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2551" } + string => "Test that merged data does not include variables from calling bundle."; + + "test_soft_fail" + string => "any", + meta => { "CFE-2551"}; + + vars: + + "a" string => "string a"; + "b" string => "string b"; + + "vars1[a]" string => "$(a)"; + "vars1[b]" string => "$(b)"; + + methods: + + # Call bundle that will define and merge it's own vars1 data structure. + + "Call another bundle" + usebundle => called_by_bundle_test; + +} + +bundle agent called_by_bundle_test +{ + vars: + "c" string => "string c"; + "vars1[c]" string => "$(c)"; + + # Create a new data structure NOT including data from calling bundle + + "merged_data_vars" + data => mergedata('{ "vars1": vars1 }'); + + DEBUG|EXTRA:: + # We only care to stringify this if we need the debug reports + "merged_data_vars_string" + string => storejson( merged_data_vars ); + + reports: + DEBUG|EXTRA:: + "Got:$(const.n)$(merged_data_vars_string)"; + 'Expect: +{ + "vars1": { + "c": "string c" + } +}'; +} + + +bundle agent check +{ + classes: + + # We pass if the variable indicies found in bundle test are NOT found in + # the bundle it called, and we find the index defined in the bundle it + # called that we expect. + + "pass" + expression => and( + and( + not( isvariable("called_by_bundle_test.merged_data_vars[vars1][a]")), + not( isvariable("called_by_bundle_test.merged_data_vars[vars1][b]")) + ), + isvariable("called_by_bundle_test.merged_data_vars[vars1][c]") + ); + + # We fail if the variable indicies found in bundle test are also found in + # the bundle it called. + + "fail" + or => { + isvariable( "called_by_bundle_test.merged_data_vars[vars1][a]" ), + isvariable( "called_by_bundle_test.merged_data_vars[vars1][b]" ), + }; + + + reports: + + fail:: + "$(this.promise_filename) FAIL"; + + pass:: + "$(this.promise_filename) Pass"; + } diff --git a/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-1.cf b/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-1.cf new file mode 100644 index 0000000000..c30701a2e9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-1.cf @@ -0,0 +1,95 @@ +# Test that merged data does not inherit variables from calling bundle + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2551" } + string => "Test that merged data does not include variables from calling bundle. Variant specifying bundle qualified variable"; + + vars: + + "a" string => "string a"; + "b" string => "string b"; + + "vars1[a]" string => "$(a)"; + "vars1[b]" string => "$(b)"; + + methods: + + # Call bundle that will define and merge it's own vars1 data structure. + + "Call another bundle" + usebundle => called_by_bundle_test; + +} + +bundle agent called_by_bundle_test +{ + vars: + "c" string => "string c"; + "vars1[c]" string => "$(c)"; + + # Create a new data structure NOT including data from calling bundle + + # NOTE: This is what differs from the base test + "merged_data_vars" + data => mergedata('{ "vars1": $(this.bundle).vars1 }'); + + DEBUG|EXTRA:: + # We only care to stringify this if we need the debug reports + "merged_data_vars_string" + string => storejson( merged_data_vars ); + + reports: + DEBUG|EXTRA:: + "Got:$(const.n)$(merged_data_vars_string)"; + 'Expect: +{ + "vars1": { + "c": "string c" + } +}'; +} + + +bundle agent check +{ + classes: + + # We pass if the variable indicies found in bundle test are NOT found in + # the bundle it called, and we find the index defined in the bundle it + # called that we expect. + + "pass" + expression => and( + and( + not( isvariable("called_by_bundle_test.merged_data_vars[vars1][a]")), + not( isvariable("called_by_bundle_test.merged_data_vars[vars1][b]")) + ), + isvariable("called_by_bundle_test.merged_data_vars[vars1][c]") + ); + + # We fail if the variable indicies found in bundle test are also found in + # the bundle it called. + + "fail" + or => { + isvariable( "called_by_bundle_test.merged_data_vars[vars1][a]" ), + isvariable( "called_by_bundle_test.merged_data_vars[vars1][b]" ), + }; + + + reports: + + fail:: + "$(this.promise_filename) FAIL"; + + pass:: + "$(this.promise_filename) Pass"; + } diff --git a/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-2.cf b/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-2.cf new file mode 100644 index 0000000000..dc0401a278 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata-CFE-2551-2.cf @@ -0,0 +1,83 @@ +# Test that merged data does not inherit variables from calling bundle + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2551" } + string => "Test that merged data does not include variables from calling bundle."; + + vars: + + "a" string => "string a"; + "b" string => "string b"; + + "vars1[a]" string => "$(a)"; + "vars1[b]" string => "$(b)"; + + methods: + + # Call bundle that will define and merge it's own vars1 data structure. + + "Call another bundle" + usebundle => called_by_bundle_test; + +} + +bundle agent called_by_bundle_test +{ + vars: + "c" string => "string c"; + "vars1[c]" string => "$(c)"; + + # Create a new data structure NOT including data from calling bundle + + # This variant is expected to not work, because the data structure (this + # bundles var1) cannot be referenced "bare" as the colon (:) makes the + # data look like JSON. So the mergedata fails, and the merged_data_vars + # isn't defined. + + "merged_data_vars" + data => mergedata('{ "vars1": $(this.namespace):$(this.bundle).vars1 }'); + + DEBUG|EXTRA:: + # We only care to stringify this if we need the debug reports + "merged_data_vars_string" + string => storejson( merged_data_vars ); + + reports: + DEBUG|EXTRA:: + "Got:$(const.n)$(merged_data_vars_string)"; + "Expect merged_data_vars to be undefined."; +} + + +bundle agent check +{ + classes: + + # We pass if the variable indicies found in bundle test are NOT found in + # the bundle it called, and we find the index defined in the bundle it + # called that we expect. + + "pass" + not => isvariable("called_by_bundle_test.merged_data_vars"); + # We fail if the variable indicies found in bundle test are also found in + # the bundle it called. + + "fail" + expression => isvariable( "called_by_bundle_test.merged_data_vars" ); + + reports: + + fail:: + "$(this.promise_filename) FAIL"; + + pass:: + "$(this.promise_filename) Pass"; + } diff --git a/tests/acceptance/01_vars/02_functions/mergedata-json-strings.cf b/tests/acceptance/01_vars/02_functions/mergedata-json-strings.cf new file mode 100644 index 0000000000..1cedd35a62 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata-json-strings.cf @@ -0,0 +1,35 @@ +#!/var/cfengine/bin/cf-agent -f- +body common control +{ + inputs => { + "../../default.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" + string => "Test that plain json strings can be merged"; + + vars: + "merged" data => mergedata( '[]', '{ "one": "1" }', '{ "two":"2"}' ); + + files: + "$(G.testfile)" + create => "true", + edit_template_string => "{{%-top-}}", + template_method => "inline_mustache", + template_data => @(merged); +} + +bundle agent check +{ + methods: + "PASS/FAIL" + usebundle => dcs_check_diff("$(G.testfile)", + "$(this.promise_filename).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/02_functions/mergedata-json-strings.cf.expected b/tests/acceptance/01_vars/02_functions/mergedata-json-strings.cf.expected new file mode 100644 index 0000000000..c8bfcf9dfc --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata-json-strings.cf.expected @@ -0,0 +1,4 @@ +{ + "one": "1", + "two": "2" +} \ No newline at end of file diff --git a/tests/acceptance/01_vars/02_functions/mergedata.cf b/tests/acceptance/01_vars/02_functions/mergedata.cf new file mode 100644 index 0000000000..9be538c7f3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata.cf @@ -0,0 +1,139 @@ +####################################################### +# +# Test mergedata() +# This test is unstable until we have canonical JSON output +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "load1" data => parsejson('[ 1, 2, 3]'); + "load2" slist => { "element1", "element2", "element3" }; + "load3" data => parsejson('{ "x": "y" }'); + "load4" slist => { }; + "load5[mykey]" slist => { "myvalue" }; + "load5[anotherkey]" string => "anothervalue"; + "load5[lastkey!]" slist => {}; + + "X" slist => { "1", "2", "3", "4", "5" }; + "Y" slist => { @(X) }; + + "load_$(X)" data => mergedata("load$(X)"); + "load_$(X)_$(Y)" data => mergedata("load$(X)", "load$(Y)"); + "wrap_key_$(X)" data => mergedata('{ "wrapkey": load$(X) }'); + "wrap_array_$(X)" data => mergedata('[ load$(X) ]'); + + "bad1" data => mergedata(missingvar); + "bad2" data => mergedata(load1, missingvar); + + "justastring" string => "me"; + "bad3" data => mergedata(justastring); + "bad4" data => mergedata(load1, justastring); +} + +####################################################### + +bundle agent check +{ + vars: + "X" slist => { @(test.X) }; + "Y" slist => { @(test.X) }; + + "expected_1" string => '[1,2,3]'; + "expected_2" string => '["element1","element2","element3"]'; + "expected_3" string => '{"x":"y"}'; + "expected_4" string => '[]'; + "expected_5" string => '{"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}'; + "expected_1_1" string => '[1,2,3,1,2,3]'; + "expected_1_2" string => '[1,2,3,"element1","element2","element3"]'; + "expected_1_3" string => '{"0":1,"1":2,"2":3,"x":"y"}'; + "expected_1_4" string => '[1,2,3]'; + "expected_1_5" string => '{"0":1,"1":2,"2":3,"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}'; + "expected_2_1" string => '["element1","element2","element3",1,2,3]'; + "expected_2_2" string => '["element1","element2","element3","element1","element2","element3"]'; + "expected_2_3" string => '{"0":"element1","1":"element2","2":"element3","x":"y"}'; + "expected_2_4" string => '["element1","element2","element3"]'; + "expected_2_5" string => '{"0":"element1","1":"element2","2":"element3","anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}'; + "expected_3_1" string => '{"0":1,"1":2,"2":3,"x":"y"}'; + "expected_3_2" string => '{"0":"element1","1":"element2","2":"element3","x":"y"}'; + "expected_3_3" string => '{"x":"y"}'; + "expected_3_4" string => '{"x":"y"}'; + "expected_3_5" string => '{"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"],"x":"y"}'; + "expected_4_1" string => '[1,2,3]'; + "expected_4_2" string => '["element1","element2","element3"]'; + "expected_4_3" string => '{"x":"y"}'; + "expected_4_4" string => '[]'; + "expected_4_5" string => '{"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}'; + "expected_5_1" string => '{"0":1,"1":2,"2":3,"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}'; + "expected_5_2" string => '{"0":"element1","1":"element2","2":"element3","anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}'; + "expected_5_3" string => '{"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"],"x":"y"}'; + "expected_5_4" string => '{"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}'; + "expected_5_5" string => '{"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}'; + + "expected_wrapped_key_1" string => '{"wrapkey":[1,2,3]}'; + "expected_wrapped_key_2" string => '{"wrapkey":["element1","element2","element3"]}'; + "expected_wrapped_key_3" string => '{"wrapkey":{"x":"y"}}'; + "expected_wrapped_key_4" string => '{"wrapkey":[]}'; + "expected_wrapped_key_5" string => '{"wrapkey":{"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}}'; + + "expected_wrapped_array_1" string => '[[1,2,3]]'; + "expected_wrapped_array_2" string => '[["element1","element2","element3"]]'; + "expected_wrapped_array_3" string => '[{"x":"y"}]'; + "expected_wrapped_array_4" string => '[[]]'; + "expected_wrapped_array_5" string => '[{"anotherkey":"anothervalue","lastkey!":[],"mykey":["myvalue"]}]'; + + "actual_$(X)_$(Y)" string => format("%S", "test.load_$(X)_$(Y)"); + "actual_$(X)" string => format("%S", "test.load_$(X)"); + "actual_wrapped_key_$(X)" string => format("%S", "test.wrap_key_$(X)"); + "actual_wrapped_array_$(X)" string => format("%S", "test.wrap_array_$(X)"); + + classes: + "ok_$(X)_$(Y)" expression => strcmp("$(actual_$(X)_$(Y))", + "$(expected_$(X)_$(Y))"); + + "ok_$(X)" expression => strcmp("$(actual_$(X))", + "$(expected_$(X))"); + + "not_ok_wrapped_key_$(X)" not => strcmp("$(actual_wrapped_key_$(X))", + "$(expected_wrapped_key_$(X))"); + + "not_ok_wrapped_array_$(X)" not => strcmp("$(actual_wrapped_array_$(X))", + "$(expected_wrapped_array_$(X))"); + + "ok" not => classmatch("not_ok_.*"); + + reports: + DEBUG:: + "$(X): actual $(actual_$(X)) != $(X) expected $(expected_$(X))" + if => "!ok_$(X)"; + + "$(X): actual wrap key $(actual_wrapped_key_$(X)) != $(X) expected wrapped key $(expected_wrapped_key_$(X))" + if => "not_ok_wrapped_key_$(X)"; + + "$(X): actual wrap array $(actual_wrapped_array_$(X)) != $(X) expected wrapped array $(expected_wrapped_array_$(X))" + if => "not_ok_wrapped_array_$(X)"; + + "$(X)+$(Y): actual $(actual_$(X)_$(Y)) != $(X)+$(Y) expected $(expected_$(X)_$(Y))" + if => "!ok_$(X)_$(Y)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-0.cf b/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-0.cf new file mode 100644 index 0000000000..5fca303518 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-0.cf @@ -0,0 +1,71 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} +bundle agent main +{ + methods: + "test"; + "check"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2551" } + string => "Test that variables defined in other bundles with the same name are not included in a merged result"; + + "test_soft_fail" + string => "any", + meta => { "CFE-2551" }; + + vars: + + "a" string => "string a"; + "b" string => "string b"; + + "vars1[a]" string => "$(a)"; + "vars1[b]" string => "$(b)"; + + methods: + "run_low_test_merged" usebundle => test_merged_low; + +} + +bundle agent test_merged_low +{ + vars: + "c" string => "string c"; + "vars1[c]" string => "$(c)"; + + # We expect that only the values from the classic array vars1 in this + # bundle will be merged together. And thus, we should only end up with ={ "vars1" : { "c": "string c" } }= + + "merged_data_vars" data => mergedata('{ "vars1": vars1 }'); + "merged_data_vars_str" string => format( "%S", merged_data_vars ); + + reports: + DEBUG|EXTRA:: + "$(this.bundle) : merged_data_vars_str : $(merged_data_vars_str)"; + +} + +bundle agent check +{ + vars: + "expected" string => '{"vars1":{"c":"string c"}}'; + + classes: + "pass" expression => strcmp( $(expected), $(test_merged_low.merged_data_vars_str) ); + + reports: + pass:: + "$(this.promise_filename) Pass"; + + !pass:: + "$(this.promise_filename) FAIL"; + "expected: '$(expected)'"; + "got: '$(test_merged_low.merged_data_vars_str)'"; + +} diff --git a/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-1.cf b/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-1.cf new file mode 100644 index 0000000000..82a17cbc3d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-1.cf @@ -0,0 +1,67 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} +bundle agent main +{ + methods: + "test"; + "check"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2551" } + string => "Test that variables defined in other bundles with the same name are not included in a merged result"; + + vars: + + "a" string => "string a"; + "b" string => "string b"; + + "vars1[a]" string => "$(a)"; + "vars1[b]" string => "$(b)"; + + methods: + "run_low_test_merged" usebundle => test_merged_low; + +} + +bundle agent test_merged_low +{ + vars: + "c" string => "string c"; + "vars1[c]" string => "$(c)"; + + # We expect that only the values from the classic array vars1 in this + # bundle will be merged together. And thus, we should only end up with ={ "vars1" : { "c": "string c" } }= + + "merged_data_vars" data => mergedata('{ "vars1": test_merged_low.vars1 }'); + "merged_data_vars_str" string => format( "%S", merged_data_vars ); + + reports: + DEBUG|EXTRA:: + "$(this.bundle) : merged_data_vars_str : $(merged_data_vars_str)"; + +} + +bundle agent check +{ + vars: + "expected" string => '{"vars1":{"c":"string c"}}'; + + classes: + "pass" expression => strcmp( $(expected), $(test_merged_low.merged_data_vars_str) ); + + reports: + pass:: + "$(this.promise_filename) Pass"; + + !pass:: + "$(this.promise_filename) FAIL"; + "expected: '$(expected)'"; + "got: '$(test_merged_low.merged_data_vars_str)'"; + +} diff --git a/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-2.cf b/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-2.cf new file mode 100644 index 0000000000..8590341dce --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata_CFE-2551-2.cf @@ -0,0 +1,67 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} +bundle agent main +{ + methods: + "test"; + "check"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2551" } + string => "Test that variables defined in other bundles with the same name are not included in a merged result"; + + vars: + + "a" string => "string a"; + "b" string => "string b"; + + "vars1[a]" string => "$(a)"; + "vars1[b]" string => "$(b)"; + + methods: + "run_low_test_merged" usebundle => test_merged_low; + +} + +bundle agent test_merged_low +{ + vars: + "c" string => "string c"; + "vars1[c]" string => "$(c)"; + + # We expect that only the values from the classic array vars1 in this + # bundle will be merged together. And thus, we should only end up with ={ "vars1" : { "c": "string c" } }= + + "merged_data_vars" data => mergedata('{ "vars1": $(this.bundle).vars1 }'); + "merged_data_vars_str" string => format( "%S", merged_data_vars ); + + reports: + DEBUG|EXTRA:: + "$(this.bundle) : merged_data_vars_str : $(merged_data_vars_str)"; + +} + +bundle agent check +{ + vars: + "expected" string => '{"vars1":{"c":"string c"}}'; + + classes: + "pass" expression => strcmp( $(expected), $(test_merged_low.merged_data_vars_str) ); + + reports: + pass:: + "$(this.promise_filename) Pass"; + + !pass:: + "$(this.promise_filename) FAIL"; + "expected: '$(expected)'"; + "got: '$(test_merged_low.merged_data_vars_str)'"; + +} diff --git a/tests/acceptance/01_vars/02_functions/mergedata_multilevel_array.cf b/tests/acceptance/01_vars/02_functions/mergedata_multilevel_array.cf new file mode 100644 index 0000000000..5161b1141d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata_multilevel_array.cf @@ -0,0 +1,35 @@ +# Test that merging a multi level classic array works +# Ref: https://dev.cfengine.com/issues/4566 +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent check +{ + vars: + "a[0][class]" string => "any"; + "a[0][command]" string => "/bin/true"; + "a[1][class]" string => "linux"; + "a[1][command]" string => "/bin/false"; + + "m" data => mergedata( a ); + + "i" slist => getindices( m ); + + classes: + "PASS" expression => strcmp( "any", "$(m[0][class])" ); + + reports: + PASS:: + "$(this.promise_filename) Pass"; + !PASS:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "i => [$(i)]"; + "class => $(m[$(i)][class])"; + "command => $(m[$(i)][command])"; + "version => $(sys.cf_version)"; +} diff --git a/tests/acceptance/01_vars/02_functions/mergedata_with_list_iteration.cf b/tests/acceptance/01_vars/02_functions/mergedata_with_list_iteration.cf new file mode 100644 index 0000000000..8b3f7a31f7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mergedata_with_list_iteration.cf @@ -0,0 +1,78 @@ +####################################################### +# +# Test that mergedata works with list iteration +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { test_1, test_2, default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test_1 +{ + vars: + "hosts" + meta => { "hosts" }, + data => parsejson('{"10.100.250.11": [ "slot2" ], "10.100.250.10": [ "slot1" ], "111": 222 }'); +} + +bundle agent test_2 +{ + vars: + "hosts" + meta => { "hosts" }, + data => parsejson('{"10.100.250.11": [ "cflin111" ], "10.100.250.10": [ "cflin110" ]}'); +} + +# this bundle is not in the bundlesequence but will be evaluated +bundle agent test_3 +{ + vars: + "hosts" + meta => { "hosts" }, + data => parsejson('{"10.100.250.12": [ "cflin111" ], "10.100.250.13": [ "cflin110" ]}'); +} + +bundle common test +{ + vars: + "variables" slist => variablesmatching(".*", "hosts"); + "sorted_vars" slist => sort("variables", "lex"); + + "merged" data => parsejson('{}'), policy => "free"; + "merged" data => mergedata(merged, $(sorted_vars)), policy => "free"; + + "result" string => format("%S", "merged"), policy => "free"; +} + + +####################################################### + +bundle agent check +{ + vars: + "expected" string => '{"10.100.250.10":["cflin110"],"10.100.250.11":["cflin111"],"10.100.250.12":["cflin111"],"10.100.250.13":["cflin110"],"111":222}'; + + classes: + "ok" expression => strcmp($(test.result), $(check.expected)); + + reports: + DEBUG.inform_mode:: + "Expected: '$(check.expected)'"; + "Result: '$(test.result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/missing_functions.cf b/tests/acceptance/01_vars/02_functions/missing_functions.cf new file mode 100644 index 0000000000..17045d1d04 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/missing_functions.cf @@ -0,0 +1,45 @@ +############################################################################## +# +# Redmine #3907: supposedly missing functions (in this case, a classes body with parameters) aren't +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + +} + + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -KI -f $(this.promise_filename).sub | $(G.grep) 'FnCall'", "useshell"); +} + +bundle agent check +{ + # If the output contains the FnCall warning about 9fdee2550bec1030e22addb0dc9d7218b9174c70, we fail + classes: + "ok" not => regcmp(".*9fdee2550bec1030e22addb0dc9d7218b9174c70.*", "$(test.subout)"); + + reports: + DEBUG:: + "agent output: $(test.subout)"; + + ok:: + "The FnCall bogus warning didn't happen"; + !ok:: + "The FnCall bogus warning happened"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/missing_functions.cf.sub b/tests/acceptance/01_vars/02_functions/missing_functions.cf.sub new file mode 100644 index 0000000000..d72ae0c342 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/missing_functions.cf.sub @@ -0,0 +1,22 @@ +############################################################################## +# +# Redmine #3907: supposedly missing functions (in this case, a classes body with parameters) aren't +# +############################################################################## + +body common control +{ + bundlesequence => {"example"}; +} + +bundle agent example +{ + vars: + "testme" string => "me?!?!?!", + classes => 9fdee2550bec1030e22addb0dc9d7218b9174c70("1"); +} + +body classes 9fdee2550bec1030e22addb0dc9d7218b9174c70(x) +{ + promise_repaired => { "9fdee2550bec1030e22addb0dc9d7218b9174c70" }; +} diff --git a/tests/acceptance/01_vars/02_functions/mustach_doesnt_render_null.cf b/tests/acceptance/01_vars/02_functions/mustach_doesnt_render_null.cf new file mode 100644 index 0000000000..381a6e9f2d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mustach_doesnt_render_null.cf @@ -0,0 +1,38 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" string => "Test that mustache doesn't render nulls that don't exist (CFE-2077)"; + + vars: + "jsondata" data => parsejson('{ "key" : ["one", "two", "three"]}'); + + "noninstersecting_list" slist => { "four" }; + + "list" slist => getvalues("jsondata[key]"); + + "diff_list" slist => difference("list", "noninstersecting_list"); + "diff_string" string => join(" ", diff_list); + + "mustache_string" string => string_mustache("{{#vars.test.diff_list}}{{{.}}} {{/vars.test.diff_list}}"); + +} + +bundle agent check +{ + + methods: + + "check" usebundle => dcs_check_regcmp("one two three ", $(test.mustache_string), $(this.promise_filename), "no"); + + reports: + DEBUG|EXTRA:: + "Diff string: $(test.diff_string)"; + "Mustache string: $(test.mustache_string)"; +} diff --git a/tests/acceptance/01_vars/02_functions/mustache_iterating_object_should_use_context_for_single_block_render.cf b/tests/acceptance/01_vars/02_functions/mustache_iterating_object_should_use_context_for_single_block_render.cf new file mode 100644 index 0000000000..d10321d0e2 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mustache_iterating_object_should_use_context_for_single_block_render.cf @@ -0,0 +1,100 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + + "template" string => "# Test1 +{{#testcase.test1}} +{{#data}} +Data = {{.}} +{{/data}} +{{/testcase.test1}} + +# Test2 +{{#testcase.test2}} +{{#data}} +Data = {{.}} +{{/data}} +{{#otherdata}} +Other data = {{.}} +{{/otherdata}} +{{/testcase.test2}}"; + + "data" data => '{ + "testcase": { + "test1": { + "data": [ + "v1", + "v2" + ] + }, + "test2": { + "data": [ + "v1", + "v2" + ], + "otherdata": [ + "v3", + "v4" + ] + } + } + }'; + +} + +bundle agent test +{ + meta: + + # https://mustache.github.io/#demo Try the template and data from above in + # the demo site. The issue we see in cfengine is that while iterating over + # testcase.test2 the rendering happens for each sub structure iterated + # duplicating the data. + + # So we get Instead of + # # Test2 | # Test2 + # Data = v1 | Data = v1 + # Data = v2 | Data = v2 + # Other data = v3 | Other data = v3 + # Other data = v4 | Other data = v4 + # Data = v1 | + # Data = v2 | + # Other data = v3 | + # Other data = v4 | + + "description" -> { "CFE-2125" } + string => "Test that iterating over object results in single block with + correct context (CFE-2125)."; + + vars: + "got" string => string_mustache( $(init.template), @(init.data) ); +} + +bundle agent check +{ + reports: + DEBUG:: + "got output: $(test.got)"; + + vars: + "expected" string => "# Test1 +Data = v1 +Data = v2 + +# Test2 +Data = v1 +Data = v2 +Other data = v3 +Other data = v4 +"; + + methods: + "check" usebundle => dcs_check_strcmp( $(test.got), $(check.expected), $(this.promise_filename), "no" ); +} diff --git a/tests/acceptance/01_vars/02_functions/mustache_iterating_object_workaround.cf b/tests/acceptance/01_vars/02_functions/mustache_iterating_object_workaround.cf new file mode 100644 index 0000000000..632dd21056 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mustache_iterating_object_workaround.cf @@ -0,0 +1,88 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + + "template" string => "# Test1 +{{#testcase.test1}} +{{#data}} +Data = {{.}} +{{/data}} +{{/testcase.test1}} + +# Test2 +{{#testcase.test2.data}} +Data = {{.}} +{{/testcase.test2.data}} +{{#testcase.test2.otherdata}} +Other data = {{.}} +{{/testcase.test2.otherdata}}"; + + "data" data => '{ + "testcase": { + "test1": { + "data": [ + "v1", + "v2" + ] + }, + "test2": { + "data": [ + "v1", + "v2" + ], + "otherdata": [ + "v3", + "v4" + ] + } + } + }'; + +} + +bundle agent test +{ + meta: + + # The issue we see in cfengine is that if a {{#testcase.test2}} section is + # used the corresponding JSON object is iterated over and the rendering + # happens for each sub structure (property) iterated duplicating the + # data. The workaround is to not use a separate section corresponding to + # the testcase.test2 JSON object with multiple properties. + + "description" -> { "CFE-2125" } + string => "Test a workaround for iterating over object to get a single + block with correct context (CFE-2125)."; + + vars: + "got" string => string_mustache( $(init.template), @(init.data) ); +} + +bundle agent check +{ + reports: + DEBUG:: + "got output: $(test.got)"; + + vars: + "expected" string => "# Test1 +Data = v1 +Data = v2 + +# Test2 +Data = v1 +Data = v2 +Other data = v3 +Other data = v4 +"; + + methods: + "check" usebundle => dcs_check_strcmp( $(test.got), $(check.expected), $(this.promise_filename), "no" ); +} diff --git a/tests/acceptance/01_vars/02_functions/mustache_iteration_with_nonfalse_values.cf b/tests/acceptance/01_vars/02_functions/mustache_iteration_with_nonfalse_values.cf new file mode 100644 index 0000000000..db591f7298 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mustache_iteration_with_nonfalse_values.cf @@ -0,0 +1,48 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} +bundle agent test +{ + meta: + "test_soft_fail" + string => "any", + meta => { "CFE-2535" }; +} + +bundle agent check { + vars: + "json" data => '{"test":[ + {"a?":{"val":"a1"}}, + {"b?":{"val":"b2"}}, + {"a?":{"val":"a3"}} + ]}'; + "json_str" string => format("%S", 'json'); + "result" string => string_mustache('{{#test}} +--- +A:{{#a?}}{{val}}{{/a?}}, B:{{#b?}}{{val}}{{/b?}} +{{/test}}', json); + "expected" string => "--- +A:a1, B: +--- +A:, B:b2 +--- +A:a3,B: +"; + + + reports: + "$(this.promise_filename) Pass" + if => strcmp( $(result), $(expected) ); + + "$(this.promise_filename) FAIL" + unless => strcmp( $(result), $(expected) ); + + EXTRA:: + "$(sys.cf_version)"; + "Result: +$(result)"; + "Should be: +$(expected)"; +} diff --git a/tests/acceptance/01_vars/02_functions/mustache_section_nonfalse_inverted.cf b/tests/acceptance/01_vars/02_functions/mustache_section_nonfalse_inverted.cf new file mode 100644 index 0000000000..32ea261644 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/mustache_section_nonfalse_inverted.cf @@ -0,0 +1,70 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + + "template" string => "{{#network_interfaces}} + +interface {{interface}} +{{#gateway}} +gateway {{.}} +{{/gateway}} +{{^gateway}} +#no gateway +{{/gateway}} + +{{/network_interfaces}}"; + + "data" data => '{ + "network_interfaces": [ + { + "interface":"eth1", + "family":"inet6", + "config_mode":"dhcp" + }, + { + "interface":"vlan10", + "device":"eth0", + "role":"Management", + "family":"inet", + "config_mode":"static", + "ip_address":"192.168.1.2", + "prefix":"24", + "gateway":"192.168.1.1" + } + ] + }'; + +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1565" } + string => "Test that inversion of non-false values works as expected with mustache. (CFE-1565)"; + + "test_soft_fail" + string => "any", + meta => { "CFE-1565" }; + vars: + "got" string => string_mustache( $(init.template), @(init.data) ); +} + +bundle agent check +{ + vars: + "expected" string => "interface eth1 +#no gateway +interface vlan10 +gateway 192.168.1.1 +"; + + methods: + "check" usebundle => dcs_check_strcmp( $(test.got), $(check.expected), $(this.promise_filename), "no" ); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/001.cf b/tests/acceptance/01_vars/02_functions/namespaces/001.cf new file mode 100644 index 0000000000..87e00eb838 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/001.cf @@ -0,0 +1,33 @@ +# Check that getindices() works correctly within and across namespaces + +# 001: getindices() with parameter variable in its argument, from a non-default namespace to the default namespace + +body common control +{ + inputs => { "../../../default.cf.sub", "001_namespaced_getindices.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace("default:g.array", "b:variables.array2"); +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/001_namespaced_getindices.cf.sub b/tests/acceptance/01_vars/02_functions/namespaces/001_namespaced_getindices.cf.sub new file mode 100644 index 0000000000..e92c400136 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/001_namespaced_getindices.cf.sub @@ -0,0 +1,23 @@ +body file control +{ + namespace => "b"; +} + +bundle common variables +{ + vars: + "array2[key]" string => "string2"; +} + +bundle agent test_in_namespace(array_name, array_name2) +{ + vars: + "repo_ids1" slist => getindices("$(array_name)"); +} + +bundle agent check_in_namespace(test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.repo_ids1)", "key", "$(test)", "no"); +} + diff --git a/tests/acceptance/01_vars/02_functions/namespaces/002.cf b/tests/acceptance/01_vars/02_functions/namespaces/002.cf new file mode 100644 index 0000000000..87059c5d9d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/002.cf @@ -0,0 +1,33 @@ +# Check that getindices() works correctly within and across namespaces + +# 002: getindices() with constant argument, from a non-default namespace to default namespace + +body common control +{ + inputs => { "../../../default.cf.sub", "002_namespaced_getindices.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace("default:g.array", "b:variables.array2"); +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/002_namespaced_getindices.cf.sub b/tests/acceptance/01_vars/02_functions/namespaces/002_namespaced_getindices.cf.sub new file mode 100644 index 0000000000..363d87542a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/002_namespaced_getindices.cf.sub @@ -0,0 +1,24 @@ +body file control +{ + namespace => "b"; +} + +bundle common variables +{ + vars: + "array2[key]" string => "string2"; +} + +bundle agent test_in_namespace(array_name, array_name2) +{ + vars: + "repo_ids2" slist => getindices("default:g.array"); + +} + +bundle agent check_in_namespace(test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.repo_ids2)", "key", "$(test)", "no"); +} + diff --git a/tests/acceptance/01_vars/02_functions/namespaces/003.cf b/tests/acceptance/01_vars/02_functions/namespaces/003.cf new file mode 100644 index 0000000000..7b73bbf334 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/003.cf @@ -0,0 +1,33 @@ +# Check that getindices() works correctly within and across namespaces + +# 003: getindices() with parameter variable argument, from non-default namespace to the same namespace + +body common control +{ + inputs => { "../../../default.cf.sub", "003_namespaced_getindices.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace("default:g.array", "b:variables.array2"); +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/003_namespaced_getindices.cf.sub b/tests/acceptance/01_vars/02_functions/namespaces/003_namespaced_getindices.cf.sub new file mode 100644 index 0000000000..b0e2850608 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/003_namespaced_getindices.cf.sub @@ -0,0 +1,24 @@ +body file control +{ + namespace => "b"; +} + +bundle common variables +{ + vars: + "array2[key]" string => "string2"; +} + +bundle agent test_in_namespace(array_name, array_name2) +{ + vars: + "repo_ids3" slist => getindices("$(array_name2)"); + +} + +bundle agent check_in_namespace(test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.repo_ids3)", "key", "$(test)", "no"); +} + diff --git a/tests/acceptance/01_vars/02_functions/namespaces/004.cf b/tests/acceptance/01_vars/02_functions/namespaces/004.cf new file mode 100644 index 0000000000..2b4611f531 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/004.cf @@ -0,0 +1,33 @@ +# Check that getindices() works correctly within and across namespaces + +# 004: getindices() with constant argument, from non-default namespace to the same namespace. + +body common control +{ + inputs => { "../../../default.cf.sub", "004_namespaced_getindices.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace("default:g.array", "b:variables.array2"); +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/004_namespaced_getindices.cf.sub b/tests/acceptance/01_vars/02_functions/namespaces/004_namespaced_getindices.cf.sub new file mode 100644 index 0000000000..e8babcbd1e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/004_namespaced_getindices.cf.sub @@ -0,0 +1,24 @@ +body file control +{ + namespace => "b"; +} + +bundle common variables +{ + vars: + "array2[key]" string => "string2"; +} + +bundle agent test_in_namespace(array_name, array_name2) +{ + vars: + "repo_ids4" slist => getindices("b:variables.array2"); + +} + +bundle agent check_in_namespace(test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.repo_ids4)", "key", "$(test)", "no"); +} + diff --git a/tests/acceptance/01_vars/02_functions/namespaces/005.cf b/tests/acceptance/01_vars/02_functions/namespaces/005.cf new file mode 100644 index 0000000000..3700bc6104 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/005.cf @@ -0,0 +1,39 @@ +# Check that getindices() works correctly within and across namespaces + +# 004: getindices() with constant argument, from non-default namespace to the same namespace. + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "any" usebundle => test_default_namespace("g.array"); +} + +bundle agent test_default_namespace(array_name) +{ + vars: + "repo_ids5" slist => getindices("$(array_name)"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_strcmp("$(test_default_namespace.repo_ids5)", "key", "$(this.promise_filename)", "no"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/006.cf b/tests/acceptance/01_vars/02_functions/namespaces/006.cf new file mode 100644 index 0000000000..9038d94c70 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/006.cf @@ -0,0 +1,39 @@ +# Check that getindices() works correctly within and across namespaces + +# 004: getindices() with constant argument, from non-default namespace to the same namespace. + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "any" usebundle => test_default_namespace; +} + +bundle agent test_default_namespace +{ + vars: + "repo_ids6" slist => getindices("g.array"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_strcmp("$(test_default_namespace.repo_ids6)", "key", "$(this.promise_filename)", "no"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/101.cf b/tests/acceptance/01_vars/02_functions/namespaces/101.cf new file mode 100644 index 0000000000..04d8674f56 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/101.cf @@ -0,0 +1,34 @@ +# Check that getindices() works correctly within and across namespaces + +# 101: getindices() with local variable in its argument, from a non-default namespace to the default namespace +# same as 001.cf but without using a parameter + +body common control +{ + inputs => { "../../../default.cf.sub", "101_namespaced_getindices.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace; +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/101_namespaced_getindices.cf.sub b/tests/acceptance/01_vars/02_functions/namespaces/101_namespaced_getindices.cf.sub new file mode 100644 index 0000000000..18c66a5ae1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/101_namespaced_getindices.cf.sub @@ -0,0 +1,24 @@ +body file control +{ + namespace => "b"; +} + +bundle common variables +{ + vars: + "array2[key]" string => "string2"; +} + +bundle agent test_in_namespace +{ + vars: + "array_name" string => "default:g.array"; + "repo_ids1" slist => getindices("$(array_name)"); +} + +bundle agent check_in_namespace(test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.repo_ids1)", "key", "$(test)", "no"); +} + diff --git a/tests/acceptance/01_vars/02_functions/namespaces/103.cf b/tests/acceptance/01_vars/02_functions/namespaces/103.cf new file mode 100644 index 0000000000..4101eefe5e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/103.cf @@ -0,0 +1,34 @@ +# Check that getindices() works correctly within and across namespaces + +# 103: getindices() with local variable argument, from non-default namespace to the same namespace +# same as 003.cf but without using a parameter + +body common control +{ + inputs => { "../../../default.cf.sub", "103_namespaced_getindices.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace; +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/103_namespaced_getindices.cf.sub b/tests/acceptance/01_vars/02_functions/namespaces/103_namespaced_getindices.cf.sub new file mode 100644 index 0000000000..faedcc2f27 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/103_namespaced_getindices.cf.sub @@ -0,0 +1,25 @@ +body file control +{ + namespace => "b"; +} + +bundle common variables +{ + vars: + "array2[key]" string => "string2"; +} + +bundle agent test_in_namespace +{ + vars: + "array_name2" string => "b:variables.array2"; + "repo_ids3" slist => getindices("$(array_name2)"); + +} + +bundle agent check_in_namespace(test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.repo_ids3)", "key", "$(test)", "no"); +} + diff --git a/tests/acceptance/01_vars/02_functions/namespaces/201.cf b/tests/acceptance/01_vars/02_functions/namespaces/201.cf new file mode 100644 index 0000000000..1d2e69f49e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/201.cf @@ -0,0 +1,33 @@ +# Check that getindices() works correctly within and across namespaces + +# 201: getindices() with parameter variable in its argument, from a non-default namespace to the default namespace, but with getindices() inside braces. + +body common control +{ + inputs => { "../../../default.cf.sub", "201_namespaced_getindices.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace("default:g.array", "b:variables.array2"); +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/201_namespaced_getindices.cf.sub b/tests/acceptance/01_vars/02_functions/namespaces/201_namespaced_getindices.cf.sub new file mode 100644 index 0000000000..f8dcf21db8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/201_namespaced_getindices.cf.sub @@ -0,0 +1,23 @@ +body file control +{ + namespace => "b"; +} + +bundle common variables +{ + vars: + "array2[key]" string => "string2"; +} + +bundle agent test_in_namespace(array_name, array_name2) +{ + vars: + "repo_ids1" slist => { getindices("$(array_name)") }; +} + +bundle agent check_in_namespace(test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.repo_ids1)", "key", "$(test)", "no"); +} + diff --git a/tests/acceptance/01_vars/02_functions/namespaces/203.cf b/tests/acceptance/01_vars/02_functions/namespaces/203.cf new file mode 100644 index 0000000000..b0cbc5c418 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/203.cf @@ -0,0 +1,33 @@ +# Check that getindices() works correctly within and across namespaces + +# 003: getindices() with parameter variable argument, from non-default namespace to the same namespace, but with getindices() inside braces. + +body common control +{ + inputs => { "../../../default.cf.sub", "203_namespaced_getindices.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "array[key]" string => "string"; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace("default:g.array", "b:variables.array2"); +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/namespaces/203_namespaced_getindices.cf.sub b/tests/acceptance/01_vars/02_functions/namespaces/203_namespaced_getindices.cf.sub new file mode 100644 index 0000000000..aa989354d7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/namespaces/203_namespaced_getindices.cf.sub @@ -0,0 +1,24 @@ +body file control +{ + namespace => "b"; +} + +bundle common variables +{ + vars: + "array2[key]" string => "string2"; +} + +bundle agent test_in_namespace(array_name, array_name2) +{ + vars: + "repo_ids3" slist => { getindices("$(array_name2)") }; + +} + +bundle agent check_in_namespace(test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.repo_ids3)", "key", "$(test)", "no"); +} + diff --git a/tests/acceptance/01_vars/02_functions/network/008.cf b/tests/acceptance/01_vars/02_functions/network/008.cf new file mode 100644 index 0000000000..72138e9a51 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/network/008.cf @@ -0,0 +1,66 @@ +####################################################### +# +# Test host2ip() +# +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + + vars: + # Neither of these are likely to change... + "localhost" string => host2ip("localhost"); + "a" string => host2ip("a.root-servers.net"); +} + +####################################################### + +bundle agent check +{ + vars: + "localhost" string => "127.0.0.1"; + "localhost_6" string => "::1"; + "a" string => "198.41.0.4"; + "a_6" string => "2001:503:ba3e::2:30"; + + classes: + "ok_a" or => { + strcmp("$(test.a)", "$(a)"), + strcmp("$(test.a)", "$(a_6)"), + }; + "ok_localhost" or => { + strcmp("$(test.localhost)", "$(localhost)"), + strcmp("$(test.localhost)", "$(localhost_6)"), + }; + "ok" and => { + "ok_a", + "ok_localhost", + }; + + reports: + DEBUG:: + "Expected $(test.localhost) == $(localhost)"; + "Expected $(test.a) == ( $(a) or $(a_6) )"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/network/selectservers.cf b/tests/acceptance/01_vars/02_functions/network/selectservers.cf new file mode 100644 index 0000000000..236b38e0a6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/network/selectservers.cf @@ -0,0 +1,52 @@ +# Test that failure to connect to remote host does not result in "Could not +# close socket" error messages. + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + # 5 seconds timeout for selectservers for non-connecting host 8.8.8.8 + default_timeout => "5"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + + # The first two hosts listen to port 80, the third does not listen + # to port 80, the fourth does not even resolve + "hosts" slist => { "cfengine.com", "www.duckduckgo.com", "smtp.mail.yahoo.com", "inexistent-server" }; + "retval" int => selectservers("@(hosts)","80","","","100","alive_servers"); +} + +bundle agent check +{ + classes: + "up2servers" expression => strcmp("$(test.retval)", "2"); + + "first_is_ok" expression => isvariable("test.alive_servers[0]"); + "second_is_ok" expression => isvariable("test.alive_servers[1]"); + "third_is_bad" not => isvariable("test.alive_servers[2]"); + "fourth_is_bad" not => isvariable("test.alive_servers[3]"); + + methods: + # All above classes must be set for test to pass + "" usebundle => dcs_passif_expected("up2servers,first_is_ok,second_is_ok,third_is_bad,fourth_is_bad", + "", $(this.promise_filename)), + inherit => "true"; + + reports: + DEBUG:: + "retval: $(test.retval)"; + "alive_servers: $(test.alive_servers[0]) $(test.alive_servers[1]) $(test.alive_servers[2])"; +} + diff --git a/tests/acceptance/01_vars/02_functions/network/url_get.cf b/tests/acceptance/01_vars/02_functions/network/url_get.cf new file mode 100644 index 0000000000..35f6535f98 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/network/url_get.cf @@ -0,0 +1,66 @@ +########################################################### +# +# Test url_get() +# +########################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent init +{ + vars: + "options_limit" data => ' +{ + "url.max_content": 200, + "nosuchoption": 100, + "url.verbose": 0, + "url.headers": [ "Foo: bar" ] +}'; + + "options_clean" data => '{}'; + "res1" data => url_get("http://acme.com", options_clean); + "res2" data => url_get("http://nosuchcfenginehost.com", options_clean); + # static content, we hope won't change much + "res3" data => url_get("http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js", options_limit); +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!feature_curl"; + + vars: + "kept" data => mergedata( + '{ "res1/returncode": init.res1[returncode] }', + '{ "res1/rc": init.res1[rc] }', + '{ "res1/success": init.res1[success] }', + + '{ "res2": init.res2[content] }', + '{ "res2/returncode": init.res2[returncode] }', + '{ "res2/rc": init.res2[rc] }', + '{ "res2/error_message": init.res2[error_message] }', + '{ "res2/success": init.res2[success] }', + + '{ "res3": init.res3[content] }', + '{ "res3/returncode": init.res3[returncode] }', + '{ "res3/rc": init.res3[rc] }', + '{ "res3/success": init.res3[success] }' + ); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/network/url_get.cf.expected.json b/tests/acceptance/01_vars/02_functions/network/url_get.cf.expected.json new file mode 100644 index 0000000000..183da7f7c6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/network/url_get.cf.expected.json @@ -0,0 +1,16 @@ +{ + "kept": { + "res1/rc": 0, + "res1/returncode": 200, + "res1/success": true, + "res2": "", + "res2/error_message": "Couldn't resolve host name", + "res2/rc": 6, + "res2/returncode": 0, + "res2/success": false, + "res3": "/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */\n!function(a,b){\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=a.document?b(a,!0):function(a)", + "res3/rc": 0, + "res3/returncode": 200, + "res3/success": true + } +} diff --git a/tests/acceptance/01_vars/02_functions/not.cf b/tests/acceptance/01_vars/02_functions/not.cf new file mode 100644 index 0000000000..e297d8ad85 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/not.cf @@ -0,0 +1,100 @@ +###################################################### +# +# Test that not() behaves as expected +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3470" } + string => "Test that not() behaves as expected"; + + classes: + "ok" + scope => "namespace", + and => { + # Simple case: + "any", + not("!any"), + "cfengine", + not("!cfengine"), + + # Variable expansion: + "$(true_variable)", + not("$(false_variable)"), + not(not("$(true_variable)")), + not(not(not("$(false_variable)"))), + + # Double expand: + not(not("$($(true_variable_name))")), + not(not(not("$($(false_variable_name))"))), + + # Triple expand: + not(not("$($($(true_variable_name_name)))")), + not(not(not("$($($(false_variable_name_name)))"))), + + # not() should always return any or !any, + # this is important for backwards compatibility: + strcmp(not("any"), "!any"), + strcmp(not("!any"), "any"), + strcmp(not("!cfengine"), "any"), + strcmp(not("!(cfengine.!cfengine)"), "!any"), + strcmp(not("$(true_variable)"), "!any"), + strcmp(not("$(false_variable)"), "any"), + }; + + # In both of these cases we expect the function call (and promise) + # to be skipped because of the unresolved variable: + # This tests function/promise skipping because of unresolved variables, + # but also that there is no syntax / type error, when the unresolved + # function call stays in the and list forever (Never resolved). + "fail_one" + and => { + "any", + not("$(unresolved_var)"), + }; + "fail_two" + and => { + "any", + not(not("$(unresolved_var)")), + }; + "fail" + scope => "namespace", + expression => "fail_one|fail_two"; + + vars: + "false_variable" + string => "cfengine.(!cfengine)"; + "true_variable" + string => "cfengine|(!cfengine)"; + "false_variable_name" + string => "false_variable"; + "true_variable_name" + string => "true_variable"; + "false_variable_name_name" + string => "false_variable_name"; + "true_variable_name_name" + string => "true_variable_name"; + +} + +####################################################### + +bundle agent check +{ + + reports: + ok.(!fail):: + "$(this.promise_filename) Pass"; + (!ok)|fail:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/nth.cf b/tests/acceptance/01_vars/02_functions/nth.cf new file mode 100644 index 0000000000..a58fe3e31f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/nth.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Test nth() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "results" slist => { + "starting list = 1", + "starting list = 2", + "starting list = 3", + "starting list = one", + "starting list = two", + "starting list = three", + "starting list = long string", + "starting list = four", + "starting list = fix", + "starting list = six", + "starting list = one", + "starting list = two", + "starting list = three", + "element #0 of the test list: 1", + "element #1 of the test list: 2", + "element #10 of the test list: one", + "element #11 of the test list: two", + "element #2 of the test list: 3", + "element #6 of the test list: long string", + "element #-1 of the test list: three", + "element #-13 of the test list: 1", + "element #-6 of the test list: four", + }; + + files: + "$(G.testfile).expected" + create => "true", + edit_defaults => init_empty, + edit_line => init_insert; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; + edit_backup => "false"; +} + +bundle edit_line init_insert +{ + insert_lines: + "$(init.results)"; +} + +####################################################### + +bundle agent test +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; + + "nth" slist => { 1, 2, 6, 10, 11, 1000, "-1", "-13", "-6", "-1000" }; + + "test[$(nth)]" string => nth("test", $(nth)); + "test[0]" string => nth("test", 0); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "starting list = $(test.test)"; + "element #$(test.nth) of the test list: $(test.test[$(test.nth)])"; + "element #0 of the test list: $(test.test[0])"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf b/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf new file mode 100644 index 0000000000..cdcc1fe67a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Test nth() with data container input +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + copy_from => local_cp("$(this.promise_filename).expected"); +} + +####################################################### + +bundle common test_common +{ + vars: + "data" data => readjson("$(this.promise_filename).json", "100k"); + "datastr" string => format("%S", data); + + "numbers" ilist => { "-100", "-1", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1000 }; + "keys" slist => getindices(data); + "object_keys" slist => getindices("data[object]"); + "list_keys" slist => getindices("data[list]"); + "all_keys" slist => + { + @(object_keys), + @(list_keys), + "foo", "bar", "", + $(numbers), + $(keys) + }; + + "primitives[$(keys)]" string => nth(data, $(keys)); + "list[$(all_keys)]" string => nth("data[list]", $(all_keys)); + "object[$(all_keys)]" string => nth("data[object]", $(all_keys)); + + classes: + + "hasprimitive_$(keys)" expression => isvariable("primitives[$(keys)]"); + "haslist_$(all_keys)" expression => isvariable("list[$(all_keys)]"); + "hasobject_$(all_keys)" expression => isvariable("object[$(all_keys)]"); +} + +bundle agent test +{ + files: + + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + + "jsonstring = $(test_common.datastr)"; + "keys:json = $(test_common.keys)"; + + "primitive:json[$(test_common.keys)] = $(test_common.primitives[$(test_common.keys)])" + if => "hasprimitive_$(test_common.keys)"; + + "list:json[$(test_common.all_keys)] = $(test_common.list[$(test_common.all_keys)])" + if => "haslist_$(test_common.all_keys)"; + + "object:json[$(test_common.all_keys)] = $(test_common.object[$(test_common.all_keys)])" + if => "hasobject_$(test_common.all_keys)"; + +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf.expected b/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf.expected new file mode 100644 index 0000000000..9375b9d119 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf.expected @@ -0,0 +1,24 @@ +jsonstring = {"boolean":true,"boolean_2":false,"integer":20130111,"integer_2":987654321,"list":["chris","dituri","was","here"],"null":null,"object":{"a":true,"b":[1,2,3],"c":"cat","d":108},"string":"Figaro. Figaro. Figaro, Figaro, Figaro... Figaro!","string_2":"Othello? Where art thou now?"} +keys:json = boolean +keys:json = boolean_2 +keys:json = integer +keys:json = integer_2 +keys:json = list +keys:json = null +keys:json = object +keys:json = string +keys:json = string_2 +primitive:json[boolean] = true +primitive:json[boolean_2] = false +primitive:json[integer] = 20130111 +primitive:json[integer_2] = 987654321 +primitive:json[null] = null +primitive:json[string] = Figaro. Figaro. Figaro, Figaro, Figaro... Figaro! +primitive:json[string_2] = Othello? Where art thou now? +list:json[0] = chris +list:json[1] = dituri +list:json[2] = was +list:json[3] = here +object:json[a] = true +object:json[c] = cat +object:json[d] = 108 diff --git a/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf.json b/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf.json new file mode 100644 index 0000000000..99251e38e4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/nth_datacontainer.cf.json @@ -0,0 +1,11 @@ +{ + "boolean": true, + "string": "Figaro. Figaro. Figaro, Figaro, Figaro... Figaro!", + "integer": 20130111, + "list": [ "chris", "dituri", "was", "here" ], + "object": { "a": true, "b": [ 1, 2, 3 ], "c": "cat", "d": 108 }, + "integer_2": 987654321, + "string_2": "Othello? Where art thou now?", + "boolean_2": false, + "null": null, +} diff --git a/tests/acceptance/01_vars/02_functions/on.cf b/tests/acceptance/01_vars/02_functions/on.cf new file mode 100644 index 0000000000..4bf10d90b1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/on.cf @@ -0,0 +1,37 @@ +####################################################### +# +# Test on(), especially integer out of bounds on some Unices +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "unix_timestamp" int => on(2014, 7 , 21 , 23 , 13 ,0); + +} + +####################################################### + +bundle agent check +{ + classes: + + "ok" expression => isvariable("test.unix_timestamp"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/or.cf b/tests/acceptance/01_vars/02_functions/or.cf new file mode 100644 index 0000000000..953ca1f485 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/or.cf @@ -0,0 +1,235 @@ +###################################################### +# +# Test that or() behaves as expected +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3470" } + string => "Test that or() behaves as expected"; + + vars: + "f" # false + string => "(cfengine.(!cfengine))"; + "T" # true, uppercase to be more different visually + string => "(cfengine|(!cfengine))"; + "f_name" + string => "f"; + "T_name" + string => "T"; + "f_name_name" + string => "f_name"; + "T_name_name" + string => "T_name"; + "many_true" + slist => { + "any", + "$(T)", + concat(not(or("$(f)"))), + "(any.cfengine)", + concat(not(or())), + concat(not(or(or()))), + }; + "many_false" + slist => { + "(!any)", + "$(f)", + concat(or(not("$(T)"))), + "(any.!cfengine)", + concat(not("any")), + concat(or()), + }; + "many_both" + slist => { @(many_true), @(many_false) }; + + classes: + + # All elements should be true, fail if one is false: + "ok" + scope => "namespace", + and => { + # Sanity check: + "any", + "cfengine", + + # or() with 0 arguments should default to false: + not(or()), + + # or() with 1 static string: + or("any"), + or("cfengine"), + or("!(!cfengine)"), + + # or() with 1 string with variable expansion(s): + or("$(T)"), + or("!$(f)"), + or("$(T).any"), + or("$(T).!(!any)"), + or("any.$(T)"), + or("!(!any).$(T)"), + or("any|$(f)"), + or("!(!any)|$(f)"), + or("$(T)|$(f)"), + or("$(f)|$(T)"), + + # or() with slist: + # Careful, if there are expressions in list (using | operator) + # they should be parenthesized for this to work: + or(join(".", many_true)), + or(join("|", many_true)), + or(join("|", many_both)), + or(not(join(".", many_false))), + or(not(join("|", many_false))), + or(not(join(".", many_both))), + + # or() with 1 function call as argument: + or(or("any")), + or(or("cfengine")), + or(not("!cfengine")), + or(not(not("cfengine"))), + or(not("$(f)")), + or(not(not("$(T)"))), + or(strcmp("cfengine", "cfengine")), + or(strcmp("any", not("$(f)"))), + + # or() with 2 arguments: + or("any", "cfengine"), + or("any", "!any"), + or("!any", "any"), + or("!(!any)", "!(!cfengine)"), + or("$(T)", "$(T)"), + or("$(T)", "$(f)"), + or("$(T)", "!$(f)"), + or("$(T)", not("$(f)")), + or(not("$(f)"), not("$(f)")), + + # or() with 3+ arguments (strings): + or("any", "any", "any"), + or("any", "any", "any", "any"), + or("any", "any", "any", "any", "any"), + or("any", "any", "any", "any", "any", "any"), + or("any", "any", "any", "any", "any", "!any"), + or("any", "any", "any", "any", "!any", "!any"), + or("any", "any", "any", "!any", "!any", "!any"), + or("any", "any", "!any", "!any", "!any", "!any"), + or("any", "!any", "!any", "!any", "!any", "!any"), + or("!any", "!any", "!any", "!any", "!any", "any"), + or("!any", "!any", "!any", "!any", "any", "any"), + or("!any", "!any", "!any", "any", "any", "any"), + or("!any", "!any", "any", "any", "any", "any"), + or("!any", "any", "any", "any", "any", "any"), + or("$(T)", "$(T)", "$(T)"), + or("$(T)", "$(T)", "$(T)", "$(T)"), + or("$(T)", "$(T)", "$(T)", "$(T)", "$(T)"), + or("$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)"), + or("$(T)", "$(T)", "$(T)", "$(T)", "$(T)", "$(f)"), + or("$(T)", "$(T)", "$(T)", "$(T)", "$(f)", "$(f)"), + or("$(T)", "$(T)", "$(T)", "$(f)", "$(f)", "$(f)"), + or("$(T)", "$(T)", "$(f)", "$(f)", "$(f)", "$(f)"), + or("$(T)", "$(f)", "$(f)", "$(f)", "$(f)", "$(f)"), + or("$(f)", "$(f)", "$(f)", "$(f)", "$(f)", "$(T)"), + or("$(f)", "$(f)", "$(f)", "$(f)", "$(T)", "$(T)"), + or("$(f)", "$(f)", "$(f)", "$(T)", "$(T)", "$(T)"), + or("$(f)", "$(f)", "$(T)", "$(T)", "$(T)", "$(T)"), + or("$(f)", "$(T)", "$(T)", "$(T)", "$(T)", "$(T)"), + + # or() with 3+ function calls: + or(not("any"), not("any"), not("!any")), + or(not("any"), not("any"), not("any"), not("!any")), + or(not("any"), not("any"), not("any"), not("any"), not("!any")), + or(not("any"), not("any"), not("any"), not("any"), not("any"), not("!any")), + or(not("$(T)"), not("$(T)"), not("$(f)")), + or(not("$(T)"), not("$(T)"), not("$(T)"), not("$(f)")), + or(not("$(T)"), not("$(T)"), not("$(T)"), not("$(T)"), not("$(f)")), + or(not("$(T)"), not("$(T)"), not("$(T)"), not("$(T)"), not("$(T)"), not("$(f)")), + + # or() with deep nesting: + not(or(or())), + not(or(or(or()))), + not(or(or(or(or())))), + not(or(or(or(or(or()))))), + not(or(or(or(or(or(or())))))), + or(or(or(or(or(or("any")))))), + or(or(or(or(or(or("any", "cfengine")))))), + + # Double expansion: + or("$($(T_name))"), + or("$($(f_name))", "$($(T_name))"), + or("$($(f_name))", "$($(f_name))", "$($(T_name))"), + or("!$($(T_name))", "!$($(f_name))"), + or("!$($(T_name))", "!$($(T_name))", "!$($(f_name))"), + or(not("$($(T_name))"), not("$($(f_name))")), + + # Triple expansion: + or("$($($(T_name_name)))"), + or("$($($(f_name_name)))", "$($($(T_name_name)))"), + or("$($($(f_name_name)))", "$($($(f_name_name)))", "$($($(T_name_name)))"), + or("!$($(T_name_name))", "!$($(f_name_name))"), + or("!$($(T_name_name))", "!$($(T_name_name))", "!$($(f_name_name))"), + or(not("$($(T_name_name))"), not("$($(f_name_name))")), + + # or() should always return any or !any, + # this is important for backwards compatibility: + strcmp(or("any"), "any"), + strcmp(or("!any"), "!any"), + strcmp(or("!cfengine"), "!any"), + strcmp(or("!(cfengine|!cfengine)"), "!any"), + strcmp(or("$(T)"), "any"), + strcmp(or("$(f)"), "!any"), + strcmp(or("$(T)", "$(T)"), "any"), + strcmp(or("$(T)", "$(f)"), "any"), + strcmp(or("$(f)", "$(T)"), "any"), + strcmp(or("$(f)", "$(f)"), "!any"), + }; + + # Cases where or() should return false (fail if one is true): + "fail_false" + or => { + or("$(f)"), + or("$(f)", "$(f)"), + or("$(f)", "$(f)", "$(f)"), + or("$(f)", "$(f)", "$(f)", "$(f)"), + or("$(f)", "$(f)", "$(f)", "$(f)", "$(f)"), + or("$(f)", "$(f)", "$(f)", "$(f)", "$(f)", "$(f)"), + or("$(f)", "$(f)", "$(f)", "$(f)", "$(f)", "$(f)", "$(f)"), + or("$(f)", "$(f)", "$(f)", "$(f)", "$(f)", "$(f)", "$(f)", "$(f)"), + }; + # Should be skipped because of unresolved variable: + "fail_unresolved" + and => { + "any", + or("$(unresolved_var)"), + }; + # Check that it's really skipped because of unresolved, + # and not that it accidentally becomes false: + "fail_not_of_unresolved" + and => { + "any", + or(not("$(unresolved_var)")), + }; + "fail" + scope => "namespace", + expression => "fail_false|fail_unresolved|fail_not_of_unresolved"; +} + +####################################################### + +bundle agent check +{ + + reports: + ok.(!fail):: + "$(this.promise_filename) Pass"; + (!ok)|fail:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/packagesmatching.cf b/tests/acceptance/01_vars/02_functions/packagesmatching.cf new file mode 100644 index 0000000000..6071e68b76 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/packagesmatching.cf @@ -0,0 +1,130 @@ +####################################################### +# +# Test packagesmatching() and that the env prefix is stripped +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + packages: + "cfe-no-such-package" + package_policy => "add", + package_method => mock1; + + "cfe-no-such-package" + package_policy => "add", + package_method => mock2; + + # the parsing of env X="y z" fails + + # "cfe-no-such-package" + # package_policy => "add", + # package_method => mock3; + + "cfe-no-such-package" + package_policy => "add", + package_method => mock4; +} + +body package_method mock1 +{ + package_changes => "individual"; + + package_list_command => "$(G.printf) 'bfoobar-1-besm6'"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-([\S]+)"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body package_method mock2 +{ + package_changes => "individual"; + + package_list_command => "/usr/bin/env $(G.printf) 'bfoobar-1-besm6'"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-([\S]+)"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body package_method mock3 +{ + package_changes => "individual"; + + package_list_command => '/usr/bin/env X="y z" $(G.printf) "bfoobar-1-besm6"'; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-([\S]+)"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body package_method mock4 +{ + package_changes => "individual"; + + package_list_command => "/usr/bin/env X=Y $(G.printf) 'bfoobar-1-besm6'"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-([\S]+)"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine4741", "ENT-10422" }; + + vars: + "all_packages" data => packagesmatching(".*", ".*", ".*", ".*"); + "all_string" string => format("packages = %S", all_packages); +} + +####################################################### + +bundle agent check +{ + vars: + "e" string => '{"arch":"besm6","method":"printf","name":"bfoobar","version":"1"}'; + methods: + "" usebundle => dcs_check_strcmp($(test.all_string), + 'packages = [$(e)]', + $(this.promise_filename), + "no"); +} diff --git a/tests/acceptance/01_vars/02_functions/parsearray_maxlen.cf b/tests/acceptance/01_vars/02_functions/parsearray_maxlen.cf new file mode 100644 index 0000000000..8932006583 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/parsearray_maxlen.cf @@ -0,0 +1,78 @@ +# Test the parse\w+array(idx)? functions honour their maxlen parameter. +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + vars: + "float" string => " +1.0 4.0 +2.0 1.0 +3.0 1e1 # 36 bytes up to 1e1 +4.0 2.0 +5.0 16.0 +6.0 3.0"; + "whole" string => " +1 4 +2 1 +3 10 # 30 bytes up to 10 +4 2 +5 16 +6 3"; + "words" string => " +one four +two one +three ten # 37 bytes up to ten +four two +five sixteen +six three"; +} + +bundle agent test +{ + vars: + "flen" int => parserealarray("float", "$(init.float)", "\s*#[^\n]*", "\s+", 1000, 36); + "ilen" int => parseintarray("whole", "$(init.whole)", "\s*#[^\n]*", "\s+", 1000, 30); + "slen" int => parsestringarray("words", "$(init.words)", "\s*#[^\n]*", "\s+", 1000, 37); + "tlen" int => parsestringarrayidx("texts", "$(init.words)", "\s*#[^\n]*", "\s+", 1000, 37); +} + +bundle agent check +{ + vars: + "arrays" slist => { "float", "whole", "words", "texts" }; + + "key_$(arrays)" slist => getindices("test.$(arrays)"); + "sub_$(key_$(arrays))" slist => getindices("test.$(arrays)[$(key_$(arrays))]"); + + classes: + "okf" expression => strcmp("$(test.flen)", "3"); + "oki" expression => strcmp("$(test.ilen)", "3"); + "oks" expression => strcmp("$(test.slen)", "3"); + "okt" expression => strcmp("$(test.tlen)", "3"); + + "ok" and => { "okf", "oki", "oks", "okt" }; + + reports: + DEBUG.!okf:: + "float: '$(test.flen)'"; + "float[$(key_float)][$(sub_$(key_float))] = $(test.float[$(key_float)][$(sub_$(key_float))])"; + DEBUG.!oki:: + "whole: '$(test.ilen)'"; + "whole[$(key_whole)][$(sub_$(key_whole))] = $(test.whole[$(key_whole)][$(sub_$(key_whole))])"; + DEBUG.!oks:: + "words: '$(test.slen)'"; + "words[$(key_words)][$(sub_$(key_words))] = $(test.words[$(key_words)][$(sub_$(key_words))])"; + DEBUG.!okt:: + "texts: '$(test.tlen)'"; + "texts[$(key_texts)][$(sub_$(key_texts))] = $(test.texts[$(key_texts)][$(sub_$(key_texts))])"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/parsestringarray_dontread_comment_duplicate_lastline2.cf b/tests/acceptance/01_vars/02_functions/parsestringarray_dontread_comment_duplicate_lastline2.cf new file mode 100644 index 0000000000..3bdae7409d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/parsestringarray_dontread_comment_duplicate_lastline2.cf @@ -0,0 +1,154 @@ +####################################################### +# +# Test parsestringarray(), don't read comment, duplicate key or last line +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","NoComment",":",6,1000); + "num" int => "6"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "bad" or => { + isvariable("test.ary[6][0]"), + isvariable("test.ary[6][1]"), + + isvariable("test.ary[7][0]"), + isvariable("test.ary[7][1]"), + + isvariable("test.ary[8][0]"), + isvariable("test.ary[8][1]"), + }; + + "ok" and => { + "!bad", + + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/parsestringarray_weird_indices_real_comments_noemptyfields2.cf b/tests/acceptance/01_vars/02_functions/parsestringarray_weird_indices_real_comments_noemptyfields2.cf new file mode 100644 index 0000000000..601c735b1b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/parsestringarray_weird_indices_real_comments_noemptyfields2.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Test parsestringarray(), add some weird indices, real comments, no empty fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","^#.*",":+",10,14); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "bad" or => { + isvariable("test.ary[he][1]"), + isvariable("test.ary[blank][0]"), + }; + + "ok" and => { + "!bad", + + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[he][0])", "he"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "saw 'bad'-class things"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[he][0] = 'he', saw '$(test.ary[he][0])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/parseyaml_with_unquoted_values_starting_with_digits.cf b/tests/acceptance/01_vars/02_functions/parseyaml_with_unquoted_values_starting_with_digits.cf new file mode 100644 index 0000000000..799822e041 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/parseyaml_with_unquoted_values_starting_with_digits.cf @@ -0,0 +1,31 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2033" } + string => "Make sure parsing YAML values starting with numbers works"; + + vars: + "class" string => "1003_efl_test"; + "d" data => parseyaml( "- class: ${class}" ); + + classes: + "PASS" expression => strcmp( ${d[0][class]}, "${class}" ); + "FAIL" not => strcmp( ${d[0][class]}, "${class}" ); + + reports: + PASS:: + "$(this.promise_filename) Pass"; + FAIL:: + "$(this.promise_filename) FAIL"; + PASS.FAIL:: + "$(this.promise_filename) EXCEPTION"; + DEBUG:: + "version => ${sys.cf_version}"; + "class => ${d[0][class]}"; +} diff --git a/tests/acceptance/01_vars/02_functions/peers.cf b/tests/acceptance/01_vars/02_functions/peers.cf new file mode 100644 index 0000000000..9f40a56f7c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/peers.cf @@ -0,0 +1,177 @@ +####################################################### +# +# Test peers() +# Ref:Redmine:4848 (https://cfengine.com/dev/issues/4848) +####################################################### + +### TODO this test is failing, because for example the variable +### default:this.test#actual_peers_nohosts_2 +### is never VariableTablePut +### regex:VariableTablePut\([^)]*actual_peers_nohosts_2 +### +### TODO figure out why actual_peers_nohosts_2 is never Put() + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### +bundle agent init +{ + methods: + "nohosts" + usebundle => file_empty("$(G.testfile).nohosts"), + comment => "Test the behavior when there are no entries in the file."; + + "nopeers" + usebundle => file_mustache_jsonstring + ( + "$(this.promise_filename).mustache", + '{ "peers": [ "a", "b", "c" ] }', + "$(G.testfile).nopeers" + ), + comment => "Test the behaviour when there are no peers (some hosts, but + when the executing host is not found in the list)"; + + "somepeers" + usebundle => file_mustache_jsonstring + ( + "$(this.promise_filename).mustache", + '{ "peers": [ "a", "b", "c", "$(sys.fqhost)" ] }', + "$(G.testfile).somepeers" + ), + comment => "Test the behaviour when there are some peers."; + + + "allpeers" usebundle => file_mustache_jsonstring + ( + "$(this.promise_filename).mustache", + '{ "peers": [ "$(sys.fqhost)", "$(sys.fqhost)", "$(sys.fqhost)", "$(sys.fqhost)" ] }', + "$(G.testfile).allpeers" + ); +} + +bundle agent test +{ + vars: + "tests" slist => { "nohosts", "nopeers", "somepeers", "allpeers" }; + "subtests" ilist => { "0", "1", "2", "3", "4", "5" }; + + "peers_$(tests)_$(subtests)" slist => peers("$(G.testfile).$(tests)", "#.*", $(subtests)); + "peerleaders_$(tests)_$(subtests)" slist => peerleaders("$(G.testfile).$(tests)", "#.*", $(subtests)); + "actual_peers_$(tests)_$(subtests)" string => format("%S", "peers_$(tests)_$(subtests)"); + "actual_peerleaders_$(tests)_$(subtests)" string => format("%S", "peerleaders_$(tests)_$(subtests)"); + "actual_peerleader_$(tests)_$(subtests)" string => peerleader("$(G.testfile).$(tests)", "#.*", $(subtests)); +} + +####################################################### + +bundle agent check +{ + vars: + "f" slist => { "peers", "peerleader", "peerleaders" }; + "tests" slist => { @(test.tests) }; + "subtests" ilist => { @(test.subtests) }; + + "good_cases" slist => + { + "peers_nohosts_2", "peers_nohosts_3", "peers_nohosts_4", "peers_nohosts_5", + "peers_nopeers_2", "peers_nopeers_3", "peers_nopeers_4", "peers_nopeers_5", + "peers_somepeers_2", "peers_somepeers_3", "peers_somepeers_4", "peers_somepeers_5", + "peers_allpeers_2", "peers_allpeers_3", "peers_allpeers_4", "peers_allpeers_5", + "peers_somepeers_2", "peers_somepeers_4", "peers_somepeers_5", + + "peerleader_somepeers_2", "peerleader_somepeers_3", "peerleader_somepeers_4", "peerleader_somepeers_5", + "peerleader_allpeers_2", "peerleader_allpeers_3", "peerleader_allpeers_4", "peerleader_allpeers_5", + + "peerleaders_nohosts_2", "peerleaders_nohosts_3", "peerleaders_nohosts_4", "peerleaders_nohosts_5", + "peerleaders_nopeers_2", "peerleaders_nopeers_3", "peerleaders_nopeers_4", "peerleaders_nopeers_5", + "peerleaders_somepeers_2", "peerleaders_somepeers_3", "peerleaders_somepeers_4", "peerleaders_somepeers_5", + "peerleaders_allpeers_2", "peerleaders_allpeers_3", "peerleaders_allpeers_4", "peerleaders_allpeers_5", + }; + + "bad_cases" slist => + { + "peers_nohosts_0", "peers_nohosts_1", + "peers_nopeers_0", "peers_nopeers_1", + "peers_somepeers_0", "peers_somepeers_1", + "peers_allpeers_0", "peers_allpeers_1", + + "peerleader_nohosts_0", "peerleader_nohosts_1", "peerleader_nohosts_2", "peerleader_nohosts_3", "peerleader_nohosts_4", "peerleader_nohosts_5", + "peerleader_nopeers_0", "peerleader_nopeers_1", "peerleader_nopeers_2", "peerleader_nopeers_3", "peerleader_nopeers_4", "peerleader_nopeers_5", + "peerleader_somepeers_0", "peerleader_somepeers_1", + "peerleader_allpeers_0", "peerleader_allpeers_1", + + "peerleaders_nohosts_0", "peerleaders_nohosts_1", + "peerleaders_nopeers_0", "peerleaders_nopeers_1", + "peerleaders_somepeers_0", "peerleaders_somepeers_1", + "peerleaders_allpeers_0", "peerleaders_allpeers_1", + }; + + "expected_peers_allpeers_$(subtests)" string => '{ }'; + "expected_peers_nopeers_$(subtests)" string => '{ }'; + "expected_peers_nohosts_$(subtests)" string => '{ }'; + "expected_peers_allpeers_$(subtests)" string => '{ }'; + "expected_peers_nopeers_$(subtests)" string => '{ }'; + "expected_peers_nohosts_$(subtests)" string => '{ }'; + "expected_peers_somepeers_2" string => '{ "c" }'; + "expected_peers_somepeers_3" string => '{ }'; + "expected_peers_somepeers_4" string => '{ "a", "b", "c" }'; + "expected_peers_somepeers_5" string => '{ "a", "b", "c" }'; + + "expected_peerleader_allpeers_$(subtests)" string => 'localhost'; + "expected_peerleader_somepeers_2" string => 'c'; + "expected_peerleader_somepeers_3" string => 'localhost'; + "expected_peerleader_somepeers_4" string => 'a'; + "expected_peerleader_somepeers_5" string => 'a'; + + "expected_peerleaders_nohosts_$(subtests)" string => '{ }'; + + "expected_peerleaders_nopeers_2" string => '{ "a", "c" }'; + "expected_peerleaders_nopeers_3" string => '{ "a" }'; + "expected_peerleaders_nopeers_4" string => '{ "a" }'; + "expected_peerleaders_nopeers_5" string => '{ "a" }'; + + "expected_peerleaders_somepeers_2" string => '{ "a", "c" }'; + "expected_peerleaders_somepeers_3" string => '{ "a", "localhost" }'; + "expected_peerleaders_somepeers_4" string => '{ "a" }'; + "expected_peerleaders_somepeers_5" string => '{ "a" }'; + + "expected_peerleaders_allpeers_2" string => '{ "localhost", "localhost" }'; + "expected_peerleaders_allpeers_3" string => '{ "localhost", "localhost" }'; + "expected_peerleaders_allpeers_4" string => '{ "localhost" }'; + "expected_peerleaders_allpeers_5" string => '{ "localhost" }'; + + "c[$(f)_$(tests)_$(subtests)]" string => "ok_$(f)_$(tests)_$(subtests)"; + "cases" slist => getvalues(c); + "cs" string => join(",", cases); + + classes: + "ok_$(bad_cases)" not => isvariable("test.actual_$(bad_cases)"); + "ok_$(good_cases)" expression => strcmp("$(expected_$(good_cases))", + "$(test.actual_$(good_cases))"); + + methods: + "" usebundle => dcs_passif_expected($(cs), "", $(this.promise_filename)), + inherit => "true"; + + reports: + EXTRA:: + "$(good_cases): as expected, '$(expected_$(good_cases))'" + if => "ok_$(good_cases)"; + + "$(bad_cases): as expected, no variable" + if => "ok_$(bad_cases)"; + + #"test.actual_$(good_cases) = '$(test.actual_$(good_cases))'"; + + DEBUG:: + "$(good_cases): NOT as expected, '$(expected_$(good_cases))' != '$(test.actual_$(good_cases))'" + if => "!ok_$(good_cases)"; + + "$(bad_cases): NOT as expected, has variable '$(test.actual_$(bad_cases))'" + if => "!ok_$(bad_cases)"; +} diff --git a/tests/acceptance/01_vars/02_functions/peers.cf.mustache b/tests/acceptance/01_vars/02_functions/peers.cf.mustache new file mode 100644 index 0000000000..4e5539919d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/peers.cf.mustache @@ -0,0 +1,2 @@ +{{#peers}}{{.}} +{{/peers}} \ No newline at end of file diff --git a/tests/acceptance/01_vars/02_functions/proc/proc/sys/net/ipv4/tcp_mem b/tests/acceptance/01_vars/02_functions/proc/proc/sys/net/ipv4/tcp_mem new file mode 100644 index 0000000000..7d493be7d7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/proc/proc/sys/net/ipv4/tcp_mem @@ -0,0 +1 @@ +383133 510845 766266 diff --git a/tests/acceptance/01_vars/02_functions/proc/proc/sys/net/unix/max_dgram_qlen b/tests/acceptance/01_vars/02_functions/proc/proc/sys/net/unix/max_dgram_qlen new file mode 100644 index 0000000000..4d0e90cbcb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/proc/proc/sys/net/unix/max_dgram_qlen @@ -0,0 +1 @@ +512 diff --git a/tests/acceptance/01_vars/02_functions/randomint.cf b/tests/acceptance/01_vars/02_functions/randomint.cf new file mode 100644 index 0000000000..d31a413b6d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/randomint.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test randomint() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "nine_a" int => randomint(9,10); + "nine_b" int => randomint(9,9); + "nine_c" int => randomint(10,9); +} + +####################################################### + +bundle agent check +{ + classes: + "ok_a" expression => strcmp("$(test.nine_a)", "9"); + "ok_b" expression => strcmp("$(test.nine_b)", "9"); + "ok_c" expression => strcmp("$(test.nine_c)", "9"); + "ok" and => {ok_a, ok_b, ok_c}; + + reports: + DEBUG:: + "nine_a: $(test.nine_a)"; + "nine_b: $(test.nine_b)"; + "nine_c: $(test.nine_c)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/read-file-endless-loop.cf b/tests/acceptance/01_vars/02_functions/read-file-endless-loop.cf new file mode 100644 index 0000000000..bcb8c86212 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/read-file-endless-loop.cf @@ -0,0 +1,28 @@ +# Test that regline, getfields and countlinesmatching do not loop endlessly if +# supplied with a directory (Redmine #2453) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ +} + +bundle agent test +{ + classes: + "foo" expression => regline("foo", "/"); + + vars: + "bar" int => getfields(".*", "/", ":", "foo"); + "baz" int => countlinesmatching(".*", "/"); +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/01_vars/02_functions/read_empty_file.cf b/tests/acceptance/01_vars/02_functions/read_empty_file.cf new file mode 100644 index 0000000000..97a1d6d621 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/read_empty_file.cf @@ -0,0 +1,47 @@ +# Test that empty file is successfully read by readfile() function + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent test +{ + vars: + "value1" string => readfile("$(G.testfile)", 10000); + "value2" string => readfile("$(G.testfile)", 0); +} + +bundle agent check +{ + classes: + "ok1" expression => strcmp("$(test.value1)", ""); + "ok2" expression => strcmp("$(test.value2)", ""); + "not_ok1" not => strcmp("$(test.value1)", ""); + "not_ok2" not => strcmp("$(test.value2)", ""); + "ok" and => { "ok1", "ok2" }; + + reports: + DEBUG.ok1:: + "passed1: '$(test.value1)' == ''"; + DEBUG.ok2:: + "passed2: '$(test.value1)' == ''"; + DEBUG.not_ok1:: + "failed1: '$(test.value1)' != ''"; + DEBUG.not_ok2:: + "failed2: '$(test.value2)' != ''"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/read_long_file.cf b/tests/acceptance/01_vars/02_functions/read_long_file.cf new file mode 100644 index 0000000000..3004032dee --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/read_long_file.cf @@ -0,0 +1,44 @@ +# Test that long file is successfully read by readstringlist() function + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "value" slist => readstringlist("$(this.promise_filename).txt", "", "\n", 100000, 1000000); +} + +bundle agent check +{ + vars: + "length" int => length("test.value"); + "last" string => nth("test.value", 599); + classes: + "ok1" expression => strcmp("$(length)", "600"); + "ok2" expression => strcmp("$(last)", "line600"); + "ok" and => { "ok1", "ok2" }; + + reports: + DEBUG.ok1:: + "passed1"; + DEBUG.ok2:: + "passed2"; + DEBUG.!ok1:: + "failed1 $(length) != 600"; + DEBUG.!ok2:: + "failed2 $(last) != line600"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/read_long_file.cf.txt b/tests/acceptance/01_vars/02_functions/read_long_file.cf.txt new file mode 100644 index 0000000000..444454f630 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/read_long_file.cf.txt @@ -0,0 +1,600 @@ +line1 +line2 +line3 +line4 +line5 +line6 +line7 +line8 +line9 +line10 +line11 +line12 +line13 +line14 +line15 +line16 +line17 +line18 +line19 +line20 +line21 +line22 +line23 +line24 +line25 +line26 +line27 +line28 +line29 +line30 +line31 +line32 +line33 +line34 +line35 +line36 +line37 +line38 +line39 +line40 +line41 +line42 +line43 +line44 +line45 +line46 +line47 +line48 +line49 +line50 +line51 +line52 +line53 +line54 +line55 +line56 +line57 +line58 +line59 +line60 +line61 +line62 +line63 +line64 +line65 +line66 +line67 +line68 +line69 +line70 +line71 +line72 +line73 +line74 +line75 +line76 +line77 +line78 +line79 +line80 +line81 +line82 +line83 +line84 +line85 +line86 +line87 +line88 +line89 +line90 +line91 +line92 +line93 +line94 +line95 +line96 +line97 +line98 +line99 +line100 +line101 +line102 +line103 +line104 +line105 +line106 +line107 +line108 +line109 +line110 +line111 +line112 +line113 +line114 +line115 +line116 +line117 +line118 +line119 +line120 +line121 +line122 +line123 +line124 +line125 +line126 +line127 +line128 +line129 +line130 +line131 +line132 +line133 +line134 +line135 +line136 +line137 +line138 +line139 +line140 +line141 +line142 +line143 +line144 +line145 +line146 +line147 +line148 +line149 +line150 +line151 +line152 +line153 +line154 +line155 +line156 +line157 +line158 +line159 +line160 +line161 +line162 +line163 +line164 +line165 +line166 +line167 +line168 +line169 +line170 +line171 +line172 +line173 +line174 +line175 +line176 +line177 +line178 +line179 +line180 +line181 +line182 +line183 +line184 +line185 +line186 +line187 +line188 +line189 +line190 +line191 +line192 +line193 +line194 +line195 +line196 +line197 +line198 +line199 +line200 +line201 +line202 +line203 +line204 +line205 +line206 +line207 +line208 +line209 +line210 +line211 +line212 +line213 +line214 +line215 +line216 +line217 +line218 +line219 +line220 +line221 +line222 +line223 +line224 +line225 +line226 +line227 +line228 +line229 +line230 +line231 +line232 +line233 +line234 +line235 +line236 +line237 +line238 +line239 +line240 +line241 +line242 +line243 +line244 +line245 +line246 +line247 +line248 +line249 +line250 +line251 +line252 +line253 +line254 +line255 +line256 +line257 +line258 +line259 +line260 +line261 +line262 +line263 +line264 +line265 +line266 +line267 +line268 +line269 +line270 +line271 +line272 +line273 +line274 +line275 +line276 +line277 +line278 +line279 +line280 +line281 +line282 +line283 +line284 +line285 +line286 +line287 +line288 +line289 +line290 +line291 +line292 +line293 +line294 +line295 +line296 +line297 +line298 +line299 +line300 +line301 +line302 +line303 +line304 +line305 +line306 +line307 +line308 +line309 +line310 +line311 +line312 +line313 +line314 +line315 +line316 +line317 +line318 +line319 +line320 +line321 +line322 +line323 +line324 +line325 +line326 +line327 +line328 +line329 +line330 +line331 +line332 +line333 +line334 +line335 +line336 +line337 +line338 +line339 +line340 +line341 +line342 +line343 +line344 +line345 +line346 +line347 +line348 +line349 +line350 +line351 +line352 +line353 +line354 +line355 +line356 +line357 +line358 +line359 +line360 +line361 +line362 +line363 +line364 +line365 +line366 +line367 +line368 +line369 +line370 +line371 +line372 +line373 +line374 +line375 +line376 +line377 +line378 +line379 +line380 +line381 +line382 +line383 +line384 +line385 +line386 +line387 +line388 +line389 +line390 +line391 +line392 +line393 +line394 +line395 +line396 +line397 +line398 +line399 +line400 +line401 +line402 +line403 +line404 +line405 +line406 +line407 +line408 +line409 +line410 +line411 +line412 +line413 +line414 +line415 +line416 +line417 +line418 +line419 +line420 +line421 +line422 +line423 +line424 +line425 +line426 +line427 +line428 +line429 +line430 +line431 +line432 +line433 +line434 +line435 +line436 +line437 +line438 +line439 +line440 +line441 +line442 +line443 +line444 +line445 +line446 +line447 +line448 +line449 +line450 +line451 +line452 +line453 +line454 +line455 +line456 +line457 +line458 +line459 +line460 +line461 +line462 +line463 +line464 +line465 +line466 +line467 +line468 +line469 +line470 +line471 +line472 +line473 +line474 +line475 +line476 +line477 +line478 +line479 +line480 +line481 +line482 +line483 +line484 +line485 +line486 +line487 +line488 +line489 +line490 +line491 +line492 +line493 +line494 +line495 +line496 +line497 +line498 +line499 +line500 +line501 +line502 +line503 +line504 +line505 +line506 +line507 +line508 +line509 +line510 +line511 +line512 +line513 +line514 +line515 +line516 +line517 +line518 +line519 +line520 +line521 +line522 +line523 +line524 +line525 +line526 +line527 +line528 +line529 +line530 +line531 +line532 +line533 +line534 +line535 +line536 +line537 +line538 +line539 +line540 +line541 +line542 +line543 +line544 +line545 +line546 +line547 +line548 +line549 +line550 +line551 +line552 +line553 +line554 +line555 +line556 +line557 +line558 +line559 +line560 +line561 +line562 +line563 +line564 +line565 +line566 +line567 +line568 +line569 +line570 +line571 +line572 +line573 +line574 +line575 +line576 +line577 +line578 +line579 +line580 +line581 +line582 +line583 +line584 +line585 +line586 +line587 +line588 +line589 +line590 +line591 +line592 +line593 +line594 +line595 +line596 +line597 +line598 +line599 +line600 diff --git a/tests/acceptance/01_vars/02_functions/read_sys_file.cf b/tests/acceptance/01_vars/02_functions/read_sys_file.cf new file mode 100644 index 0000000000..3763be585c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/read_sys_file.cf @@ -0,0 +1,67 @@ +# Redmine#1032: Test that /sys and /proc files with 0 length are successfully read by readfile() function + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + classes: + "have_sys" expression => fileexists("/sys/kernel/vmcoreinfo"); + "have_proc0" expression => fileexists("/proc/meminfo"); + "have_proc1" expression => fileexists("/proc/kallsyms"); + + vars: + have_sys:: + "sys_info" string => readfile("/sys/kernel/vmcoreinfo", 0); + !have_sys:: + "sys_info" string => "42"; + + have_proc0:: + "proc0_info" string => readfile("/proc/meminfo", 0); + !have_proc0:: + "proc0_info" string => "42"; + + have_proc1:: + "proc1_info" string => readfile("/proc/kallsyms", 0); + !have_proc1:: + "proc1_info" string => "42"; + +} + +bundle agent check +{ + classes: + "ok_proc0" expression => regcmp(".*\d+.*", $(test.proc0_info)); + "ok_proc1" expression => regcmp(".*\d+.*", $(test.proc1_info)); + "not_ok_proc0" not => regcmp(".*\d+.*", $(test.proc0_info)); + "not_ok_proc1" not => regcmp(".*\d+.*", $(test.proc1_info)); + "ok_sys" expression => regcmp(".*\d+.*", $(test.sys_info)); + "not_ok_sys" not => regcmp(".*\d+.*", $(test.sys_info)); + + "ok" not => classmatch("not_ok_.+"); + + reports: + DEBUG:: + "/proc0 test OK" if => "ok_proc0"; + "/proc1 test OK" if => "ok_proc1"; + "/sys test OK" if => "ok_sys"; + "/proc0 test NOT OK: data '$(test.proc0_info)' did not contain digits" if => "not_ok_proc0"; + "/proc1 test NOT OK: data '$(test.proc1_info)' did not contain digits" if => "not_ok_proc1"; + "/sys test NOT OK: data '$(test.sys_info)' did not contain digits" if => "not_ok_sys"; + DEBUG.inform_mode:: + "/proc0 test got data '$(test.proc0_info)'"; + "/proc1 test got data '$(test.proc1_info)'"; + "/sys test got data '$(test.sys_info)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/readcsv.cf b/tests/acceptance/01_vars/02_functions/readcsv.cf new file mode 100644 index 0000000000..a62d79b68a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readcsv.cf @@ -0,0 +1,27 @@ +# test readcsv() + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "c" data => readcsv("$(this.promise_filename).csv"); + "cs" string => format("%S", c); +} + +bundle agent check +{ + vars: + "expected" string => '[["emptydata","99","xyz","/default/default/methods/\'any\'[0]","p","[]","[]","default","ny","default.cf.sub"],["emptydata","99","def","/default/default/methods/\'any\'/default/test_run/methods/\'any\'[0]","q","[]","[]","default","ny","default.cf.sub"],["emptydata","99","abc","/default/default/methods/\'any\'/default/test_run/methods/\'any\'[0]","r","[]","[]","default","ny","default.cf.sub"],["emptydata","99","pqr","/default/default/methods/\'any\'/default/test_run/methods/\'any\'[0]","s","[]","[]","default","ny","default.cf.sub"],["emptydata","110","???","[\\"Proposed executable file \'/TEST.cfengine.willfail\' doesn\'t exist\\",\\"\'/TEST.cfengine.willfail\' promises to be executable but isn\'t\\"]","default"]]'; + + methods: + "check" usebundle => dcs_check_strcmp("$(test.cs)", + $(expected), + "$(this.promise_filename)", + "no"); +} diff --git a/tests/acceptance/01_vars/02_functions/readcsv.cf.csv b/tests/acceptance/01_vars/02_functions/readcsv.cf.csv new file mode 100644 index 0000000000..0f3f391176 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readcsv.cf.csv @@ -0,0 +1,5 @@ +emptydata,99,xyz,/default/default/methods/'any'[0],p,[],[],default,ny,default.cf.sub +emptydata,99,def,/default/default/methods/'any'/default/test_run/methods/'any'[0],q,[],[],default,ny,default.cf.sub +emptydata,99,abc,/default/default/methods/'any'/default/test_run/methods/'any'[0],r,[],[],default,ny,default.cf.sub +emptydata,99,pqr,/default/default/methods/'any'/default/test_run/methods/'any'[0],s,[],[],default,ny,default.cf.sub +emptydata,110,???,"[""Proposed executable file '/TEST.cfengine.willfail' doesn't exist"",""'/TEST.cfengine.willfail' promises to be executable but isn't""]",default diff --git a/tests/acceptance/01_vars/02_functions/readdata.cf b/tests/acceptance/01_vars/02_functions/readdata.cf new file mode 100644 index 0000000000..0485a19ea7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata.cf @@ -0,0 +1,49 @@ +########################################################### +# +# Test readdata() with YAML +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10252" }; + + vars: + "explicit_csv" data => readdata("$(this.promise_filename).csv", "CSV"); + "explicit_env" data => readdata("$(this.promise_filename).env", "ENV"); + "explicit_json" data => readdata("$(this.promise_filename).json", "JSON"); + + "extension_csv" data => readdata("$(this.promise_filename).csv", "auto"); + "extension_env" data => readdata("$(this.promise_filename).env", "auto"); + "extension_json" data => readdata("$(this.promise_filename).json", "auto"); + + "guess_csv" data => readdata("$(this.promise_filename).csv.guess", "auto"); # should be empty (JSON is attempted) + "guess_json" data => readdata("$(this.promise_filename).json.guess", "auto"); + + "failed_explicit_csv" data => readdata($(this.promise_filename), "CSV"); + "failed_explicit_env" data => readdata("$(this.promise_filename).csv", "ENV"); + "failed_explicit_json" data => readdata($(this.promise_filename), "JSON"); + + "failed_guess" data => readdata($(this.promise_filename), "auto"); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/readdata.cf.csv b/tests/acceptance/01_vars/02_functions/readdata.cf.csv new file mode 100644 index 0000000000..407c5d4b76 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata.cf.csv @@ -0,0 +1,3 @@ +a,b,c +d,"e",f +"g,h,i" diff --git a/tests/acceptance/01_vars/02_functions/readdata.cf.csv.guess b/tests/acceptance/01_vars/02_functions/readdata.cf.csv.guess new file mode 100644 index 0000000000..407c5d4b76 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata.cf.csv.guess @@ -0,0 +1,3 @@ +a,b,c +d,"e",f +"g,h,i" diff --git a/tests/acceptance/01_vars/02_functions/readdata.cf.env b/tests/acceptance/01_vars/02_functions/readdata.cf.env new file mode 100644 index 0000000000..71aaa7dd27 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata.cf.env @@ -0,0 +1,44 @@ +# Example env file used for acceptance test readdata.cf +# We can't test for all weird possibilites, +# but this should serve most use cases + +A=B +C="D" +E="F\"G" +x=y + +EMPTY= +SPACE=" " +BACKSLASH=\\ +BACKSLASH2="\\" +DOUBLE_QUOTE1=\" +DOUBLE_QUOTE2="\"" +DOUBLE_QUOTE3='\"' +SINGLE_QUOTE1=\' +SINGLE_QUOTE2='\'' +SINGLE_QUOTE3="\'" +QUOTE_IN_TEXT="text \"quote\" text" +INDENT = "hello" +INDENT1 = "world" +INDENT12 = "!" + INDENT123= "!" +# override +OVERRIDE=old +OVERRIDE=new +MESSAGE_TO_NICK='Single quotes are "fine"!' +UNFINISHED_QUOTE1='OOPS +UNFINISHED_QUOTE2="WOOPS + + +NAME="Test Linux by CFEngine" +ID=cfengineos +VERSION=1234.5.0 +BUILD_ID=2017-02-14-2245 +PRETTY_NAME="Test Linux by CFEngine 1234.5.0 (Leafant)" +ANSI_COLOR="37;4;65" +HOME_URL="https://cfengine.com/" +BUG_REPORT_URL="https://northerntech.atlassian.net/projects/CFE/issues" + +# Quote closing +X_COMMENT="Outisde content not included" This part shouldn't be included +Y_COMMENT="Comment #included" diff --git a/tests/acceptance/01_vars/02_functions/readdata.cf.expected.json b/tests/acceptance/01_vars/02_functions/readdata.cf.expected.json new file mode 100644 index 0000000000..11c9b724c0 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata.cf.expected.json @@ -0,0 +1,138 @@ +{ + "explicit_csv": [ + [ + "a", + "b", + "c" + ], + [ + "d", + "e", + "f" + ], + [ + "g,h,i" + ] + ], + "explicit_env": { + "A": "B", + "ANSI_COLOR": "37;4;65", + "BACKSLASH": "\\", + "BACKSLASH2": "\\", + "BUG_REPORT_URL": "https://northerntech.atlassian.net/projects/CFE/issues", + "BUILD_ID": "2017-02-14-2245", + "C": "D", + "DOUBLE_QUOTE1": "\"", + "DOUBLE_QUOTE2": "\"", + "DOUBLE_QUOTE3": "\"", + "E": "F\"G", + "EMPTY": "", + "HOME_URL": "https://cfengine.com/", + "ID": "cfengineos", + "INDENT": "hello", + "INDENT1": "world", + "INDENT12": "!", + "INDENT123": "!", + "MESSAGE_TO_NICK": "Single quotes are \"fine\"!", + "NAME": "Test Linux by CFEngine", + "OVERRIDE": "new", + "PRETTY_NAME": "Test Linux by CFEngine 1234.5.0 (Leafant)", + "QUOTE_IN_TEXT": "text \"quote\" text", + "SINGLE_QUOTE1": "'", + "SINGLE_QUOTE2": "'", + "SINGLE_QUOTE3": "'", + "SPACE": " ", + "UNFINISHED_QUOTE1": "OOPS", + "UNFINISHED_QUOTE2": "WOOPS", + "VERSION": "1234.5.0", + "X_COMMENT": "Outisde content not included", + "Y_COMMENT": "Comment #included", + "x": "y" + }, + "explicit_json": { + "x": [ + 1, + 2, + 3 + ], + "y": "more data here", + "z": { + "300": 400 + } + }, + "extension_csv": [ + [ + "a", + "b", + "c" + ], + [ + "d", + "e", + "f" + ], + [ + "g,h,i" + ] + ], + "extension_env": { + "A": "B", + "ANSI_COLOR": "37;4;65", + "BACKSLASH": "\\", + "BACKSLASH2": "\\", + "BUG_REPORT_URL": "https://northerntech.atlassian.net/projects/CFE/issues", + "BUILD_ID": "2017-02-14-2245", + "C": "D", + "DOUBLE_QUOTE1": "\"", + "DOUBLE_QUOTE2": "\"", + "DOUBLE_QUOTE3": "\"", + "E": "F\"G", + "EMPTY": "", + "HOME_URL": "https://cfengine.com/", + "ID": "cfengineos", + "INDENT": "hello", + "INDENT1": "world", + "INDENT12": "!", + "INDENT123": "!", + "MESSAGE_TO_NICK": "Single quotes are \"fine\"!", + "NAME": "Test Linux by CFEngine", + "OVERRIDE": "new", + "PRETTY_NAME": "Test Linux by CFEngine 1234.5.0 (Leafant)", + "QUOTE_IN_TEXT": "text \"quote\" text", + "SINGLE_QUOTE1": "'", + "SINGLE_QUOTE2": "'", + "SINGLE_QUOTE3": "'", + "SPACE": " ", + "UNFINISHED_QUOTE1": "OOPS", + "UNFINISHED_QUOTE2": "WOOPS", + "VERSION": "1234.5.0", + "X_COMMENT": "Outisde content not included", + "Y_COMMENT": "Comment #included", + "x": "y" + }, + "extension_json": { + "x": [ + 1, + 2, + 3 + ], + "y": "more data here", + "z": { + "300": 400 + } + }, + "failed_explicit_csv": [], + "failed_explicit_env": { + }, + "guess_json": { + "x": [ + 1, + 2, + 3 + ], + "y": "more data here", + "z": { + "300": 400 + } + } +} diff --git a/tests/acceptance/01_vars/02_functions/readdata.cf.json b/tests/acceptance/01_vars/02_functions/readdata.cf.json new file mode 100644 index 0000000000..94e8e1fa87 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata.cf.json @@ -0,0 +1,5 @@ +{ + "x": [ 1, 2, 3 ], + "y": "more data here", + "z": { "300": 400 } +} diff --git a/tests/acceptance/01_vars/02_functions/readdata.cf.json.guess b/tests/acceptance/01_vars/02_functions/readdata.cf.json.guess new file mode 100644 index 0000000000..94e8e1fa87 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata.cf.json.guess @@ -0,0 +1,5 @@ +{ + "x": [ 1, 2, 3 ], + "y": "more data here", + "z": { "300": 400 } +} diff --git a/tests/acceptance/01_vars/02_functions/readdata_yaml.cf b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf new file mode 100644 index 0000000000..25c1e5df00 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf @@ -0,0 +1,43 @@ +########################################################### +# +# Test readdata() with YAML +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + vars: + feature_yaml:: + "explicit_yaml" data => readdata("$(this.promise_filename).yaml", "YAML"); + + "extension_yaml" data => readdata("$(this.promise_filename).yaml", "auto"); + + "extension_yml" data => readdata("$(this.promise_filename).yml", "auto"); + + "guess_yaml" data => readdata("$(this.promise_filename).yaml.guess", "auto"); # should be empty (JSON is attempted) + + "failed_explicit_yaml" data => readdata($(this.promise_filename), "YAML"); +} + +########################################################### + +bundle agent check +{ + methods: + feature_yaml:: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); + !feature_yaml:: + "check" usebundle => dcs_pass($(this.promise_filename)); + +} diff --git a/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.expected.json b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.expected.json new file mode 100644 index 0000000000..2cf66fd75e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.expected.json @@ -0,0 +1,38 @@ +{ + "explicit_yaml": { + "x": 100, + "y": [ + 1, + 2, + "a", + "b", + { + "c": 200 + } + ] + }, + "extension_yaml": { + "x": 100, + "y": [ + 1, + 2, + "a", + "b", + { + "c": 200 + } + ] + }, + "extension_yml": { + "x": 100, + "y": [ + 1, + 2, + "a", + "b", + { + "c": 200 + } + ] + } +} diff --git a/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yaml b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yaml new file mode 100644 index 0000000000..aa4a7428d5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yaml @@ -0,0 +1,10 @@ +--- +x: 100 +y: + - 1 + - 2 + - a + - b + - c: + 200 + 300 diff --git a/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yaml.guess b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yaml.guess new file mode 100644 index 0000000000..aa4a7428d5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yaml.guess @@ -0,0 +1,10 @@ +--- +x: 100 +y: + - 1 + - 2 + - a + - b + - c: + 200 + 300 diff --git a/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yml b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yml new file mode 100644 index 0000000000..aa4a7428d5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readdata_yaml.cf.yml @@ -0,0 +1,10 @@ +--- +x: 100 +y: + - 1 + - 2 + - a + - b + - c: + 200 + 300 diff --git a/tests/acceptance/01_vars/02_functions/readfile.cf b/tests/acceptance/01_vars/02_functions/readfile.cf new file mode 100644 index 0000000000..c8b2c086de --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readfile.cf @@ -0,0 +1,67 @@ +# test readfile() + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "sizes" ilist => { "0", "1", "4094", "4095", "4096", "4097", "99999999" }; + "read_$(sizes)" string => readfile("$(this.promise_filename).txt", + $(sizes)); + "length_$(sizes)" int => string_length("$(read_$(sizes))"); + + "read_no_size" string => readfile("$(this.promise_filename).txt"); + "length_no_size" int => string_length("$(read_no_size)"); +} + +bundle agent check +{ + vars: + "sizes" ilist => { @(test.sizes) }; + "expected[0]" int => "10240"; + "expected[1]" int => "1"; + "expected[4094]" int => "4094"; + "expected[4095]" int => "4095"; + "expected[4096]" int => "4096"; + "expected[4097]" int => "4097"; + "expected[99999999]" int => "10240"; + "expected_no_size" int => "10240"; + + classes: + "ok_$(sizes)" expression => strcmp("$(test.length_$(sizes))", + "$(expected[$(sizes)])"); + "ok_no_size" expression => strcmp("$(test.length_no_size)", + "$(expected_no_size)"); + + "ok" and => { + "ok_0", + "ok_1", + "ok_4094", + "ok_4095", + "ok_4096", + "ok_4097", + "ok_99999999", + "ok_no_size", + }; + + reports: + DEBUG:: + "Got expected size for read of $(sizes): $(test.length_$(sizes))" + if => "ok_$(sizes)"; + "Did NOT get expected size for read of $(sizes): $(test.length_$(sizes)) != expected $(expected[$(sizes)])" + if => "!ok_$(sizes)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/readfile.cf.txt b/tests/acceptance/01_vars/02_functions/readfile.cf.txt new file mode 100644 index 0000000000..6d5bc11fe5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readfile.cf.txt @@ -0,0 +1 @@ +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 diff --git a/tests/acceptance/01_vars/02_functions/readfile_absent_does_not_error_in_pre_eval.cf b/tests/acceptance/01_vars/02_functions/readfile_absent_does_not_error_in_pre_eval.cf new file mode 100644 index 0000000000..c3780e5894 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readfile_absent_does_not_error_in_pre_eval.cf @@ -0,0 +1,39 @@ +body common control +{ + + # If you want to see this work on 3.6.x comment out + # inputs and bundlesequence, and inline the following + # bundles from dcs.cf.sub + # - dcs_passif_output + # - dcs_passif_expected + # - dcs_passif + + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +# bundlesequence => { "test", "check" }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + meta: + + "description" + string => "Test that readfile() does not throw an error during pre-eval if the file does not exist."; + +} + +########################################################### + +bundle agent check +{ + + methods: + "check" + usebundle => dcs_passif_output(".*Hello World.*", ".*failed to read file.*", + "$(sys.cf_agent) -Kf $(this.promise_filename).sub", $(this.promise_filename)); + +} + diff --git a/tests/acceptance/01_vars/02_functions/readfile_absent_does_not_error_in_pre_eval.cf.sub b/tests/acceptance/01_vars/02_functions/readfile_absent_does_not_error_in_pre_eval.cf.sub new file mode 100644 index 0000000000..610816f8ca --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readfile_absent_does_not_error_in_pre_eval.cf.sub @@ -0,0 +1,23 @@ +body common control +{ + bundlesequence =>{ main }; +} + +########################################################### + +bundle agent pre_evaluated +{ + vars: + "none" string => readfile("/tmp/supercalifragilisticexpialidocious", 33); +} + +bundle agent main +{ + meta: + + "description" + string => "Test that readfile() does not throw an error during pre-eval if the file does not exist."; + + reports: + "Hello World!"; +} diff --git a/tests/acceptance/01_vars/02_functions/readstringarray_dontread_comment_duplicatekey_lastline.cf b/tests/acceptance/01_vars/02_functions/readstringarray_dontread_comment_duplicatekey_lastline.cf new file mode 100644 index 0000000000..724e06e182 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readstringarray_dontread_comment_duplicatekey_lastline.cf @@ -0,0 +1,144 @@ +####################################################### +# +# Test readstringarray(), don't read comment, duplicate key or last line +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","NoComment",":",6,1000); + "num" int => "6"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", ""), + strcmp("$(test.ary[blank][3])", ""), + strcmp("$(test.ary[blank][4])", "in here"), + strcmp("$(test.ary[blank][5])", ""), + strcmp("$(test.ary[blank][6])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "is"), + strcmp("$(test.ary[this][2])", "a"), + strcmp("$(test.ary[this][3])", "test"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = '', saw '$(test.ary[blank][2])'"; + "expected test.ary[blank][3] = '', saw '$(test.ary[blank][3])'"; + "expected test.ary[blank][4] = 'in here', saw '$(test.ary[blank][4])'"; + "expected test.ary[blank][5] = '', saw '$(test.ary[blank][5])'"; + "expected test.ary[blank][6] = '', saw '$(test.ary[blank][6])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'is', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = 'a', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = 'test', saw '$(test.ary[this][3])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/readstringarrayidx.cf b/tests/acceptance/01_vars/02_functions/readstringarrayidx.cf new file mode 100644 index 0000000000..1073441beb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readstringarrayidx.cf @@ -0,0 +1,59 @@ +# Redmine#2926: test long lines with readstringarrayidx() + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "dim" int => readstringarrayidx("parms", + "$(this.promise_filename).txt", + "\s*#[^\n]*", + ";", + 9999, + 99999); + "pk" slist => getindices("parms"); + + reports: + DEBUG:: + "$(parms[$(pk)]): $(parms[$(pk)][0]) $(parms[$(pk)][16])"; +} + +bundle agent check +{ + vars: + "length" int => length("test.pk"); + "last" string => nth("test.value", 599); + classes: + "ok1" expression => strcmp("$(test.dim)", "3"); + "ok2" expression => strcmp("$(test.dim)", "$(length)"); + "ok3" expression => strcmp("$(test.parms[2][0])", "not_working_app_config"); + "ok" and => { "ok1", "ok2", "ok3" }; + + reports: + DEBUG.ok1:: + "passed1"; + DEBUG.ok2:: + "passed2"; + DEBUG.ok3:: + "passed3"; + DEBUG.!ok1:: + "failed1 $(test.dim) != 3"; + DEBUG.!ok2:: + "failed2 $(test.dim) != $(length)"; + DEBUG.!ok3:: + "failed3 $(test.parms[2][0]) != not_working_app_config"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/readstringarrayidx.cf.txt b/tests/acceptance/01_vars/02_functions/readstringarrayidx.cf.txt new file mode 100644 index 0000000000..45959f69c3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readstringarrayidx.cf.txt @@ -0,0 +1,4 @@ +# app_name;instance;mmax;mmin;mperm;zone;tomcat_version;env;setenv;app_properties;newrelic;newrelic_name;newrelic_version;context_static;checks;action;context +some_app_config;9123;8192;512;192;europe;tomcat7;live;;this string is short and no problem at all;true;blah;2.0.3;false;true;fix;app_server +working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789.;true;blah;2.0.3;false;true;fix;app_server +not_working_app_config;9123;8192;512;192;europe;tomcat7;live;;.23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..23456789..;true;blah;2.0.3;false;true;fix;app_server diff --git a/tests/acceptance/01_vars/02_functions/readstringarrayidx_order.cf b/tests/acceptance/01_vars/02_functions/readstringarrayidx_order.cf new file mode 100644 index 0000000000..4acc70cfda --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readstringarrayidx_order.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Acceptance test for RedMine 6466. +# +# Order and duplication in data should be preserved. +# Based on an initial test by Neil Watson. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + # Note: order is deliberately one unlikely to happen + # automatically; and there are duplicates. + "file" slist => { "1 ;; other field 1", + "2 ;; other field 2", + "4 ;; other field 4", + "8 ;; other field 8", + "16 ;; other field 16", + "2 ;; other field 2", + "3 ;; other field 3", + "6 ;; other field 6", + "12 ;; other field 12", + "9 ;; other field 9", + "18 ;; other field 18", + "3 ;; other field 3", + "5 ;; other field 5", + "10 ;; other field 10", + "15 ;; other field 15", + "5 ;; other field 5", + "7 ;; other field 7", + "14 ;; other field 14", + "7 ;; other field 7", + "0 ;; other field 0" }; + + files: + "$(G.testfile).orig.txt" + create => 'true', + edit_defaults => empty, + edit_line => insert_all_lines( "@{file}" ); +} + +####################################################### +# Insert lines, preserving duplicates: + +bundle edit_line insert_all_lines(lines) +{ + vars: + "whole" string => join(" +", "lines"); + + insert_lines: + "$(whole)" + insert_type => "preserve_block"; +} + +####################################################### + +bundle agent test +{ + vars: + "num" int => readstringarrayidx("mylines", + "$(G.testfile).orig.txt", + "\s*#[^\n]*", + "\s*;;\s*", + 50, 9999); + + "file" slist => { "${mylines[0][0]} ;; ${mylines[0][1]}", + "${mylines[1][0]} ;; ${mylines[1][1]}", + "${mylines[2][0]} ;; ${mylines[2][1]}", + "${mylines[3][0]} ;; ${mylines[3][1]}", + "${mylines[4][0]} ;; ${mylines[4][1]}", + "${mylines[5][0]} ;; ${mylines[5][1]}", + "${mylines[6][0]} ;; ${mylines[6][1]}", + "${mylines[7][0]} ;; ${mylines[7][1]}", + "${mylines[8][0]} ;; ${mylines[8][1]}", + "${mylines[9][0]} ;; ${mylines[9][1]}", + "${mylines[10][0]} ;; ${mylines[10][1]}", + "${mylines[11][0]} ;; ${mylines[11][1]}", + "${mylines[12][0]} ;; ${mylines[12][1]}", + "${mylines[13][0]} ;; ${mylines[13][1]}", + "${mylines[14][0]} ;; ${mylines[14][1]}", + "${mylines[15][0]} ;; ${mylines[15][1]}", + "${mylines[16][0]} ;; ${mylines[16][1]}", + "${mylines[17][0]} ;; ${mylines[17][1]}", + "${mylines[18][0]} ;; ${mylines[18][1]}", + "${mylines[19][0]} ;; ${mylines[19][1]}" }; + + files: + "$(G.testfile).copy.txt" + create => 'true', + edit_defaults => empty, + edit_line => insert_all_lines( "@{file}" ); +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).copy.txt", + "$(G.testfile).orig.txt", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/readstringlist-many-comments.cf b/tests/acceptance/01_vars/02_functions/readstringlist-many-comments.cf new file mode 100755 index 0000000000..5533a3d044 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readstringlist-many-comments.cf @@ -0,0 +1,35 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "descripton" -> { "ENT-5042" } + string => "Test that readstringlist correctly ignores many comment lines"; + # https://regex101.com/r/OZUgku/1/ + + vars: + "my_list_of_strings" + slist => readstringlist( "$(this.promise_filename).list.txt", # File to read + "\s*#[^\n]*", # Exclude hash comment lines + "\s", # Split on whitespace + inf, # Maximum number of entries + inf); # Maximum number of bytes to read +} +bundle agent check +{ + vars: + "expected" slist => { "jay", "dave" }; + + methods: + + "Pass/FAIL" + usebundle => dcs_check_diff_elements( @(test.my_list_of_strings), # First set of elements + @(expected), # Second set of elements + $(this.promise_filename), # Full path to test policy file + "no"); # Expected difference in elements +} diff --git a/tests/acceptance/01_vars/02_functions/readstringlist-many-comments.cf.list.txt b/tests/acceptance/01_vars/02_functions/readstringlist-many-comments.cf.list.txt new file mode 100644 index 0000000000..e85d1b23c6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readstringlist-many-comments.cf.list.txt @@ -0,0 +1,25 @@ +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +# comment +jay +dave diff --git a/tests/acceptance/01_vars/02_functions/readtcp_connection_fail.cf b/tests/acceptance/01_vars/02_functions/readtcp_connection_fail.cf new file mode 100644 index 0000000000..b2feaf430b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readtcp_connection_fail.cf @@ -0,0 +1,38 @@ +# Test that failure to connect to remote host does not result in "Could not +# close socket" error messages. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "useshell"); +} + +bundle agent check +{ + classes: + "ok_output" not => regcmp(".*Bad file descriptor.*", "$(test.subout)"); + "ok_no_crash" expression => isvariable("test.subout"); + "ok" and => { "ok_output", "ok_no_crash" }; + + reports: + DEBUG:: + "$(test.subout)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/readtcp_connection_fail.cf.sub b/tests/acceptance/01_vars/02_functions/readtcp_connection_fail.cf.sub new file mode 100644 index 0000000000..5467ac1952 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/readtcp_connection_fail.cf.sub @@ -0,0 +1,10 @@ +body common control +{ + bundlesequence => { "tcpread" }; +} + +bundle agent tcpread +{ +vars: + 'trace_output' string => readtcp('127.0.0.1', '1', '', 1); +} diff --git a/tests/acceptance/01_vars/02_functions/regarray.cf b/tests/acceptance/01_vars/02_functions/regarray.cf new file mode 100644 index 0000000000..ef09add86d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/regarray.cf @@ -0,0 +1,48 @@ +####################################################### +# +# Test regarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + + "myarray[0]" string => "bla1"; + "myarray[1]" string => "bla2"; + "myarray[3]" string => "bla"; + "myarray" string => "345"; + "not" string => "345"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => regarray("test.myarray","b.*2"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/regex_replace.cf b/tests/acceptance/01_vars/02_functions/regex_replace.cf new file mode 100644 index 0000000000..aacfc4bd7d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/regex_replace.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test regex_replace function +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "test" string => "abcdefghij"; + + # replace the empty string with empty string, globally + "void" string => regex_replace($(test), "", "", "g"); + # replace any character with empty string, globally + "void1" string => regex_replace($(test), ".", "", "g"); + # replace the empty string with 'x', globally (matches on character boundaries) + "void2" string => regex_replace($(test), "", "x", "g"); + + # replace the whole string + "allofitwithTEST" string => regex_replace($(test), ".*", "TEST", ""); + # no matches, original intact + "nomatch" string => regex_replace($(test), "nonesuch", "TEST", "g"); + "nomatch2" string => regex_replace($(test), "nonesuch", "TEST\1\2\3\4\5\6\7\8\9", "g"); + # replace any character with -DOT- + "dots_everywhere" string => regex_replace($(test), ".", "-DOT-", "g"); + # capture any three characters and replace with a backreference + "cap123" string => regex_replace($(test), "(...)", "[cap=$1]", "g"); + # capture any three characters and replace with a backreference + "cap123_backslash" string => regex_replace($(test), "(...)", "[cap=\1]", "g"); + # note that this is the same as above because the CFE parser tries to be clever in this case, converting \\1 to \1 + "cap123_backslash2" string => regex_replace($(test), "(...)", "[cap=\\1]", "g"); + "cap123_backslashes1to9missing" string => regex_replace($(test), "...", "[cap=\1\2\3\4\5\6\7\8\9]", "g"); + "cap123_backslashes1to9havesome" string => regex_replace($(test), "(.)(.)(.)", "[cap=\1\2\3\4\5\6\7\8\9]", "g"); + # match ABC case-insensitive and replace with ABC + "simple_nocase" string => regex_replace($(test), "ABC", "ABC", "gi"); # case-insensitive + # match abc and replace with ABC + "simple" string => regex_replace($(test), "abc", "ABC", "g"); + + # empty cases + "empty1" string => regex_replace("", "abc", "ABC", "g"); + "empty2" string => regex_replace("", "abc", "", "g"); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/regex_replace.cf.expected.json b/tests/acceptance/01_vars/02_functions/regex_replace.cf.expected.json new file mode 100644 index 0000000000..1232fcf564 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/regex_replace.cf.expected.json @@ -0,0 +1,19 @@ +{ + "allofitwithTEST": "TEST", + "cap123": "[cap=abc][cap=def][cap=ghi]j", + "cap123_backslash": "[cap=abc][cap=def][cap=ghi]j", + "cap123_backslash2": "[cap=abc][cap=def][cap=ghi]j", + "cap123_backslashes1to9havesome": "[cap=abc][cap=def][cap=ghi]j", + "cap123_backslashes1to9missing": "[cap=][cap=][cap=]j", + "dots_everywhere": "-DOT--DOT--DOT--DOT--DOT--DOT--DOT--DOT--DOT--DOT-", + "empty1": "", + "empty2": "", + "nomatch": "abcdefghij", + "nomatch2": "abcdefghij", + "simple": "ABCdefghij", + "simple_nocase": "ABCdefghij", + "test": "abcdefghij", + "void": "abcdefghij", + "void1": "", + "void2": "xaxbxcxdxexfxgxhxixjx" +} diff --git a/tests/acceptance/01_vars/02_functions/regextract.cf b/tests/acceptance/01_vars/02_functions/regextract.cf new file mode 100644 index 0000000000..c69fc07a99 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/regextract.cf @@ -0,0 +1,63 @@ +####################################################### +# +# Test regextract +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "text" string => "node-01-10.0"; + + classes: + "parsed_text" expression => regextract("^\w+-(\d+)-(\d+\.\d+)", "$(text)", "pair"); + + reports: + DEBUG.!parsed_text:: + "Failed to parse $(text)"; +} + +####################################################### + +bundle agent test +{ + vars: + "lefts" string => "$(init.pair[1])"; + "lefti" int => "$(init.pair[1])"; + "rights" string => "$(init.pair[2])"; + "rightr" real => "$(init.pair[2])"; +} + + +####################################################### + +bundle agent check +{ + classes: + "sok" expression => strcmp("$(test.lefts):$(test.rights)", "01:10.0"); + "iok" expression => strcmp("$(test.lefti)", "1"); + "rok" expression => strcmp("$(test.rightr)", "10.000000"); + "ok" and => { "sok", "iok", "rok" }; + + reports: + DEBUG.!iok:: + "Parsed integer '$(test.lefti)' isn't 1"; + DEBUG.!rok:: + "Parsed real '$(test.rightr)' isn't 10.000000"; + DEBUG.!sok:: + "Parsed numbers '$(test.lefts)' and '$(test.rights)' aren't '01' and '10.0'"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/regextract2.cf b/tests/acceptance/01_vars/02_functions/regextract2.cf new file mode 100644 index 0000000000..347f13b808 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/regextract2.cf @@ -0,0 +1,52 @@ +####################################################### +# +# Test regextract +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + parsed:: + "part1_as_int" int => "$(part[1])"; + "part2_as_int" int => "$(part[2])"; + + "part1_as_str" string => "$(part[1])"; + "part2_as_str" string => "$(part[2])"; + + classes: + "parsed" expression => regextract("^node-(\d+)-0*(\d+)", "node-8-01", "part"); +} + + +####################################################### + +bundle agent check +{ + classes: + "ok1" expression => strcmp("$(test.part1_as_int)", "$(test.part1_as_str)"); + "ok2" expression => strcmp("$(test.part2_as_int)", "$(test.part2_as_str)"); + + "ok" and => { "ok1", "ok2" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/regline.cf b/tests/acceptance/01_vars/02_functions/regline.cf new file mode 100644 index 0000000000..0130edc0a6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/regline.cf @@ -0,0 +1,80 @@ +####################################################### +# +# Test filter() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "results" slist => { + "five trailing spaces ", + "no trailing spaces", + }; + + files: + "$(G.testfile)" + create => "true", + edit_defaults => init_empty, + edit_line => init_insert; +} + + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; + edit_backup => "false"; +} + +bundle edit_line init_insert +{ + insert_lines: + "$(init.results)"; +} + + +bundle agent test +{ + classes: + "matched1" + expression => regline("five trailing spaces ", + $(G.testfile)), + scope => "namespace"; + "matched2" + expression => regline("five trailing spaces ", + $(G.testfile)), + scope => "namespace"; + + + + reports: + DEBUG.matched1:: + "Line matched1 matched"; + DEBUG.!matched1:: + "Line matched1 did not match"; + DEBUG.matched2:: + "Line matched2 matched"; + DEBUG.!matched2:: + "Line matched2 did not match"; + +} + +bundle agent check +{ + classes: + "ok" expression => "matched1.!matched2"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + +} diff --git a/tests/acceptance/01_vars/02_functions/reverse.cf b/tests/acceptance/01_vars/02_functions/reverse.cf new file mode 100644 index 0000000000..88fb5fe040 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/reverse.cf @@ -0,0 +1,42 @@ +####################################################### +# +# Test reverse() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "a" slist => { "c", "b", "a" }; + "b" slist => { }; +} + +####################################################### + +bundle agent test +{ + vars: + "sa" slist => reverse("init.a"); + "sb" slist => reverse("init.b"); + + "inline" slist => reverse('[ "q", "p", "r" ]'); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/reverse.cf.expected.json b/tests/acceptance/01_vars/02_functions/reverse.cf.expected.json new file mode 100644 index 0000000000..d038ff0f38 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/reverse.cf.expected.json @@ -0,0 +1,13 @@ +{ + "inline": [ + "r", + "p", + "q" + ], + "sa": [ + "a", + "b", + "c" + ], + "sb": [] +} diff --git a/tests/acceptance/01_vars/02_functions/setop_unique_difference_intersection.cf b/tests/acceptance/01_vars/02_functions/setop_unique_difference_intersection.cf new file mode 100644 index 0000000000..815ac9e40d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/setop_unique_difference_intersection.cf @@ -0,0 +1,66 @@ +####################################################### +# +# Test unique(), difference() and intersection() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "a" slist => { "x", "y", "z" }; + "b" slist => { "100", "9", "10" }; + "c" slist => { }; + "d" slist => { "" }; + "e" slist => { "x" }; + "f" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "four", "fix", "six", + "one", "two", "three", + }; +} + +####################################################### + +bundle agent test +{ + vars: + "list1" slist => { "a", "b", "c", "d", "e", "f" }; + "list2" slist => { "a", "b", "c", "d", "e", "f" }; + + "unique_$(list1)" slist => unique("init.$(list1)"); + "difference_$(list1)_$(list2)" slist => difference("init.$(list1)", + "init.$(list2)"); + + "intersection_$(list1)_$(list2)" slist => intersection("init.$(list1)", + "init.$(list2)"); + + "unique_inline_array" slist => unique('[ "one", "b", "one", "q" ]'); + "unique_inline_object" slist => unique('{ "one": "b", "c": "q", "d": "one" }'); + + "difference_inline_$(list1)" slist => difference('[ "one", "b", "one", "q" ]', + "init.$(list2)"); + + "intersection_$(list1)_inline" slist => intersection("init.$(list1)", + '[ "one", "b", "one", "q" ]'); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/setop_unique_difference_intersection.cf.expected.json b/tests/acceptance/01_vars/02_functions/setop_unique_difference_intersection.cf.expected.json new file mode 100644 index 0000000000..a0b3c53460 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/setop_unique_difference_intersection.cf.expected.json @@ -0,0 +1,300 @@ +{ + "difference_a_a": [], + "difference_a_b": [ + "x", + "y", + "z" + ], + "difference_a_c": [ + "x", + "y", + "z" + ], + "difference_a_d": [ + "x", + "y", + "z" + ], + "difference_a_e": [ + "y", + "z" + ], + "difference_a_f": [ + "x", + "y", + "z" + ], + "difference_b_a": [ + "100", + "9", + "10" + ], + "difference_b_b": [], + "difference_b_c": [ + "100", + "9", + "10" + ], + "difference_b_d": [ + "100", + "9", + "10" + ], + "difference_b_e": [ + "100", + "9", + "10" + ], + "difference_b_f": [ + "100", + "9", + "10" + ], + "difference_c_a": [], + "difference_c_b": [], + "difference_c_c": [], + "difference_c_d": [], + "difference_c_e": [], + "difference_c_f": [], + "difference_d_a": [ + "" + ], + "difference_d_b": [ + "" + ], + "difference_d_c": [ + "" + ], + "difference_d_d": [], + "difference_d_e": [ + "" + ], + "difference_d_f": [ + "" + ], + "difference_e_a": [], + "difference_e_b": [ + "x" + ], + "difference_e_c": [ + "x" + ], + "difference_e_d": [ + "x" + ], + "difference_e_e": [], + "difference_e_f": [ + "x" + ], + "difference_f_a": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "four", + "fix", + "six" + ], + "difference_f_b": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "four", + "fix", + "six" + ], + "difference_f_c": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "four", + "fix", + "six" + ], + "difference_f_d": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "four", + "fix", + "six" + ], + "difference_f_e": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "four", + "fix", + "six" + ], + "difference_f_f": [], + "difference_inline_a": [ + "b", + "q" + ], + "difference_inline_b": [ + "b", + "q" + ], + "difference_inline_c": [ + "b", + "q" + ], + "difference_inline_d": [ + "b", + "q" + ], + "difference_inline_e": [ + "b", + "q" + ], + "difference_inline_f": [ + "b", + "q" + ], + "intersection_a_a": [ + "x", + "y", + "z" + ], + "intersection_a_b": [], + "intersection_a_c": [], + "intersection_a_d": [], + "intersection_a_e": [ + "x" + ], + "intersection_a_f": [], + "intersection_a_inline": [], + "intersection_b_a": [], + "intersection_b_b": [ + "100", + "9", + "10" + ], + "intersection_b_c": [], + "intersection_b_d": [], + "intersection_b_e": [], + "intersection_b_f": [], + "intersection_b_inline": [], + "intersection_c_a": [], + "intersection_c_b": [], + "intersection_c_c": [], + "intersection_c_d": [], + "intersection_c_e": [], + "intersection_c_f": [], + "intersection_c_inline": [], + "intersection_d_a": [], + "intersection_d_b": [], + "intersection_d_c": [], + "intersection_d_d": [ + "" + ], + "intersection_d_e": [], + "intersection_d_f": [], + "intersection_d_inline": [], + "intersection_e_a": [ + "x" + ], + "intersection_e_b": [], + "intersection_e_c": [], + "intersection_e_d": [], + "intersection_e_e": [ + "x" + ], + "intersection_e_f": [], + "intersection_e_inline": [], + "intersection_f_a": [], + "intersection_f_b": [], + "intersection_f_c": [], + "intersection_f_d": [], + "intersection_f_e": [], + "intersection_f_f": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "four", + "fix", + "six" + ], + "intersection_f_inline": [ + "one" + ], + "list1": [ + "a", + "b", + "c", + "d", + "e", + "f" + ], + "list2": [ + "a", + "b", + "c", + "d", + "e", + "f" + ], + "unique_a": [ + "x", + "y", + "z" + ], + "unique_b": [ + "100", + "9", + "10" + ], + "unique_c": [], + "unique_d": [ + "" + ], + "unique_e": [ + "x" + ], + "unique_f": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "four", + "fix", + "six" + ], + "unique_inline_array": [ + "one", + "b", + "q" + ], + "unique_inline_object": [ + "b", + "q", + "one" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/shuffle-exact.cf b/tests/acceptance/01_vars/02_functions/shuffle-exact.cf new file mode 100644 index 0000000000..bc54c104f2 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/shuffle-exact.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test shuffle() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + meta: + "test_soft_fail" string => "!linux", + meta => { "redmine7950" }; + + vars: + "a" slist => { "a", "b", "c", "d", "e", "f", "g" }; + "b" slist => { }; +} + +####################################################### + +bundle agent test +{ + meta: + # for some reason, shuffle() produces different results on 64bit RHEL 4 + # and Debian 4 than everywhere else + "test_soft_fail" + string => "((centos_4|centos_5|debian_4).64_bit)|windows", + meta => { "CFE-2301,ENT-10254" }; + vars: + "lists" slist => { "a", "b" }; + "seeds" slist => { "skruf", "cormorant", "dollhouse" }; + + "shuffle_$(lists)_$(seeds)" slist => shuffle("init.$(lists)", $(seeds)); + "shuffle_inline_$(seeds)" slist => shuffle('["a", "b", "delta", "farmer"]', $(seeds)); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/shuffle-exact.cf.expected.json b/tests/acceptance/01_vars/02_functions/shuffle-exact.cf.expected.json new file mode 100644 index 0000000000..6069015e39 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/shuffle-exact.cf.expected.json @@ -0,0 +1,59 @@ +{ + "lists": [ + "a", + "b" + ], + "seeds": [ + "skruf", + "cormorant", + "dollhouse" + ], + "shuffle_a_cormorant": [ + "d", + "b", + "c", + "f", + "e", + "a", + "g" + ], + "shuffle_a_dollhouse": [ + "b", + "f", + "a", + "e", + "g", + "c", + "d" + ], + "shuffle_a_skruf": [ + "f", + "d", + "b", + "e", + "c", + "g", + "a" + ], + "shuffle_b_cormorant": [], + "shuffle_b_dollhouse": [], + "shuffle_b_skruf": [], + "shuffle_inline_cormorant": [ + "farmer", + "b", + "a", + "delta" + ], + "shuffle_inline_dollhouse": [ + "delta", + "farmer", + "a", + "b" + ], + "shuffle_inline_skruf": [ + "b", + "a", + "delta", + "farmer" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/shuffle.cf b/tests/acceptance/01_vars/02_functions/shuffle.cf new file mode 100644 index 0000000000..2cb24ee19e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/shuffle.cf @@ -0,0 +1,63 @@ +####################################################### +# +# Test shuffle() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "a" slist => { "a", "b", "c", "d", "e", "f", "g" }; + "b" slist => { }; +} + +####################################################### + +bundle agent test +{ + vars: + "sa" slist => shuffle("init.a", "skruf"); + "sb" slist => shuffle("init.b", "skruf"); +} + + +####################################################### + +bundle agent check +{ + vars: + "asorted" slist => sort("test.sa", "lex"); + + "jaorig" string => join(",", "init.a"); + "jasorted" string => join(",", "asorted"); + "ja" string => join(",", "test.sa"); + "jb" string => join(",", "test.sb"); + + classes: + "a_not_changed" expression => strcmp($(jaorig), $(ja)); + "a_is_permutation" expression => strcmp($(jasorted), $(jaorig)); + "ok_a" and => { "!a_not_changed", "a_is_permutation" }; + + "ok_b" expression => strcmp("", $(jb)); + + "ok" and => { "ok_a", "ok_b" }; + + reports: + DEBUG:: + "a '$(jaorig)' <> sort('$(ja)')"; + "b '$(jb)' <> ''"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/sort.cf b/tests/acceptance/01_vars/02_functions/sort.cf new file mode 100644 index 0000000000..ae10bb3c4b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sort.cf @@ -0,0 +1,87 @@ +########################################################### +# +# Test sort() +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent init +{ + vars: + "a" slist => { "b", "c", "a" }; + "b" slist => { "100", "9", "10" }; + "c" slist => { }; + "d" slist => { "", "a", "", "b" }; + "e" slist => { "a", "1", "b" }; + "f" slist => { "0", "100.0", "0.1", "3.2" }; + "g" slist => { "", "-2.1", "0.88", "b", "00.88" }; + + "ip" slist => { "100.200.100.0", "1.2.3.4", "9.7.5.1", "9", "9.7", "9.7.5", "", "-1", "where are the IP addresses?" }; + "ipv6" slist => { "FE80:0000:0000:0000:0202:B3FF:FE1E:8329", + "FE80::0202:B3FF:FE1E:8329", + "::1", + # the following should all be parsed as the same address and sorted together + "2001:db8:0:0:1:0:0:1", + "2001:0db8:0:0:1:0:0:1", + "2001:db8::1:0:0:1", + "2001:db8::0:1:0:0:1", + "2001:0db8::1:0:0:1", + "2001:db8:0:0:1::1", + "2001:db8:0000:0:1::1", + "2001:DB8:0:0:1::1", # note uppercase IPv6 addresses are invalid + # examples from https://www.ripe.net/lir-services/new-lir/ipv6_reference_card.pdf + "8000:63bf:3fff:fdd2", + "::ffff:192.0.2.47", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "2001:0000:4136:e378:", + "8000:63bf:3fff:fdd2", + "2001:0002:6c::430", + "2001:10:240:ab::a", + "2002:cb0a:3cdd:1::1", + "2001:db8:8:4::2", + "ff01:0:0:0:0:0:0:2", + "-1", "where are the IP addresses?" }; + + "mac" slist => { "00:14:BF:F7:23:1D", "0:14:BF:F7:23:1D", ":14:BF:F7:23:1D", "00:014:BF:0F7:23:01D", + "00:14:BF:F7:23:1D", "0:14:BF:F7:23:1D", ":14:BF:F7:23:1D", "00:014:BF:0F7:23:01D", + "01:14:BF:F7:23:1D", "1:14:BF:F7:23:1D", + "01:14:BF:F7:23:2D", "1:14:BF:F7:23:2D", + "-1", "where are the MAC addresses?" }; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "hpux|sunos_5_9", + meta => { "redmine4934", "redmine5107" }; + "test_flakey_fail" string => "windows", + meta => { "ENT-10254" }; + + + vars: + "test" slist => { "a", "b", "c", "d", "e", "f", "g", "ip", "ipv6", "mac" }; + "sort" slist => { "lex", "int", "real", "IP", "MAC" }; + + "$(sort)_sorted_$(test)" slist => sort("init.$(test)", $(sort)); + "inline_$(sort)_sorted" slist => sort('["b", "c", "a", "100", "9", "10"]', $(sort)); + "sorted_$(test)" slist => sort("init.$(test)"); # implicit "lex" +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/sort.cf.expected.json b/tests/acceptance/01_vars/02_functions/sort.cf.expected.json new file mode 100644 index 0000000000..a01860578c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sort.cf.expected.json @@ -0,0 +1,589 @@ +{ + "IP_sorted_a": [ + "a", + "b", + "c" + ], + "IP_sorted_b": [ + "10", + "100", + "9" + ], + "IP_sorted_c": [], + "IP_sorted_d": [ + "", + "", + "a", + "b" + ], + "IP_sorted_e": [ + "1", + "a", + "b" + ], + "IP_sorted_f": [ + "0", + "0.1", + "100.0", + "3.2" + ], + "IP_sorted_g": [ + "", + "-2.1", + "0.88", + "00.88", + "b" + ], + "IP_sorted_ip": [ + "", + "-1", + "9", + "9.7", + "9.7.5", + "where are the IP addresses?", + "1.2.3.4", + "9.7.5.1", + "100.200.100.0" + ], + "IP_sorted_ipv6": [ + "-1", + "2001:0000:4136:e378:", + "2001:DB8:0:0:1::1", + "8000:63bf:3fff:fdd2", + "8000:63bf:3fff:fdd2", + "::ffff:192.0.2.47", + "FE80:0000:0000:0000:0202:B3FF:FE1E:8329", + "FE80::0202:B3FF:FE1E:8329", + "where are the IP addresses?", + "::1", + "2001:0002:6c::430", + "2001:10:240:ab::a", + "2001:db8:0000:0:1::1", + "2001:db8:0:0:1::1", + "2001:0db8::1:0:0:1", + "2001:db8::0:1:0:0:1", + "2001:db8::1:0:0:1", + "2001:0db8:0:0:1:0:0:1", + "2001:db8:0:0:1:0:0:1", + "2001:db8:8:4::2", + "2002:cb0a:3cdd:1::1", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "ff01:0:0:0:0:0:0:2" + ], + "IP_sorted_mac": [ + "-1", + "00:014:BF:0F7:23:01D", + "00:014:BF:0F7:23:01D", + "00:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "01:14:BF:F7:23:1D", + "01:14:BF:F7:23:2D", + "0:14:BF:F7:23:1D", + "0:14:BF:F7:23:1D", + "1:14:BF:F7:23:1D", + "1:14:BF:F7:23:2D", + ":14:BF:F7:23:1D", + ":14:BF:F7:23:1D", + "where are the MAC addresses?" + ], + "MAC_sorted_a": [ + "a", + "b", + "c" + ], + "MAC_sorted_b": [ + "10", + "100", + "9" + ], + "MAC_sorted_c": [], + "MAC_sorted_d": [ + "", + "", + "a", + "b" + ], + "MAC_sorted_e": [ + "1", + "a", + "b" + ], + "MAC_sorted_f": [ + "0", + "0.1", + "100.0", + "3.2" + ], + "MAC_sorted_g": [ + "", + "-2.1", + "0.88", + "00.88", + "b" + ], + "MAC_sorted_ip": [ + "", + "-1", + "1.2.3.4", + "100.200.100.0", + "9", + "9.7", + "9.7.5", + "9.7.5.1", + "where are the IP addresses?" + ], + "MAC_sorted_ipv6": [ + "-1", + "2001:0000:4136:e378:", + "2001:0002:6c::430", + "2001:0db8::1:0:0:1", + "2001:10:240:ab::a", + "2001:DB8:0:0:1::1", + "2001:db8:0000:0:1::1", + "2001:db8:0:0:1::1", + "2001:db8:8:4::2", + "2001:db8::0:1:0:0:1", + "2001:db8::1:0:0:1", + "2002:cb0a:3cdd:1::1", + "8000:63bf:3fff:fdd2", + "8000:63bf:3fff:fdd2", + "::1", + "::ffff:192.0.2.47", + "FE80::0202:B3FF:FE1E:8329", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "where are the IP addresses?", + "ff01:0:0:0:0:0:0:2", + "2001:0db8:0:0:1:0:0:1", + "2001:db8:0:0:1:0:0:1", + "FE80:0000:0000:0000:0202:B3FF:FE1E:8329" + ], + "MAC_sorted_mac": [ + "-1", + ":14:BF:F7:23:1D", + ":14:BF:F7:23:1D", + "where are the MAC addresses?", + "00:014:BF:0F7:23:01D", + "0:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "00:014:BF:0F7:23:01D", + "0:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "1:14:BF:F7:23:1D", + "01:14:BF:F7:23:1D", + "1:14:BF:F7:23:2D", + "01:14:BF:F7:23:2D" + ], + "inline_IP_sorted": [ + "10", + "100", + "9", + "a", + "b", + "c" + ], + "inline_MAC_sorted": [ + "10", + "100", + "9", + "a", + "b", + "c" + ], + "inline_int_sorted": [ + "a", + "b", + "c", + "9", + "10", + "100" + ], + "inline_lex_sorted": [ + "10", + "100", + "9", + "a", + "b", + "c" + ], + "inline_real_sorted": [ + "a", + "b", + "c", + "9", + "10", + "100" + ], + "int_sorted_a": [ + "a", + "b", + "c" + ], + "int_sorted_b": [ + "9", + "10", + "100" + ], + "int_sorted_c": [], + "int_sorted_d": [ + "", + "", + "a", + "b" + ], + "int_sorted_e": [ + "a", + "b", + "1" + ], + "int_sorted_f": [ + "0.1", + "0", + "3.2", + "100.0" + ], + "int_sorted_g": [ + "", + "b", + "-2.1", + "00.88", + "0.88" + ], + "int_sorted_ip": [ + "", + "where are the IP addresses?", + "-1", + "1.2.3.4", + "9.7.5", + "9.7", + "9", + "9.7.5.1", + "100.200.100.0" + ], + "int_sorted_ipv6": [ + "::1", + "::ffff:192.0.2.47", + "FE80:0000:0000:0000:0202:B3FF:FE1E:8329", + "FE80::0202:B3FF:FE1E:8329", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "ff01:0:0:0:0:0:0:2", + "where are the IP addresses?", + "-1", + "2001:db8:8:4::2", + "2001:10:240:ab::a", + "2001:0002:6c::430", + "2001:0000:4136:e378:", + "2001:DB8:0:0:1::1", + "2001:db8:0000:0:1::1", + "2001:db8:0:0:1::1", + "2001:0db8::1:0:0:1", + "2001:db8::0:1:0:0:1", + "2001:db8::1:0:0:1", + "2001:0db8:0:0:1:0:0:1", + "2001:db8:0:0:1:0:0:1", + "2002:cb0a:3cdd:1::1", + "8000:63bf:3fff:fdd2", + "8000:63bf:3fff:fdd2" + ], + "int_sorted_mac": [ + ":14:BF:F7:23:1D", + ":14:BF:F7:23:1D", + "where are the MAC addresses?", + "-1", + "00:014:BF:0F7:23:01D", + "0:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "00:014:BF:0F7:23:01D", + "0:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "1:14:BF:F7:23:2D", + "01:14:BF:F7:23:2D", + "1:14:BF:F7:23:1D", + "01:14:BF:F7:23:1D" + ], + "lex_sorted_a": [ + "a", + "b", + "c" + ], + "lex_sorted_b": [ + "10", + "100", + "9" + ], + "lex_sorted_c": [], + "lex_sorted_d": [ + "", + "", + "a", + "b" + ], + "lex_sorted_e": [ + "1", + "a", + "b" + ], + "lex_sorted_f": [ + "0", + "0.1", + "100.0", + "3.2" + ], + "lex_sorted_g": [ + "", + "-2.1", + "0.88", + "00.88", + "b" + ], + "lex_sorted_ip": [ + "", + "-1", + "1.2.3.4", + "100.200.100.0", + "9", + "9.7", + "9.7.5", + "9.7.5.1", + "where are the IP addresses?" + ], + "lex_sorted_ipv6": [ + "-1", + "2001:0000:4136:e378:", + "2001:0002:6c::430", + "2001:0db8:0:0:1:0:0:1", + "2001:0db8::1:0:0:1", + "2001:10:240:ab::a", + "2001:DB8:0:0:1::1", + "2001:db8:0000:0:1::1", + "2001:db8:0:0:1:0:0:1", + "2001:db8:0:0:1::1", + "2001:db8:8:4::2", + "2001:db8::0:1:0:0:1", + "2001:db8::1:0:0:1", + "2002:cb0a:3cdd:1::1", + "8000:63bf:3fff:fdd2", + "8000:63bf:3fff:fdd2", + "::1", + "::ffff:192.0.2.47", + "FE80:0000:0000:0000:0202:B3FF:FE1E:8329", + "FE80::0202:B3FF:FE1E:8329", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "ff01:0:0:0:0:0:0:2", + "where are the IP addresses?" + ], + "lex_sorted_mac": [ + "-1", + "00:014:BF:0F7:23:01D", + "00:014:BF:0F7:23:01D", + "00:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "01:14:BF:F7:23:1D", + "01:14:BF:F7:23:2D", + "0:14:BF:F7:23:1D", + "0:14:BF:F7:23:1D", + "1:14:BF:F7:23:1D", + "1:14:BF:F7:23:2D", + ":14:BF:F7:23:1D", + ":14:BF:F7:23:1D", + "where are the MAC addresses?" + ], + "real_sorted_a": [ + "a", + "b", + "c" + ], + "real_sorted_b": [ + "9", + "10", + "100" + ], + "real_sorted_c": [], + "real_sorted_d": [ + "", + "", + "a", + "b" + ], + "real_sorted_e": [ + "a", + "b", + "1" + ], + "real_sorted_f": [ + "0", + "0.1", + "3.2", + "100.0" + ], + "real_sorted_g": [ + "", + "b", + "-2.1", + "00.88", + "0.88" + ], + "real_sorted_ip": [ + "", + "where are the IP addresses?", + "-1", + "1.2.3.4", + "9", + "9.7.5", + "9.7", + "9.7.5.1", + "100.200.100.0" + ], + "real_sorted_ipv6": [ + "::1", + "::ffff:192.0.2.47", + "FE80:0000:0000:0000:0202:B3FF:FE1E:8329", + "FE80::0202:B3FF:FE1E:8329", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "ff01:0:0:0:0:0:0:2", + "where are the IP addresses?", + "-1", + "2001:db8:8:4::2", + "2001:10:240:ab::a", + "2001:0002:6c::430", + "2001:0000:4136:e378:", + "2001:DB8:0:0:1::1", + "2001:db8:0000:0:1::1", + "2001:db8:0:0:1::1", + "2001:0db8::1:0:0:1", + "2001:db8::0:1:0:0:1", + "2001:db8::1:0:0:1", + "2001:0db8:0:0:1:0:0:1", + "2001:db8:0:0:1:0:0:1", + "2002:cb0a:3cdd:1::1", + "8000:63bf:3fff:fdd2", + "8000:63bf:3fff:fdd2" + ], + "real_sorted_mac": [ + ":14:BF:F7:23:1D", + ":14:BF:F7:23:1D", + "where are the MAC addresses?", + "-1", + "00:014:BF:0F7:23:01D", + "0:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "00:014:BF:0F7:23:01D", + "0:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "1:14:BF:F7:23:2D", + "01:14:BF:F7:23:2D", + "1:14:BF:F7:23:1D", + "01:14:BF:F7:23:1D" + ], + "sort": [ + "lex", + "int", + "real", + "IP", + "MAC" + ], + "sorted_a": [ + "a", + "b", + "c" + ], + "sorted_b": [ + "10", + "100", + "9" + ], + "sorted_c": [], + "sorted_d": [ + "", + "", + "a", + "b" + ], + "sorted_e": [ + "1", + "a", + "b" + ], + "sorted_f": [ + "0", + "0.1", + "100.0", + "3.2" + ], + "sorted_g": [ + "", + "-2.1", + "0.88", + "00.88", + "b" + ], + "sorted_ip": [ + "", + "-1", + "1.2.3.4", + "100.200.100.0", + "9", + "9.7", + "9.7.5", + "9.7.5.1", + "where are the IP addresses?" + ], + "sorted_ipv6": [ + "-1", + "2001:0000:4136:e378:", + "2001:0002:6c::430", + "2001:0db8:0:0:1:0:0:1", + "2001:0db8::1:0:0:1", + "2001:10:240:ab::a", + "2001:DB8:0:0:1::1", + "2001:db8:0000:0:1::1", + "2001:db8:0:0:1:0:0:1", + "2001:db8:0:0:1::1", + "2001:db8:8:4::2", + "2001:db8::0:1:0:0:1", + "2001:db8::1:0:0:1", + "2002:cb0a:3cdd:1::1", + "8000:63bf:3fff:fdd2", + "8000:63bf:3fff:fdd2", + "::1", + "::ffff:192.0.2.47", + "FE80:0000:0000:0000:0202:B3FF:FE1E:8329", + "FE80::0202:B3FF:FE1E:8329", + "fdf8:f53b:82e4::53", + "fe80::200:5aee:feaa:20a2", + "ff01:0:0:0:0:0:0:2", + "where are the IP addresses?" + ], + "sorted_mac": [ + "-1", + "00:014:BF:0F7:23:01D", + "00:014:BF:0F7:23:01D", + "00:14:BF:F7:23:1D", + "00:14:BF:F7:23:1D", + "01:14:BF:F7:23:1D", + "01:14:BF:F7:23:2D", + "0:14:BF:F7:23:1D", + "0:14:BF:F7:23:1D", + "1:14:BF:F7:23:1D", + "1:14:BF:F7:23:2D", + ":14:BF:F7:23:1D", + ":14:BF:F7:23:1D", + "where are the MAC addresses?" + ], + "test": [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "ip", + "ipv6", + "mac" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/split.cf b/tests/acceptance/01_vars/02_functions/split.cf new file mode 100644 index 0000000000..2871bbd3be --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/split.cf @@ -0,0 +1,65 @@ +####################################################### +# +# Test string_split() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +############################################### + +bundle agent init +{ + +vars: + "states" slist => { "actual", "expected" }; + + "expected" string => + "one +two:three"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => ensure_lines("$(init.expected)"), + edit_defaults => empty; + +} + + +####################################################### + +bundle agent test +{ + vars: + "result" slist => string_split("one:two:three", ":", "2"); + + files: + "$(G.testfile).actual" + create => "true", + edit_defaults => empty, + edit_line => ensure_lines(@(result)); + +} + + +####################################################### + +bundle edit_line ensure_lines(list) +{ + insert_lines: + "$(list)"; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/staging/accumulated.cf b/tests/acceptance/01_vars/02_functions/staging/accumulated.cf new file mode 100644 index 0000000000..03e7b7e437 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/accumulated.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(315,0,0,0,0,0); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "9933840000"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/staging/accumulated2.cf b/tests/acceptance/01_vars/02_functions/staging/accumulated2.cf new file mode 100644 index 0000000000..ca070113cf --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/accumulated2.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test accumulated() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "time" int => accumulated(0,1000,1000,1000,1000,40000); +} + +####################################################### + +bundle agent check +{ + vars: + "time" int => "2682100000"; # 1 year == 365 days + + classes: + "ok" expression => strcmp("$(time)", "$(test.time)"); + + reports: + DEBUG:: + "time: $(time)"; + "test.time: $(test.time)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/02_functions/staging/bundlesmatching_dynamicbundlesequence.cf b/tests/acceptance/01_vars/02_functions/staging/bundlesmatching_dynamicbundlesequence.cf new file mode 100644 index 0000000000..e963b0cb56 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/bundlesmatching_dynamicbundlesequence.cf @@ -0,0 +1,49 @@ +# Test that bundlesmatching works correctly for dynamic sequences + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)"), @(init.runs) }; +} + +bundle common init +{ + vars: + "runs" slist => bundlesmatching("default:run.*"); +} + +bundle agent test +{ +} + +bundle agent check +{ + classes: + "ok" and => { ok1, ok2, ok3 }; + + reports: + DEBUG:: + "Found bundles $(test.runs)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +bundle agent run_123_456 +{ + classes: + "ok1" expression => "any", scope => "namespace"; +} + +bundle agent run_789_0ab +{ + classes: + "ok2" expression => "any", scope => "namespace"; +} + +bundle agent run_cde_fgh +{ + classes: + "ok3" expression => "any", scope => "namespace"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/datatype.cf b/tests/acceptance/01_vars/02_functions/staging/datatype.cf new file mode 100644 index 0000000000..782d8dfaec --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/datatype.cf @@ -0,0 +1,270 @@ +####################################################### +# +# Test datatype() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "results" string => 'jsonstring = { + "person": { + "first": "miyamoto", + "last": "musashi" + }, + "birth": null, + "death": 16450613, + "go rin no sho": { + "1": "ground", + "2": "water", + "3": "fire", + "4": "wind", + "5": "void" + }, + "dokkōdō": [ + "accept everything just the way it is.", + "do not seek pleasure for its own sake.", + "do not, under any circumstances, depend on a partial feeling.", + "think lightly of yourself and deeply of the world.", + "be detached from desire your whole life.", + "do not regret what you have done.", + "never be jealous.", + "never let yourself be saddened by a separation.", + "resentment and complaint are appropriate neither for oneself nor others.", + "do not let yourself be guided by the feeling of lust or love.", + "In all things, have no preferences.", + "be indifferent to where you live.", + "do not pursue the taste of good food.", + "do not hold on to possessions you no longer need.", + "do not act following customary beliefs.", + "do not collect weapons or practice with weapons beyond what is useful.", + "do not fear death.", + "do not seek to possess either goods or fiefs for your old age.", + "respect buddha and the gods without counting on their help.", + "you may abandon your own body but you must preserve your honor.", + "never stray from the way." + ], + "styles": [ + "two heavens as one", + "two swords as one" + ], + "decidable": false, + "dituri was here": 3.1416 +}'; + + "results2" string => '--------------------- +toplevel:key = person +toplevel:key = birth +toplevel:key = death +toplevel:key = go rin no sho +toplevel:key = dokkōdō +toplevel:key = styles +toplevel:key = decidable +toplevel:key = dituri was here +toplevel[person]:type = json_object +toplevel[birth]:type = json_null +toplevel[death]:type = json_integer +toplevel[go rin no sho]:type = json_object +toplevel[dokkōdō]:type = json_array +toplevel[styles]:type = json_array +toplevel[decidable]:type = json_bool +toplevel[dituri was here]:type = json_real +------------------------------------------ +toplevel[person]:key= first +toplevel[go rin no sho]:key= 1 +toplevel[dokkōdō]:key= 0 +toplevel[styles]:key= 0 +toplevel[person]:key= last +toplevel[go rin no sho]:key= 2 +toplevel[go rin no sho]:key= 3 +toplevel[go rin no sho]:key= 4 +toplevel[go rin no sho]:key= 5 +toplevel[dokkōdō]:key= 1 +toplevel[dokkōdō]:key= 2 +toplevel[dokkōdō]:key= 3 +toplevel[dokkōdō]:key= 4 +toplevel[dokkōdō]:key= 5 +toplevel[dokkōdō]:key= 6 +toplevel[dokkōdō]:key= 7 +toplevel[dokkōdō]:key= 8 +toplevel[dokkōdō]:key= 9 +toplevel[dokkōdō]:key= 10 +toplevel[dokkōdō]:key= 11 +toplevel[dokkōdō]:key= 12 +toplevel[dokkōdō]:key= 13 +toplevel[dokkōdō]:key= 14 +toplevel[dokkōdō]:key= 15 +toplevel[dokkōdō]:key= 16 +toplevel[dokkōdō]:key= 17 +toplevel[dokkōdō]:key= 18 +toplevel[dokkōdō]:key= 19 +toplevel[dokkōdō]:key= 20 +toplevel[styles]:key= 1'; + + "results3" string => 'toplevel[person][first]:type = json_string +toplevel[go rin no sho][1]:type = json_string +toplevel[dokkōdō][0]:type = json_string +toplevel[styles][0]:type = json_string +toplevel[person][last]:type = json_string +toplevel[go rin no sho][2]:type = json_string +toplevel[go rin no sho][3]:type = json_string +toplevel[go rin no sho][4]:type = json_string +toplevel[go rin no sho][5]:type = json_string +toplevel[dokkōdō][1]:type = json_string +toplevel[dokkōdō][2]:type = json_string +toplevel[dokkōdō][3]:type = json_string +toplevel[dokkōdō][4]:type = json_string +toplevel[dokkōdō][5]:type = json_string +toplevel[dokkōdō][6]:type = json_string +toplevel[dokkōdō][7]:type = json_string +toplevel[dokkōdō][8]:type = json_string +toplevel[dokkōdō][9]:type = json_string +toplevel[dokkōdō][10]:type = json_string +toplevel[dokkōdō][11]:type = json_string +toplevel[dokkōdō][12]:type = json_string +toplevel[dokkōdō][13]:type = json_string +toplevel[dokkōdō][14]:type = json_string +toplevel[dokkōdō][15]:type = json_string +toplevel[dokkōdō][16]:type = json_string +toplevel[dokkōdō][17]:type = json_string +toplevel[dokkōdō][18]:type = json_string +toplevel[dokkōdō][19]:type = json_string +toplevel[dokkōdō][20]:type = json_string +toplevel[styles][1]:type = json_string +ok: datatype(datastr) failed properly'; + + files: + "$(G.testfile).expected" + create => "true", + edit_defaults => init_empty, + edit_line => init_insert; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; + edit_backup => "false"; +} + +bundle edit_line init_insert +{ + insert_lines: + "$(init.results)"; + "$(init.results2)"; + "$(init.results3)"; +} + +####################################################### + +bundle common test_common +{ + vars: + "datastr" string => storejson("data"); + "data" data => parsejson(' +{ + "person": { "first": "miyamoto", "last": "musashi" }, + "birth": null, + "death": 16450613, + "go rin no sho": { "1": "ground", "2": "water", "3": "fire", "4": "wind", "5": "void" }, + "dokkōdō": [ + "accept everything just the way it is.", + "do not seek pleasure for its own sake.", + "do not, under any circumstances, depend on a partial feeling.", + "think lightly of yourself and deeply of the world.", + "be detached from desire your whole life.", + "do not regret what you have done.", + "never be jealous.", + "never let yourself be saddened by a separation.", + "resentment and complaint are appropriate neither for oneself nor others.", + "do not let yourself be guided by the feeling of lust or love.", + "In all things, have no preferences.", + "be indifferent to where you live.", + "do not pursue the taste of good food.", + "do not hold on to possessions you no longer need.", + "do not act following customary beliefs.", + "do not collect weapons or practice with weapons beyond what is useful.", + "do not fear death.", + "do not seek to possess either goods or fiefs for your old age.", + "respect buddha and the gods without counting on their help.", + "you may abandon your own body but you must preserve your honor.", + "never stray from the way." + ], + "styles": ["two heavens as one","two swords as one"], + "decidable": false + "dituri was here": 3.14159265 +} +'); + + "typenames" slist => { "person", "go rin no sho", "dokkōdō", "styles" }; + + "keys[top]" + slist => getindices("data"); + + "types[top][$(keys[top])]" + string => datatype("data[$(keys[top])]"); + + "keys[$(typenames)]" + slist => getindices("data[$(typenames)]"); + + "types[$(typenames)][$(keys[$(typenames)])]" + string => datatype("data[$(typenames)][$(keys[$(typenames)])]"); + + "bad_type_test" + string => datatype("datastr"); + + classes: + "ok_invalid_test_failed" not => isvariable("bad_type_test"); +} + + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + cfengine:: + "jsonstring = $(test_common.datastr)"; + + "---------------------"; + "toplevel:key = $(test_common.keys[top])"; + "toplevel[$(test_common.keys[top])]:type = $(test_common.types[top][$(test_common.keys[top])])"; + + "------------------------------------------"; + "toplevel[$(test_common.typenames)]:key= $(test_common.keys[$(test_common.typenames)])"; + "toplevel[$(test_common.typenames)][$(test_common.keys[$(test_common.typenames)])]:type = $(test_common.types[$(test_common.typenames)][$(test_common.keys[$(test_common.typenames)])])"; + + ok_invalid_test_failed:: + "------------------------------------------"; + "ok: datatype(datastr) failed properly"; + + !ok_invalid_test_failed:: + "------------------------------------------"; + "NOT_OK: datatype(datastr) was supposed to fail"; + +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/02_functions/staging/execresult_large.cf b/tests/acceptance/01_vars/02_functions/staging/execresult_large.cf new file mode 100644 index 0000000000..63bc73ebf4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/execresult_large.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test execresult() of large files +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ +} + +####################################################### + +bundle agent check +{ + vars: + "testlengths" ilist => { "1024", "4096", "100000", "500000" }; + "result_$(testlengths)" string => execresult("$(G.perl) -e 'my $x = qw/x/ x $(testlengths); print $x;'", "useshell"); + "length_$(testlengths)" int => strlen("$(result_$(testlengths))"); + + classes: + "ok_$(testlengths)" expression => strcmp("$(length_$(testlengths))", + $(testlengths)); + "ok" and => { + "ok_1024", "ok_4096", "ok_100000", "ok_500000", + }; + + reports: + DEBUG:: + "the read of $(testlengths) bytes gave $(length_$(testlengths)) bytes"; + + "the read of $(testlengths) bytes failed to match the expected $(testlengths) bytes, actual = $(length_$(testlengths))" + if => "!ok_$(testlengths)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/format_empty_lists.cf b/tests/acceptance/01_vars/02_functions/staging/format_empty_lists.cf new file mode 100644 index 0000000000..0e5e1b37de --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/format_empty_lists.cf @@ -0,0 +1,35 @@ +# ensure interpolated empty lists don't drag in cf_null + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "list" slist => { "item1", "item2", "item3", "item4", "item5" }; +} + +bundle agent test +{ + vars: + "local" slist => { }; + # should *not* bring in cf_null + "splice" slist => { @(init.list), @(local) }; +} + +bundle agent check +{ + vars: + "expected" string => '{ "item1", "item2", "item3", "item4", "item5" }'; + "joined" string => format("%S", "test.splice"); + + methods: + "check" usebundle => dcs_check_strcmp($(expected), + $(joined), + "$(this.promise_filename)", + "no"); +} diff --git a/tests/acceptance/01_vars/02_functions/staging/functions_and_nulls.cf b/tests/acceptance/01_vars/02_functions/staging/functions_and_nulls.cf new file mode 100644 index 0000000000..b75c6d4c88 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/functions_and_nulls.cf @@ -0,0 +1,56 @@ +####################################################### +# +# Test hash() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + # The nulls terminate the string here + "test1" string => execresult("$(G.perl) -e 'print \"Test of \0\1\2\n nulls in a string\";'", "noshell"); + + # Write the same string to a file and read it in again, they are converted + "test2" string => execresult("$(G.perl) -e 'print \"Test of \0\1\2\n nulls in a string\";' > $(G.testfile)", "useshell"); + "test3" string => readfile("$(G.testfile)","100"); + + # They are converted here, too, so the split fails + "test4a" slist => readstringlist("$(G.testfile)","#no comments", "\0", "2", "100"); + # And simply quoting the backslash doesn't work, because I think the + # regex is also converted + "test4b" slist => readstringlist("$(G.testfile)","#no comments", "\\0", "2", "100"); + + # So this doubly incorrect split succeeds because of the two conversions + "test4c" slist => readstringlist("$(G.testfile)","#no comments", "\\\0", "2", "100"); + + # readstringarray() (or perhaps getindices) simply breaks + "i" int => readstringarray("test5", "$(G.testfile)","#no comments", "no split", "2", "100"); + "test5_idx" slist => getindices("test5"); + + # readstringarrayidx() converts the results (same split issues as test4) + "j" int => readstringarrayidx("test6", "$(G.testfile)","#no comments", "no split", "2", "100"); + "test6_idx" slist => getindices("test6"); + + # This shows that the nulls are really there in the file + "od_file" string => execresult("$(G.od) -c $(G.testfile)", "noshell"); + + reports: + cfengine_3:: + "test1: $(test1)"; + "test2: $(test2) (<== we expect nothing here)"; + "test3: $(test3)"; + "test4a: $(test4a)"; + "test4b: $(test4b)"; + "test4c: $(test4c)"; + "test5, $(i) entries: $(test5[$(test5_idx)])"; + "test5_idx: $(test5_idx)"; + "test6, $(j) entries: $(test6[$(test6_idx)][0])"; + "od_file: $(od_file)"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/getindices_size5_namespaces.cf b/tests/acceptance/01_vars/02_functions/staging/getindices_size5_namespaces.cf new file mode 100644 index 0000000000..f0210f0f4a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/getindices_size5_namespaces.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test getindices() across namespaces, size 5 +# +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub", "206-namespaced.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "alpha"; + "beta"; + "gamma's"; + "delta-delta:delta"; + "last"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[alpha]" string => "zero"; + "array[beta]" string => "two"; + "array[gamma's]" string => "three's"; + "array[delta-delta:delta]" string => "four-fore:quatre"; + "array[last]" string => "last"; + + methods: + "run" usebundle => testing_arrays:namespaced_test("$(G.testfile).actual", + "default:test.array"); +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/staging/getindices_size5_namespaces.cf.sub b/tests/acceptance/01_vars/02_functions/staging/getindices_size5_namespaces.cf.sub new file mode 100644 index 0000000000..17f13e6ba6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/getindices_size5_namespaces.cf.sub @@ -0,0 +1,29 @@ +body file control +{ + namespace => "testing_arrays"; +} + +bundle agent namespaced_test(file, arrayname) +{ + vars: + "keys" slist => getindices("$(arrayname)"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Working in namespace; array name $(arrayname)"; + "Inserting line: $(keys)"; +} + +bundle edit_line test_insert +{ + vars: + "keys" slist => { @{namespaced_test.keys} }; + + insert_lines: + "$(keys)"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/getvalues_size2.cf b/tests/acceptance/01_vars/02_functions/staging/getvalues_size2.cf new file mode 100644 index 0000000000..e3ec892983 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/getvalues_size2.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Test getvalues(), size 2 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "zero"; + "one"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[alpha]" string => "zero"; + "array[beta]" string => "two"; + + "vals" slist => getvalues("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/staging/getvalues_size2_with_expansion.cf b/tests/acceptance/01_vars/02_functions/staging/getvalues_size2_with_expansion.cf new file mode 100644 index 0000000000..7b8730f8c4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/getvalues_size2_with_expansion.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Test getvalues(), size 2 with variable expansion +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "one +extra line" + "the end$(const.dollar)fini"; + "XXX dummy XXX"; + "YYY $(init.dummy) YYY"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[the fini$end]" string => + "additional line +again"; + "array[the end$(const.dollar)fini]" string => "one$(const.n)extra line"; + "array[XXX dummy XXX]" string => "XXX dummy XXX"; + "array[YYY $(init.dummy) YYY]" string => "YYY $(init.dummy) YYY"; + + "vals" slist => getvalues("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/staging/getvalues_size5.cf b/tests/acceptance/01_vars/02_functions/staging/getvalues_size5.cf new file mode 100644 index 0000000000..d8ea13c633 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/getvalues_size5.cf @@ -0,0 +1,83 @@ +####################################################### +# +# Test getvalues(), size 5 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "zero"; + "one"; + "three's"; + "four-fore:quatre"; + "last"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[alpha]" string => "zero"; + "array[beta]" string => "two"; + "array[gamma's]" string => "three's"; + "array[delta-delta:delta]" string => "four-fore:quatre"; + "array[last]" string => "last"; + + "vals" slist => getvalues("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/staging/getvalues_size5_with_duplicate_triplicate.cf b/tests/acceptance/01_vars/02_functions/staging/getvalues_size5_with_duplicate_triplicate.cf new file mode 100644 index 0000000000..11da0049cd --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/getvalues_size5_with_duplicate_triplicate.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Test getvalues(), size 5 with a duplicate and a triplicate +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "zero" string => "zero"; + "one" string => "one"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "zero"; + "one"; +} + +####################################################### + +bundle agent test +{ + vars: + "array[alpha]" string => "zero"; + "array[beta]" string => "$(zero)"; + "array[gamma's]" string => "one"; + "array[delta-delta:delta]" string => "$(one)"; + "array[last]" string => "one"; + + "vals" slist => getvalues("array"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; + + reports: + DEBUG:: + "Inserting line: $(vals)"; +} + +bundle edit_line test_insert +{ + vars: + "vals" slist => { @{test.vals} }; + + insert_lines: + "$(vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} diff --git a/tests/acceptance/01_vars/02_functions/staging/host2ip.cf b/tests/acceptance/01_vars/02_functions/staging/host2ip.cf new file mode 100644 index 0000000000..18e03245d9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/host2ip.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Test host2ip() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + # Neither of these are likely to change... + "localhost" string => ip2host("127.0.0.1"); + "localhost_6" string => ip2host("::1"); + "a" string => ip2host("198.41.0.4"); + "a_6" string => ip2host("2001:503:ba3e::2:30"); +} + +####################################################### + +bundle agent check +{ + vars: + "localhost" string => "localhost"; + "a" string => "a.root-servers.net"; + + classes: + "ok_a" and => { + strcmp("$(test.a)", "$(a)"), + strcmp("$(test.a_6)", "$(a)"), + }; + "ok_localhost" and => { + strcmp("$(test.localhost)", "$(localhost)"), + strcmp("$(test.localhost_6)", "$(localhost)"), + }; + "ok" and => { + "ok_a", + "ok_localhost", + }; + + reports: + DEBUG:: + "Expected ($(test.localhost) and $(test.localhost_6)) == $(localhost)"; + "Expected ($(test.a) and $(test.a_6)) == $(a)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + vars: + "dummy" string => "dummy"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parserealarray_duplicate_key.cf b/tests/acceptance/01_vars/02_functions/staging/parserealarray_duplicate_key.cf new file mode 100644 index 0000000000..96a5f23e77 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parserealarray_duplicate_key.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Test parserealarray(), introduce 777 duplicate key +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readrealarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999.9:888:777:666.6 +999.9:000 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parserealarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; # 4 lines, but 3 unique keys +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + # The second value should overwrite the first + strcmp("$(test.ary[999.9][0])", "999.9"), + strcmp("$(test.ary[999.9][1])", "0"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999.9][2]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999.9][0] = '999.9', saw '$(test.ary[999.9][0])'"; + "expected test.ary[999.9][1] = '0', saw '$(test.ary[999.9][1])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_change_separator_to_s.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_change_separator_to_s.cf new file mode 100644 index 0000000000..1161c99dcc --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_change_separator_to_s.cf @@ -0,0 +1,126 @@ +####################################################### +# +# Test parsestringarray(), change separator to 's' +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","^#.*","s+",10,1000); + "num" int => "7"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0:1:2][0])", "0:1:2"), + + strcmp("$(test.ary[1:2:3][0])", "1:2:3"), + + strcmp("$(test.ary[here i][0])", "here i"), + strcmp("$(test.ary[here i][1])", ":a line: with space"), + strcmp("$(test.ary[here i][2])", " : in it"), + + strcmp("$(test.ary[blank field][0])", "blank field"), + strcmp("$(test.ary[blank field][2])", "::: in here::"), + + strcmp("$(test.ary[:leading blank field][0])", "leading blank field"), + + strcmp("$(test.ary[thi][0])", "thi"), + strcmp("$(test.ary[thi][1])", ":also"), + strcmp("$(test.ary[thi][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[thi][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[la][0])", "la"), + strcmp("$(test.ary[la][1])", "t:one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0:1:2][0] = '0:1:2', saw '$(test.ary[0:1:2][0])'"; + + "expected test.ary[1:2:3][0] = '1:2:3', saw '$(test.ary[1:2:3][0])'"; + + "expected test.ary[here i][0] = 'here i', saw '$(test.ary[here i][0])'"; + "expected test.ary[here i][1] = ':a line: with space', saw '$(test.ary[here i][1])'"; + "expected test.ary[here i][2] = ' : in it', saw '$(test.ary[here i][2])'"; + + "expected test.ary[blank field][0] = 'blank field', saw '$(test.ary[blank field][0])'"; + "expected test.ary[blank field][2] = '::: in here::', saw '$(test.ary[blank field][2])'"; + + "expected test.ary[:leading blank field][0] = 'leading blank field', saw '$(test.ary[:leading blank field][0])'"; + + "expected test.ary[thi][0] = 'thi', saw '$(test.ary[thi][0])'"; + "expected test.ary[thi][1]) = ':also', saw '$(test.ary[thi][1]))'"; + "expected test.ary[thi][2]) = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[thi][2]))'"; + "expected test.ary[thi][3]) = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[thi][3]))'"; + + "expected test.ary[la][0]) = 'la', saw '$(test.ary[la][0]))'"; + "expected test.ary[la][1]) = 't:one', saw '$(test.ary[la][1]))'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_dontread_comment_duplicate_lastline.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_dontread_comment_duplicate_lastline.cf new file mode 100644 index 0000000000..059f157e53 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_dontread_comment_duplicate_lastline.cf @@ -0,0 +1,145 @@ +####################################################### +# +# Test parsestringarray(), don't read comment, duplicate key or last line +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(testfile)","NoComment",":",6,1000); + "num" int => "6"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", ""), + strcmp("$(test.ary[blank][3])", ""), + strcmp("$(test.ary[blank][4])", "in here"), + strcmp("$(test.ary[blank][5])", ""), + strcmp("$(test.ary[blank][6])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "is"), + strcmp("$(test.ary[this][2])", "a"), + strcmp("$(test.ary[this][3])", "test"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = '', saw '$(test.ary[blank][2])'"; + "expected test.ary[blank][3] = '', saw '$(test.ary[blank][3])'"; + "expected test.ary[blank][4] = 'in here', saw '$(test.ary[blank][4])'"; + "expected test.ary[blank][5] = '', saw '$(test.ary[blank][5])'"; + "expected test.ary[blank][6] = '', saw '$(test.ary[blank][6])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'is', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = 'a', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = 'test', saw '$(test.ary[this][3])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_dontread_comment_duplicate_lastline3.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_dontread_comment_duplicate_lastline3.cf new file mode 100644 index 0000000000..b5c2cc2ef1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_dontread_comment_duplicate_lastline3.cf @@ -0,0 +1,155 @@ +####################################################### +# +# Test parsestringarray(), don't read comment, duplicate key or last line +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","NoComment",":",6,1000); + "num" int => "6"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "bad" or => { + isvariable("test.ary[6][0]"), + isvariable("test.ary[6][1]"), + + isvariable("test.ary[7][0]"), + isvariable("test.ary[7][1]"), + + isvariable("test.ary[8][0]"), + isvariable("test.ary[8][1]"), + }; + + "ok" and => { + "!bad", + + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_introduce_duplicate_key.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_introduce_duplicate_key.cf new file mode 100644 index 0000000000..bbc1aa408e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_introduce_duplicate_key.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Test parsestringarray(), introduce a duplicate key +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +this:too +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; # 4 lines, but 3 unique keys +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + # The second value should overwrite the first + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "too"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[this][2]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'too', saw '$(test.ary[this][1])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_introduce_nonparsed_comment.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_introduce_nonparsed_comment.cf new file mode 100644 index 0000000000..9e945e423f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_introduce_nonparsed_comment.cf @@ -0,0 +1,127 @@ +####################################################### +# +# Test parsestringarray(), introduce a non-parsed comment +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +# Not parsed as a comment +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "4"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "is"), + strcmp("$(test.ary[this][2])", "a"), + strcmp("$(test.ary[this][3])", "test"), + + strcmp("$(test.ary[# Not parsed as a comment][0])", "# Not parsed as a comment"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[# Not parsed as a comment][1]"), + isvariable("test.ary[this][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[# Not parsed as a comment][0] = '# Not parsed as a comment', saw 'expected $(test.ary[# Not parsed as a comment][0])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'is', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = 'a', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = 'test', saw '$(test.ary[this][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_change_comment_parsing.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_change_comment_parsing.cf new file mode 100644 index 0000000000..fced203b69 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_change_comment_parsing.cf @@ -0,0 +1,154 @@ +####################################################### +# +# Test parsestringarray(), weird indices, change comment parsing +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","",":",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", ""), + strcmp("$(test.ary[blank][3])", ""), + strcmp("$(test.ary[blank][4])", "in here"), + strcmp("$(test.ary[blank][5])", ""), + strcmp("$(test.ary[blank][6])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[# A duplicate follows][0])", "# A duplicate follows"), + strcmp("$(test.ary[# A duplicate follows][1])", "this line is not always a comment"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = '', saw '$(test.ary[blank][2])'"; + "expected test.ary[blank][3] = '', saw '$(test.ary[blank][3])'"; + "expected test.ary[blank][4] = 'in here', saw '$(test.ary[blank][4])'"; + "expected test.ary[blank][5] = '', saw '$(test.ary[blank][5])'"; + "expected test.ary[blank][6] = '', saw '$(test.ary[blank][6])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_change_comment_parsing2.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_change_comment_parsing2.cf new file mode 100644 index 0000000000..2e0b2892c2 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_change_comment_parsing2.cf @@ -0,0 +1,160 @@ +####################################################### +# +# Test parsestringarray(), weird indices, change comment parsing +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)","",":"1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","",":",10,1000); + "num" int => "9"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "# A duplicate follows"), + strcmp("$(test.ary[6][1])", " this line is not always a comment"), + + strcmp("$(test.ary[7][0])", "this"), + strcmp("$(test.ary[7][1])", "also"), + + strcmp("$(test.ary[8][0])", "last"), + strcmp("$(test.ary[8][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = '# A duplicate follows', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = ' this line is not always a comment', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'this', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'also', saw '$(test.ary[7][1])'"; + + "expected test.ary[8][0] = 'last', saw '$(test.ary[8][0])'"; + "expected test.ary[8][1] = 'one', saw '$(test.ary[8][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_duplicates_trailing_newlines.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_duplicates_trailing_newlines.cf new file mode 100644 index 0000000000..5e98697316 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_duplicates_trailing_newlines.cf @@ -0,0 +1,157 @@ +####################################################### +# +# Test parsestringarray(), weird indices, duplicate and trailing newlines +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one + + +"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)", + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", ""), + strcmp("$(test.ary[blank][3])", ""), + strcmp("$(test.ary[blank][4])", "in here"), + strcmp("$(test.ary[blank][5])", ""), + strcmp("$(test.ary[blank][6])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[# A duplicate follows][0])", "# A duplicate follows"), + strcmp("$(test.ary[# A duplicate follows][1])", "this line is not always a comment"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = '', saw '$(test.ary[blank][2])'"; + "expected test.ary[blank][3] = '', saw '$(test.ary[blank][3])'"; + "expected test.ary[blank][4] = 'in here', saw '$(test.ary[blank][4])'"; + "expected test.ary[blank][5] = '', saw '$(test.ary[blank][5])'"; + "expected test.ary[blank][6] = '', saw '$(test.ary[blank][6])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; + } diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_duplicates_trailing_newlines2.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_duplicates_trailing_newlines2.cf new file mode 100644 index 0000000000..431879e648 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_duplicates_trailing_newlines2.cf @@ -0,0 +1,163 @@ +####################################################### +# +# Test parsestringarray(), weird indices, duplicate and trailing newlines +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one + + +"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "9"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "# A duplicate follows"), + strcmp("$(test.ary[6][1])", " this line is not always a comment"), + + strcmp("$(test.ary[7][0])", "this"), + strcmp("$(test.ary[7][1])", "also"), + + strcmp("$(test.ary[8][0])", "last"), + strcmp("$(test.ary[8][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = '# A duplicate follows', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = ' this line is not always a comment', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'this', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'also', saw '$(test.ary[7][1])'"; + + "expected test.ary[8][0] = 'last', saw '$(test.ary[8][0])'"; + "expected test.ary[8][1] = 'one', saw '$(test.ary[8][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments.cf new file mode 100644 index 0000000000..32343af143 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Test parsestringarray(), weird indices, real comments +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","^#.*",":",10,1000); + "num" int => "7"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][0])", "fields"), + strcmp("$(test.ary[blank][0])", ""), + strcmp("$(test.ary[blank][0])", ""), + strcmp("$(test.ary[blank][0])", "in here"), + strcmp("$(test.ary[blank][0])", ""), + strcmp("$(test.ary[blank][0])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = 'fields', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = '', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = '', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = 'in here', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = '', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = '', saw '$(test.ary[blank][0])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments2.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments2.cf new file mode 100644 index 0000000000..99694c8dd6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments2.cf @@ -0,0 +1,153 @@ +####################################################### +# +# Test parsestringarray(), weird indices, real comments +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","^#.*",":",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "this"), + strcmp("$(test.ary[6][1])", "also"), + + strcmp("$(test.ary[7][0])", "last"), + strcmp("$(test.ary[7][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = 'this', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = 'also', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'last', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'one', saw '$(test.ary[7][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments3.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments3.cf new file mode 100644 index 0000000000..ea627e5e2d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments3.cf @@ -0,0 +1,154 @@ +####################################################### +# +# Test parsestringarray(), weird indices, real comments +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)","^#.*",":"1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","^#.*",":",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "this"), + strcmp("$(test.ary[6][1])", "also"), + + strcmp("$(test.ary[7][0])", "last"), + strcmp("$(test.ary[7][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = 'this', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = 'also', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'last', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'one', saw '$(test.ary[7][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields.cf new file mode 100644 index 0000000000..3f57afa06c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields.cf @@ -0,0 +1,143 @@ +####################################################### +# +# Test parsestringarray(), weird indices, real comments, no empty fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","^#.*",":+",10,1000); + "num" int => "7"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", "in here"), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = 'in here', saw '$(test.ary[blank][2])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields3.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields3.cf new file mode 100644 index 0000000000..81524a49c1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields3.cf @@ -0,0 +1,147 @@ +####################################################### +# +# Test parsestringarray(), weird indices, real comments, no empty fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","^#.*",":+",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", "in here"), + strcmp("$(test.ary[3][3])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "this"), + strcmp("$(test.ary[6][1])", "also"), + + strcmp("$(test.ary[7][0])", "last"), + strcmp("$(test.ary[7][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = 'in here', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = 'this', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = 'also', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'last', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'one', saw '$(test.ary[7][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields4.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields4.cf new file mode 100644 index 0000000000..fd27791836 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_real_comments_noemptyfields4.cf @@ -0,0 +1,148 @@ +####################################################### +# +# Test parsestringarray(), weird indices, real comments, no empty fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)","^#.*",":+"1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","^#.*",":+",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", "in here"), + strcmp("$(test.ary[3][3])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "this"), + strcmp("$(test.ary[6][1])", "also"), + + strcmp("$(test.ary[7][0])", "last"), + strcmp("$(test.ary[7][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = 'in here', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = 'this', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = 'also', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'last', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'one', saw '$(test.ary[7][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_with_duplicate.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_with_duplicate.cf new file mode 100644 index 0000000000..d227b74410 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarray_weird_indices_with_duplicate.cf @@ -0,0 +1,154 @@ +####################################################### +# +# Test parsestringarray(), add some weird indices (including a duplicate) +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", ""), + strcmp("$(test.ary[blank][3])", ""), + strcmp("$(test.ary[blank][4])", "in here"), + strcmp("$(test.ary[blank][5])", ""), + strcmp("$(test.ary[blank][6])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[# A duplicate follows][0])", "# A duplicate follows"), + strcmp("$(test.ary[# A duplicate follows][1])", "this line is not always a comment"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = '', saw '$(test.ary[blank][2])'"; + "expected test.ary[blank][3] = '', saw '$(test.ary[blank][3])'"; + "expected test.ary[blank][4] = 'in here', saw '$(test.ary[blank][4])'"; + "expected test.ary[blank][5] = '', saw '$(test.ary[blank][5])'"; + "expected test.ary[blank][6] = '', saw '$(test.ary[blank][6])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_change_separator_to_s.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_change_separator_to_s.cf new file mode 100644 index 0000000000..01d2a8e4bb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_change_separator_to_s.cf @@ -0,0 +1,136 @@ +####################################################### +# +# Test parsestringarrayidx(), change separator to 's' +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)","^#.*",1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","^#.*","s+",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0:1:2"), + + strcmp("$(test.ary[1][0])", "1:2:3"), + + strcmp("$(test.ary[2][0])", "here i"), + strcmp("$(test.ary[2][1])", ":a line: with "), + strcmp("$(test.ary[2][2])", "pace"), + strcmp("$(test.ary[2][3])", " : in it"), + + strcmp("$(test.ary[3][0])", "blank:field"), + strcmp("$(test.ary[3][1])", ":::in here::"), + + strcmp("$(test.ary[4][0])", ":leading blank field"), + + strcmp("$(test.ary[5][0])", "thi"), + strcmp("$(test.ary[5][1])", ":i"), + strcmp("$(test.ary[5][2])", ":a:te"), + strcmp("$(test.ary[5][3])", "t"), + + strcmp("$(test.ary[6][0])", "thi"), + strcmp("$(test.ary[6][1])", ":al"), + strcmp("$(test.ary[6][2])", "o"), + + strcmp("$(test.ary[7][0])", "la"), + strcmp("$(test.ary[7][1])", "t:one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0:1:2', saw '$(test.ary[0][0])'"; + + "expected test.ary[1][0] = '1:2:3', saw '$(test.ary[1][0])'"; + + "expected test.ary[2][0] = 'here i', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = ':a line: with ', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = 'pace', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' : in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank:field', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = ':::in here::', saw '$(test.ary[3][1])'"; + + "expected test.ary[4][0] = ':leading blank field', saw '$(test.ary[4][0])'"; + + "expected test.ary[5][0] = 'thi', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = ':i', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = ':a:te', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 't', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = 'thi', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = ':al', saw '$(test.ary[6][1])'"; + "expected test.ary[6][2] = 'o', saw '$(test.ary[6][2])'"; + + "expected test.ary[7][0] = 'la', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 't:one', saw '$(test.ary[7][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_duplicate_key.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_duplicate_key.cf new file mode 100644 index 0000000000..49e0864f70 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_duplicate_key.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Test parsestringarrayidx(), introduce a duplicate key +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +this:too +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "4"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "this"), + strcmp("$(test.ary[1][1])", "is"), + strcmp("$(test.ary[1][2])", "a"), + strcmp("$(test.ary[1][3])", "test"), + + strcmp("$(test.ary[2][0])", "this"), + strcmp("$(test.ary[2][1])", "too"), + + strcmp("$(test.ary[3][0])", "1"), + strcmp("$(test.ary[3][1])", "2"), + strcmp("$(test.ary[3][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[1][4]"), + isvariable("test.ary[2][2]"), + isvariable("test.ary[3][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = 'this', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = 'is', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = 'a', saw '$(test.ary[1][2])'"; + "expected test.ary[1][3] = 'test', saw '$(test.ary[1][3])'"; + + "expected test.ary[2][0] = 'this', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'too', saw '$(test.ary[2][1])'"; + + "expected test.ary[3][0] = '1', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = '2', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '3', saw '$(test.ary[3][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_nonparsed_comment.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_nonparsed_comment.cf new file mode 100644 index 0000000000..cd931ed7bb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_nonparsed_comment.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Test parsestringarrayidx(), introduce a non-parsed comment +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +# Not parsed as a comment +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "4"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "# Not parsed as a comment"), + + strcmp("$(test.ary[2][0])", "this"), + strcmp("$(test.ary[2][1])", "is"), + strcmp("$(test.ary[2][2])", "a"), + strcmp("$(test.ary[2][3])", "test"), + + strcmp("$(test.ary[3][0])", "1"), + strcmp("$(test.ary[3][1])", "2"), + strcmp("$(test.ary[3][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[1][1]"), + isvariable("test.ary[2][4]"), + isvariable("test.ary[3][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'is', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = 'a', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = 'test', saw '$(test.ary[this][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_weird_indices_duplicate.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_weird_indices_duplicate.cf new file mode 100644 index 0000000000..8a31d49a46 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_weird_indices_duplicate.cf @@ -0,0 +1,160 @@ +####################################################### +# +# Test parsestringarrayidx(), add some weird indices (including a duplicate) +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "9"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "here is"), + strcmp("$(test.ary[2][1])", "a line"), + strcmp("$(test.ary[2][2])", " with spaces "), + strcmp("$(test.ary[2][3])", " in it"), + + strcmp("$(test.ary[3][0])", "blank"), + strcmp("$(test.ary[3][1])", "fields"), + strcmp("$(test.ary[3][2])", ""), + strcmp("$(test.ary[3][3])", ""), + strcmp("$(test.ary[3][4])", "in here"), + strcmp("$(test.ary[3][5])", ""), + strcmp("$(test.ary[3][6])", ""), + + strcmp("$(test.ary[4][0])", ""), + strcmp("$(test.ary[4][1])", "leading blank field"), + + strcmp("$(test.ary[5][0])", "this"), + strcmp("$(test.ary[5][1])", "is"), + strcmp("$(test.ary[5][2])", "a"), + strcmp("$(test.ary[5][3])", "test"), + + strcmp("$(test.ary[6][0])", "# A duplicate follows"), + strcmp("$(test.ary[6][1])", " this line is not always a comment"), + + strcmp("$(test.ary[7][0])", "this"), + strcmp("$(test.ary[7][1])", "also"), + + strcmp("$(test.ary[8][0])", "last"), + strcmp("$(test.ary[8][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'here is', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = 'a line', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = ' with spaces ', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = 'fields', saw '$(test.ary[3][1])'"; + "expected test.ary[3][2] = '', saw '$(test.ary[3][2])'"; + "expected test.ary[3][3] = '', saw '$(test.ary[3][3])'"; + "expected test.ary[3][4] = 'in here', saw '$(test.ary[3][4])'"; + "expected test.ary[3][5] = '', saw '$(test.ary[3][5])'"; + "expected test.ary[3][6] = '', saw '$(test.ary[3][6])'"; + + "expected test.ary[4][0] = '', saw '$(test.ary[4][0])'"; + "expected test.ary[4][1] = 'leading blank field', saw '$(test.ary[4][1])'"; + + "expected test.ary[5][0] = 'this', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = 'is', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = 'a', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 'test', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = '# A duplicate follows', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = ' this line is not always a comment', saw '$(test.ary[6][1])'"; + + "expected test.ary[7][0] = 'this', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 'also', saw '$(test.ary[7][1])'"; + + "expected test.ary[8][0] = 'last', saw '$(test.ary[8][0])'"; + "expected test.ary[8][1] = 'one', saw '$(test.ary[8][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_weird_indices_real_comments_noemptyfields.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_weird_indices_real_comments_noemptyfields.cf new file mode 100644 index 0000000000..d47c0e52a6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayidx_weird_indices_real_comments_noemptyfields.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Test parsestringarrayidx(), add some weird indices, real comments, no empty fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)","^#.*",":+"14); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","^#.*",":+",10,14); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "bad" or => { + isvariable("test.ary[2][1]"), + isvariable("test.ary[3][0]"), + }; + + "ok" and => { + "!bad", + + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[2][0])", "he"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "saw 'bad'-class things"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[2][0] = 'he', saw '$(test.ary[2][0])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarrayinx_simple.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayinx_simple.cf new file mode 100644 index 0000000000..20178a2172 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayinx_simple.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Test parsestringarrayidx(), simple +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "this"), + strcmp("$(test.ary[1][1])", "is"), + strcmp("$(test.ary[1][2])", "a"), + strcmp("$(test.ary[1][3])", "test"), + + strcmp("$(test.ary[2][0])", "1"), + strcmp("$(test.ary[2][1])", "2"), + strcmp("$(test.ary[2][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[1][4]"), + isvariable("test.ary[2][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = 'this', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = 'is', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = 'a', saw '$(test.ary[1][2])'"; + "expected test.ary[1][3] = 'test', saw '$(test.ary[1][3])'"; + + "expected test.ary[2][0] = '1', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = '2', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = '3', saw '$(test.ary[2][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/parsestringarrayinx_singleton_nootherfields.cf b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayinx_singleton_nootherfields.cf new file mode 100644 index 0000000000..a37daa5127 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/parsestringarrayinx_singleton_nootherfields.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Test parsestringarrayidx(), introduce a singleton with no other fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +singleton +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" str => readfile("$(G.testfile)",1000); + "cnt" int => parsestringarrayidx("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "singleton"), + + strcmp("$(test.ary[2][0])", "1"), + strcmp("$(test.ary[2][1])", "2"), + strcmp("$(test.ary[2][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[1][1]"), + isvariable("test.ary[2][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = 'singleton', saw '$(test.ary[1][0])'"; + + "expected test.ary[2][0] = '1', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = '2', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = '3', saw '$(test.ary[2][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readintarray_duplicate_key.cf b/tests/acceptance/01_vars/02_functions/staging/readintarray_duplicate_key.cf new file mode 100644 index 0000000000..56fd6e5eb0 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readintarray_duplicate_key.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Test readintarray(), introduce 777 duplicate key +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readintarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999:888:777:666 +999:000 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readintarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; # 4 lines, but 3 unique keys +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + # The second value should overwrite the first + strcmp("$(test.ary[999][0])", "999"), + strcmp("$(test.ary[999][1])", "0"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999][2]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999][0] = '999', saw '$(test.ary[999][0])'"; + "expected test.ary[999][1] = '0', saw '$(test.ary[999][1])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers.cf b/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers.cf new file mode 100644 index 0000000000..5f51605978 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Test parseintarray(), introduce 777 duplicate key +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readintarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999:888:777:666 +999:000 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parseintarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; # 4 lines, but 3 unique keys +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + # The second value should overwrite the first + strcmp("$(test.ary[999][0])", "999"), + strcmp("$(test.ary[999][1])", "0"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999][2]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999][0] = '999', saw '$(test.ary[999][0])'"; + "expected test.ary[999][1] = '0', saw '$(test.ary[999][1])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers2.cf b/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers2.cf new file mode 100644 index 0000000000..b3b7365aa8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers2.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Test readintarray(), introduce non-integers (issue 313 and 368) +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readintarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999::bogus:666 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readintarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[999][0])", "999"), + strcmp("$(test.ary[999][1])", "0"), + strcmp("$(test.ary[999][2])", "0"), + strcmp("$(test.ary[999][3])", "666"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999][0] = '999', saw '$(test.ary[999][0])'"; + "expected test.ary[999][1] = '0', saw '$(test.ary[999][1])'"; + "expected test.ary[999][2] = '0', saw '$(test.ary[999][2])'"; + "expected test.ary[999][3] = '999', saw '$(test.ary[999][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers3.cf b/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers3.cf new file mode 100644 index 0000000000..f088d78d62 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readintarray_non_integers3.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test readintarray(), introduce non-integers (issue 313 and 368) +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readintarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999::bogus:666 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parseintarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[999][0])", "999"), + strcmp("$(test.ary[999][1])", "0"), + strcmp("$(test.ary[999][2])", "0"), + strcmp("$(test.ary[999][3])", "666"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999][0] = '999', saw '$(test.ary[999][0])'"; + "expected test.ary[999][1] = '0', saw '$(test.ary[999][1])'"; + "expected test.ary[999][2] = '0', saw '$(test.ary[999][2])'"; + "expected test.ary[999][3] = '999', saw '$(test.ary[999][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readintlist.cf b/tests/acceptance/01_vars/02_functions/staging/readintlist.cf new file mode 100644 index 0000000000..cd83f00be0 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readintlist.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readintlist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123, ,456,789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readintlist2.cf b/tests/acceptance/01_vars/02_functions/staging/readintlist2.cf new file mode 100644 index 0000000000..52905f472a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readintlist2.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readintlist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123,broken,456,789"; # non-numeric fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" ilist => readintlist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" ilist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readrealarray_duplicate_key.cf b/tests/acceptance/01_vars/02_functions/staging/readrealarray_duplicate_key.cf new file mode 100644 index 0000000000..825f6ffcce --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readrealarray_duplicate_key.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Test readrealarray(), introduce 777 duplicate key +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readrealarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999.9:888:777:666.6 +999.9:000 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readrealarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; # 4 lines, but 3 unique keys +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + # The second value should overwrite the first + strcmp("$(test.ary[999.9][0])", "999.9"), + strcmp("$(test.ary[999.9][1])", "0"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999.9][2]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999.9][0] = '999.9', saw '$(test.ary[999.9][0])'"; + "expected test.ary[999.9][1] = '0', saw '$(test.ary[999.9][1])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readrealarray_non_integers.cf b/tests/acceptance/01_vars/02_functions/staging/readrealarray_non_integers.cf new file mode 100644 index 0000000000..e3e33be47a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readrealarray_non_integers.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Test readrealarray(), introduce non-integers (issue 313 and 368) +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readrealarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999.9::bogus:666.6 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readrealarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[999.9][0])", "999.9"), + strcmp("$(test.ary[999.9][1])", "0"), + strcmp("$(test.ary[999.9][2])", "0"), + strcmp("$(test.ary[999.9][3])", "666.6"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999.9][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999.9][0] = '999.9', saw '$(test.ary[999.9][0])'"; + "expected test.ary[999.9][1] = '0', saw '$(test.ary[999.9][1])'"; + "expected test.ary[999.9][2] = '0', saw '$(test.ary[999.9][2])'"; + "expected test.ary[999.9][3] = '666.6', saw '$(test.ary[999.9][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readrealarray_non_integers2.cf b/tests/acceptance/01_vars/02_functions/staging/readrealarray_non_integers2.cf new file mode 100644 index 0000000000..67b6bddd62 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readrealarray_non_integers2.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test readrealarray(), introduce non-integers (issue 313 and 368) +# +# The 5xx tests look at the same thing as their corresponding 4xx tests +# 500-519 are readrealarray, 520-539 test the same things for parseintarray +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +999.9::bogus:666.6 +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "teststr" string => readfile("$(G.testfile)",1000); + "cnt" int => parserealarray("ary", "$(teststr)","NoComment",":",10,1000); + "num" int => "3"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[999.9][0])", "999.9"), + strcmp("$(test.ary[999.9][1])", "0"), + strcmp("$(test.ary[999.9][2])", "0"), + strcmp("$(test.ary[999.9][3])", "666.6"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[999.9][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[999.9][0] = '999.9', saw '$(test.ary[999.9][0])'"; + "expected test.ary[999.9][1] = '0', saw '$(test.ary[999.9][1])'"; + "expected test.ary[999.9][2] = '0', saw '$(test.ary[999.9][2])'"; + "expected test.ary[999.9][3] = '666.6', saw '$(test.ary[999.9][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readreallist.cf b/tests/acceptance/01_vars/02_functions/staging/readreallist.cf new file mode 100644 index 0000000000..1275320aff --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readreallist.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readreallist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123, ,456,789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readreallist2.cf b/tests/acceptance/01_vars/02_functions/staging/readreallist2.cf new file mode 100644 index 0000000000..7fa2986a4b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readreallist2.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readreallist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123,broken,456,789"; # non-numeric fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" rlist => readreallist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" rlist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarray_change_separator_to_s.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarray_change_separator_to_s.cf new file mode 100644 index 0000000000..1428d57cf0 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarray_change_separator_to_s.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Test readstringarray(), change separator to 's' +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","^#.*","s+",10,1000); + "num" int => "7"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0:1:2][0])", "0:1:2"), + + strcmp("$(test.ary[1:2:3][0])", "1:2:3"), + + strcmp("$(test.ary[here i][0])", "here i"), + strcmp("$(test.ary[here i][1])", ":a line: with space"), + strcmp("$(test.ary[here i][2])", " : in it"), + + strcmp("$(test.ary[blank field][0])", "blank field"), + strcmp("$(test.ary[blank field][2])", "::: in here::"), + + strcmp("$(test.ary[:leading blank field][0])", "leading blank field"), + + strcmp("$(test.ary[thi][0])", "thi"), + strcmp("$(test.ary[thi][1])", ":also"), + strcmp("$(test.ary[thi][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[thi][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[la][0])", "la"), + strcmp("$(test.ary[la][1])", "t:one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0:1:2][0] = '0:1:2', saw '$(test.ary[0:1:2][0])'"; + + "expected test.ary[1:2:3][0] = '1:2:3', saw '$(test.ary[1:2:3][0])'"; + + "expected test.ary[here i][0] = 'here i', saw '$(test.ary[here i][0])'"; + "expected test.ary[here i][1] = ':a line: with space', saw '$(test.ary[here i][1])'"; + "expected test.ary[here i][2] = ' : in it', saw '$(test.ary[here i][2])'"; + + "expected test.ary[blank field][0] = 'blank field', saw '$(test.ary[blank field][0])'"; + "expected test.ary[blank field][2] = '::: in here::', saw '$(test.ary[blank field][2])'"; + + "expected test.ary[:leading blank field][0] = 'leading blank field', saw '$(test.ary[:leading blank field][0])'"; + + "expected test.ary[thi][0] = 'thi', saw '$(test.ary[thi][0])'"; + "expected test.ary[thi][1]) = ':also', saw '$(test.ary[thi][1]))'"; + "expected test.ary[thi][2]) = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[thi][2]))'"; + "expected test.ary[thi][3]) = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[thi][3]))'"; + + "expected test.ary[la][0]) = 'la', saw '$(test.ary[la][0]))'"; + "expected test.ary[la][1]) = 't:one', saw '$(test.ary[la][1]))'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarray_introduce_duplicate.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarray_introduce_duplicate.cf new file mode 100644 index 0000000000..a03fffcb14 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarray_introduce_duplicate.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Test readstringarray(), introduce a duplicate key +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +this:is:a:test +this:too +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "3"; # 4 lines, but 3 unique keys +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + # The second value should overwrite the first + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "too"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[this][2]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'too', saw '$(test.ary[this][1])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarray_introduce_nonparsed_comment.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarray_introduce_nonparsed_comment.cf new file mode 100644 index 0000000000..1f82c29551 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarray_introduce_nonparsed_comment.cf @@ -0,0 +1,126 @@ +####################################################### +# +# Test readstringarray(), introduce a non-parsed comment +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "g", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +# Not parsed as a comment +this:is:a:test +1:2:3"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "4"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "good" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[# Not parsed as a comment][0])", "# Not parsed as a comment"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "is"), + strcmp("$(test.ary[this][2])", "a"), + strcmp("$(test.ary[this][3])", "test"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + }; + + # Make sure there are no trailing elements + "bad" or => { + isvariable("test.ary[0][3]"), + isvariable("test.ary[# Not parsed as a comment][1]"), + isvariable("test.ary[this][4]"), + isvariable("test.ary[1][3]"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[# Not parsed as a comment][0] = '# Not parsed as a comment', saw 'expected $(test.ary[# Not parsed as a comment][0])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'is', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = 'a', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = 'test', saw '$(test.ary[this][3])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices.cf new file mode 100644 index 0000000000..44ca873498 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices.cf @@ -0,0 +1,153 @@ +####################################################### +# +# Test readstringarray(), add some weird indices (including a duplicate) +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", ""), + strcmp("$(test.ary[blank][3])", ""), + strcmp("$(test.ary[blank][4])", "in here"), + strcmp("$(test.ary[blank][5])", ""), + strcmp("$(test.ary[blank][6])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[# A duplicate follows][0])", "# A duplicate follows"), + strcmp("$(test.ary[# A duplicate follows][1])", "this line is not always a comment"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = '', saw '$(test.ary[blank][2])'"; + "expected test.ary[blank][3] = '', saw '$(test.ary[blank][3])'"; + "expected test.ary[blank][4] = 'in here', saw '$(test.ary[blank][4])'"; + "expected test.ary[blank][5] = '', saw '$(test.ary[blank][5])'"; + "expected test.ary[blank][6] = '', saw '$(test.ary[blank][6])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_change_comment_parsing.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_change_comment_parsing.cf new file mode 100644 index 0000000000..bc84e52a96 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_change_comment_parsing.cf @@ -0,0 +1,153 @@ +####################################################### +# +# Test readstringarray(), weird indices, change comment parsing +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","",":",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", ""), + strcmp("$(test.ary[blank][3])", ""), + strcmp("$(test.ary[blank][4])", "in here"), + strcmp("$(test.ary[blank][5])", ""), + strcmp("$(test.ary[blank][6])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[# A duplicate follows][0])", "# A duplicate follows"), + strcmp("$(test.ary[# A duplicate follows][1])", "this line is not always a comment"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = '', saw '$(test.ary[blank][2])'"; + "expected test.ary[blank][3] = '', saw '$(test.ary[blank][3])'"; + "expected test.ary[blank][4] = 'in here', saw '$(test.ary[blank][4])'"; + "expected test.ary[blank][5] = '', saw '$(test.ary[blank][5])'"; + "expected test.ary[blank][6] = '', saw '$(test.ary[blank][6])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_real_comments.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_real_comments.cf new file mode 100644 index 0000000000..c63af1e92d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_real_comments.cf @@ -0,0 +1,150 @@ +####################################################### +# +# Test readstringarray(), weird indices, real comments +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","^#.*",":",10,1000); + "num" int => "7"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][0])", "fields"), + strcmp("$(test.ary[blank][0])", ""), + strcmp("$(test.ary[blank][0])", ""), + strcmp("$(test.ary[blank][0])", "in here"), + strcmp("$(test.ary[blank][0])", ""), + strcmp("$(test.ary[blank][0])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = 'fields', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = '', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = '', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = 'in here', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = '', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][0] = '', saw '$(test.ary[blank][0])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_real_comments_noemptyfields.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_real_comments_noemptyfields.cf new file mode 100644 index 0000000000..e913d39964 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_real_comments_noemptyfields.cf @@ -0,0 +1,142 @@ +####################################################### +# +# Test readstringarray(), weird indices, real comments, no empty fields +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","^#.*",":+",10,1000); + "num" int => "7"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", "in here"), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = 'in here', saw '$(test.ary[blank][2])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_trailing_newlines.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_trailing_newlines.cf new file mode 100644 index 0000000000..fd471656eb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarray_weird_indices_trailing_newlines.cf @@ -0,0 +1,156 @@ +####################################################### +# +# Test readstringarray(), weird indices, duplicate and trailing newlines +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one + + +"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarray("ary", "$(G.testfile)","NoComment",":",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0"), + strcmp("$(test.ary[0][1])", "1"), + strcmp("$(test.ary[0][2])", "2"), + + strcmp("$(test.ary[1][0])", "1"), + strcmp("$(test.ary[1][1])", "2"), + strcmp("$(test.ary[1][2])", "3"), + + strcmp("$(test.ary[here is][0])", "here is"), + strcmp("$(test.ary[here is][1])", "a line"), + strcmp("$(test.ary[here is][2])", " with spaces "), + strcmp("$(test.ary[here is][3])", " in it"), + + strcmp("$(test.ary[blank][0])", "blank"), + strcmp("$(test.ary[blank][1])", "fields"), + strcmp("$(test.ary[blank][2])", ""), + strcmp("$(test.ary[blank][3])", ""), + strcmp("$(test.ary[blank][4])", "in here"), + strcmp("$(test.ary[blank][5])", ""), + strcmp("$(test.ary[blank][6])", ""), + + strcmp("$(test.ary[][0])", ""), + strcmp("$(test.ary[][1])", "leading blank field"), + + strcmp("$(test.ary[# A duplicate follows][0])", "# A duplicate follows"), + strcmp("$(test.ary[# A duplicate follows][1])", "this line is not always a comment"), + + strcmp("$(test.ary[this][0])", "this"), + strcmp("$(test.ary[this][1])", "also"), + strcmp("$(test.ary[this][2])", "$(const.dollar)test.ary[this][2]"), + strcmp("$(test.ary[this][3])", "$(const.dollar)test.ary[this][3]"), + + strcmp("$(test.ary[last][0])", "last"), + strcmp("$(test.ary[last][1])", "one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0', saw '$(test.ary[0][0])'"; + "expected test.ary[0][1] = '1', saw '$(test.ary[0][1])'"; + "expected test.ary[0][2] = '2', saw '$(test.ary[0][2])'"; + + "expected test.ary[1][0] = '1', saw '$(test.ary[1][0])'"; + "expected test.ary[1][1] = '2', saw '$(test.ary[1][1])'"; + "expected test.ary[1][2] = '3', saw '$(test.ary[1][2])'"; + + "expected test.ary[here is][0] = 'here is', saw '$(test.ary[here is][0])'"; + "expected test.ary[here is][1] = 'a line', saw '$(test.ary[here is][1])'"; + "expected test.ary[here is][2] = ' with spaces ', saw '$(test.ary[here is][2])'"; + "expected test.ary[here is][3] = ' in it', saw '$(test.ary[here is][3])'"; + + "expected test.ary[blank][0] = 'blank', saw '$(test.ary[blank][0])'"; + "expected test.ary[blank][1] = 'fields', saw '$(test.ary[blank][1])'"; + "expected test.ary[blank][2] = '', saw '$(test.ary[blank][2])'"; + "expected test.ary[blank][3] = '', saw '$(test.ary[blank][3])'"; + "expected test.ary[blank][4] = 'in here', saw '$(test.ary[blank][4])'"; + "expected test.ary[blank][5] = '', saw '$(test.ary[blank][5])'"; + "expected test.ary[blank][6] = '', saw '$(test.ary[blank][6])'"; + + "expected test.ary[][0] = '', saw '$(test.ary[][0])'"; + "expected test.ary[][1] = 'leading blank field', saw '$(test.ary[][1])'"; + + "expected test.ary[# A duplicate follows][0] = '# A duplicate follows', saw '$(test.ary[# A duplicate follows][0])'"; + "expected test.ary[# A duplicate follows][1] = 'this line is not always a comment', saw '$(test.ary[# A duplicate follows][1])'"; + + "expected test.ary[this][0] = 'this', saw '$(test.ary[this][0])'"; + "expected test.ary[this][1] = 'also', saw '$(test.ary[this][1])'"; + "expected test.ary[this][2] = '$(const.dollar)test.ary[this][2]', saw '$(test.ary[this][2])'"; + "expected test.ary[this][3] = '$(const.dollar)test.ary[this][3]', saw '$(test.ary[this][3])'"; + + "expected test.ary[last][0] = 'last', saw '$(test.ary[last][0])'"; + "expected test.ary[last][1] = 'one', saw '$(test.ary[last][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringarrayidx_change_separator_to_s.cf b/tests/acceptance/01_vars/02_functions/staging/readstringarrayidx_change_separator_to_s.cf new file mode 100644 index 0000000000..a8ba98be31 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringarrayidx_change_separator_to_s.cf @@ -0,0 +1,135 @@ +####################################################### +# +# Test readstringarrayidx(), change separator to 's' +# +# The 4xx tests are all related, and 400-419 are the readstringarray tests, +# 420-439 the same for readstringarrayidx, 440-459 parsestringarray, and +# 460-479 parsestringarrayidx +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + + "0:1:2 +1:2:3 +here is:a line: with spaces : in it +blank:fields:::in here:: +:leading blank field +this:is:a:test +# A duplicate follows: this line is not always a comment +this:also +last:one"; + +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "cnt" int => readstringarrayidx("ary", "$(G.testfile)","^#.*","s+",10,1000); + "num" int => "8"; +} + +####################################################### + +bundle agent check +{ + vars: + "idx" slist => getindices("test.ary"); + + classes: + "ok" and => { + strcmp("$(test.num)", "$(test.cnt)"), + + strcmp("$(test.ary[0][0])", "0:1:2"), + + strcmp("$(test.ary[1][0])", "1:2:3"), + + strcmp("$(test.ary[2][0])", "here i"), + strcmp("$(test.ary[2][1])", ":a line: with "), + strcmp("$(test.ary[2][2])", "pace"), + strcmp("$(test.ary[2][3])", " : in it"), + + strcmp("$(test.ary[3][0])", "blank:field"), + strcmp("$(test.ary[3][1])", ":::in here::"), + + strcmp("$(test.ary[4][0])", ":leading blank field"), + + strcmp("$(test.ary[5][0])", "thi"), + strcmp("$(test.ary[5][1])", ":i"), + strcmp("$(test.ary[5][2])", ":a:te"), + strcmp("$(test.ary[5][3])", "t"), + + strcmp("$(test.ary[6][0])", "thi"), + strcmp("$(test.ary[6][1])", ":al"), + strcmp("$(test.ary[6][2])", "o"), + + strcmp("$(test.ary[7][0])", "la"), + strcmp("$(test.ary[7][1])", "t:one"), + }; + + reports: + DEBUG:: + "expected $(test.num) entries, saw $(test.cnt)"; + + "saw array indices '$(idx)'"; + + "expected test.ary[0][0] = '0:1:2', saw '$(test.ary[0][0])'"; + + "expected test.ary[1][0] = '1:2:3', saw '$(test.ary[1][0])'"; + + "expected test.ary[2][0] = 'here i', saw '$(test.ary[2][0])'"; + "expected test.ary[2][1] = ':a line: with ', saw '$(test.ary[2][1])'"; + "expected test.ary[2][2] = 'pace', saw '$(test.ary[2][2])'"; + "expected test.ary[2][3] = ' : in it', saw '$(test.ary[2][3])'"; + + "expected test.ary[3][0] = 'blank:field', saw '$(test.ary[3][0])'"; + "expected test.ary[3][1] = ':::in here::', saw '$(test.ary[3][1])'"; + + "expected test.ary[4][0] = ':leading blank field', saw '$(test.ary[4][0])'"; + + "expected test.ary[5][0] = 'thi', saw '$(test.ary[5][0])'"; + "expected test.ary[5][1] = ':i', saw '$(test.ary[5][1])'"; + "expected test.ary[5][2] = ':a:te', saw '$(test.ary[5][2])'"; + "expected test.ary[5][3] = 't', saw '$(test.ary[5][3])'"; + + "expected test.ary[6][0] = 'thi', saw '$(test.ary[6][0])'"; + "expected test.ary[6][1] = ':al', saw '$(test.ary[6][1])'"; + "expected test.ary[6][2] = 'o', saw '$(test.ary[6][2])'"; + + "expected test.ary[7][0] = 'la', saw '$(test.ary[7][0])'"; + "expected test.ary[7][1] = 't:one', saw '$(test.ary[7][1])'"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringlist.cf b/tests/acceptance/01_vars/02_functions/staging/readstringlist.cf new file mode 100644 index 0000000000..50d291c2fe --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringlist.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readstringlist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123, ,456,789"; # "empty" fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/staging/readstringlist2.cf b/tests/acceptance/01_vars/02_functions/staging/readstringlist2.cf new file mode 100644 index 0000000000..afc1f17d1f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/staging/readstringlist2.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test readstringlist() issue 364, also 366 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile)" + create => "true", + edit_line => init_fill_in; +} + +bundle edit_line init_fill_in +{ + insert_lines: + "123,broken,456,789"; # non-numeric fields +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "nums" slist => readstringlist("$(G.testfile)","NoComment",",",5,100); + "sum" real => sum("nums"); +} + +####################################################### + +bundle agent check +{ + vars: + "nums" slist => { @{test.nums} }; + + classes: + "ok_list" not => strcmp("won't match", "$(nums)"); + "ok123" expression => strcmp("123", "$(nums)"); + "ok456" expression => strcmp("456", "$(nums)"); + "ok789" expression => strcmp("789", "$(nums)"); + "ok" and => { "ok_list", "ok123", "ok456", "ok789", + islessthan("$(test.sum)", "1368.1"), + isgreaterthan("$(test.sum)", "1367.9") + }; + + reports: + DEBUG:: + "nums: $(nums)"; + "sum: $(test.sum)"; + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/storejson.cf b/tests/acceptance/01_vars/02_functions/storejson.cf new file mode 100644 index 0000000000..70375aee4d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/storejson.cf @@ -0,0 +1,36 @@ +# Test that the storejson() function will convert a data container back to JSON + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "mylist" slist => { "x", "y" }; + "data" data => parsejson('{ + "x": [ + 1, + 2, + 3 + ] +}'); + + "datas" string => storejson(data); + + "data_from_slist" string => storejson(mylist); + "data_from_inline" string => storejson('{ "a": "b" }'); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/storejson.cf.expected.json b/tests/acceptance/01_vars/02_functions/storejson.cf.expected.json new file mode 100644 index 0000000000..f556051660 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/storejson.cf.expected.json @@ -0,0 +1,16 @@ +{ + "data": { + "x": [ + 1, + 2, + 3 + ] + }, + "data_from_inline": "{\n \"a\": \"b\"\n}", + "data_from_slist": "[\n \"x\",\n \"y\"\n]", + "datas": "{\n \"x\": [\n 1,\n 2,\n 3\n ]\n}", + "mylist": [ + "x", + "y" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/storejson_edge_case.cf b/tests/acceptance/01_vars/02_functions/storejson_edge_case.cf new file mode 100644 index 0000000000..ef12c462b0 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/storejson_edge_case.cf @@ -0,0 +1,22 @@ +# Test fix for previous bug that truncates JSON data greater than 4096 bytes + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +########################################################## + +bundle agent test { + vars: + "data" string => storejson('{ "A very long text": "Hello, everyone! This is the LONGEST TEXT EVER! I was inspired by the various other longest texts ever on the internet, and I wanted to make my own. So here it is! This is going to be a WORLD RECORD! This is actually my third attempt at doing this. The first time, I didnt save it. The second time, the Neocities editor crashed. Now Im writing this in Notepad, then copying it into the Neocities editor instead of typing it directly in the Neocities editor to avoid crashing. It sucks that my past two attempts are gone now. Those actually got pretty long. Not the longest, but still pretty long. I hope this one wont get lost somehow. Anyways, lets talk about WAFFLES! I like waffles. Waffles are cool. Waffles is a funny word. Theres a Teen Titans Go episode called Waffles where the word Waffles is said a hundred-something times. Its pretty annoying. Theres also a Teen Titans Go episode about Pig Latin. Dont know what Pig Latin is? Its a language where you take all the consonants before the first vowel, move them to the end, and add -ay to the end. If the word begins with a vowel, you just add -way to the end. For example, Waffles becomes Afflesway. Ive been speaking Pig Latin fluently since the fourth grade, so it surprised me when I saw the episode for the first time. I speak Pig Latin with my sister sometimes. Its pretty fun. I like speaking it in public so that everyone around us gets confused. Thats never actually happened before, but if it ever does, twill be pretty funny. By the way, twill is a word I invented recently, and its a contraction of it will. I really hope it gains popularity in the near future, because twill is WAY more fun than saying itll. Itll is too boring. Nobody likes boring. This is nowhere near being the longest text ever, but eventually it will be! I might still be writing this a decade later, who knows? But right now, its not very long. But Ill just keep writing until it is the longest! Have you ever heard the song Dau Dau by Awesome Scampis? Its an amazing song. Look it up on YouTube! I play that song all the time around my sister! It drives her crazy, and I love it. Another way I like driving my sister crazy is by speaking my own made up language to her. She hates the languages I make! The only language that we both speak besides English is Pig Latin. I think you already knew that. Whatever. I think Im gonna go for now. Bye! Hi, Im back now. Im gonna contribute more to this soon-to-be giant wall of text. I just realised I have a giant stuffed frog on my bed. I forgot his name. Im pretty sure it was something stupid though. I think it was FROG in Morse Code or something. Morse Code is cool. I know a bit of it, but Im not very good at it. Im also not very good at French. I barely know anything in French, and my pronunciation probably sucks. But Im learning it, at least. Im also learning Esperanto. Its this language that was made up by some guy a long time ago to be the universal language. A lot of people speak it. I am such a language nerd. Half of this text is probably gonna be about languages. But hey, as long as its long! Ha, get it? As LONG as its LONG? Im so funny, right? No, Im not. I should probably get some sleep. Goodnight! Hello, Im back again. I basically have only two interests nowadays: languages and furries. What? Oh, sorry, I thought you knew I was a furry. Haha, oops. Anyway, yeah, Im a furry, but since Im a young furry, I cant really do as much as I would like to do in the fandom. When Im older, I would like to have a fursuit, go to furry conventions, all that stuff. But for now I can only dream of that. Sorry you had to deal with me talking about furries, but Im honestly very desperate for this to be the longest text ever. Last night I was watching nothing but fursuit unboxings. I think I need help. This one time, me and my mom were going to go to a furry Christmas party, but we didnt end up going because of the fact that there was alcohol on the premises, and that she didnt wanna have to be a mom dragging her son through a crowd of furries. Both of those reasons were understandable. Okay, hopefully I wont have to talk about furries anymore. I dont care if youre a furry reading this right now, I just dont wanna have to torture everyone else."}'); +} + +bundle agent check { + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/storejson_edge_case.cf.expected.json b/tests/acceptance/01_vars/02_functions/storejson_edge_case.cf.expected.json new file mode 100644 index 0000000000..2a82a0fcaa --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/storejson_edge_case.cf.expected.json @@ -0,0 +1,3 @@ +{ + "data": "{\n \"A very long text\": \"Hello, everyone! This is the LONGEST TEXT EVER! I was inspired by the various other longest texts ever on the internet, and I wanted to make my own. So here it is! This is going to be a WORLD RECORD! This is actually my third attempt at doing this. The first time, I didnt save it. The second time, the Neocities editor crashed. Now Im writing this in Notepad, then copying it into the Neocities editor instead of typing it directly in the Neocities editor to avoid crashing. It sucks that my past two attempts are gone now. Those actually got pretty long. Not the longest, but still pretty long. I hope this one wont get lost somehow. Anyways, lets talk about WAFFLES! I like waffles. Waffles are cool. Waffles is a funny word. Theres a Teen Titans Go episode called Waffles where the word Waffles is said a hundred-something times. Its pretty annoying. Theres also a Teen Titans Go episode about Pig Latin. Dont know what Pig Latin is? Its a language where you take all the consonants before the first vowel, move them to the end, and add -ay to the end. If the word begins with a vowel, you just add -way to the end. For example, Waffles becomes Afflesway. Ive been speaking Pig Latin fluently since the fourth grade, so it surprised me when I saw the episode for the first time. I speak Pig Latin with my sister sometimes. Its pretty fun. I like speaking it in public so that everyone around us gets confused. Thats never actually happened before, but if it ever does, twill be pretty funny. By the way, twill is a word I invented recently, and its a contraction of it will. I really hope it gains popularity in the near future, because twill is WAY more fun than saying itll. Itll is too boring. Nobody likes boring. This is nowhere near being the longest text ever, but eventually it will be! I might still be writing this a decade later, who knows? But right now, its not very long. But Ill just keep writing until it is the longest! Have you ever heard the song Dau Dau by Awesome Scampis? Its an amazing song. Look it up on YouTube! I play that song all the time around my sister! It drives her crazy, and I love it. Another way I like driving my sister crazy is by speaking my own made up language to her. She hates the languages I make! The only language that we both speak besides English is Pig Latin. I think you already knew that. Whatever. I think Im gonna go for now. Bye! Hi, Im back now. Im gonna contribute more to this soon-to-be giant wall of text. I just realised I have a giant stuffed frog on my bed. I forgot his name. Im pretty sure it was something stupid though. I think it was FROG in Morse Code or something. Morse Code is cool. I know a bit of it, but Im not very good at it. Im also not very good at French. I barely know anything in French, and my pronunciation probably sucks. But Im learning it, at least. Im also learning Esperanto. Its this language that was made up by some guy a long time ago to be the universal language. A lot of people speak it. I am such a language nerd. Half of this text is probably gonna be about languages. But hey, as long as its long! Ha, get it? As LONG as its LONG? Im so funny, right? No, Im not. I should probably get some sleep. Goodnight! Hello, Im back again. I basically have only two interests nowadays: languages and furries. What? Oh, sorry, I thought you knew I was a furry. Haha, oops. Anyway, yeah, Im a furry, but since Im a young furry, I cant really do as much as I would like to do in the fandom. When Im older, I would like to have a fursuit, go to furry conventions, all that stuff. But for now I can only dream of that. Sorry you had to deal with me talking about furries, but Im honestly very desperate for this to be the longest text ever. Last night I was watching nothing but fursuit unboxings. I think I need help. This one time, me and my mom were going to go to a furry Christmas party, but we didnt end up going because of the fact that there was alcohol on the premises, and that she didnt wanna have to be a mom dragging her son through a crowd of furries. Both of those reasons were understandable. Okay, hopefully I wont have to talk about furries anymore. I dont care if youre a furry reading this right now, I just dont wanna have to torture everyone else.\"\n}" +} diff --git a/tests/acceptance/01_vars/02_functions/strftime.cf b/tests/acceptance/01_vars/02_functions/strftime.cf new file mode 100644 index 0000000000..0beb211a2e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/strftime.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Test strftime() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + # we don't use locale-sensitive formats! + "results" slist => { "1973-03-03 09:46:40", "124 1 18 200000000", $(dow) }; + + Monday:: + "dow" string => "1"; + Tuesday:: + "dow" string => "2"; + Wednesday:: + "dow" string => "3"; + Thursday:: + "dow" string => "4"; + Friday:: + "dow" string => "5"; + Saturday:: + "dow" string => "6"; + Sunday:: + "dow" string => "0"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "$(init.results)"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10253" }; + + vars: + "vals" slist => { + strftime('gmtime', '%F %T', 100000000), + strftime('localtime', '%j %w %W %s', 200000000), + strftime('localtime', '%w', now()), + }; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/strftime_GMT.cf b/tests/acceptance/01_vars/02_functions/strftime_GMT.cf new file mode 100644 index 0000000000..a218afbcdc --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/strftime_GMT.cf @@ -0,0 +1,68 @@ +####################################################### +# +# Test strftime() against some GMT classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + # These classes don't depend on the locale + # we require two digits to filter out GMT_Hr0 through 9 + # (note that %k has a leading space, so we can't use it below) + "results" slist => classesmatching("^GMT_(Hr|Min|Yr)\d\d+$"); + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert; +} + +bundle edit_line init_insert +{ + insert_lines: + "$(init.results)"; +} + +####################################################### + +bundle agent test +{ + vars: + "vals" slist => { + # note that %k has a leading space, don't use it + strftime('gmtime', 'GMT_Hr%H', now()), + strftime('gmtime', 'GMT_Min%M', now()), + strftime('gmtime', 'GMT_Yr%Y', now()), + }; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.vals)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/string.cf b/tests/acceptance/01_vars/02_functions/string.cf new file mode 100644 index 0000000000..e7df0853b7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/string.cf @@ -0,0 +1,57 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default( "$(this.promise_filename)" ) }; +} + +bundle agent init +{ + classes: + "ExistingClass"; + + vars: + "a_int" int => "1k"; + "a_real" real => "6.066"; + "a_string" string => "Really interesting string"; + "a_data_container_1" data => '{"foo": "bar"}'; + "a_data_container_2" data => '[1, 2, 3]'; + "a_data_container_3" data => '{"list": [1, 2, 3, 4], "obj": {"key": ["b", "a", "r"]}}'; + + "string_of_int" string => string("$(a_int)"); + "string_of_real" string => string("$(a_real)"); + "string_of_string" string => string("$(a_string)"); + "string_of_bool_true" string => string(and("ExistingClass")); + "string_of_bool_false" string => string(and("NonExistingClass")); + "string_of_data_container_1" string => string(a_data_container_1); # Strings not interpretted as references + "string_of_data_container_2" string => string("a_data_container_2"); # Strings not interpretted as references + "string_of_data_container_3" string => string(@(a_data_container_3)); + + # "test_args" string => string(); # This is caught by the parser +} + +bundle agent check +{ + meta: + "description" -> { "CFE-3476" } + string => "Test whether string() properly converts argument to string + or string-serialises JSON *only* when explicitely passed as a '@()' reference"; + + classes: + "ok_int" expression => strcmp("1000", "$(init.string_of_int)"); + "ok_real" expression => strcmp("6.066000", "$(init.string_of_real)"); + "ok_string" expression => strcmp("Really interesting string", "$(init.string_of_string)"); + "ok_bool_true" expression => strcmp("any", "$(init.string_of_bool_true)"); + "ok_bool_false" expression => strcmp("!any", "$(init.string_of_bool_false)"); + "ok_data_container_1" expression => strcmp("a_data_container_1", "$(init.string_of_data_container_1)"); + "ok_data_container_2" expression => strcmp("a_data_container_2", "$(init.string_of_data_container_2)"); + "ok_data_container_3" expression => strcmp('{"list":[1,2,3,4],"obj":{"key":["b","a","r"]}}', + "$(init.string_of_data_container_3)"); + + "ok" expression => and("ok_int", "ok_real", "ok_string", "ok_bool_true", + "ok_bool_false", "ok_data_container_1", "ok_data_container_2", "ok_data_container_3"); + + methods: + "Pass/Fail" + usebundle => dcs_passif( "ok", "$(this.promise_filename)" ), + inherit => "true"; +} diff --git a/tests/acceptance/01_vars/02_functions/string_mustache.cf b/tests/acceptance/01_vars/02_functions/string_mustache.cf new file mode 100644 index 0000000000..0ac07f76a5 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/string_mustache.cf @@ -0,0 +1,37 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "desert" string => "Sahara"; + "state2" data => parsejson('{"x": 1, "y": [{"item": 2}, {"item": 3}, {"item": 4}]}'); +} + +bundle agent test +{ + vars: + "out11" string => string_mustache("desert = {{vars.init.desert}}"); + + "out21" string => string_mustache("desert = {{vars.init.desert}}", "init.state2"); + "out22" string => string_mustache("x = {{x}}", "init.state2"); + "out23" string => string_mustache("{{#y}}{{item}} {{/y}}", "init.state2"); + + "inline" string => string_mustache("{{#y}}{{item}} {{/y}}", '{ "y": [{"item": 2}, {"item": 3}, {"item": 4}]}'); + + "badref" string => string_mustache("desert = {{vars.init.desert}}", nosuch); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/string_mustache.cf.expected.json b/tests/acceptance/01_vars/02_functions/string_mustache.cf.expected.json new file mode 100644 index 0000000000..ef287be655 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/string_mustache.cf.expected.json @@ -0,0 +1,7 @@ +{ + "inline": "2 3 4 ", + "out11": "desert = Sahara", + "out21": "desert = ", + "out22": "x = 1", + "out23": "2 3 4 " +} diff --git a/tests/acceptance/01_vars/02_functions/string_replace.cf b/tests/acceptance/01_vars/02_functions/string_replace.cf new file mode 100644 index 0000000000..c3caf164ca --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/string_replace.cf @@ -0,0 +1,53 @@ + +####################################################### +# +# Test string_replace function +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "test" string => "abcdefghij\t\n"; + "test2" string => "(){}[].*?"; + + # normal tests + "match_once" string => string_replace("abcd", "abc", "ABC"); + "match_twice" string => string_replace("abcdabcd", "abcd", "hello"); + "match_none" string => string_replace("abdc", "abcd", "nope"); + "match_none2" string => string_replace($(test), "nonesuch", "TEST"); + + "overlap" string => string_replace("aaaa", "aaa", "yes"); + + "replace" string => string_replace($(test), "\t", "\r"); + "replace2" string => string_replace($(test), "ghij\t", "tEsT"); + "replace3" string => string_replace($(test), "abcdefghij\t\n", "no"); + + # tests for regex special characters + "special" string => string_replace($(test2), "()", "1"); + "special2" string => string_replace($(test2), "{}", "\1"); + "special3" string => string_replace($(test2), ".*?", "\2"); + + # empty cases + "empty" string => string_replace("", "abc", "ABC"); + "empty2" string => string_replace("", "abc", ""); +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/string_replace.cf.expected.json b/tests/acceptance/01_vars/02_functions/string_replace.cf.expected.json new file mode 100644 index 0000000000..ff9ba7584a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/string_replace.cf.expected.json @@ -0,0 +1,17 @@ +{ + "empty": "", + "empty2": "", + "match_none": "abdc", + "match_none2": "abcdefghij\\t\\n", + "match_once": "ABCd", + "match_twice": "hellohello", + "overlap": "yesa", + "replace": "abcdefghij\\r\\n", + "replace2": "abcdeftEsT\\n", + "replace3": "no", + "special": "1{}[].*?", + "special2": "()\\1[].*?", + "special3": "(){}[]\\2", + "test": "abcdefghij\\t\\n", + "test2": "(){}[].*?" +} diff --git a/tests/acceptance/01_vars/02_functions/string_split_2k_variable.cf b/tests/acceptance/01_vars/02_functions/string_split_2k_variable.cf new file mode 100644 index 0000000000..491896e44f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/string_split_2k_variable.cf @@ -0,0 +1,13 @@ +bundle agent main { + vars: + any:: + "test_str" + string => "147.228.4.62#443:www.afs2010.civ.zcu.cz www.agt.zcu.cz www.ah2015.zcu.cz www.ait.fdu.zcu.cz www.akv.zcu.cz www.albumjuristarum.zcu.cz www.amp.zcu.cz www.quiz.zcu.cz www.app.fdu.zcu.cz www.arnica.zcu.cz www.aud.zcu.cz www.audio.zcu.cz www.bedekr.fdu.zcu.cz www.beeronomics2019.zcu.cz www.beyondai.zcu.cz www.caduv.cz www.camop.zcu.cz www.canut.zcu.cz www.careercentre.zcu.cz www.cars.fdu.zcu.cz www.centem.zcu.cz www.chat2014.zcu.cz www.cidam.zcu.cz www.cvts.zcu.cz www.daah.zcu.cz www.dask.zcu.cz www.db-webz.zcu.cz www.dceryasynovenilu.zcu.cz www.dd-uwb-esiee.zcu.cz www.deti.zcu.cz www.diagnostika.zcu.cz www.didap.zcu.cz www.digipod.zcu.cz www.direct.zcu.cz www.disco.zcu.cz www.dispecink.zcu.cz www.dode.fek.zcu.cz www.eb-vyztymdp.zcu.cz www.economics.zcu.cz www.edom.zcu.cz www.eduproeu.zcu.cz www.edutest.zcu.cz www.ehotrod.zcu.cz www.elektromotorsport.zcu.cz www.elmon.fel.zcu.cz www.eltech.fel.zcu.cz www.envirogis.fpe.zcu.cz www.esecc.zcu.cz www.esf.kfi.zcu.cz www.esn.zcu.cz www.fel-utef.zcu.cz www.flab.zcu.cz www.florbal.zcu.cz www.frodja.zcu.cz www.futsal.zcu.cz www.gapps.zcu.cz www.gcindy.zcu.cz www.ha30.zcu.cz www.hb2014.ntc.zcu.cz www.hcenat.zcu.cz www.historie.zcu.cz www.hss.zcu.cz www.hsvv.zcu.cz www.icmd2016.zcu.cz www.inem.fek.zcu.cz www.info-studium.zcu.cz www.infoplatforma.zcu.cz www.informace.zcu.cz www.integrita.zcu.cz www.interdisciplinarita.zcu.cz www.ish2015.zcu.cz www.iso690.zcu.cz www.kee.zcu.cz www.kev.zcu.cz www.kohout.zcu.cz www.konstruktiv.zcu.cz www.kvk2.zcu.cz www.kvnt.zcu.cz www.kvpr.kks.zcu.cz www.lego.zcu.cz www.logickehry.zcu.cz www.lsfek.zcu.cz www.lsipc.zcu.cz www.masekjan.zcu.cz www.mobility.fpe.zcu.cz www.mobility.fpr.zcu.cz www.mojefavka.zcu.cz www.mojepc.zcu.cz www.multilab.fdu.zcu.cz www.mupic.eu www.nacelnik.civ.zcu.cz www.ocv.fzs.zcu.cz www.pc.fpe.zcu.cz www.pes.zcu.cz www.pf.zcu.cz www.pgs.zcu.cz www.pilinterreg.zcu.cz www.pilsencube.zcu.cz www.pir.fel.zcu.cz www.pldsdev.zcu.cz www.pohybovapruprava.zcu.cz www.poplatky.zcu.cz www.ppa2017.zcu.cz www.pracesdaty.zcu.cz www.profilzadavatele.zcu.cz www.projekt-minos.zcu.cz www.prvakoviny.zcu.cz www.radio.zcu.cz www.rcths.zcu.cz www.robotipk.zcu.cz www.ropovportal.zcu.cz www.rozvrhy.fpe.zcu.cz www.ruskecentrum.zcu.cz www.sauron6.zcu.cz www.scicom.zcu.cz www.ska.zcu.cz www.speedtest.zcu.cz www.stf2013.zcu.cz www.strojarna.zcu.cz www.studentskeotazniky.zcu.cz www.studykom.zcu.cz www.summon.zcu.cz www.sutnarka.zcu.cz www.svoc.fel.zcu.cz www.svoc.zcu.cz www.talent.zcu.cz www.technicka-ekologie.zcu.cz www.tfz.zcu.cz www.tvp.zcu.cz www.unipranet.zcu.cz www.usa.fek.zcu.cz www.usk.zcu.cz www.uspoint.zcu.cz www.veletrh.zcu.cz www.virtualbody.zcu.cz www.westernstars.zcu.cz www.wittgenstein.zcu.cz www.zkusebna.zcu.cz$(const.n)147.228.4.60#443:www.pit-plzen.zcu.cz"; + + "test_list" -> { "CFE-3047" } # Promise to ticket that we don't crash + slist => string_split("$(test_str)", "\n", "5" ); # Segment 1 is truncated + + reports: + any:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/01_vars/02_functions/string_trim.cf b/tests/acceptance/01_vars/02_functions/string_trim.cf new file mode 100644 index 0000000000..13b46a3a2d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/string_trim.cf @@ -0,0 +1,47 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "abcd1" string => string_trim("abcd"); + "abcd2" string => string_trim(" abcd"); + "abcd3" string => string_trim("abcd "); + "abcd4" string => string_trim(" abcd "); + "abcd5" string => string_trim("abcd + + "); + "abcd6" string => string_trim(" + + abcd"); + "abcd7" string => string_trim(" + + abcd + + "); + "abcd_abcd" string => string_trim(" abcd abcd "); + + # empty cases + "empty1" string => string_trim(""); + "empty2" string => string_trim(" "); + "empty3" string => string_trim(" + + + "); +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/string_trim.cf.expected.json b/tests/acceptance/01_vars/02_functions/string_trim.cf.expected.json new file mode 100644 index 0000000000..32e26414f8 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/string_trim.cf.expected.json @@ -0,0 +1,13 @@ +{ + "abcd1": "abcd", + "abcd2": "abcd", + "abcd3": "abcd", + "abcd4": "abcd", + "abcd5": "abcd", + "abcd6": "abcd", + "abcd7": "abcd", + "abcd_abcd": "abcd abcd", + "empty1": "", + "empty2": "", + "empty3": "" +} diff --git a/tests/acceptance/01_vars/02_functions/sublist.cf b/tests/acceptance/01_vars/02_functions/sublist.cf new file mode 100644 index 0000000000..af137477c7 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sublist.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test sublist() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "test" slist => { + 1,2,3, + "one", "two", "three", + "long string", + "one", "two", "three", + }; + + "test_head9999" slist => sublist("test", "head", 9999); + "test_head1" slist => sublist("test", "head", 1); + "test_head0" slist => sublist("test", "head", 0); + + "test_tail9999" slist => sublist("test", "tail", 9999); + "test_tail10" slist => sublist("test", "tail", 10); + "test_tail2" slist => sublist("test", "tail", 2); + "test_tail1" slist => sublist("test", "tail", 1); + "test_tail0" slist => sublist("test", "tail", 0); + + "test_inline" slist => sublist('[1,2,3,4,5,6,7]', "tail", 3); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/sublist.cf.expected.json b/tests/acceptance/01_vars/02_functions/sublist.cf.expected.json new file mode 100644 index 0000000000..5bd7c7854b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sublist.cf.expected.json @@ -0,0 +1,67 @@ +{ + "test": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "one", + "two", + "three" + ], + "test_head0": [], + "test_head1": [ + "1" + ], + "test_head9999": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "one", + "two", + "three" + ], + "test_inline": [ + "5", + "6", + "7" + ], + "test_tail0": [], + "test_tail1": [ + "three" + ], + "test_tail10": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "one", + "two", + "three" + ], + "test_tail2": [ + "two", + "three" + ], + "test_tail9999": [ + "1", + "2", + "3", + "one", + "two", + "three", + "long string", + "one", + "two", + "three" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/sum_and_product.cf b/tests/acceptance/01_vars/02_functions/sum_and_product.cf new file mode 100644 index 0000000000..86ccbc32b4 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sum_and_product.cf @@ -0,0 +1,58 @@ +####################################################### +# +# Test 'sum' and 'product' functions +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "normal_list" slist => { "1", "2", "3" }; + "empty_list" slist => { }; + "normal_object" data => parsejson('{ "a": 1, "b": 2 }'); + "empty_object" data => parsejson('{}'); + + "normal_list_sum" real => sum(normal_list); # 6 + "empty_list_sum" real => sum(empty_list); # 0 + "normal_object_sum" real => sum(normal_object); # 3 + "empty_object_sum" real => sum(empty_object); # 0 + + "inline_object_sum" real => sum('{ "a": 1, "b": 2 }'); # 3 + "inline_array_sum" real => sum('[ "a", 1, "b", 2 ]'); # 3 + + "normal_list_product" real => product(normal_list); # 6 + "empty_list_product" real => product(empty_list); # 1 + "normal_object_product" real => product(normal_object); # 2 + "empty_object_product" real => product(empty_object); # 1 + + "inline_object_product" real => product('{ "a": 1, "b": 2 }'); # 2 + "inline_array_product" real => product('[ "a", 1, "b", 2 ]'); # 0 +} + + +####################################################### + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/sum_and_product.cf.expected.json b/tests/acceptance/01_vars/02_functions/sum_and_product.cf.expected.json new file mode 100644 index 0000000000..b2f1b93837 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sum_and_product.cf.expected.json @@ -0,0 +1,26 @@ +{ + "empty_list": [], + "empty_list_product": "1.000000", + "empty_list_sum": "0.000000", + "empty_object": { + }, + "empty_object_product": "1.000000", + "empty_object_sum": "0.000000", + "inline_array_product": "0.000000", + "inline_array_sum": "3.000000", + "inline_object_product": "2.000000", + "inline_object_sum": "3.000000", + "normal_list": [ + "1", + "2", + "3" + ], + "normal_list_product": "6.000000", + "normal_list_sum": "6.000000", + "normal_object": { + "a": 1, + "b": 2 + }, + "normal_object_product": "2.000000", + "normal_object_sum": "3.000000" +} diff --git a/tests/acceptance/01_vars/02_functions/sysctl.cf b/tests/acceptance/01_vars/02_functions/sysctl.cf new file mode 100644 index 0000000000..dba5dda097 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sysctl.cf @@ -0,0 +1,17 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!linux"; + + commands: + "$(G.env) CFENGINE_TEST_OVERRIDE_PROCDIR=$(this.promise_dirname)/proc $(sys.cf_agent) -DAUTO -f $(this.promise_filename).sub"; +} diff --git a/tests/acceptance/01_vars/02_functions/sysctl.cf.sub b/tests/acceptance/01_vars/02_functions/sysctl.cf.sub new file mode 100644 index 0000000000..71cdccd0cb --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sysctl.cf.sub @@ -0,0 +1,34 @@ +# you can run this test directly with + +# CFENGINE_TEST_OVERRIDE_PROCDIR=`pwd`/01_vars/02_functions/proc ./testall 01_vars/02_functions/sysctl.cf.sub + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "sysctl" data => data_sysctlvalues(); + "tested" slist => { "net.ipv4.tcp_mem", "net.unix.max_dgram_qlen" }; + "values[$(tested)]" string => sysctlvalue($(tested)); +} + +bundle agent check +{ + vars: + "testname" string => regex_replace($(this.promise_filename), "\\.sub$", "", ""); + + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(testname)); + + test_debug:: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/sysctl.cf.sub.expected.json b/tests/acceptance/01_vars/02_functions/sysctl.cf.sub.expected.json new file mode 100644 index 0000000000..3020524c88 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/sysctl.cf.sub.expected.json @@ -0,0 +1,12 @@ +{ + "sysctl": { + "net.ipv4.tcp_mem": "383133\t510845\t766266", + "net.unix.max_dgram_qlen": "512" + }, + "tested": [ + "net.ipv4.tcp_mem", + "net.unix.max_dgram_qlen" + ], + "values[net.ipv4.tcp_mem]": "383133\t510845\t766266", + "values[net.unix.max_dgram_qlen]": "512" +} diff --git a/tests/acceptance/01_vars/02_functions/text.txt b/tests/acceptance/01_vars/02_functions/text.txt new file mode 100644 index 0000000000..6cc7be057d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text.txt @@ -0,0 +1 @@ +Succeeded diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf b/tests/acceptance/01_vars/02_functions/text_xform.cf new file mode 100644 index 0000000000..191dfb166b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf @@ -0,0 +1,42 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "tests" slist => { "q1", "q2", "q3", "q4" }; + "offsets" ilist => { "-1", "-2", "-5", "-10", "-20", "-26", "-27", "-28", "-10240", + "0", "1", "2", "5", "10", "20", "26", "27", "28", "10240" }; + "fn1" slist => { "string_head", "string_tail" }; + "data[q1]" string => "this is the Question"; + "data[q2]" string => ""; + "data[q3]" string => "some text is not $(const.t) simple + +"; + "data[q4]" string => readfile("$(this.promise_filename).txt", "inf"); + + "data[$(tests)_string_reverse]" string => string_reverse("$(data[$(tests)])"); + "data[$(tests)_string_length]" int => string_length("$(data[$(tests)])"); + "data[$(tests)_string_upcase]" string => string_upcase("$(data[$(tests)])"); + "data[$(tests)_string_downcase]" string => string_downcase("$(data[$(tests)])"); + "data[$(tests)_string_head_$(offsets)]" string => string_head("$(data[$(tests)])", $(offsets)); + "data[$(tests)_string_tail_$(offsets)]" string => string_tail("$(data[$(tests)])", $(offsets)); + "data[$(tests)_string_head_inline_pos]" string => string_head("$(data[$(tests)])", 3); + "data[$(tests)_string_tail_inline_pos]" string => string_tail("$(data[$(tests)])", 3); + "data[$(tests)_string_head_inline_neg]" string => string_head("$(data[$(tests)])", "-3"); + "data[$(tests)_string_tail_inline_neg]" string => string_tail("$(data[$(tests)])", "-3"); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.expected.json b/tests/acceptance/01_vars/02_functions/text_xform.cf.expected.json new file mode 100644 index 0000000000..2d45af6dbd --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.expected.json @@ -0,0 +1,221 @@ +{ + "data[q1]": "this is the Question", + "data[q1_string_downcase]": "this is the question", + "data[q1_string_head_-10240]": "", + "data[q1_string_head_-10]": "this is th", + "data[q1_string_head_-1]": "this is the Questio", + "data[q1_string_head_-20]": "", + "data[q1_string_head_-26]": "", + "data[q1_string_head_-27]": "", + "data[q1_string_head_-28]": "", + "data[q1_string_head_-2]": "this is the Questi", + "data[q1_string_head_-5]": "this is the Que", + "data[q1_string_head_0]": "", + "data[q1_string_head_10240]": "this is the Question", + "data[q1_string_head_10]": "this is th", + "data[q1_string_head_1]": "t", + "data[q1_string_head_20]": "this is the Question", + "data[q1_string_head_26]": "this is the Question", + "data[q1_string_head_27]": "this is the Question", + "data[q1_string_head_28]": "this is the Question", + "data[q1_string_head_2]": "th", + "data[q1_string_head_5]": "this ", + "data[q1_string_head_inline_neg]": "this is the Quest", + "data[q1_string_head_inline_pos]": "thi", + "data[q1_string_length]": "20", + "data[q1_string_reverse]": "noitseuQ eht si siht", + "data[q1_string_tail_-10240]": "", + "data[q1_string_tail_-10]": "e Question", + "data[q1_string_tail_-1]": "his is the Question", + "data[q1_string_tail_-20]": "", + "data[q1_string_tail_-26]": "", + "data[q1_string_tail_-27]": "", + "data[q1_string_tail_-28]": "", + "data[q1_string_tail_-2]": "is is the Question", + "data[q1_string_tail_-5]": "is the Question", + "data[q1_string_tail_0]": "", + "data[q1_string_tail_10240]": "this is the Question", + "data[q1_string_tail_10]": "e Question", + "data[q1_string_tail_1]": "n", + "data[q1_string_tail_20]": "this is the Question", + "data[q1_string_tail_26]": "this is the Question", + "data[q1_string_tail_27]": "this is the Question", + "data[q1_string_tail_28]": "this is the Question", + "data[q1_string_tail_2]": "on", + "data[q1_string_tail_5]": "stion", + "data[q1_string_tail_inline_neg]": "s is the Question", + "data[q1_string_tail_inline_pos]": "ion", + "data[q1_string_upcase]": "THIS IS THE QUESTION", + "data[q2]": "", + "data[q2_string_downcase]": "", + "data[q2_string_head_-10240]": "", + "data[q2_string_head_-10]": "", + "data[q2_string_head_-1]": "", + "data[q2_string_head_-20]": "", + "data[q2_string_head_-26]": "", + "data[q2_string_head_-27]": "", + "data[q2_string_head_-28]": "", + "data[q2_string_head_-2]": "", + "data[q2_string_head_-5]": "", + "data[q2_string_head_0]": "", + "data[q2_string_head_10240]": "", + "data[q2_string_head_10]": "", + "data[q2_string_head_1]": "", + "data[q2_string_head_20]": "", + "data[q2_string_head_26]": "", + "data[q2_string_head_27]": "", + "data[q2_string_head_28]": "", + "data[q2_string_head_2]": "", + "data[q2_string_head_5]": "", + "data[q2_string_head_inline_neg]": "", + "data[q2_string_head_inline_pos]": "", + "data[q2_string_length]": "0", + "data[q2_string_reverse]": "", + "data[q2_string_tail_-10240]": "", + "data[q2_string_tail_-10]": "", + "data[q2_string_tail_-1]": "", + "data[q2_string_tail_-20]": "", + "data[q2_string_tail_-26]": "", + "data[q2_string_tail_-27]": "", + "data[q2_string_tail_-28]": "", + "data[q2_string_tail_-2]": "", + "data[q2_string_tail_-5]": "", + "data[q2_string_tail_0]": "", + "data[q2_string_tail_10240]": "", + "data[q2_string_tail_10]": "", + "data[q2_string_tail_1]": "", + "data[q2_string_tail_20]": "", + "data[q2_string_tail_26]": "", + "data[q2_string_tail_27]": "", + "data[q2_string_tail_28]": "", + "data[q2_string_tail_2]": "", + "data[q2_string_tail_5]": "", + "data[q2_string_tail_inline_neg]": "", + "data[q2_string_tail_inline_pos]": "", + "data[q2_string_upcase]": "", + "data[q3]": "some text is not \t simple\n\n", + "data[q3_string_downcase]": "some text is not \t simple\n\n", + "data[q3_string_head_-10240]": "", + "data[q3_string_head_-10]": "some text is not ", + "data[q3_string_head_-1]": "some text is not \t simple\n", + "data[q3_string_head_-20]": "some te", + "data[q3_string_head_-26]": "s", + "data[q3_string_head_-27]": "", + "data[q3_string_head_-28]": "", + "data[q3_string_head_-2]": "some text is not \t simple", + "data[q3_string_head_-5]": "some text is not \t sim", + "data[q3_string_head_0]": "", + "data[q3_string_head_10240]": "some text is not \t simple\n\n", + "data[q3_string_head_10]": "some text ", + "data[q3_string_head_1]": "s", + "data[q3_string_head_20]": "some text is not \t s", + "data[q3_string_head_26]": "some text is not \t simple\n", + "data[q3_string_head_27]": "some text is not \t simple\n\n", + "data[q3_string_head_28]": "some text is not \t simple\n\n", + "data[q3_string_head_2]": "so", + "data[q3_string_head_5]": "some ", + "data[q3_string_head_inline_neg]": "some text is not \t simpl", + "data[q3_string_head_inline_pos]": "som", + "data[q3_string_length]": "27", + "data[q3_string_reverse]": "\n\nelpmis \t ton si txet emos", + "data[q3_string_tail_-10240]": "", + "data[q3_string_tail_-10]": "is not \t simple\n\n", + "data[q3_string_tail_-1]": "ome text is not \t simple\n\n", + "data[q3_string_tail_-20]": "imple\n\n", + "data[q3_string_tail_-26]": "\n", + "data[q3_string_tail_-27]": "", + "data[q3_string_tail_-28]": "", + "data[q3_string_tail_-2]": "me text is not \t simple\n\n", + "data[q3_string_tail_-5]": "text is not \t simple\n\n", + "data[q3_string_tail_0]": "", + "data[q3_string_tail_10240]": "some text is not \t simple\n\n", + "data[q3_string_tail_10]": "\t simple\n\n", + "data[q3_string_tail_1]": "\n", + "data[q3_string_tail_20]": "xt is not \t simple\n\n", + "data[q3_string_tail_26]": "ome text is not \t simple\n\n", + "data[q3_string_tail_27]": "some text is not \t simple\n\n", + "data[q3_string_tail_28]": "some text is not \t simple\n\n", + "data[q3_string_tail_2]": "\n\n", + "data[q3_string_tail_5]": "ple\n\n", + "data[q3_string_tail_inline_neg]": "e text is not \t simple\n\n", + "data[q3_string_tail_inline_pos]": "e\n\n", + "data[q3_string_upcase]": "SOME TEXT IS NOT \t SIMPLE\n\n", + "data[q4]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_downcase]": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghij", + "data[q4_string_head_-10240]": "", + "data[q4_string_head_-10]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + "data[q4_string_head_-1]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghi", + "data[q4_string_head_-20]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + "data[q4_string_head_-26]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST", + "data[q4_string_head_-27]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS", + "data[q4_string_head_-28]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR", + "data[q4_string_head_-2]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh", + "data[q4_string_head_-5]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcde", + "data[q4_string_head_0]": "", + "data[q4_string_head_10240]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_head_10]": "abcdefghij", + "data[q4_string_head_1]": "a", + "data[q4_string_head_20]": "abcdefghijklmnopqrst", + "data[q4_string_head_26]": "abcdefghijklmnopqrstuvwxyz", + "data[q4_string_head_27]": "abcdefghijklmnopqrstuvwxyzA", + "data[q4_string_head_28]": "abcdefghijklmnopqrstuvwxyzAB", + "data[q4_string_head_2]": "ab", + "data[q4_string_head_5]": "abcde", + "data[q4_string_head_inline_neg]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefg", + "data[q4_string_head_inline_pos]": "abc", + "data[q4_string_length]": "10240", + "data[q4_string_reverse]": "jihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba", + "data[q4_string_tail_-10240]": "", + "data[q4_string_tail_-10]": "klmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_-1]": "bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_-20]": "uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_-26]": "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_-27]": "BCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_-28]": "CDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_-2]": "cdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_-5]": "fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_0]": "", + "data[q4_string_tail_10240]": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_10]": "abcdefghij", + "data[q4_string_tail_1]": "j", + "data[q4_string_tail_20]": "0123456789abcdefghij", + "data[q4_string_tail_26]": "UVWXYZ0123456789abcdefghij", + "data[q4_string_tail_27]": "TUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_28]": "STUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_2]": "ij", + "data[q4_string_tail_5]": "fghij", + "data[q4_string_tail_inline_neg]": "defghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij", + "data[q4_string_tail_inline_pos]": "hij", + "data[q4_string_upcase]": "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJ", + "fn1": [ + "string_head", + "string_tail" + ], + "offsets": [ + "-1", + "-2", + "-5", + "-10", + "-20", + "-26", + "-27", + "-28", + "-10240", + "0", + "1", + "2", + "5", + "10", + "20", + "26", + "27", + "28", + "10240" + ], + "tests": [ + "q1", + "q2", + "q3", + "q4" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.lowercase.txt b/tests/acceptance/01_vars/02_functions/text_xform.cf.lowercase.txt new file mode 100644 index 0000000000..ee55c52f0f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.lowercase.txt @@ -0,0 +1 @@ +abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789abcdefghij diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.reverse.txt b/tests/acceptance/01_vars/02_functions/text_xform.cf.reverse.txt new file mode 100644 index 0000000000..b77bfb0763 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.reverse.txt @@ -0,0 +1 @@ +jihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.tail20.txt b/tests/acceptance/01_vars/02_functions/text_xform.cf.tail20.txt new file mode 100644 index 0000000000..4ba3e9de30 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.tail20.txt @@ -0,0 +1 @@ +0123456789abcdefghij diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.tail26.txt b/tests/acceptance/01_vars/02_functions/text_xform.cf.tail26.txt new file mode 100644 index 0000000000..c45871c680 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.tail26.txt @@ -0,0 +1 @@ +UVWXYZ0123456789abcdefghij diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.tail27.txt b/tests/acceptance/01_vars/02_functions/text_xform.cf.tail27.txt new file mode 100644 index 0000000000..1d5ff7864c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.tail27.txt @@ -0,0 +1 @@ +TUVWXYZ0123456789abcdefghij diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.tail28.txt b/tests/acceptance/01_vars/02_functions/text_xform.cf.tail28.txt new file mode 100644 index 0000000000..7126cd265b --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.tail28.txt @@ -0,0 +1 @@ +STUVWXYZ0123456789abcdefghij diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.txt b/tests/acceptance/01_vars/02_functions/text_xform.cf.txt new file mode 100644 index 0000000000..eff442500a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.txt @@ -0,0 +1 @@ +abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij diff --git a/tests/acceptance/01_vars/02_functions/text_xform.cf.uppercase.txt b/tests/acceptance/01_vars/02_functions/text_xform.cf.uppercase.txt new file mode 100644 index 0000000000..9a6ba1a6ec --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/text_xform.cf.uppercase.txt @@ -0,0 +1 @@ +ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJ diff --git a/tests/acceptance/01_vars/02_functions/type.cf b/tests/acceptance/01_vars/02_functions/type.cf new file mode 100644 index 0000000000..4326d3cfa1 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/type.cf @@ -0,0 +1,92 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "_data" data => ' + { + "_string": "abc", + "_int": 123, + "_real": 123.4, + "_true": true, + "_false": false, + "_object": { "abc": 123 }, + "_array": [ 1, 2, 3 ], + "_null": null + }'; + "_string" string => "abc"; + "_int" int => "123"; + "_real" real => "123.4"; + "_slist" slist => { "a", "b", "c" }; + "_ilist" ilist => { "1", "2", "3" }; + "_rlist" rlist => { "1.2", "3.4", "5.6" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2240" } + string => "Test for expected results from policy function type"; + + vars: + "test_00" string => type("init._data"); + "test_01" string => type("init._data", "true"); + + "test_02" string => type("init._data[_string]"); + "test_03" string => type("init._data[_string]", "true"); + + "test_04" string => type("init._data[_int]"); + "test_05" string => type("init._data[_int]", "true"); + + "test_06" string => type("init._data[_real]"); + "test_07" string => type("init._data[_real]", "true"); + + "test_08" string => type("init._data[_true]"); + "test_09" string => type("init._data[_true]", "true"); + + "test_10" string => type("init._data[_false]"); + "test_11" string => type("init._data[_false]", "true"); + + "test_12" string => type("init._data[_object]"); + "test_13" string => type("init._data[_object]", "true"); + + "test_14" string => type("init._data[_array]"); + "test_15" string => type("init._data[_array]", "true"); + + "test_16" string => type("init._data[_null]"); + "test_17" string => type("init._data[_null]", "true"); + + "test_18" string => type("init._string"); + "test_19" string => type("init._string", "true"); + + "test_20" string => type("init._int"); + "test_21" string => type("init._int", "true"); + + "test_22" string => type("init._real"); + "test_23" string => type("init._real", "true"); + + "test_24" string => type("init._slist", "false"); + "test_25" string => type("init._slist", "true"); + + "test_26" string => type("init._ilist", "no"); + "test_27" string => type("init._ilist", "yes"); + + "test_28" string => type("init._rlist", "off"); + "test_29" string => type("init._rlist", "on"); + + "test_30" string => type("undefined"); + "test_31" string => type("undefined", "true"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/type.cf.expected.json b/tests/acceptance/01_vars/02_functions/type.cf.expected.json new file mode 100644 index 0000000000..3922cb5589 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/type.cf.expected.json @@ -0,0 +1,34 @@ +{ + "test_00": "data", + "test_01": "data object", + "test_02": "data", + "test_03": "data string", + "test_04": "data", + "test_05": "data int", + "test_06": "data", + "test_07": "data real", + "test_08": "data", + "test_09": "data boolean", + "test_10": "data", + "test_11": "data boolean", + "test_12": "data", + "test_13": "data object", + "test_14": "data", + "test_15": "data array", + "test_16": "data", + "test_17": "data null", + "test_18": "string", + "test_19": "policy string", + "test_20": "int", + "test_21": "policy int", + "test_22": "real", + "test_23": "policy real", + "test_24": "slist", + "test_25": "policy slist", + "test_26": "ilist", + "test_27": "policy ilist", + "test_28": "rlist", + "test_29": "policy rlist", + "test_30": "none", + "test_31": "policy none" +} diff --git a/tests/acceptance/01_vars/02_functions/url_get_local.cf b/tests/acceptance/01_vars/02_functions/url_get_local.cf new file mode 100644 index 0000000000..907c00bffe --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/url_get_local.cf @@ -0,0 +1,56 @@ +########################################################### +# +# Test url_get() locally +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent init +{ + vars: + "options_limit" data => ' +{ + "url.max_content": 200, + "nosuchoption": 100, + "url.verbose": 0, + "url.headers": [ "Foo: bar" ] +}'; + + "options_clean" data => '{}'; + # local file: does it work on Windows? + "res4" data => url_get("file://$(this.promise_filename)", options_limit); +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!feature_curl"; + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + vars: + "kept" data => mergedata( + '{ "res4": init.res4[content] }', + '{ "res4/returncode": init.res4[returncode] }', + '{ "res4/rc": init.res4[rc] }', + '{ "res4/success": init.res4[success] }' + ); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/url_get_local.cf.expected.json b/tests/acceptance/01_vars/02_functions/url_get_local.cf.expected.json new file mode 100644 index 0000000000..68b0d49204 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/url_get_local.cf.expected.json @@ -0,0 +1,8 @@ +{ + "kept": { + "res4": "###########################################################\n#\n# Test url_get() locally\n#\n###########################################################\n\nbody common control\n{\n inputs => { \"../../defau", + "res4/rc": 0, + "res4/returncode": 0, + "res4/success": true + } +} diff --git a/tests/acceptance/01_vars/02_functions/validdata.cf b/tests/acceptance/01_vars/02_functions/validdata.cf new file mode 100644 index 0000000000..c25d3d0b8f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/validdata.cf @@ -0,0 +1,38 @@ +########################################################### +# +# Test validdata() with valid and invalid data files +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + vars: + "valid_json" string => "should appear", + if => validdata(readfile("$(this.promise_filename).json", inf), "JSON"); + "invalid_json" string => "should not appear", + if => validdata(readfile("$(this.promise_filename).inv.json", inf), "JSON"); + + "valid_validjson" string => "should appear", + if => validjson(readfile("$(this.promise_filename).json", inf)); + "invalid_validjson" string => "should not appear", + if => validjson(readfile("$(this.promise_filename).inv.json", inf)); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/validdata.cf.expected.json b/tests/acceptance/01_vars/02_functions/validdata.cf.expected.json new file mode 100644 index 0000000000..4bb8da7c66 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/validdata.cf.expected.json @@ -0,0 +1,4 @@ +{ + "valid_json": "should appear", + "valid_validjson": "should appear" +} diff --git a/tests/acceptance/01_vars/02_functions/validdata.cf.inv.json b/tests/acceptance/01_vars/02_functions/validdata.cf.inv.json new file mode 100644 index 0000000000..2e3b24fe5e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/validdata.cf.inv.json @@ -0,0 +1,7 @@ +{ + "test" : [1,2,3,4], + "test2" : { + }, + "test3" : { + } +{ diff --git a/tests/acceptance/01_vars/02_functions/validdata.cf.json b/tests/acceptance/01_vars/02_functions/validdata.cf.json new file mode 100644 index 0000000000..831e8e7b6d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/validdata.cf.json @@ -0,0 +1,7 @@ +{ + "test": [1,2,3,4], + "test2": { + "test3": 1 + }, + "test3": "Yes" +} diff --git a/tests/acceptance/01_vars/02_functions/validjson_trailing_bogus_data.cf b/tests/acceptance/01_vars/02_functions/validjson_trailing_bogus_data.cf new file mode 100644 index 0000000000..e1f6686f0e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/validjson_trailing_bogus_data.cf @@ -0,0 +1,56 @@ +############################################################################## +# +# Test validjson() evaluates to !any:: when there is trailing bogus data after +# termination for JSON object. +# +############################################################################## + +body common control +{ + bundlesequence => { "test", "check" }; + version => "1.0"; +} + +############################################################################## + +bundle agent test +{ + meta: + "description" -> { "CFE-4080" } + string => "Test validjson() with trailing bogus data"; + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; +} + +############################################################################## + +bundle agent check +{ + classes: + "ok" + and => { + not(validjson('""a')), + validjson('""'), + not(validjson('{}b')), + validjson('{}'), + not(validjson('[]c')), + not(validjson('{}}')), + not(validjson('{"d": "e"}}')), + not(validjson('[]]')), + not(validjson('[[]]]')), + not(validjson('[[[]]]]')), + not(validjson(' []]')), + not(validjson('"some": [ "json" ] }')), + not(validjson('{ "some": [ "json" ] } [')), + not(validjson('["some", "json"]!')), + not(validjson(' ["some", "json"]a')), + not(validjson('["some", "json"] {"foo": "var"} ')), + validjson('{"test": [1, 2, 3]}'), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/variable_references_from_function_that_never_succeeds.cf b/tests/acceptance/01_vars/02_functions/variable_references_from_function_that_never_succeeds.cf new file mode 100644 index 0000000000..dcf7f87fd6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/variable_references_from_function_that_never_succeeds.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test that FnFailure results in unresolved references +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle common test +{ + classes: + "resolved_found" expression => returnszero("$(command) resolved_a557d39e04d666075754b8f78ea17fbc175925d5 2>&1", "useshell"); + "unresolved_found" expression => returnszero("$(command) unresolved_76201d0eaac49a884308358aa487147b4db70e8a 2>&1", "useshell"); + + vars: + "command" string => "$(sys.cf_agent) -K -f $(this.promise_filename).sub | $(G.grep) MARKER | $(G.grep)"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!resolved_found", "unresolved_found" }; + + reports: + DEBUG.!resolved_found:: + "Resolved variable not found"; + DEBUG.resolved_found:: + "Resolved variable found?!?!"; + DEBUG.unresolved_found:: + "Unresolved variable found"; + DEBUG.!unresolved_found:: + "Unresolved variable not found?!?!"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/variable_references_from_function_that_never_succeeds.cf.sub b/tests/acceptance/01_vars/02_functions/variable_references_from_function_that_never_succeeds.cf.sub new file mode 100644 index 0000000000..1c84f649fc --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/variable_references_from_function_that_never_succeeds.cf.sub @@ -0,0 +1,20 @@ +####################################################### +# +# Subtest that FnFailure results in unresolved references +# +####################################################### + +body common control +{ + bundlesequence => { test }; +} + +bundle agent test +{ + vars: + "unresolved_76201d0eaac49a884308358aa487147b4db70e8a" slist => { canonify($(missing)) }; + "resolved_a557d39e04d666075754b8f78ea17fbc175925d5" slist => { "a", "b" }; + + reports: + "MARKER$(unresolved_76201d0eaac49a884308358aa487147b4db70e8a)MARKER MARKER$(resolved_a557d39e04d666075754b8f78ea17fbc175925d5)MARKER"; +} diff --git a/tests/acceptance/01_vars/02_functions/variablesmatching.cf b/tests/acceptance/01_vars/02_functions/variablesmatching.cf new file mode 100644 index 0000000000..074513b4ab --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/variablesmatching.cf @@ -0,0 +1,46 @@ +# Test that variablesmatching works correctly + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common init +{ + vars: + "test_fbeae67f3e347b5e0032302200141131" string => "abc", meta => { "x" }; + "test_fbeae67f3e347b5e0032302200141131_1" string => "def", meta => { "x" }; + "test_fbeae67f3e347b5e0032302200141131_2" string => "ghi", meta => { "y" }; + + "test_b37ce1b03955522848f0a82d0b2b2e21_1" slist => { "a", "b" }, meta => { "x" }; + "test_b37ce1b03955522848f0a82d0b2b2e21_2" data => '{ "a": "b" }', meta => { "x" }; + "test_b37ce1b03955522848f0a82d0b2b2e21_3" string => 'mydata', meta => { "y" }; + + "test_variable_with_weird_tag" string => 'some_data', meta => { "weird[tag]" }; +} + +bundle agent test +{ + vars: + "vars" slist => variablesmatching("default:init.test_fbeae67f3e347b5e0032302200141131.*"); + "x_vars" slist => variablesmatching("default:init.test_fbeae67f3e347b5e0032302200141131.*", "x"); + "z_vars" slist => variablesmatching("default:init.test_fbeae67f3e347b5e0032302200141131.*", "z"); + + "fullvars" data => variablesmatching_as_data("default:init.test_b37ce1b03955522848f0a82d0b2b2e21.*"); + "x_fullvars" data => variablesmatching_as_data("default:init.test_b37ce1b03955522848f0a82d0b2b2e21.*", "x"); + "z_fullvars" data => variablesmatching_as_data("default:init.test_b37ce1b03955522848f0a82d0b2b2e21.*", "z"); + + "tag_match" slist => variablesmatching(".*", "weird\[tag\]"); + "tag_no_match" slist => variablesmatching(".*", "weird[tag]"); + "tag_match_data" data => variablesmatching_as_data(".*", "weird\[tag\]"); + "tag_no_match_data" data => variablesmatching_as_data(".*", "weird[tag]"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/variablesmatching.cf.expected.json b/tests/acceptance/01_vars/02_functions/variablesmatching.cf.expected.json new file mode 100644 index 0000000000..3d3fe0ab44 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/variablesmatching.cf.expected.json @@ -0,0 +1,42 @@ +{ + "fullvars": { + "default:init.test_b37ce1b03955522848f0a82d0b2b2e21_1": [ + "a", + "b" + ], + "default:init.test_b37ce1b03955522848f0a82d0b2b2e21_2": { + "a": "b" + }, + "default:init.test_b37ce1b03955522848f0a82d0b2b2e21_3": "mydata" + }, + "tag_match": [ + "default:init.test_variable_with_weird_tag" + ], + "tag_match_data": { + "default:init.test_variable_with_weird_tag": "some_data" + }, + "tag_no_match": [], + "tag_no_match_data": { + }, + "vars": [ + "default:init.test_fbeae67f3e347b5e0032302200141131_1", + "default:init.test_fbeae67f3e347b5e0032302200141131_2", + "default:init.test_fbeae67f3e347b5e0032302200141131" + ], + "x_fullvars": { + "default:init.test_b37ce1b03955522848f0a82d0b2b2e21_1": [ + "a", + "b" + ], + "default:init.test_b37ce1b03955522848f0a82d0b2b2e21_2": { + "a": "b" + } + }, + "x_vars": [ + "default:init.test_fbeae67f3e347b5e0032302200141131_1", + "default:init.test_fbeae67f3e347b5e0032302200141131" + ], + "z_fullvars": { + }, + "z_vars": [] +} diff --git a/tests/acceptance/01_vars/02_functions/version_compare.cf b/tests/acceptance/01_vars/02_functions/version_compare.cf new file mode 100644 index 0000000000..d874a33765 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/version_compare.cf @@ -0,0 +1,109 @@ +########################################################### +# +# Test version_compare function +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + vars: + "true_a" + string => version_compare("1.0.0", "=", "1.0.0"); + "true_b" + string => version_compare("0.1.0", "=", "0.1.0"); + "true_c" + string => version_compare("0.0.1", "=", "0.0.1"); + "true_d" + string => version_compare("1.0.1", "!=", "1.0.0"); + "true_e" + string => version_compare("0.0.0", "==", "0.0.0"); + "true_f" + string => version_compare("1.2.3", ">", "1.1.999"); + "true_g" + string => version_compare("1.2.3", ">", "1.1.999"); + "true_h" + string => version_compare("1.2.3", "!=", "1.1.999"); + "true_i" + string => version_compare("0.0.0", "<", "0.0.1"); + "true_j" + string => version_compare("0.0.0", "<=", "0.0.1"); + "true_k" + string => version_compare("0.0.0", "!=", "0.0.1"); + "true_l" + string => version_compare("0.0.0", "<=", "0.0.1"); + "true_m" + string => version_compare("7.7.7", "==", "7.7.7-1"); + "true_n" + string => version_compare("7.7.7-1", "==", "7.7.7-2"); + "true_o" + string => version_compare("7.7.7a", "==", "7.7.7"); + "true_p" + string => version_compare("7.7.7a", ">=", "7.7.7"); + "true_q" + string => version_compare("7.7.7a", "<=", "7.7.7"); + "true_r" + string => version_compare("3.2.1", "==", "3.2"); + "true_s" + string => version_compare("3", "=", "3.2.1"); + "true_t" + string => version_compare("3.2", "<=", "3"); + "false_a" + string => version_compare("1.0.0", "!=", "1.0.0"); + "false_b" + string => version_compare("0.1.0", "!=", "0.1.0"); + "false_c" + string => version_compare("0.0.1", "!=", "0.0.1"); + "false_d" + string => version_compare("1.0.1", "=", "1.0.0"); + "false_e" + string => version_compare("0.0.0", "!=", "0.0.0"); + "false_f" + string => version_compare("1.2.3", "<=", "1.1.999"); + "false_g" + string => version_compare("1.2.3", "<=", "1.1.999"); + "false_h" + string => version_compare("1.2.3", "=", "1.1.999"); + "false_i" + string => version_compare("0.0.0", ">=", "0.0.1"); + "false_j" + string => version_compare("0.0.0", ">", "0.0.1"); + "false_k" + string => version_compare("0.0.0", "==", "0.0.1"); + "false_l" + string => version_compare("0.0.0", ">", "0.0.1"); + "false_m" + string => version_compare("7.7.7", "!=", "7.7.7-1"); + "false_n" + string => version_compare("7.7.7-1", "!=", "7.7.7-2"); + "false_o" + string => version_compare("7.7.7a", "!=", "7.7.7"); + "false_p" + string => version_compare("7.7.7a", "<", "7.7.7"); + "false_q" + string => version_compare("7.7.7a", ">", "7.7.7"); + "false_r" + string => version_compare("3.2.1", "!=", "3.2"); + "false_s" + string => version_compare("3", "!=", "3.2.1"); + "false_t" + string => version_compare("3.2", ">", "3"); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/version_compare.cf.expected.json b/tests/acceptance/01_vars/02_functions/version_compare.cf.expected.json new file mode 100644 index 0000000000..b93b5e2923 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/version_compare.cf.expected.json @@ -0,0 +1,42 @@ +{ + "false_a": "!any", + "false_b": "!any", + "false_c": "!any", + "false_d": "!any", + "false_e": "!any", + "false_f": "!any", + "false_g": "!any", + "false_h": "!any", + "false_i": "!any", + "false_j": "!any", + "false_k": "!any", + "false_l": "!any", + "false_m": "!any", + "false_n": "!any", + "false_o": "!any", + "false_p": "!any", + "false_q": "!any", + "false_r": "!any", + "false_s": "!any", + "false_t": "!any", + "true_a": "any", + "true_b": "any", + "true_c": "any", + "true_d": "any", + "true_e": "any", + "true_f": "any", + "true_g": "any", + "true_h": "any", + "true_i": "any", + "true_j": "any", + "true_k": "any", + "true_l": "any", + "true_m": "any", + "true_n": "any", + "true_o": "any", + "true_p": "any", + "true_q": "any", + "true_r": "any", + "true_s": "any", + "true_t": "any" +} diff --git a/tests/acceptance/01_vars/02_functions/wrap_container_fncalls.cf b/tests/acceptance/01_vars/02_functions/wrap_container_fncalls.cf new file mode 100644 index 0000000000..5d1c24392e --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/wrap_container_fncalls.cf @@ -0,0 +1,60 @@ +####################################################### +# +# Test wrapped fncalls that return data containers +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common init +{ + classes: + "predefined" expression => "any"; +} + +bundle agent test +{ + vars: + "base_list" slist => { "a", "b", "c", "aaa", "bbb", "ccc" }; + "base_data" data => '{ "x": 100, "y": "z" }'; + "mapdata_spec" string => "<<< $(this.k)=$(this.v) >>>"; + + "sort_mapdata_inline_json" slist => sort(mapdata('none', $(mapdata_spec), '[1,2,3]'), lex); + "sort_mapdata_slist" slist => sort(mapdata('none', $(mapdata_spec), base_list), lex); + "sort_mapdata_data" slist => sort(mapdata('none', $(mapdata_spec), base_data), lex); + + "sort_mapdata_mergedata_inline_json" slist => sort(mapdata('none', $(mapdata_spec), '[1,2,3]'), lex); + "sort_mapdata_mergedata_slist" slist => sort(mapdata('none', $(mapdata_spec), mergedata(base_list)), lex); + "sort_mapdata_mergedata_data" slist => sort(mapdata('none', $(mapdata_spec), mergedata(base_data)), lex); + + # we can nest functions that return slists too! + "sort_mapdata_sort_slist" slist => sort(mapdata('none', $(mapdata_spec), sort(base_list, int)), lex); + "sort_mapdata_mergedata_sort_slist" slist => sort(mapdata('none', $(mapdata_spec), mergedata(sort(base_list, int))), lex); + "reverse_sort_slist" slist => reverse(sort(base_list, int)); + + "concat_sort_mapdata_mergedata_inline_json" string => concat(sort(mapdata('none', $(mapdata_spec), '[1,2,3]'), lex)); + "concat_sort_mapdata_mergedata_slist" string => concat(sort(mapdata('none', $(mapdata_spec), mergedata(base_list)), lex)); + "concat_sort_mapdata_mergedata_data" string => concat(sort(mapdata('none', $(mapdata_spec), mergedata(base_data)), lex)); + + "nth_sort_mapdata_mergedata_inline_json" string => nth(sort(mapdata('none', $(mapdata_spec), '[1,2,3]'), lex), 0); + "nth_mapdata_mergedata_slist" string => nth(mapdata('none', $(mapdata_spec), mergedata(base_list)), 1); + "nth_inline_json" string => nth('{ "x": "88888888" }', "x"); + "nth_missing_inline_json" string => nth('{ "x": "88888888" }', "y"); + "nth_data" string => nth(base_data, "x"); + "nth_list" string => nth(base_list, 3); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/02_functions/wrap_container_fncalls.cf.expected.json b/tests/acceptance/01_vars/02_functions/wrap_container_fncalls.cf.expected.json new file mode 100644 index 0000000000..054d93ee61 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/wrap_container_fncalls.cf.expected.json @@ -0,0 +1,81 @@ +{ + "base_data": { + "x": 100, + "y": "z" + }, + "base_list": [ + "a", + "b", + "c", + "aaa", + "bbb", + "ccc" + ], + "concat_sort_mapdata_mergedata_data": "<<< x=100 >>><<< y=z >>>", + "concat_sort_mapdata_mergedata_inline_json": "<<< 0=1 >>><<< 1=2 >>><<< 2=3 >>>", + "concat_sort_mapdata_mergedata_slist": "<<< 0=a >>><<< 1=b >>><<< 2=c >>><<< 3=aaa >>><<< 4=bbb >>><<< 5=ccc >>>", + "mapdata_spec": "<<< $(this.k)=$(this.v) >>>", + "nth_data": "100", + "nth_inline_json": "88888888", + "nth_list": "aaa", + "nth_mapdata_mergedata_slist": "<<< 1=b >>>", + "nth_sort_mapdata_mergedata_inline_json": "<<< 0=1 >>>", + "reverse_sort_slist": [ + "ccc", + "c", + "bbb", + "b", + "aaa", + "a" + ], + "sort_mapdata_data": [ + "<<< x=100 >>>", + "<<< y=z >>>" + ], + "sort_mapdata_inline_json": [ + "<<< 0=1 >>>", + "<<< 1=2 >>>", + "<<< 2=3 >>>" + ], + "sort_mapdata_mergedata_data": [ + "<<< x=100 >>>", + "<<< y=z >>>" + ], + "sort_mapdata_mergedata_inline_json": [ + "<<< 0=1 >>>", + "<<< 1=2 >>>", + "<<< 2=3 >>>" + ], + "sort_mapdata_mergedata_slist": [ + "<<< 0=a >>>", + "<<< 1=b >>>", + "<<< 2=c >>>", + "<<< 3=aaa >>>", + "<<< 4=bbb >>>", + "<<< 5=ccc >>>" + ], + "sort_mapdata_mergedata_sort_slist": [ + "<<< 0=a >>>", + "<<< 1=aaa >>>", + "<<< 2=b >>>", + "<<< 3=bbb >>>", + "<<< 4=c >>>", + "<<< 5=ccc >>>" + ], + "sort_mapdata_slist": [ + "<<< 0=a >>>", + "<<< 1=b >>>", + "<<< 2=c >>>", + "<<< 3=aaa >>>", + "<<< 4=bbb >>>", + "<<< 5=ccc >>>" + ], + "sort_mapdata_sort_slist": [ + "<<< 0=a >>>", + "<<< 1=aaa >>>", + "<<< 2=b >>>", + "<<< 3=bbb >>>", + "<<< 4=c >>>", + "<<< 5=ccc >>>" + ] +} diff --git a/tests/acceptance/01_vars/02_functions/wrap_invalid_container_fncalls.x.cf b/tests/acceptance/01_vars/02_functions/wrap_invalid_container_fncalls.x.cf new file mode 100644 index 0000000000..effbf6914c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/wrap_invalid_container_fncalls.x.cf @@ -0,0 +1,29 @@ +####################################################### +# +# Test wrapped fncalls with mismatched data types +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "base_list" slist => { "a", "b", "c", "aaa", "bbb", "ccc" }; + + # this should be a function call failure + "canonify" string => canonify(reverse(base_list)); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_pass($(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/03_lists/001.cf b/tests/acceptance/01_vars/03_lists/001.cf new file mode 100644 index 0000000000..845b5c10ba --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/001.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Nested iterations of slists (the ordering is not guaranteed, so we sort) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => + " +a x +a y +a z +b x +b y +b z +c x +c y +c z +a cf_null +b cf_null +c cf_null +cf_null +blah"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle edit_line test_insert +{ + vars: + "one" slist => { "a", "b", "c" }; + "two" slist => { "x", "y", "z" }; + "empty" slist => {}; + "null" slist => { "cf_null" }; + "nulls" slist => { "cf_null", "cf_null", "cf_null" }; + + "not_an_array" string => "blah"; + "no_values" slist => getvalues("not_an_array"); + + insert_lines: + "$(one) $(two)"; + # cf_null is no longer treated specially, they are regular list elements + "$(one) $(nulls)"; + "$(null)"; + "$(nulls)"; + # the following should do nothing + "$(one) $(two) $(empty)"; + "$(empty) $(one) $(null) $(two)"; + "$(null) $(empty)"; + "$(no_values)"; +} + +####################################################### + +bundle agent check +{ + + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/002.cf b/tests/acceptance/01_vars/03_lists/002.cf new file mode 100644 index 0000000000..2561009e71 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/002.cf @@ -0,0 +1,82 @@ +####################################################### +# +# Iterations of slists +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => + " +a +b +c"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle edit_line test_insert +{ + vars: + "one" slist => { "a", "b", "c" }; + + insert_lines: + "$(one)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/003.cf b/tests/acceptance/01_vars/03_lists/003.cf new file mode 100644 index 0000000000..9e220069da --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/003.cf @@ -0,0 +1,80 @@ +####################################################### +# +# Iteration on slist of size 1 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => + " +a"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle edit_line test_insert +{ + vars: + "one" slist => { "a" }; + + insert_lines: + "$(one)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/004.cf b/tests/acceptance/01_vars/03_lists/004.cf new file mode 100644 index 0000000000..bbe361040a --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/004.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Iterations of non-local slists +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => + " +a +b +c"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle agent otherbundle +{ + vars: + "one" slist => { "a", "b", "c" }; +} + +bundle edit_line test_insert +{ + vars: + "one" slist => { "c", "b", "a" }; + + insert_lines: + "$(otherbundle.one)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/005.cf b/tests/acceptance/01_vars/03_lists/005.cf new file mode 100644 index 0000000000..775d5063b6 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/005.cf @@ -0,0 +1,91 @@ +####################################################### +# +# Iterations of non-local slists +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => + " +XX +XXX +XXXX"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle agent otherbundle +{ + vars: + "list" slist => { "one", "two", "three" }; +} + +bundle edit_line test_insert +{ + vars: + "array[one]" string => "XX"; + "array[two]" string => "XXX"; + "array[three]" string => "XXXX"; + "list" slist => { "three", "two", "one" }; + + insert_lines: + "$(array[$(otherbundle.list)])"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/006.cf b/tests/acceptance/01_vars/03_lists/006.cf new file mode 100644 index 0000000000..2c6784a2e9 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/006.cf @@ -0,0 +1,39 @@ +# Test that reglist() does not match cf_null values + +# NOTE: tests conditions have been reversed! cf_null is no longer +# treated specially! + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => ""; +} + +bundle agent test +{ + vars: + "dummy" string => ""; +} + +bundle agent check +{ + vars: + "emptylist" slist => { "cf_null" }; + + classes: + "ok" expression => reglist("@(emptylist)", ".+"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/01_vars/03_lists/007.cf b/tests/acceptance/01_vars/03_lists/007.cf new file mode 100644 index 0000000000..a6c00ee3bb --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/007.cf @@ -0,0 +1,43 @@ +# Test that cf_null lists can be joined + +# NOTE: tests conditions have been reversed! cf_null is no longer +# treated specially! + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => ""; +} + +bundle agent test +{ + vars: + "dummy" string => ""; +} + +bundle agent check +{ + vars: + "emptylist" slist => { "cf_null" }; + "joined" string => join(":", "emptylist"); + + classes: + "ok1" expression => strcmp($(joined), ""); + + "ok" not => "ok1"; # the list should contain cf_null + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + DEBUG:: + "Expected cf_null string, got $(joined)!"; +} diff --git a/tests/acceptance/01_vars/03_lists/008.cf b/tests/acceptance/01_vars/03_lists/008.cf new file mode 100644 index 0000000000..da9f6fae6b --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/008.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Test list iteration over cf_null special blank character, start of list +# +# NOTE: tests conditions have been reversed! cf_null is no longer +# treated specially! +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + + "expected" string => +"cf_null +1 +2 +3"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert("$(expected)"), + edit_defaults => init_empty; + +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_list_null, + edit_defaults => init_empty; + +} + +bundle edit_line test_list_null +{ + vars: + + "lines" slist => { "cf_null", "1", "2", "3" }; + + insert_lines: + "$(lines)" + whitespace_policy => { "exact_match" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/03_lists/009.cf b/tests/acceptance/01_vars/03_lists/009.cf new file mode 100644 index 0000000000..93f7e3280e --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/009.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Test list iteration over cf_null special blank character, mid list +# +# NOTE: tests conditions have been reversed! cf_null is no longer +# treated specially! +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + + "expected" string => +"1 +cf_null +2 +3"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert("$(expected)"), + edit_defaults => init_empty; + +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_list_null, + edit_defaults => init_empty; + +} + +bundle edit_line test_list_null +{ + vars: + + "lines" slist => { "1", "cf_null", "2", "3" }; + + insert_lines: + "$(lines)" + whitespace_policy => { "exact_match" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/03_lists/010.cf b/tests/acceptance/01_vars/03_lists/010.cf new file mode 100644 index 0000000000..a9ed24221f --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/010.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Test list iteration over cf_null special blank character, end of list +# +# NOTE: tests conditions have been reversed! cf_null is no longer +# treated specially! +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + + "expected" string => +"1 +2 +3 +cf_null"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert("$(expected)"), + edit_defaults => init_empty; + +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_list_null, + edit_defaults => init_empty; + +} + +bundle edit_line test_list_null +{ + vars: + + "lines" slist => { "1", "2", "3", "cf_null" }; + + insert_lines: + "$(lines)" + whitespace_policy => { "exact_match" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/03_lists/011.cf b/tests/acceptance/01_vars/03_lists/011.cf new file mode 100644 index 0000000000..ee25c57443 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/011.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test list iteration over cf_null special blank characters, all blank +# +# NOTE: tests conditions have been reversed! cf_null is no longer +# treated specially! +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + + files: + "$(G.testfile).expected" + create => "true", + edit_defaults => init_empty, + edit_line => init_insert("cf_null"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_list_null, + edit_defaults => init_empty; + +} + +bundle edit_line test_list_null +{ + vars: + + "lines" slist => { "cf_null", "cf_null", "cf_null" }; + + insert_lines: + "$(lines)" + whitespace_policy => { "exact_match" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/01_vars/03_lists/012.cf b/tests/acceptance/01_vars/03_lists/012.cf new file mode 100644 index 0000000000..07a2cebc43 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/012.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Iterations of non-local slists inside non-local (sclar) array +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => + " +XX +XXX +XXXX"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle agent otherbundle +{ + vars: + "array[one]" string => "XX"; + "array[two]" string => "XXX"; + "array[three]" string => "XXXX"; + "list" slist => { "one", "two", "three" }; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(otherbundle.array[$(otherbundle.list)])"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/013.cf b/tests/acceptance/01_vars/03_lists/013.cf new file mode 100644 index 0000000000..6b56a53f26 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/013.cf @@ -0,0 +1,71 @@ +# Test expansion of remote arrays containing lists + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + nova_edition:: + host_licenses_paid => "5"; +} + +bundle agent init +{ + vars: + "array[one]" string => "1"; + "array[two]" string => "2"; + "array[list]" slist => { "first", "second", "last" }; + + "keys" slist => { "one", "two", "list" }; + + "expected" string => " +1 +2 +first +second +last"; + + "actual" string => ""; + + "states" slist => { "expected", "actual" }; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$($(states))"), + edit_defaults => init_empty; +} + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle edit_line init_insert(str) { + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty { + empty_file_before_editing => "true"; +} + +bundle edit_line test_insert { + insert_lines: + "$(init.array[$(init.keys)])"; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); + + reports: + DEBUG:: + "can expand $(init.array[$(init.keys)])"; +} diff --git a/tests/acceptance/01_vars/03_lists/014.cf b/tests/acceptance/01_vars/03_lists/014.cf new file mode 100644 index 0000000000..763fc2bd13 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/014.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Iteration on non-local empty slist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => ""; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle agent otherbundle +{ + vars: + "list" slist => { }; +} + +bundle edit_line test_insert +{ + vars: + "list" slist => { "a", "b", "c" }; + + insert_lines: + "$(otherbundle.list)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/015.cf b/tests/acceptance/01_vars/03_lists/015.cf new file mode 100644 index 0000000000..ad0bf08f7c --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/015.cf @@ -0,0 +1,78 @@ +####################################################### +# +# Iteration on slist of size 1 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => ""; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert; + +} + +bundle edit_line test_insert +{ + vars: + "one" slist => { }; + + insert_lines: + "$(one)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/016.cf b/tests/acceptance/01_vars/03_lists/016.cf new file mode 100644 index 0000000000..239e68b81a --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/016.cf @@ -0,0 +1,56 @@ +############################################################################## +# +# Redmine #2936: Check that list variables under reserved scope expand +# as they should. The initial bug report was for reports promises only, +# but here we check it for files promises. +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle agent init +{ +# TODO delete the testfile +} + + +bundle agent test +{ +files: + "$(G.testfile).actual" + create => "true", + edit_defaults => init_empty, + edit_line => test_insert_macs; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line test_insert_macs +{ +insert_lines: + "$(sys.hardware_addresses)"; +} + + +bundle agent check +{ +# If the file contains the string "sys.hardware_addresses" then we +# failed to expand the variable! +classes: + "ok" not => regline(".*sys\.hardware_addresses.*", "$(G.testfile).actual"); + +reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/03_lists/double_expand_remote_list.cf b/tests/acceptance/01_vars/03_lists/double_expand_remote_list.cf new file mode 100644 index 0000000000..3832c5fc95 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/double_expand_remote_list.cf @@ -0,0 +1,40 @@ +# Redmine#6866 +# Test that varibles are fully expanded + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check +{ + meta: + # "tags" + # slist => { "redmine#6866" }; + "test_soft_fail" + meta => { "redmine#6866" }, + string => "any"; + + + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + "expected" string => "R: Direct: 1 +R: Direct: 2 +R: Direct: 3 +R: Indirect: 1 +R: Indirect: 2 +R: Indirect: 3"; + + methods: + "check" + usebundle => dcs_passif_output(".*$(expected).*", "", $(command), $(this.promise_filename)); + + reports: + DEBUG:: + "=== Expected === +'$(expected)' +================"; + +} diff --git a/tests/acceptance/01_vars/03_lists/double_expand_remote_list.cf.sub b/tests/acceptance/01_vars/03_lists/double_expand_remote_list.cf.sub new file mode 100644 index 0000000000..7d75a1ba17 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/double_expand_remote_list.cf.sub @@ -0,0 +1,31 @@ +# Redmine#6866 +# Test that nested expansion of arrays works + +body common control +{ + bundlesequence => { "init", "test", "report" }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "list" slist => { "1", "2", "3" }; + "list2" slist => { "a", "b", "c" }; +} + +bundle agent test +{ + vars: + "other_list" string => "init.list"; + "list_of_lists" slist => { "init.list", "init.list2" }; +} + +bundle agent report +{ + reports: + "Direct: $(init.list)"; + "Indirect: $($(test.other_list))"; +#TODO "Indirect lists: $($(test.list_of_lists))"; +# Expected different lines of: 1 2 3 a b c +} diff --git a/tests/acceptance/01_vars/03_lists/extend_list.cf b/tests/acceptance/01_vars/03_lists/extend_list.cf new file mode 100644 index 0000000000..a70e362a99 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/extend_list.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Test that slists can be extended (redefined using its own previous value) +# Redmine:4335 and 6541 +####################################################### + +body common control +{ + version => "1.0"; + bundlesequence => { "init", "test", "check" }; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "x" slist => { "xyz" }, policy => "free"; + "x" slist => { @(x), "extended" }, policy => "free"; + "x" slist => { @(x), "more" }, policy => "free"; + + "x1" slist => { @(x), @(undefined1), @(undefined2) }, policy => "ifdefined"; + "x2" slist => { @(undefined1), @(x), @(undefined2) }, policy => "ifdefined"; + "x3" slist => { @(undefined1), @(undefined2), @(x) }, policy => "ifdefined"; + "x4" slist => { @(x), @(undefined1), @(undefined2), @(x) }, policy => "ifdefined"; + "x5" slist => { @(undefined1), "entry", @(undefined2) }, policy => "ifdefined"; +} + +####################################################### + +bundle agent check +{ + vars: + "expected" string => "xyz,extended,more"; + "joined_test_x" string => join(",", "test.x"); + "joined_test_x1" string => join(",", "test.x1"); + "joined_test_x2" string => join(",", "test.x2"); + "joined_test_x3" string => join(",", "test.x3"); + "joined_test_x4" string => join(",", "test.x4"); + "joined_test_x5" string => join(",", "test.x5"); + + classes: + "ok0" expression => strcmp($(expected), $(joined_test_x)); + "ok1" expression => strcmp($(expected), $(joined_test_x1)); + "ok2" expression => strcmp($(expected), $(joined_test_x2)); + "ok3" expression => strcmp($(expected), $(joined_test_x3)); + "ok4" expression => strcmp("$(expected),$(expected)", $(joined_test_x4)); + "ok5" expression => strcmp("entry", $(joined_test_x5)); + + "ok" and => { "ok0", "ok1", "ok2", "ok3", "ok4", "ok5" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG.!ok0:: + "x: $(joined_test_x)"; + DEBUG.!ok1:: + "expected x1: $(expected)"; + " x1: $(joined_test_x1)"; + DEBUG.!ok2:: + "x2: $(joined_test_x2)"; + DEBUG.!ok3:: + "x3: $(joined_test_x3)"; + DEBUG.!ok4:: + "x4: $(joined_test_x4)"; + DEBUG.!ok5:: + "x5: $(joined_test_x5)"; +} diff --git a/tests/acceptance/01_vars/03_lists/interpolation_order.cf b/tests/acceptance/01_vars/03_lists/interpolation_order.cf new file mode 100644 index 0000000000..785d5d1ce6 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/interpolation_order.cf @@ -0,0 +1,34 @@ +# Redmine#3122: ensure lists are interpolated in the correct order + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "first_list" slist => { "item1", "item2", "item3", "item4", "item5" }; +} + +bundle agent test +{ + vars: + "local" slist => { @(second_list) }; + "second_list" slist => { @(init.first_list), "item6" }; +} + +bundle agent check +{ + vars: + "expected" string => '{ "item1", "item2", "item3", "item4", "item5", "item6" }'; + "joined" string => format("%S", "test.local"); + + methods: + "check" usebundle => dcs_check_strcmp($(expected), + $(joined), + "$(this.promise_filename)", + "no"); +} diff --git a/tests/acceptance/01_vars/03_lists/namespaced_list_including_list_expanded.cf b/tests/acceptance/01_vars/03_lists/namespaced_list_including_list_expanded.cf new file mode 100644 index 0000000000..81d5b368ac --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/namespaced_list_including_list_expanded.cf @@ -0,0 +1,47 @@ +# Redmine#4445: Ensure that namespaced lists that include other lists are fully expanded + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "agent_output" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "noshell"); +} + +bundle agent check +{ + classes: + "unexpanded_list" + expression => regcmp(".*packages.*", $(test.agent_output)), + comment => "check to see if an included list is unexpanded"; + + "found_from_packages" + expression => regcmp(".*mysql-client.*", $(test.agent_output)), + comment => "found something good from one list"; + + "found_from_server_packages" + expression => regcmp(".*mysql-server.*", $(test.agent_output)), + comment => "found something good from one list"; + + "found_expected_output" + and => { "found_from_packages", "found_from_server_packages" }, + comment => "Found something good from both included lists"; + + + "ok" expression => "!unexpanded_list.found_expected_output"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/03_lists/namespaced_list_including_list_expanded.cf.sub b/tests/acceptance/01_vars/03_lists/namespaced_list_including_list_expanded.cf.sub new file mode 100644 index 0000000000..cea8f78d9d --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/namespaced_list_including_list_expanded.cf.sub @@ -0,0 +1,32 @@ +body common control +{ + bundlesequence => { "go" }; + version => "1.0"; +} + +bundle agent go +{ + methods: + "if you like then you better put a namespace on it" + usebundle => test:namespace_check; +} + +body file control +{ + namespace => "test"; +} + +bundle agent namespace_check +{ + classes: + "server" expression => "any"; + + vars: + "packages" slist => { "mysql-client", "mysql-common" }, policy => "free"; + "server_packages" slist => { "mysql-server", "mysql-server-core" }, policy => "free"; + + "all_packages" slist => { @(packages), @(server_packages) }, policy => "free"; + +reports: + "$(all_packages)"; +} diff --git a/tests/acceptance/01_vars/03_lists/namespaces/passing_slists.cf b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists.cf new file mode 100644 index 0000000000..a60e758edc --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists.cf @@ -0,0 +1,34 @@ +body common control +{ + inputs => { "../../../default.cf.sub", "passing_slists.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + abortclasses => { "mylist___mylist_" }; +} + +bundle common g +{ + vars: + "slist_pass" slist => { "1", "2", "3" }; +} + +bundle agent init +{ + vars: +} + +bundle agent test +{ + methods: + "test_in_namespace" usebundle => b:test_in_namespace(@(g.slist_pass)); +} + +bundle agent check +{ + methods: + "check_in_namespace" usebundle => b:check_in_namespace(join(",", "g.slist_pass"), "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/03_lists/namespaces/passing_slists.cf.sub b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists.cf.sub new file mode 100644 index 0000000000..1495341de4 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists.cf.sub @@ -0,0 +1,33 @@ +body file control +{ + namespace => "b"; +} + +bundle agent test_in_namespace(passed_list) +{ + vars: + "mylist" slist => { @(passed_list) }; + "joined" string => join(",", "mylist"); + + classes: + "mylist_$(mylist)" expression => "any"; + + reports: + default:DEBUG:: + "BUG: mylist___mylist_ was defined!" ifvarclass => "mylist___mylist_"; + "BUG: mylist_1 not defined" ifvarclass => "!mylist_1"; + "GOOD: mylist_1 was defined" ifvarclass => "mylist_1"; + "mylist = $(b:test_in_namespace.mylist)"; + "mylist without namespace = $(test_in_namespace.mylist)"; + "mylist without qualifiers = $(mylist)"; + "Set joined to $(joined)"; +} + +bundle agent check_in_namespace(target,test) +{ + methods: + "any" usebundle => default:dcs_check_strcmp("$(b:test_in_namespace.joined)", "$(target)", "$(test)", "no"); + reports: + default:DEBUG:: + "comparing '$(b:test_in_namespace.joined)' and '$(target)'"; +} diff --git a/tests/acceptance/01_vars/03_lists/namespaces/passing_slists2.cf b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists2.cf new file mode 100644 index 0000000000..e41439d036 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists2.cf @@ -0,0 +1,33 @@ +# Redmine#4494 + +# the agent should not abort when a bundle is passed a qualified slist +# name that doesn't actually exist + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + methods: + "watch" usebundle => watch(@(run.watch)); +} + +bundle agent check +{ + methods: + "pass" usebundle => dcs_passif("any", $(this.promise_filename)); +} + +bundle agent watch(given_watch) +{ + vars: + "watch" slist => { @(given_watch) }; +} diff --git a/tests/acceptance/01_vars/03_lists/namespaces/passing_slists3.cf b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists3.cf new file mode 100644 index 0000000000..e355ebf9ab --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists3.cf @@ -0,0 +1,36 @@ +# Redmine#4494 + +# the agent should not try to process files promises on the unexpanded +# version of a passed list + +# here, the error manifests as "Failed to chdir into @(given_watch)" +# but it also generates a NOTKEPT compliance entry + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "output" string => execresult("$(sys.cf_agent) -K -f $(this.promise_filename).sub | $(G.grep) 'Failed to chdir' 2>&1", "useshell"); +} + +bundle agent check +{ + methods: + "pass" usebundle => dcs_check_regcmp(".*Failed to chdir into '@.given_watch.'.*", + $(test.output), + $(this.promise_filename), + "true"); + reports: + EXTRA:: + "Output = $(test.output)"; +} diff --git a/tests/acceptance/01_vars/03_lists/namespaces/passing_slists3.cf.sub b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists3.cf.sub new file mode 100644 index 0000000000..61a70f1216 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/namespaces/passing_slists3.cf.sub @@ -0,0 +1,74 @@ +# Redmine#4494 + +# - passing a slist to a non-namespaced agent bundle (works) + +# - passing a slist to a namespaced agent bundle with no "classes" promises (works) + +# - passing a slist to a namespaced agent bundle with a "classes" promises (emits spurious "Failed to chdir..." message) + + +body common control +{ + bundlesequence => { test }; +} + +bundle agent test +{ + vars: + "watch" slist => { "/" }; + + methods: + "watch" usebundle => watch(@(test.watch)); + "nswatch" usebundle => ns:watch(@(test.watch)); + "ns2watch" usebundle => ns2:watch(@(test.watch)); +} + +bundle agent watch(given_watch) +{ + classes: + + vars: + "watch" slist => { @(given_watch) }; + + files: + "$(watch)"; + + reports: + "$(this.namespace):$(this.bundle): got local watch = $(watch)"; +} + +body file control +{ + namespace => "ns"; +} + +bundle agent watch(given_watch) +{ + classes: + + vars: + "watch" slist => { @(given_watch) }; + + files: + "$(watch)"; + + reports: + "$(this.namespace):$(this.bundle): got local watch = $(watch)"; +} + +body file control +{ + namespace => "ns2"; +} + +bundle agent watch(given_watch) +{ + vars: + "watch" slist => { @(given_watch) }; + + files: + "$(watch)"; + + reports: + "$(this.namespace):$(this.bundle): got local watch = $(watch)"; +} diff --git a/tests/acceptance/01_vars/03_lists/nested_lists.cf b/tests/acceptance/01_vars/03_lists/nested_lists.cf new file mode 100644 index 0000000000..44e397e349 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/nested_lists.cf @@ -0,0 +1,126 @@ +####################################################### +# +# Nested iterations of slists (the ordering is not guaranteed, so we sort) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => ""; + + "expected" string => + " +Nesting: 1 +Nesting: 2 + +Nothing: +Unknown: $(unknown) +A list: @(lists) +Other empty: 5 +Other empty: 6 +Parameter nesting: 7 +Parameter nesting: 8 +Sub nesting: 7 +Sub nesting: 8"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => insert_line("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line insert_line(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent otherbundle +{ + vars: + "list_a" slist => { }; + "list_b" slist => { "5", "6" }; + "lists" slist => { "a", "b" }; +} + +bundle edit_line test_insert +{ + vars: + "list_a" slist => { "1", "2" }; + "list_b" slist => {}; + "lists" slist => { "a", "b" }; + "nothing" string => ""; + + insert_lines: + "Nesting: $(list_$(lists))"; + "Expanded: $(lists) $(list_a) $(list_b)"; + "$(nothing)" insert_type => "preserve_all_lines"; # ignore existing newline + "Nothing: $(nothing)"; + "Unknown: $(unknown)"; + "A list: @(lists)"; + "Other empty: $(otherbundle.list_$(otherbundle.lists))"; +} + +bundle agent sub(text) +{ + files: + "$(G.testfile).actual" + edit_line => insert_line("Sub nesting: $(text)"); +} + +bundle agent test +{ + vars: + "list_a" slist => { }; + "list_b" slist => { "7", "8" }; + "lists" slist => { "a", "b" }; + + files: + "$(G.testfile).actual" + edit_line => test_insert; + + "$(G.testfile).actual" + edit_line => insert_line("Parameter nesting: $(list_$(lists))"); + + methods: + "nesting" usebundle => sub("$(list_$(lists))"); +} + +####################################################### + +bundle agent check +{ + + methods: + "any" usebundle => dcs_sort("$(G.testfile).actual", "$(G.testfile).actual.sorted"); + "any" usebundle => dcs_sort("$(G.testfile).expected", "$(G.testfile).expected.sorted"); + "any" usebundle => dcs_check_diff("$(G.testfile).actual.sorted", + "$(G.testfile).expected.sorted", + "$(this.promise_filename)"); +} + +body contain check_in_shell +{ + useshell => "true"; +} + diff --git a/tests/acceptance/01_vars/03_lists/usebundle_naked.cf b/tests/acceptance/01_vars/03_lists/usebundle_naked.cf new file mode 100644 index 0000000000..c8e71a7669 --- /dev/null +++ b/tests/acceptance/01_vars/03_lists/usebundle_naked.cf @@ -0,0 +1,40 @@ +# Test usebundle passing a naked list from local scope + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "expected" string => "1,2,3"; +} + +bundle agent test +{ + vars: + "testlist" slist => {"1","2","3"}; + methods: + "call sub" usebundle => sub(@{testlist}); +} + +bundle agent sub(sublist) +{ + vars: + "joined" string => join(",", "sublist"); + reports: + DEBUG:: + "sub joined $(joined)"; +} + +bundle agent check +{ + methods: + "check results" usebundle => dcs_check_strcmp("$(init.expected)", "$(sub.joined)", "$(this.promise_filename)", "no"); + reports: + DEBUG:: + "comparing '$(init.expected)' with '$(sub.joined)'"; +} diff --git a/tests/acceptance/01_vars/04_containers/canonical_json.cf b/tests/acceptance/01_vars/04_containers/canonical_json.cf new file mode 100644 index 0000000000..b186235722 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/canonical_json.cf @@ -0,0 +1,41 @@ +####################################################### +# +# Test canonical JSON output +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "c" data => parsejson('{ +"q": 100, +"a": 200, +"c": [1,2,3,4] +}'); +} + +bundle agent test +{ + vars: + "actual" string => format("%S", "init.c"); +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_strcmp($(test.actual), + '{"a":200,"c":[1,2,3,4],"q":100}', + $(this.promise_filename), + "no"); +} diff --git a/tests/acceptance/01_vars/04_containers/execresult_and_as_data.cf b/tests/acceptance/01_vars/04_containers/execresult_and_as_data.cf new file mode 100644 index 0000000000..8404a26236 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/execresult_and_as_data.cf @@ -0,0 +1,41 @@ +####################################################### +# +# Test the ability to call the same command with execresult and execresult_as_data +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + !windows:: + "res1" string => execresult("echo test", "useshell"); + "res2" data => execresult_as_data("echo test", "useshell", "stdout"); + windows:: + "res1" string => execresult("echo test", "powershell"); + "res2" data => execresult_as_data("echo test", "powershell", "stdout"); +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_strcmp("${test.res1}", "${test.res2[output]}", "$(this.promise_filename)", "no"); +} diff --git a/tests/acceptance/01_vars/04_containers/extraction.cf b/tests/acceptance/01_vars/04_containers/extraction.cf new file mode 100644 index 0000000000..891c1b2573 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/extraction.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Redmine#4301: "Couldn't find extracted variable" +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "config" data => parsejson('{"*.alert": "root"}'); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_set_line_based("test.config", " ", "\s+", ".*", "\s*#\s*"); +} + +# this is the stdlib set_line_based cut down +bundle edit_line test_set_line_based(v, sep, bp, kp, cp) +{ + + vars: + # even though it's not used in the iteration, the vkeys variable + # is required for the test to fail + "vkeys" slist => getindices("$(v)"); + "i" slist => grep($(kp), vkeys); + + # Escape the value (had a problem with special characters and regex's) + "ev[$(i)]" string => escape("$($(v)[$(i)])"); +} + +####################################################### + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/01_vars/04_containers/inline_json.cf b/tests/acceptance/01_vars/04_containers/inline_json.cf new file mode 100644 index 0000000000..40287b0188 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/inline_json.cf @@ -0,0 +1,52 @@ +####################################################### +# +# Test inline JSON and YAML data +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "included" string => "( included text )"; + "options1" data => ' +{ + "max_content": 512, + "verbose": 0, + "headers": [ "Foo: bar" ] +}'; + + "options3" data => ' +{ + "max_content": 512, + "verbose": "$(included)", + "headers": [ "Foo: bar" ] +}'; + + # same as options3 but with bareword keys + "options4" data => ' +{ + max_content: 512, + verbose: "$(included)", + headers: [ "Foo: bar" ] +}'; + +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/04_containers/inline_json.cf.expected.json b/tests/acceptance/01_vars/04_containers/inline_json.cf.expected.json new file mode 100644 index 0000000000..e55417eef6 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/inline_json.cf.expected.json @@ -0,0 +1,24 @@ +{ + "included": "( included text )", + "options1": { + "headers": [ + "Foo: bar" + ], + "max_content": 512, + "verbose": 0 + }, + "options3": { + "headers": [ + "Foo: bar" + ], + "max_content": 512, + "verbose": "( included text )" + }, + "options4": { + "headers": [ + "Foo: bar" + ], + "max_content": 512, + "verbose": "( included text )" + } +} diff --git a/tests/acceptance/01_vars/04_containers/inline_json_parse.cf b/tests/acceptance/01_vars/04_containers/inline_json_parse.cf new file mode 100644 index 0000000000..8dd306336a --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/inline_json_parse.cf @@ -0,0 +1,23 @@ +####################################################### +# +# Test inline JSON and YAML data +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_passif_output(".*e8hoeAM1UJC5xmqwCx9iJARKZ9qGk8GU.*", + "", + "$(sys.cf_promises) -p json $(this.promise_filename).sub", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/04_containers/inline_json_parse.cf.sub b/tests/acceptance/01_vars/04_containers/inline_json_parse.cf.sub new file mode 100644 index 0000000000..92ff088206 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/inline_json_parse.cf.sub @@ -0,0 +1,5 @@ +bundle agent mytest +{ + vars: + "inline" data => '{ "key": "special value e8hoeAM1UJC5xmqwCx9iJARKZ9qGk8GU" }'; +} diff --git a/tests/acceptance/01_vars/04_containers/inline_yaml.cf b/tests/acceptance/01_vars/04_containers/inline_yaml.cf new file mode 100644 index 0000000000..7d518a5db3 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/inline_yaml.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test inline JSON and YAML data +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + feature_yaml:: + "included" string => "( included text )"; + "options2" data => '--- +a: b +c: d +e: + - 1 + - 2 +'; + + "options4" data => '--- +a: $(included) +c: d +e: + - 1 + - 2 +'; +} + +####################################################### + +bundle agent check +{ + methods: + feature_yaml:: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); + !feature_yaml:: + "check" usebundle => dcs_pass($(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/04_containers/inline_yaml.cf.expected.json b/tests/acceptance/01_vars/04_containers/inline_yaml.cf.expected.json new file mode 100644 index 0000000000..460cc75d87 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/inline_yaml.cf.expected.json @@ -0,0 +1,19 @@ +{ + "included": "( included text )", + "options2": { + "a": "b", + "c": "d", + "e": [ + 1, + 2 + ] + }, + "options4": { + "a": "( included text )", + "c": "d", + "e": [ + 1, + 2 + ] + } +} diff --git a/tests/acceptance/01_vars/04_containers/iterate_over_data_array.cf b/tests/acceptance/01_vars/04_containers/iterate_over_data_array.cf new file mode 100644 index 0000000000..dde9ecac55 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/iterate_over_data_array.cf @@ -0,0 +1,38 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => tidy; +} + +bundle agent test +{ + meta: + + "description" -> { "zendesk:3437" } + string => "Test that we can iterate over an array inside of + a data container without first extracting it. And that the order of + iteration is preserved."; + + vars: + "data3" data => '{ "iteration_order":[ "b", "c", "a" ], "b":{ "key": "bkeyval" }, "c":{"key":"ckeyval"}, "a":{"key":"akeyval"}}'; + + files: + "$(G.testfile)" + create => "true", + edit_line => append_if_no_line("$(data3[$(data3[iteration_order])][key])"); +} + +bundle agent check +{ + methods: + "Check" usebundle => dcs_check_diff( $(G.testfile), "$(this.promise_filename).expected", $(this.promise_filename) ); +} + diff --git a/tests/acceptance/01_vars/04_containers/iterate_over_data_array.cf.expected b/tests/acceptance/01_vars/04_containers/iterate_over_data_array.cf.expected new file mode 100644 index 0000000000..16826ba03d --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/iterate_over_data_array.cf.expected @@ -0,0 +1,3 @@ +bkeyval +ckeyval +akeyval diff --git a/tests/acceptance/01_vars/04_containers/iterate_over_data_container_with_nulls.cf b/tests/acceptance/01_vars/04_containers/iterate_over_data_container_with_nulls.cf new file mode 100644 index 0000000000..795bd02e1e --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/iterate_over_data_container_with_nulls.cf @@ -0,0 +1,80 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + commands: + windows:: + "$(G.dos2unix) $(this.promise_filename).expected" -> { "ENT-10433" }; + + files: + "$(G.testdir)/actual.txt" + delete => tidy; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2230" } + string => "Test that iteration over the getindices() of a data container + is not interrupted by nulls."; + + vars: + "data" data => '{ + "instanceId": "i-f444554e", + "billingProducts": null, + "instanceType": "m3.large", + "accountId": "444444444444", + "pendingTime": "2015-12-02T19:47:57Z", + "imageId": "ami-44444444", + "kernelId": "aki-44444444", + "ramdiskId": null, + "architecture": "x86_64", + "region": "eu-west-1", + "version": "2010-08-31", + "availabilityZone": "eu-west-1c", + "privateIp": "10.44.44.4", + "devpayProductCodes": null +}'; + + "data2" data => '{ + "instanceId" : "i-f444554e", + "instanceType" : "m3.large", + "accountId" : "444444444444", + "pendingTime" : "2015-12-02T19:47:57Z", + "imageId" : "ami-44444444", + "kernelId" : "aki-44444444", + "architecture" : "x86_64", + "region" : "eu-west-1", + "version" : "2010-08-31", + "availabilityZone" : "eu-west-1c", + "privateIp" : "10.44.44.4", +}'; + + "keys" slist => getindices("data"); + "$(keys)" string => "$(data[$(keys)])"; + + "keys2" slist => getindices("data2"); + "$(keys2)" string => "$(data2[$(keys2)])"; + + reports: + + "with nulls: $(keys) = $(data[$(keys)])" + report_to_file => "$(G.testdir)/actual.txt"; + + "without nulls: $(keys2) = $(data2[$(keys2)])" + report_to_file => "$(G.testdir)/actual.txt"; +} + +bundle agent check +{ + methods: + "Pass/Fail" + usebundle => dcs_check_diff( "$(G.testdir)/actual.txt", + "$(this.promise_filename).expected", + $(this.promise_filename) ); +} diff --git a/tests/acceptance/01_vars/04_containers/iterate_over_data_container_with_nulls.cf.expected b/tests/acceptance/01_vars/04_containers/iterate_over_data_container_with_nulls.cf.expected new file mode 100644 index 0000000000..5ee2874534 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/iterate_over_data_container_with_nulls.cf.expected @@ -0,0 +1,22 @@ +with nulls: instanceId = i-f444554e +with nulls: instanceType = m3.large +with nulls: accountId = 444444444444 +with nulls: pendingTime = 2015-12-02T19:47:57Z +with nulls: imageId = ami-44444444 +with nulls: kernelId = aki-44444444 +with nulls: architecture = x86_64 +with nulls: region = eu-west-1 +with nulls: version = 2010-08-31 +with nulls: availabilityZone = eu-west-1c +with nulls: privateIp = 10.44.44.4 +without nulls: instanceId = i-f444554e +without nulls: instanceType = m3.large +without nulls: accountId = 444444444444 +without nulls: pendingTime = 2015-12-02T19:47:57Z +without nulls: imageId = ami-44444444 +without nulls: kernelId = aki-44444444 +without nulls: architecture = x86_64 +without nulls: region = eu-west-1 +without nulls: version = 2010-08-31 +without nulls: availabilityZone = eu-west-1c +without nulls: privateIp = 10.44.44.4 diff --git a/tests/acceptance/01_vars/04_containers/mergecontainer.cf b/tests/acceptance/01_vars/04_containers/mergecontainer.cf new file mode 100644 index 0000000000..fde02e2605 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/mergecontainer.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test mergedata() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + 'a'; + 'b'; + 'c'; +} + +####################################################### + +bundle agent test +{ + vars: + "load1" data => parsejson('{"x": "y", "third": 123}'); + + "load2" data => parsejson(' +{ + "first": 1, + "seconds": 2, + "third": [ "a", "b", "c" ], + "fourth": null +} +'); + + "load" data => mergedata("load1", "load2"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load[third])"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/mergecontainer_arrays.cf b/tests/acceptance/01_vars/04_containers/mergecontainer_arrays.cf new file mode 100644 index 0000000000..00f4382884 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/mergecontainer_arrays.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test mergejson() with two JSON arrays +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + 'a'; + 'b'; + 'c'; +} + +####################################################### + +bundle agent test +{ + vars: + "load1" data => parsejson('["a", "b"]'); + "load2" data => parsejson('["c"]'); + "load" data => mergedata("load1", "load2"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/mergecontainer_conflict.cf b/tests/acceptance/01_vars/04_containers/mergecontainer_conflict.cf new file mode 100644 index 0000000000..d951260965 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/mergecontainer_conflict.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test mergejson() with mismatched datas +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + "$(const.dollar)(test.load[third])"; +} + +####################################################### + +bundle agent test +{ + vars: + "load1" data => parsejson('["x": "y", "third"]'); + + "load2" data => parsejson(' +{ + "first": 1, + "seconds": 2, + "third": [ "a", "b", "c" ], + "fourth": null +} +'); + + # this should fail + "load" data => mergedata("load1", "load2"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load[third])"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/mergedata_with_unresolved_variables_does_not_define_datacontainer.cf b/tests/acceptance/01_vars/04_containers/mergedata_with_unresolved_variables_does_not_define_datacontainer.cf new file mode 100644 index 0000000000..a39627aff2 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/mergedata_with_unresolved_variables_does_not_define_datacontainer.cf @@ -0,0 +1,39 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that data containers are NOT defined when mergedata is used with a non-existing data container"; +} + +bundle agent check +{ + vars: + "data" + data => mergedata("missing_ns:missing_bundle.missing_var"); + + classes: + "fail" + expression => isvariable("data"); + + "ok" + not => isvariable("data"); + + "exception" + and => { "fail", "ok" }; + + reports: + fail:: + "$(this.promise_filename) FAIL"; + + ok.!fail:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf new file mode 100644 index 0000000000..f910fa561f --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf @@ -0,0 +1,162 @@ +# This policy is synthetic, but represents a real life pattern where data is +# published from multiple bundles and then merged before use. +# Note: The multiple_cmerge_variable_static.cf test is nearly identical. It simply uses hard coded variables instead of variable variables. + +#@@ -97,7 +102,7 @@ +# +# vars: +# "cron" +#- data => readjson( "$(this.promise_filename).policy_1.cron.json"), +#+ data => readjson( "$(this.promise_filename).$(this.bundle).$(this.promiser).json"), +# meta => { "cron_rules" }; +# +# "s" string => format( "%S", cron ); +#@@ -120,7 +125,7 @@ +# # # For example: Cron entries +# "cron" +# +#- data => readjson( "$(this.promise_filename).policy_2.cron.json"), +#+ data => readjson( "$(this.promise_filename).$(this.bundle).$(this.promiser).json"), +# meta => { "cron_rules" }; + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "autorun" slist => bundlesmatching(".*", "autorun"); + + commands: + windows:: + "$(G.dos2unix) $(this.promise_filename).expected" -> { "ENT-10433" }; + + files: + "$(G.testfile)" + edit_line => empty; + + methods: + + "$(autorun)" + + comment => "Bundles tagged with 'autorun' expect to be automatically + actuated. Some of the policies publish data by tagging it + for other policies to use later in the bundlesequence. This + allows for the implementation of automatic cleanup when a + service stops publishing specifc data."; +} + +bundle agent test +{ + meta: + + "description" + string => "Test that this real life pattern for loading data based on a + bundle name and merging it for later use works."; + + methods: + + "cron" + comment => "Manage /etc/crontab using published cron rules"; +} + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff( $(G.testfile), "$(this.promise_filename).expected", $(this.promise_filename) ); +} + +bundle agent cron +{ + vars: + "published_rules" + slist => variablesmatching( ".*", "cron_rules" ), + comment => "We find all variables tagged with cron_rules so that we can + merge them together and manage /etc/crontab from a single + policy. Allowing other polices to easily subscribe to the + service."; + "sorted" + slist => sort( published_rules, "lex" ); + + # It is possible to perform some data validation at this point. filtering + # or restricting what areas of policy are allowed to subscribe to the + # cron implementation. + + methods: + # We use the cmerge bundle from the standard library to merge all of the + # data containers into a single container that will be used to render + # /etc/crontab + "" usebundle => cmerge("merged_cron_rules", @(sorted) ); + "" usebundle => cron_entries( @(cmerge.merged_cron_rules) ); +} + +bundle agent cron_entries(rules) +{ + vars: + "s" string => format("%S", rules ); + + reports: + "$(this.bundle): '$(s)'" + report_to_file => "$(G.testfile)"; +} + +bundle edit_line empty +{ + delete_lines: + ".*"; +} +bundle agent policy_1 +# This represents a bundle for some service. It might be some custom in house +# written application. They publish information about some cron jobs they wish +# to have run. That data originates in this case from a plain text file. +{ + meta: + "tags" slist => { "autorun" }; + + vars: + "cron" + data => readjson( "$(this.promise_filename).$(this.bundle).$(this.promiser).json"), + meta => { "cron_rules" }; + + "s" string => format( "%S", cron ); + + reports: + "$(this.bundle): cron = '$(s)'" + report_to_file => "$(G.testfile)"; + +} + +bundle agent policy_2 +# This represents a bundle for some service. It might be some custom in house +# written application. They publish information about some cron jobs they wish +# to have run. That data originates in this case from a plain text file. +{ + meta: + "tags" slist => { "autorun" }; + + vars: + # # For example: Cron entries + "cron" + + data => readjson( "$(this.promise_filename).$(this.bundle).$(this.promiser).json"), + meta => { "cron_rules" }; + + "s" string => format( "%S", cron ); + + reports: + "$(this.bundle): cron = '$(s)'" + report_to_file => "$(G.testfile)"; +} + +bundle agent cmerge(name, varlist) +{ + vars: + "$(name)" data => parsejson('[]'), policy => "free"; + "$(name)" data => mergedata($(name), $(varlist)), policy => "free"; # iterates! + "$(name)_str" string => format("%S", $(name)), policy => "free"; +} diff --git a/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.expected b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.expected new file mode 100644 index 0000000000..6092600737 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.expected @@ -0,0 +1,3 @@ +policy_2: cron = '[{"command":"/bin/echo 'Hi there'","comment":"This job is a failsafe to keep cfengine services running","day":"*","hour":"*","minute":"*","month":"*"}]' +policy_1: cron = '[{"command":"/bin/echo 'back me up!'","comment":"a backup job that runs","day":"1","hour":"1","minute":"1","month":"1"}]' +cron_entries: '[{"command":"/bin/echo 'back me up!'","comment":"a backup job that runs","day":"1","hour":"1","minute":"1","month":"1"},{"command":"/bin/echo 'Hi there'","comment":"This job is a failsafe to keep cfengine services running","day":"*","hour":"*","minute":"*","month":"*"}]' diff --git a/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.policy_1.cron.json b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.policy_1.cron.json new file mode 100644 index 0000000000..494e64c993 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.policy_1.cron.json @@ -0,0 +1,10 @@ +[ + { + "hour": "1", + "minute": "1", + "day": "1", + "month": "1", + "comment": "a backup job that runs", + "command": "/bin/echo 'back me up!'" + } +] diff --git a/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.policy_2.cron.json b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.policy_2.cron.json new file mode 100644 index 0000000000..c8e8ce99ac --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_dynamic.cf.policy_2.cron.json @@ -0,0 +1,10 @@ +[ + { + "hour": "*", + "minute": "*", + "day": "*", + "month": "*", + "comment": "This job is a failsafe to keep cfengine services running", + "command": "/bin/echo 'Hi there'" + } +] diff --git a/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf new file mode 100644 index 0000000000..b60b96afbb --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf @@ -0,0 +1,143 @@ +# This policy is synthetic, but represents a real life pattern where data is +# published from multiple bundles and then merged before use. +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "autorun" slist => bundlesmatching(".*", "autorun"); + + commands: + windows:: + "$(G.dos2unix) $(this.promise_filename).expected" -> { "ENT-10433" }; + + files: + "$(G.testfile)" + edit_line => empty; + + methods: + + "$(autorun)" + + comment => "Bundles tagged with 'autorun' expect to be automatically + actuated. Some of the policies publish data by tagging it + for other policies to use later in the bundlesequence. This + allows for the implementation of automatic cleanup when a + service stops publishing specifc data."; +} + +bundle agent test +{ + meta: + + "description" + string => "Test that this real life pattern for loading data based on a + bundle name and merging it for later use works."; + + methods: + + "cron" + comment => "Manage /etc/crontab using published cron rules"; +} + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff( $(G.testfile), "$(this.promise_filename).expected", $(this.promise_filename) ); +} + +bundle agent cron +{ + vars: + "published_rules" + slist => variablesmatching( ".*", "cron_rules" ), + comment => "We find all variables tagged with cron_rules so that we can + merge them together and manage /etc/crontab from a single + policy. Allowing other polices to easily subscribe to the + service."; + "sorted" + slist => sort( published_rules, "lex" ); + + # It is possible to perform some data validation at this point. filtering + # or restricting what areas of policy are allowed to subscribe to the + # cron implementation. + + methods: + # We use the cmerge bundle from the standard library to merge all of the + # data containers into a single container that will be used to render + # /etc/crontab + "" usebundle => cmerge("merged_cron_rules", @(sorted) ); + "" usebundle => cron_entries( @(cmerge.merged_cron_rules) ); +} + +bundle agent cron_entries(rules) +{ + vars: + "s" string => format("%S", rules ); + + reports: + "$(this.bundle): '$(s)'" + report_to_file => "$(G.testfile)"; +} + +bundle edit_line empty +{ + delete_lines: + ".*"; +} +bundle agent policy_1 +# This represents a bundle for some service. It might be some custom in house +# written application. They publish information about some cron jobs they wish +# to have run. That data originates in this case from a plain text file. +{ + meta: + "tags" slist => { "autorun" }; + + vars: + "cron" + data => readjson( "$(this.promise_filename).policy_1.cron.json"), + meta => { "cron_rules" }; + + "s" string => format( "%S", cron ); + + reports: + "$(this.bundle): cron = '$(s)'" + report_to_file => "$(G.testfile)"; + +} + +bundle agent policy_2 +# This represents a bundle for some service. It might be some custom in house +# written application. They publish information about some cron jobs they wish +# to have run. That data originates in this case from a plain text file. +{ + meta: + "tags" slist => { "autorun" }; + + vars: + # # For example: Cron entries + "cron" + + data => readjson( "$(this.promise_filename).policy_2.cron.json"), + meta => { "cron_rules" }; + + "s" string => format( "%S", cron ); + + reports: + "$(this.bundle): cron = '$(s)'" + report_to_file => "$(G.testfile)"; +} + +bundle agent cmerge(name, varlist) +{ + vars: + "$(name)" data => parsejson('[]'), policy => "free"; + "$(name)" data => mergedata($(name), $(varlist)), policy => "free"; # iterates! + "$(name)_str" string => format("%S", $(name)), policy => "free"; +} diff --git a/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.expected b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.expected new file mode 100644 index 0000000000..6092600737 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.expected @@ -0,0 +1,3 @@ +policy_2: cron = '[{"command":"/bin/echo 'Hi there'","comment":"This job is a failsafe to keep cfengine services running","day":"*","hour":"*","minute":"*","month":"*"}]' +policy_1: cron = '[{"command":"/bin/echo 'back me up!'","comment":"a backup job that runs","day":"1","hour":"1","minute":"1","month":"1"}]' +cron_entries: '[{"command":"/bin/echo 'back me up!'","comment":"a backup job that runs","day":"1","hour":"1","minute":"1","month":"1"},{"command":"/bin/echo 'Hi there'","comment":"This job is a failsafe to keep cfengine services running","day":"*","hour":"*","minute":"*","month":"*"}]' diff --git a/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.policy_1.cron.json b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.policy_1.cron.json new file mode 100644 index 0000000000..494e64c993 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.policy_1.cron.json @@ -0,0 +1,10 @@ +[ + { + "hour": "1", + "minute": "1", + "day": "1", + "month": "1", + "comment": "a backup job that runs", + "command": "/bin/echo 'back me up!'" + } +] diff --git a/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.policy_2.cron.json b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.policy_2.cron.json new file mode 100644 index 0000000000..c8e8ce99ac --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/multiple_cmerge_variable_static.cf.policy_2.cron.json @@ -0,0 +1,10 @@ +[ + { + "hour": "*", + "minute": "*", + "day": "*", + "month": "*", + "comment": "This job is a failsafe to keep cfengine services running", + "command": "/bin/echo 'Hi there'" + } +] diff --git a/tests/acceptance/01_vars/04_containers/parsejson.cf b/tests/acceptance/01_vars/04_containers/parsejson.cf new file mode 100644 index 0000000000..84258303c2 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/parsejson.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test parsejson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + 'a'; + 'b'; + 'c'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => parsejson('["a", "b", "c"]'); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/parsejson_indexed.cf b/tests/acceptance/01_vars/04_containers/parsejson_indexed.cf new file mode 100644 index 0000000000..6b1c2e48ac --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/parsejson_indexed.cf @@ -0,0 +1,66 @@ +####################################################### +# +# Test parsejson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + 'a'; + 'b'; + 'c'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => parsejson(' +{ + "first": 1, + "seconds": 2, + "third": [ "a", "b", "c" ], + "fourth": null +} +'); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load[third])"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/parsejson_storejson_with_missing_container_does_not_create_new_container.cf b/tests/acceptance/01_vars/04_containers/parsejson_storejson_with_missing_container_does_not_create_new_container.cf new file mode 100644 index 0000000000..c27cdf3101 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/parsejson_storejson_with_missing_container_does_not_create_new_container.cf @@ -0,0 +1,48 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that a data container defined by using parsejson(storejson()) against a non existing data container will not define a new data container."; +} + +bundle agent init +{ + vars: + "data" + data => parsejson(storejson("missing_ns:missing_bundle.missing_var")); + + "serialized_data" string => format("%S", data); + + reports: + DEBUG:: + "DEBUG $(this.bundle): serialized_data: '$(serialized_data)'"; +} + +bundle agent check +{ + classes: + "fail" + expression => isvariable("init.data"); + + "ok" + not => isvariable("init.data"); + + "exception" + and => { "fail", "ok" }; + + reports: + fail:: + "$(this.promise_filename) FAIL"; + + ok.!fail:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/01_vars/04_containers/parsejson_unicode.cf b/tests/acceptance/01_vars/04_containers/parsejson_unicode.cf new file mode 100644 index 0000000000..cb05625e8e --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/parsejson_unicode.cf @@ -0,0 +1,24 @@ +####################################################### +# +# Test that we don't barf on unicode escapes in JSON +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => + dcs_passif_output(".*R: response index: unicode\b.*", + ".*Error parsing JSON.*", + "$(sys.cf_agent) -KIf $(this.promise_filename).sub", + $(this.promise_filename)); +} diff --git a/tests/acceptance/01_vars/04_containers/parsejson_unicode.cf.sub b/tests/acceptance/01_vars/04_containers/parsejson_unicode.cf.sub new file mode 100644 index 0000000000..7f42b3ecab --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/parsejson_unicode.cf.sub @@ -0,0 +1,14 @@ +body common control +{ + bundlesequence => { "redmine_6549" }; +} + +bundle agent redmine_6549 +{ + vars: + "response" data => parsejson('{ "unicode": "Linux kernel \u03c0 = 3,14" }'); + "response_idx" slist => getindices("response"); + + reports: + "response index: $(response_idx)"; +} diff --git a/tests/acceptance/01_vars/04_containers/parsejson_with_unresolved_varibales_does_not_define_datacontainer.cf b/tests/acceptance/01_vars/04_containers/parsejson_with_unresolved_varibales_does_not_define_datacontainer.cf new file mode 100644 index 0000000000..ecd0e201d9 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/parsejson_with_unresolved_varibales_does_not_define_datacontainer.cf @@ -0,0 +1,41 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that data containers defined with parsejson that conatains unexpanded variables does NOT define a new datacontainer"; +} + +bundle agent check +{ + vars: + "data" + data => parsejson('{ + "key1": "$(sys.fqhost)", + "key2" : "$(undefined_namespace:undefined_bundle.undefined_variable)" + }'); + classes: + "fail" + expression => isvariable("data"); + + "ok" + not => isvariable("data"); + + "exception" + and => { "fail", "ok" }; + + reports: + fail:: + "$(this.promise_filename) FAIL"; + + ok.!fail:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/01_vars/04_containers/parseyaml.cf b/tests/acceptance/01_vars/04_containers/parseyaml.cf new file mode 100644 index 0000000000..5da5d56c2c --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/parseyaml.cf @@ -0,0 +1,134 @@ +####################################################### +# +# Test parseyaml() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common yaml_check +{ + classes: + "no_yaml_support" + not => regline(" *# *define *HAVE_LIBYAML.*", "$(this.promise_dirname)/../../../../libutils/config.h"); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "no_yaml_support"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + '{ + "a": "b", + "c": "d", + "e": [ + { + "where": 1 + }, + { + "where_else": 100.2300 + }, + { + "boolean_spears": true, + "bows": "maybe", + "nowhere": true, + "string_spears": "yes" + } + ], + "f": [ + { + "hostname": "prd-web", + "packages": [ + "apache", + "memcache" + ], + "users": [ + "root", + "mike" + ] + }, + { + "hostname": "prd-db", + "packages": [ + "postgresql" + ], + "users": [ + "root", + "john", + "debbie" + ] + } + ] +}'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => parseyaml('a: b +c: d +e: + - where: 1 + - where_else: 1.0023E2 + - nowhere: true + boolean_spears: yes + string_spears: "yes" + bows: maybe +f: + - hostname: prd-web + packages: + - apache + - memcache + users: + - root + - mike + - hostname: prd-db + packages: + - postgresql + users: + - root + - john + - debbie +'); + + "load_s" string => storejson(load); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load_s)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/parseyaml_abuse.cf b/tests/acceptance/01_vars/04_containers/parseyaml_abuse.cf new file mode 100644 index 0000000000..2062abea48 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/parseyaml_abuse.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Abuse parseyaml() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "strings" data => parsejson(' +[ + "- valid", + "xyzzz", + "- xyzzz", + "[xyzzz]", + "- { xyzzz: }", + "xyzzz: { p: 100 }", + "- p class=\\"some class\\" id=\\"some-id\\": abusing the free naming of properties in YAML.", +]'); + + "n" slist => getindices(strings); + + "load_$(n)" data => parseyaml("$(strings[$(n)])"); + + "load_s_$(n)" string => format("%S", "load_$(n)"); + + reports: + EXTRA:: + "parsing $(n) = $(strings[$(n)])"; + "convert $(n) = $(load_s_$(n))"; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_pass("$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/pass_variable_container.cf b/tests/acceptance/01_vars/04_containers/pass_variable_container.cf new file mode 100644 index 0000000000..40727d8a3f --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/pass_variable_container.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Test datacontainers can be passed with a variable name. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + meta: + "tags" slist => { "find" }; + + commands: + windows:: + "$(G.dos2unix) $(this.promise_filename).expected" -> { "ENT-10433" }; + + vars: +# We want to be sure we can reference this data subsequently + "d" data => parsejson( '{ +"hosts": { + "path": "/etc/hosts" + }, +"auto_master" { + "path": "/etc/auto.master" +} +}'); + +} + +bundle agent test +{ + + meta: + "description" -> { "CFE-2434" } + string => "Test that variable data containers can be passed."; + + vars: + "found" slist => bundlesmatching(".*", "find"); + # "ns_bundle" string => "default:init"; # This is the same content as the + # "found" variable, so we just use that instead. + + "bundle_name" string => "init"; + "bundle_name_suffix" string => "it"; + + methods: + "" usebundle => report_data("Pass by name:", @(init.d)); + "" usebundle => report_data("Pass by namespace:name:", @(default:init.d)); + "" usebundle => report_data("Pass by variable name:", "@($(bundle_name).d)"); + "" usebundle => report_data("Pass by variable name including namespace:", "@($(found).d)"); + "" usebundle => report_data("Pass by variable name with prefix:", "@(in$(bundle_name_suffix).d)"); + "" usebundle => report_data("Pass by variable name with namespace:", "@(default:$(bundle_name).d)"); +} + +bundle agent check +{ + methods: + "" usebundle => dcs_check_diff( $(G.testfile), + "$(this.promise_filename).expected", + $(this.promise_filename)); +} + + +bundle agent report_data(id, datacontainer) +{ + vars: + "keys" slist => getindices("datacontainer"); + + classes: + "some_keys" expression => some(".*", keys); + + reports: + DEBUG:: + "$(id) found keys in data container" + if => "some_keys"; + + "$(id) did *not* find keys in data container" + unless => "some_keys"; + + any:: + "$(id) $(keys)=$(datacontainer[$(keys)][path])" + report_to_file => "$(G.testfile)"; +} diff --git a/tests/acceptance/01_vars/04_containers/pass_variable_container.cf.expected b/tests/acceptance/01_vars/04_containers/pass_variable_container.cf.expected new file mode 100644 index 0000000000..53d03e88c4 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/pass_variable_container.cf.expected @@ -0,0 +1,12 @@ +Pass by name: hosts=/etc/hosts +Pass by name: auto_master=/etc/auto.master +Pass by namespace:name: hosts=/etc/hosts +Pass by namespace:name: auto_master=/etc/auto.master +Pass by variable name: hosts=/etc/hosts +Pass by variable name: auto_master=/etc/auto.master +Pass by variable name including namespace: hosts=/etc/hosts +Pass by variable name including namespace: auto_master=/etc/auto.master +Pass by variable name with prefix: hosts=/etc/hosts +Pass by variable name with prefix: auto_master=/etc/auto.master +Pass by variable name with namespace: hosts=/etc/hosts +Pass by variable name with namespace: auto_master=/etc/auto.master diff --git a/tests/acceptance/01_vars/04_containers/pass_variable_container_index.cf b/tests/acceptance/01_vars/04_containers/pass_variable_container_index.cf new file mode 100644 index 0000000000..35bfe052ca --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/pass_variable_container_index.cf @@ -0,0 +1,114 @@ +####################################################### +# +# Test datacontainers can be passed with a variable name. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + meta: + "tags" slist => { "find" }; + commands: + windows:: + "$(G.dos2unix) $(this.promise_filename).expected" -> { "ENT-10433" }; + +} + +bundle agent test + +{ + meta: + "description" -> { "CFE-3299" } + string => "Test that lists and data containers with variable index can be passed."; + + vars: + "data" + data => parsejson('{ "key1" : { "list" : [ "a", "b", "c" ], "scalar" : "value1" }, + "key2" : { "list" : [ "x", "y", "z" ], "scalar" : "value2" } }') ; + + "array[key1][list]" slist => { "a", "b", "c" } ; + "array[key1][scalar]" string => "value1" ; + "array[key2][list]" slist => { "x", "y", "z" } ; + "array[key2][scalar]" string => "value2" ; + + "datakey" slist => getindices("data") ; + "arraykey" slist => getindices("array") ; + + methods: + "test_data" + usebundle => launcher_data("test.data[${datakey}][list]") ; + + "test_array" + usebundle => launcher_array("test.array[${arraykey}][list]") ; + + "test_executor_data" + usebundle => executor("@{test.data[${datakey}][list]}") ; + + "test_executor_array" + usebundle => executor("@{test.data[${arraykey}][list]}") ; +} + +bundle agent check +{ + methods: + "" usebundle => dcs_check_diff( $(G.testfile), + "$(this.promise_filename).expected", + $(this.promise_filename)); +} + +bundle agent launcher_data(list_name) +{ + vars: + any:: + "expanded_list" slist => getvalues("${list_name}") ; + "callers" string => join(":", callstack_promisers()) ; + + methods: + any:: + # If we don't add something variable in the following promise, and the promise is kept, + # CFEngine will think that the promise is already kept and refuse to keep it more than + # once. Inserting a variable handle will make CFEngine understand that it's not the + # same promise we are asking it to keep over and over again, and execute it again every + # time we pass it a different list. + "run_list" + usebundle => executor(@{launcher_data.expanded_list}), + handle => "run_list_${list_name}" ; +} + +bundle agent launcher_array(list_name) +{ + vars: + any:: + "expanded_list" slist => { "@{${list_name}}"} ; + "callers" string => join(":", callstack_promisers()) ; + + methods: + any:: + # If we don't add something variable in the following promise, and the promise is kept, + # CFEngine will think that the promise is already kept and refuse to keep it more than + # once. Inserting a variable handle will make CFEngine understand that it's not the + # same promise we are asking it to keep over and over again, and execute it again every + # time we pass it a different list. + "run_list" + usebundle => executor(@{launcher_array.expanded_list}), + handle => "run_list_${list_name}" ; +} + +bundle agent executor(list) +{ + vars: + "callers" string => join(":", callstack_promisers()) ; + + reports: + "[${callers}:${this.bundle}] ${list}" + report_to_file => "$(G.testfile)"; +} diff --git a/tests/acceptance/01_vars/04_containers/pass_variable_container_index.cf.expected b/tests/acceptance/01_vars/04_containers/pass_variable_container_index.cf.expected new file mode 100644 index 0000000000..153d590163 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/pass_variable_container_index.cf.expected @@ -0,0 +1,24 @@ +[any:any:test_data:run_list:executor] a +[any:any:test_data:run_list:executor] b +[any:any:test_data:run_list:executor] c +[any:any:test_data:run_list:executor] x +[any:any:test_data:run_list:executor] y +[any:any:test_data:run_list:executor] z +[any:any:test_array:run_list:executor] x +[any:any:test_array:run_list:executor] y +[any:any:test_array:run_list:executor] z +[any:any:test_array:run_list:executor] a +[any:any:test_array:run_list:executor] b +[any:any:test_array:run_list:executor] c +[any:any:test_executor_data:executor] a +[any:any:test_executor_data:executor] b +[any:any:test_executor_data:executor] c +[any:any:test_executor_data:executor] x +[any:any:test_executor_data:executor] y +[any:any:test_executor_data:executor] z +[any:any:test_executor_array:executor] x +[any:any:test_executor_array:executor] y +[any:any:test_executor_array:executor] z +[any:any:test_executor_array:executor] a +[any:any:test_executor_array:executor] b +[any:any:test_executor_array:executor] c diff --git a/tests/acceptance/01_vars/04_containers/passing_containers.cf b/tests/acceptance/01_vars/04_containers/passing_containers.cf new file mode 100644 index 0000000000..8083f9902a --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/passing_containers.cf @@ -0,0 +1,60 @@ +####################################################### +# +# Test passing of datas with @(ref) notation +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + '1'; + '2'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => parsejson(' +{ "first": 1, "seconds": 2, "third": [ "a", "b", "c" ], "fourth": null} +'); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert(@(load)); +} + +bundle edit_line test_insert(data) +{ + insert_lines: + "$(data)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readjson.cf b/tests/acceptance/01_vars/04_containers/readjson.cf new file mode 100644 index 0000000000..e3a60d5194 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readjson.cf @@ -0,0 +1,68 @@ +####################################################### +# +# Test datas creation through readjson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + '[ "hello", "there" ]'; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + "hello"; + "there"; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readjson("$(G.testfile).json", 1024); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readjson_arrays.cf b/tests/acceptance/01_vars/04_containers/readjson_arrays.cf new file mode 100644 index 0000000000..d7e639560a --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readjson_arrays.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test nested datas iteration through readjson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + '[ 1, 2, 3, [ "a", "b", "c" ], "d", 5, null]'; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + '1'; + '2'; + '3'; + 'd'; + '5'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readjson("$(G.testfile).json", 1024); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readjson_new.cf b/tests/acceptance/01_vars/04_containers/readjson_new.cf new file mode 100644 index 0000000000..e21ad53c8e --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readjson_new.cf @@ -0,0 +1,68 @@ +####################################################### +# +# Test data creation through readjson() with just 1 arg +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + '[ "hello", "there" ]'; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + "hello"; + "there"; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readjson("$(G.testfile).json"); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readjson_null.cf b/tests/acceptance/01_vars/04_containers/readjson_null.cf new file mode 100644 index 0000000000..75e840fd78 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readjson_null.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Test datas creation from a null through readjson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + 'null'; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + "$(const.dollar)(test.load)"; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readjson("$(G.testfile).json", 1024); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readjson_objects.cf b/tests/acceptance/01_vars/04_containers/readjson_objects.cf new file mode 100644 index 0000000000..37e08189f0 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readjson_objects.cf @@ -0,0 +1,68 @@ +####################################################### +# +# Test nested datas iteration through readjson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + '{ "first": 1, "seconds": 2, "third": [ "a", "b", "c" ], "fourth": null}'; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + '1'; + '2'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readjson("$(G.testfile).json", 1024); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readjson_scalar.cf b/tests/acceptance/01_vars/04_containers/readjson_scalar.cf new file mode 100644 index 0000000000..0eddfe5b30 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readjson_scalar.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Test datas creation from a scalar through readjson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + '123456'; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + "$(const.dollar)(test.load)"; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readjson("$(G.testfile).json", 1024); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readjson_subindex.cf b/tests/acceptance/01_vars/04_containers/readjson_subindex.cf new file mode 100644 index 0000000000..58496a5d61 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readjson_subindex.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test subreferencing of datas loaded through readjson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + '{ "first": 1, "seconds": 2, "third": [ "a", "b", "c" ], "fourth": null}'; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + 'a'; + 'b'; + 'c'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readjson("$(G.testfile).json", 1024); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load[third])"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readjson_subindex_quoted.cf b/tests/acceptance/01_vars/04_containers/readjson_subindex_quoted.cf new file mode 100644 index 0000000000..3d05ef7bc0 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readjson_subindex_quoted.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test subreferencing with quoted keys of datas loaded through readjson() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + '{ "first": 1, "seconds": 2, "third key! can? almost be anything& but no dollar or bracket yet": [ "a", "b", "c" ]}'; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + 'a'; + 'b'; + 'c'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readjson("$(G.testfile).json", 1024); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + '$(test.load[third key! can? almost be anything& but no dollar or bracket yet])'; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readyaml.cf b/tests/acceptance/01_vars/04_containers/readyaml.cf new file mode 100644 index 0000000000..4946fceb20 --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readyaml.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Test datas creation through readyaml() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common yaml_check +{ + classes: + "no_yaml_support" + not => regline(" *# *define *HAVE_LIBYAML.*", "$(this.promise_dirname)/../../../../libutils/config.h"); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "no_yaml_support"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).yaml" + create => "true", + edit_line => init_insert_yaml; +} + +bundle edit_line init_insert_yaml +{ + insert_lines: + "- hello +- there +- where: 1 +- where_else: 1.0023E2 +- nowhere: true + boolean_spears: yes + string_spears: \"yes\" + bows: maybe"; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + '[ + "hello", + "there", + { + "where": 1 + }, + { + "where_else": 100.2300 + }, + { + "boolean_spears": true, + "bows": "maybe", + "nowhere": true, + "string_spears": "yes" + } +]'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readyaml("$(G.testfile).yaml", 1024); + "load_s" string => storejson(load); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load_s)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/readyaml_new.cf b/tests/acceptance/01_vars/04_containers/readyaml_new.cf new file mode 100644 index 0000000000..b3daab226c --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/readyaml_new.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Test data creation through readyaml() with just 1 arg +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common yaml_check +{ + classes: + "no_yaml_support" + not => regline(" *# *define *HAVE_LIBYAML.*", "$(this.promise_dirname)/../../../../libutils/config.h"); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "no_yaml_support"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert_lines; + + "$(G.testfile).yaml" + create => "true", + edit_line => init_insert_yaml; +} + +bundle edit_line init_insert_yaml +{ + insert_lines: + "- hello +- there +- where: 1 +- where_else: 1.0023E2 +- nowhere: true + boolean_spears: yes + string_spears: \"yes\" + bows: maybe"; +} + +bundle edit_line init_insert_lines +{ + insert_lines: + '[ + "hello", + "there", + { + "where": 1 + }, + { + "where_else": 100.2300 + }, + { + "boolean_spears": true, + "bows": "maybe", + "nowhere": true, + "string_spears": "yes" + } +]'; +} + +####################################################### + +bundle agent test +{ + vars: + "load" data => readyaml("$(G.testfile).yaml"); + "load_s" string => storejson(load); + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(test.load_s)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf b/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf new file mode 100644 index 0000000000..09ac24123f --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf @@ -0,0 +1,119 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent test +{ + meta: + "description" + string => "Test that null keys, and mixed datatypes in json data are + handeled appropriately."; + + "test_soft_fail" + string => "any", + meta => { "redmine7821" }; + + methods: + "ec2" + comment => "We activate the bundle so that we can grab its state"; +} + +bundle agent check +{ + methods: + "check" + usebundle => dcs_check_state("ec2", + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} +bundle agent ec2 +{ + vars: + + # This data has nulls in it, and consists of a single data type for + # values (strings) + "data" + data => '{ + "instanceId" : "i-f444554e", + "billingProducts" : null, + "instanceType" : "m3.large", + "accountId" : "444444444444", + "pendingTime" : "2015-12-02T19:47:57Z", + "imageId" : "ami-44444444", + "kernelId" : "aki-44444444", + "ramdiskId" : null, + "architecture" : "x86_64", + "region" : "eu-west-1", + "version" : "2010-08-31", + "availabilityZone" : "eu-west-1c", + "privateIp" : "10.44.44.4", + "devpayProductCodes" : null + }'; + + "keys" slist => getindices("data"); + "data_$(keys)" string => "$(data[$(keys)])"; + + + # This data has no nulls in it and consists of a single type for values + # (strings) + "data2" + data => '{ + "instanceId" : "i-f444554e", + "instanceType" : "m3.large", + "accountId" : "444444444444", + "pendingTime" : "2015-12-02T19:47:57Z", + "imageId" : "ami-44444444", + "kernelId" : "aki-44444444", + "architecture" : "x86_64", + "region" : "eu-west-1", + "version" : "2010-08-31", + "availabilityZone" : "eu-west-1c", + "privateIp" : "10.44.44.4", + }'; + + "keys2" slist => getindices("data2"); + "data2_$(keys2)" string => "$(data2[$(keys2)])"; + + + # The values in this data contain nulls and are of mixed types + # (strings, lists) + "data3" + data => '{ + "a": [], + "b": ["one", "two"], + "c": null, + "d": "should be here" + }'; + + "keys3" slist => getindices("data3"); + # I expect that data3_a will not be defined + # - because the list is empty and there is nothing to iterate over + # - potentially it would make sense to be cf_null (thats what + # iterating over empty slist does) or null + # I expect that data3_b will contain 'two' + # - because the list will be iterated over and two is the last + # element. + # I expect data3_c to not be defined + # - potentially cf_null or null would make sense as with data3_a + # I expect data3_d to be 'should be here' + "data3_$(keys3)" string => "$(data3[$(keys3)])"; + + reports: + DEBUG:: + "Key from 'data': $(keys)"; + "key from 'data2': $(keys2)"; + "key from 'data3': $(keys3)"; + "Value from 'data': $(data_$(keys)) = $(data[$(keys)])"; + "Value from 'data2': $(data_$(keys2)) = $(data2[$(keys2)])"; + "Value from 'data3': $(data_$(keys3)) = $(data3[$(keys3)])"; +} diff --git a/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf.expected.json b/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf.expected.json new file mode 100644 index 0000000000..ca52f57b7c --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf.expected.json @@ -0,0 +1,99 @@ +{ + "data": { + "accountId": "444444444444", + "architecture": "x86_64", + "availabilityZone": "eu-west-1c", + "billingProducts": null, + "devpayProductCodes": null, + "imageId": "ami-44444444", + "instanceId": "i-f444554e", + "instanceType": "m3.large", + "kernelId": "aki-44444444", + "pendingTime": "2015-12-02T19:47:57Z", + "privateIp": "10.44.44.4", + "ramdiskId": null, + "region": "eu-west-1", + "version": "2010-08-31" + }, + "data2": { + "accountId": "444444444444", + "architecture": "x86_64", + "availabilityZone": "eu-west-1c", + "imageId": "ami-44444444", + "instanceId": "i-f444554e", + "instanceType": "m3.large", + "kernelId": "aki-44444444", + "pendingTime": "2015-12-02T19:47:57Z", + "privateIp": "10.44.44.4", + "region": "eu-west-1", + "version": "2010-08-31" + }, + "data_accountId": "444444444444", + "data_architecture": "x86_64", + "data_availabilityZone": "eu-west-1c", + "data_imageId": "ami-44444444", + "data_instanceId": "i-f444554e", + "data_instanceType": "m3.large", + "data_kernelId": "aki-44444444", + "data_pendingTime": "2015-12-02T19:47:57Z", + "data_privateIp": "10.44.44.4", + "data_region": "eu-west-1", + "data_version": "2010-08-31", + "data2_accountId": "444444444444", + "data2_architecture": "x86_64", + "data2_availabilityZone": "eu-west-1c", + "data2_imageId": "ami-44444444", + "data2_instanceId": "i-f444554e", + "data2_instanceType": "m3.large", + "data2_kernelId": "aki-44444444", + "data2_pendingTime": "2015-12-02T19:47:57Z", + "data2_privateIp": "10.44.44.4", + "data2_region": "eu-west-1", + "data2_version": "2010-08-31", + "data3": { + "a": [], + "b": [ + "one", + "two" + ], + "c": null, + "d": "should be here" + }, + "data3_b": "two", + "data3_d": "should be here", + "keys": [ + "instanceId", + "billingProducts", + "instanceType", + "accountId", + "pendingTime", + "imageId", + "kernelId", + "ramdiskId", + "architecture", + "region", + "version", + "availabilityZone", + "privateIp", + "devpayProductCodes" + ], + "keys2": [ + "instanceId", + "instanceType", + "accountId", + "pendingTime", + "imageId", + "kernelId", + "architecture", + "region", + "version", + "availabilityZone", + "privateIp" + ], + "keys3": [ + "a", + "b", + "c", + "d" + ] +} diff --git a/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf.mustache b/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf.mustache new file mode 100644 index 0000000000..cbc44179be --- /dev/null +++ b/tests/acceptance/01_vars/04_containers/test_json_with_and_without_nulls.cf.mustache @@ -0,0 +1 @@ +{{%vars.test.state}} diff --git a/tests/acceptance/01_vars/05_defaults/basics.cf b/tests/acceptance/01_vars/05_defaults/basics.cf new file mode 100644 index 0000000000..a4ab835f93 --- /dev/null +++ b/tests/acceptance/01_vars/05_defaults/basics.cf @@ -0,0 +1,58 @@ +body common control +{ +bundlesequence => { "main" }; +} + +bundle agent main +{ + methods: + "example" usebundle => class_default, + useresult => "value"; + "example" usebundle => no_class_default, + useresult => "value"; + + classes: + "fail1" expression => strcmp("$(value[1])", "one"); + "fail2" expression => strcmp("$(value[2])", "two"); + "fail" or => { "fail1", "fail2" }; + "ok" and => { strcmp("$(value[1])", "default"), strcmp("$(value[2])", "default") }; + + reports: + fail1.!ok:: + "value 1: $(value[1])"; + fail2.!ok:: + "value 2: $(value[2])"; + + !fail.ok:: + "$(this.promise_filename) Pass"; + fail.!ok:: + "$(this.promise_filename) FAIL"; +} + +bundle agent class_default +{ + vars: + "a" string => "one"; + + defaults: + "a" string => "default", if_match_regex => "one"; + + classes: + "test" expression => "any"; + + reports: + "${a}" bundle_return_value_index => "1"; +} + +bundle agent no_class_default +{ + vars: + "b" string => "two"; + + defaults: + "b" string => "default", if_match_regex => "two"; + + reports: + "${b}" bundle_return_value_index => "2"; +} + diff --git a/tests/acceptance/01_vars/deep_delayed_expansion/def.json b/tests/acceptance/01_vars/deep_delayed_expansion/def.json new file mode 100644 index 0000000000..cdb7cff99e --- /dev/null +++ b/tests/acceptance/01_vars/deep_delayed_expansion/def.json @@ -0,0 +1,6 @@ +{ + "vars": { + "policy_root": "$(this.promise_dirname)" + }, + "inputs": ["my_globals.cf.sub"] +} diff --git a/tests/acceptance/01_vars/deep_delayed_expansion/main.cf b/tests/acceptance/01_vars/deep_delayed_expansion/main.cf new file mode 100644 index 0000000000..86f99bcbe2 --- /dev/null +++ b/tests/acceptance/01_vars/deep_delayed_expansion/main.cf @@ -0,0 +1,27 @@ +body common control +{ + inputs => { "../../default.cf.sub", @(def.augments_inputs) }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" string => "Test that variables containing other variales are de-referenced"; + "test_soft_fail" string => "windows", + meta => { "ENT-10256" }; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass" + if => strcmp( $(this.promise_dirname), $(my_globals.policy_root) ); + + "$(this.promise_filename) FAIL" + unless => strcmp( $(this.promise_dirname), $(my_globals.policy_root) ); + + EXTRA:: + "my_globals.policy_root = $(my_globals.policy_root)"; + "def.policy_root = $(def.policy_root)"; +} diff --git a/tests/acceptance/01_vars/deep_delayed_expansion/my_globals.cf.sub b/tests/acceptance/01_vars/deep_delayed_expansion/my_globals.cf.sub new file mode 100644 index 0000000000..616bb14b3c --- /dev/null +++ b/tests/acceptance/01_vars/deep_delayed_expansion/my_globals.cf.sub @@ -0,0 +1,5 @@ +bundle common my_globals +{ + vars: + "policy_root" string => "$(def.policy_root)"; +} diff --git a/tests/acceptance/02_classes/01_basic/001.cf b/tests/acceptance/02_classes/01_basic/001.cf new file mode 100644 index 0000000000..286c979b55 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/001.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test not +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => "class_never_defined"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/002.cf b/tests/acceptance/02_classes/01_basic/002.cf new file mode 100644 index 0000000000..11bb1bf3c5 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/002.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Test not +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "not_ok" not => "any"; + "ok" not => "not_ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/003.cf b/tests/acceptance/02_classes/01_basic/003.cf new file mode 100644 index 0000000000..ca43913307 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/003.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Test and +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + "cfengine_3" + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/004.cf b/tests/acceptance/02_classes/01_basic/004.cf new file mode 100644 index 0000000000..87d8f434bd --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/004.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test and +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + "any", + "cfengine_3" + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/005.cf b/tests/acceptance/02_classes/01_basic/005.cf new file mode 100644 index 0000000000..09fefaae6c --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/005.cf @@ -0,0 +1,48 @@ +####################################################### +# +# Test and +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "not_ok" and => { + "any", + "cfengine_3", + "this_was_not_defined" + }; + "ok" not => "ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/006.cf b/tests/acceptance/02_classes/01_basic/006.cf new file mode 100644 index 0000000000..9cf7b96d64 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/006.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test and +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "not_ok" and => { + "this_was_not_defined" + }; + "ok" not => "ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/007.cf b/tests/acceptance/02_classes/01_basic/007.cf new file mode 100644 index 0000000000..a509c16aa0 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/007.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test or +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "not_ok" or => { + "this_was_not_defined" + }; + "ok" not => "ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/008.cf b/tests/acceptance/02_classes/01_basic/008.cf new file mode 100644 index 0000000000..54a6a72bfd --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/008.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test or +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "not_ok" or => { + "this_was_not_defined", + "neither_was_this", + }; + "ok" not => "ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/009.cf b/tests/acceptance/02_classes/01_basic/009.cf new file mode 100644 index 0000000000..03824586af --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/009.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test or +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" or => { + "this_was_not_defined", + "cfengine_3", + "neither_was_this", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/010.cf b/tests/acceptance/02_classes/01_basic/010.cf new file mode 100644 index 0000000000..23b6fd6acd --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/010.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test or +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" or => { + "cfengine_3", + "any", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/011.cf b/tests/acceptance/02_classes/01_basic/011.cf new file mode 100644 index 0000000000..cdce2b5f16 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/011.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test xor +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "not_ok" xor => { + "cfengine_3", + "any", + }; + "ok" not => "not_ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/012.cf b/tests/acceptance/02_classes/01_basic/012.cf new file mode 100644 index 0000000000..810ad1e2b8 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/012.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test xor +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "not_ok" xor => { + "this_was_not_defined", + "neither_was_this", + }; + "ok" not => "not_ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/013.cf b/tests/acceptance/02_classes/01_basic/013.cf new file mode 100644 index 0000000000..0d66d56fb0 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/013.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test xor +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { + "cfengine_3", + "this_was_not_defined", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/014.cf b/tests/acceptance/02_classes/01_basic/014.cf new file mode 100644 index 0000000000..891dd43a3b --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/014.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test xor +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { + "cfengine_3", + "!any", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/015.cf b/tests/acceptance/02_classes/01_basic/015.cf new file mode 100644 index 0000000000..cd4b618e8e --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/015.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test xor +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { + "!any", + "!any", + "any", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/016.cf b/tests/acceptance/02_classes/01_basic/016.cf new file mode 100644 index 0000000000..acc92be338 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/016.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test xor +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { + "any", + "any", + "any", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/017.cf b/tests/acceptance/02_classes/01_basic/017.cf new file mode 100644 index 0000000000..df15be73dc --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/017.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test brackets on "and" (Issue 234) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { userexists("root") }; + + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/018.cf b/tests/acceptance/02_classes/01_basic/018.cf new file mode 100644 index 0000000000..81e33e26e6 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/018.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test brackets on "or" (Issue 234) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" or => { userexists("root") }; + + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/019.cf b/tests/acceptance/02_classes/01_basic/019.cf new file mode 100644 index 0000000000..a02a92a393 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/019.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test brackets on "xor" (Issue 234) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { userexists("root") }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/020.x.cf b/tests/acceptance/02_classes/01_basic/020.x.cf new file mode 100644 index 0000000000..a7e8549203 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/020.x.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test brackets on "expression" (Issue 234) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => { + userexists("root"), + }; + + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/021.x.cf b/tests/acceptance/02_classes/01_basic/021.x.cf new file mode 100644 index 0000000000..113b1c2e60 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/021.x.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test brackets on "not" (Issue 234) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => { + userexists("root"), + }; + + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/022.x.cf b/tests/acceptance/02_classes/01_basic/022.x.cf new file mode 100644 index 0000000000..424102490b --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/022.x.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Test syntactically errorneous class expression +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + !.!:: + "undererror" string => "undererror"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/023.cf b/tests/acceptance/02_classes/01_basic/023.cf new file mode 100644 index 0000000000..6e8f32004b --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/023.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test complex expression (issue 487) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "class1" expression => "any"; + "class2" expression => "any"; + "class3" expression => "any"; + "class4" expression => "any"; + + class1.(class2|(class3.class4)):: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/024.cf b/tests/acceptance/02_classes/01_basic/024.cf new file mode 100644 index 0000000000..3ef42ca0e6 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/024.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test complex expression (issue 487) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "class1" expression => "any"; + "class2" expression => "any"; + "class3" expression => "any"; + "class4" expression => "any"; + + class1.(class2|class3.class4):: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/025.cf b/tests/acceptance/02_classes/01_basic/025.cf new file mode 100644 index 0000000000..f0e9091550 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/025.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Check if with array +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + classes: + "classtotest" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "foobar[0]" string => "classtotest"; + + classes: + "ok" expression => "any", + if => "$(foobar[0])"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/026.cf b/tests/acceptance/02_classes/01_basic/026.cf new file mode 100644 index 0000000000..4bd0269ee3 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/026.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Check if with qualified variable +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + classes: + "classtotest" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "foobar" string => "classtotest"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any", + if => "$(test.foobar)"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/027.c b/tests/acceptance/02_classes/01_basic/027.c new file mode 100644 index 0000000000..de0045b66b --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/027.c @@ -0,0 +1,60 @@ +#include +#include +#include +#define SPLAY_PSEUDO_RANDOM_CONSTANT 8192 + +#define HOURLY 12 +main() +{ + char s[] = "a"; + char c; + int hash, box, minblocks; + int period = HOURLY - 1; + char *boxes[HOURLY]; + for (c = 0; c < HOURLY; c++) + { + boxes[c] = 0; + } + for (c = 'a'; c <= 'z'; c++) + { + *s = c; + // The block algorithm is copied from evalfunction.c + hash = OatHash(s); + box = + (int) (0.5 + period * hash / (double) SPLAY_PSEUDO_RANDOM_CONSTANT); + minblocks = box % HOURLY; + // Back to original code + if (!boxes[minblocks]) + { + boxes[minblocks] = strncpy((char *) malloc(2), s, 2); + } + } + printf(" \"ok\" xor => {\n"); + for (c = 0; c < HOURLY; c++) + { + printf("\tsplayclass(\"%s\",\"hourly\"), # Box %d\n", boxes[c], c); + } + printf("\t};\n"); +} + +// This is copied from files_hashes.c +int OatHash(char *key) +{ + unsigned int hashtablesize = SPLAY_PSEUDO_RANDOM_CONSTANT; + unsigned char *p = key; + unsigned h = 0; + int i, len = strlen(key); + + for (i = 0; i < len; i++) + { + h += p[i]; + h += (h << 10); + h ^= (h >> 6); + } + + h += (h << 3); + h ^= (h >> 11); + h += (h << 15); + + return (h & (hashtablesize - 1)); +} diff --git a/tests/acceptance/02_classes/01_basic/027.cf b/tests/acceptance/02_classes/01_basic/027.cf new file mode 100644 index 0000000000..21489ff600 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/027.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Check canonification in if +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.true)" + classes => if_repaired("/test"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "_test"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/028.cf b/tests/acceptance/02_classes/01_basic/028.cf new file mode 100644 index 0000000000..a91b03cedd --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/028.cf @@ -0,0 +1,54 @@ +####################################################### +# +# Check that we can use || in class expressions +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + + any||something:: + "oklhs" expression => "any"; + something||any:: + "okrhs" expression => "any"; + any||any:: + "okboth" expression => "any"; + something||something:: + "okno" expression => "any"; + + any:: + "ok" and => { "oklhs", "okrhs", "okboth", "!okno" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/030.cf b/tests/acceptance/02_classes/01_basic/030.cf new file mode 100644 index 0000000000..6ebef23e10 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/030.cf @@ -0,0 +1,43 @@ +body common control +{ + inputs => { "../../default.cf.sub", "030.cf.namespaced.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; + methods: + "call" usebundle => test_namespace:classchecker; +} + +####################################################### + +bundle agent check +{ + classes: + "ok1" expression => strcmp("$(test_namespace:classchecker.var1)", "data1"); + + any:: + "ok" and => { "ok1" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + DEBUG:: + "Expected data1, got $(test_namespace:classchecker.var1)"; +} diff --git a/tests/acceptance/02_classes/01_basic/030.cf.namespaced.sub b/tests/acceptance/02_classes/01_basic/030.cf.namespaced.sub new file mode 100644 index 0000000000..929656baf8 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/030.cf.namespaced.sub @@ -0,0 +1,20 @@ +body file control +{ + namespace => "test_namespace"; +} + +bundle agent classchecker +{ + classes: + "noedit_$(cindex[$(key)])" expression => strcmp("x","dontchange"); + + vars: + "key" string => "1 2 3"; + "cindex[$(key)]" string => canonify("$(key)"); + + # "var1" string => "data1", + # ifvarclass => "noedit_$(cindex[$(key)])"; + + "var1" string => "data1", + ifvarclass => "!noedit_$(cindex[$(key)])"; +} diff --git a/tests/acceptance/02_classes/01_basic/031.cf b/tests/acceptance/02_classes/01_basic/031.cf new file mode 100644 index 0000000000..30b3c4dd0a --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/031.cf @@ -0,0 +1,54 @@ +body common control +{ + inputs => { "../../default.cf.sub", "031.cf.namespaced.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle common globalclasses +{ + classes: + "GLOBALCLASS" expression => "any"; +} + +bundle agent test +{ + methods: + "globals" usebundle => globalclasses; + "call" usebundle => test_namespace:classchecker; +} + +####################################################### + +bundle agent check +{ + classes: + "ok1" expression => strcmp("$(test_namespace:classchecker.var1)", "data1"); + "ok2" expression => strcmp("$(test_namespace:classchecker.var2)", "data2"); + "ok3" expression => strcmp("$(test_namespace:classchecker.var3)", "data3"); + + any:: + "ok" and => { "ok1", "ok2", "ok3" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + !ok1||DEBUG:: + "Expected data1, got $(test_namespace:classchecker.var1)"; + !ok2||DEBUG:: + "Expected data2, got $(test_namespace:classchecker.var2)"; + !ok3||DEBUG:: + "Expected data3, got $(test_namespace:classchecker.var3)"; +} diff --git a/tests/acceptance/02_classes/01_basic/031.cf.namespaced.sub b/tests/acceptance/02_classes/01_basic/031.cf.namespaced.sub new file mode 100644 index 0000000000..6084cbe872 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/031.cf.namespaced.sub @@ -0,0 +1,22 @@ +body file control +{ + namespace => "test_namespace"; +} + +bundle agent classchecker +{ + classes: + "localclass1" and => { "default:GLOBALCLASS" }; + "localclass2" or => { "default:GLOBALCLASS" }; + "localclass3" and => { "default:$(classname)" }; + + vars: + "classname" string => "GLOBALCLASS"; + + localclass1:: + "var1" string => "data1"; + localclass2:: + "var2" string => "data2"; + localclass3:: + "var3" string => "data3"; +} diff --git a/tests/acceptance/02_classes/01_basic/032.cf b/tests/acceptance/02_classes/01_basic/032.cf new file mode 100644 index 0000000000..c8f4ce2b73 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/032.cf @@ -0,0 +1,54 @@ +# +# Test whether classes defined by promises end up in right namespace +# + +body common control +{ + inputs => { "../../default.cf.sub", "031.cf.namespaced.sub", "032.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + methods: + "call" usebundle => xxx:localclass; + + reports: + + cfengine:: + "testing class 1" + classes => xxx:always("another_class_global_from_command"); + +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "xxx:a_class_global_from_command", "default:another_class_global_from_command" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + DEBUG:: + "xxx:a_class_global_from_command is set" + if => "xxx:a_class_global_from_command"; + + DEBUG.another_class_global_from_command:: + "another_class_global_from_command is set"; + +} diff --git a/tests/acceptance/02_classes/01_basic/032.cf.sub b/tests/acceptance/02_classes/01_basic/032.cf.sub new file mode 100644 index 0000000000..32cbd4a68d --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/032.cf.sub @@ -0,0 +1,31 @@ +body file control +{ + namespace => "xxx"; +} + +bundle agent localclass +{ + reports: + + cfengine:: + "testing class 2" + classes => always("a_class_global_from_command"); + + a_class_global_from_command:: + "Global class from 'classes' attribute, accessed in the same namespace"; + default:a_class_global_from_command:: + "Global class from 'classes' attribute, accessed in the default namespace"; +} + +body classes always(x) + +# Define a class no matter what the outcome of the promise is + +{ + promise_repaired => { "$(x)" }; + promise_kept => { "$(x)" }; + repair_failed => { "$(x)" }; + repair_denied => { "$(x)" }; + repair_timeout => { "$(x)" }; + # persist_time => "1"; +} diff --git a/tests/acceptance/02_classes/01_basic/040.cf b/tests/acceptance/02_classes/01_basic/040.cf new file mode 100644 index 0000000000..1bcef8aa42 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/040.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Check that countclassesmatching() works +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + + "num" int => countclassesmatching("any"); + + classes: + + # One and only one match + + "ok" and => { isgreaterthan("$(num)", "0"), islessthan("$(num)", "2") }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/041.cf b/tests/acceptance/02_classes/01_basic/041.cf new file mode 100644 index 0000000000..0fdf7aea16 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/041.cf @@ -0,0 +1,56 @@ +####################################################### +# +# Check that weekday classes make sense +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + + # Only one of these should be set at a time + + "num" int => countclassesmatching("(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)"); + + classes: + + # One and only one match + + "ok" and => { isgreaterthan("$(num)", "0"), islessthan("$(num)", "2") }; + + reports: + + cfengine:: + "Found val $(num) matches when there should be 1"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/042.cf b/tests/acceptance/02_classes/01_basic/042.cf new file mode 100644 index 0000000000..d9975f3987 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/042.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Check that weekday classes make sense +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + + # Only one of these should be set at a time + + "num" int => countclassesmatching("(Min00_05|Min05_10|Min10_15|Min15_20|Min20_25|Min25_30|Min30_35|Min35_40|Min40_45|Min45_50|Min50_55|Min55_00)"); + + classes: + + # One and only one match + + "ok" and => { isgreaterthan("$(num)", "0"), islessthan("$(num)", "2") }; + + reports: + cfengine:: + "Found $(num) matches when there should be 1"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/109.cf b/tests/acceptance/02_classes/01_basic/109.cf new file mode 100644 index 0000000000..e4393c73d7 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/109.cf @@ -0,0 +1,41 @@ +# Test that classes in common bundles can be passed across bundles and namespaces + +body common control +{ + inputs => { "../../default.cf.sub", "109.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle common shared +{ + classes: + "shared_class" expression => "any"; +} + +bundle agent test +{ + methods: + "shared" usebundle => shared; + "namespaced" usebundle => ns109:pass(); +} + +bundle agent check +{ + classes: + "ok" expression => strcmp("works", "$(ns109:pass.shared_dependent)"); + + reports: + DEBUG:: + "The class expression in ns109:pass() was $(ns109:pass.class_needed)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/109.cf.sub b/tests/acceptance/02_classes/01_basic/109.cf.sub new file mode 100644 index 0000000000..f3c929ad63 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/109.cf.sub @@ -0,0 +1,29 @@ +body file control +{ + namespace => "ns109"; +} + +bundle agent pass() +{ + classes: + "shred" expression => "default:shared_class"; + + vars: + "shared_dependent" string => "works", + ifvarclass => "default:shared_class"; + + shred:: + "forced_dependent" string => "works"; + + reports: + DEBUG:: + "Does the shared class work in a variable? Is '$(shared_dependent)' == 'works'?"; + + "Does the shared class work explicitly? Is '$(forced_dependent)' == 'works'?"; + + "The shared class claims to work." + ifvarclass => "$(class_needed)"; + + "The shared class does not work" + ifvarclass => "!$(class_needed)"; +} diff --git a/tests/acceptance/02_classes/01_basic/117.x.cf b/tests/acceptance/02_classes/01_basic/117.x.cf new file mode 100644 index 0000000000..bf200006a0 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/117.x.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Test no brackets on "and" (Issue 234) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => + userexists("root"); + + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/118.x.cf b/tests/acceptance/02_classes/01_basic/118.x.cf new file mode 100644 index 0000000000..47ed25f7a0 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/118.x.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Test no brackets on "or" (Issue 234) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" or => + userexists("root"); + + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/119.x.cf b/tests/acceptance/02_classes/01_basic/119.x.cf new file mode 100644 index 0000000000..fdd92009ef --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/119.x.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Test no brackets on "xor" (Issue 234) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => + userexists("root"); + + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/action_policy_warn_sets_classes.cf b/tests/acceptance/02_classes/01_basic/action_policy_warn_sets_classes.cf new file mode 100644 index 0000000000..ba191f96a8 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/action_policy_warn_sets_classes.cf @@ -0,0 +1,78 @@ +####################################################### +# +# Test not +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + files: + "/tmp/dont_create_me" + create => 'true', + classes => classes_generic( "test_files" ), + action => warnonly; + + commands: + "$(G.echo) 'dont_print_me'" + classes => classes_generic( "test_commands" ), + action => warnonly; + + processes: + "cf-agent-x" + restart_class => "restart_cf_agent", + classes => classes_generic( "test_processes" ), + action => warnonly; + + reports: + "Test output" + classes => classes_generic("test_reports"), + action => warnonly; +} + +body action warnonly +{ + action_policy => "warn"; +} + +####################################################### + +bundle agent check +{ + vars: + "classes" slist => classesmatching( "test_.*" ); + + classes: + "ok" and => { + "test_files_not_kept", + "test_commands_not_kept", + "test_processes_not_kept", + "test_reports_not_kept" + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + debug:: + "$(classes)"; +} + diff --git a/tests/acceptance/02_classes/01_basic/agent_class_scope.cf b/tests/acceptance/02_classes/01_basic/agent_class_scope.cf new file mode 100644 index 0000000000..5ac1f7125f --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/agent_class_scope.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Check that we can use scope attributes to promise global classes +# from agent bundles +# +####################################################### + +body common control { + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init { + vars: + "classes" slist => { "global_c", "local_c", "unglobal_c", "unlocal_c" }; +} + +bundle agent test { + classes: + "global_c" expression => "any", scope => "namespace"; + "unglobal_c" expression => "!any", scope => "namespace"; + "local_c" expression => "any"; + "unlocal_c" expression => "!any"; + reports: + DEBUG:: + "$(init.classes) defined from test" if => "${init.classes}"; + "$(init.classes) not defined from test" if => "!${init.classes}"; +} + +bundle agent check { + classes: + global_c.!local_c.!unglobal_c.!unlocal_c:: + "ok" expression => "any"; + + reports: + DEBUG:: + "$(init.classes) defined from check" if => "${init.classes}"; + "$(init.classes) not defined from check" if => "!${init.classes}"; + + ok:: + "${this.promise_filename} Pass"; + !ok:: + "${this.promise_filename} FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/array_key_with_space_isvariable.cf b/tests/acceptance/02_classes/01_basic/array_key_with_space_isvariable.cf new file mode 100644 index 0000000000..0b77d2b92e --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/array_key_with_space_isvariable.cf @@ -0,0 +1,39 @@ +# isvariable should be able to check array indexes that have spaces +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "array[one]" string => "1"; + "array[twenty one]" string => "21"; + + classes: + "have_one" + expression => isvariable("array[one]"), + scope => "namespace"; + + "have_twenty_one" + expression => isvariable("array[twenty one]"), + scope => "namespace"; + + reports: + have_one.DEBUG:: + "Have array key 'one' as expected containing value '$(array[one])'"; + have_twenty_one.DEBUG:: + "Have array key 'twenty one' as expected containing value '$(array[twenty one])'"; +} + +bundle agent check +{ + methods: + have_one.have_twenty_one:: + "result" usebundle => dcs_pass("$(this.promise_filename)"); + + !(have_one.have_twenty_one):: + "result" usebundle => dcs_fail("$(this.promise_filename)"); +} diff --git a/tests/acceptance/02_classes/01_basic/avoid_reevaluation.cf b/tests/acceptance/02_classes/01_basic/avoid_reevaluation.cf new file mode 100644 index 0000000000..1d4c8bf17a --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/avoid_reevaluation.cf @@ -0,0 +1,23 @@ +####################################################### +# +# Redmine#5241: do not reevaluate classes if they are known already +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_output(".*", + ".*loquacious quaalude.*|.*/etc/debian_version.*", + "$(sys.cf_agent) -KI -f $(this.promise_filename).sub -Dloquacious", + $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/01_basic/avoid_reevaluation.cf.sub b/tests/acceptance/02_classes/01_basic/avoid_reevaluation.cf.sub new file mode 100644 index 0000000000..1c2bbfe28a --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/avoid_reevaluation.cf.sub @@ -0,0 +1,15 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + "path" string => "/etc/debian_version"; + + classes: + "loquacious" expression => returnszero("$(G.echo) loquacious quaalude", "noshell"); + "$(path)" expression => returnszero("$(G.echo) redmine6561", "noshell"); +} diff --git a/tests/acceptance/02_classes/01_basic/cancel_hardclass.cf b/tests/acceptance/02_classes/01_basic/cancel_hardclass.cf new file mode 100644 index 0000000000..39b8d95ef3 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/cancel_hardclass.cf @@ -0,0 +1,46 @@ +############################################################################# +# +# Test that undefining hardclasses is not be permitted. +# +############################################################################# + +body common control +{ + bundlesequence => { "init", "test", "check" }; +} + +bundle agent init +{ + +} + +body classes undefine(class) +{ + cancel_kept => { "$(class)" }; + cancel_repaired => { "$(class)" }; + cancel_notkept => { "$(class)" }; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-7718" } + string => "Test that undefining hardclasses is not be permitted."; + + commands: + "/bin/true" + classes => undefine("cfengine"); +} + +bundle agent check +{ + classes: + "passed" + expression => "cfengine"; + + reports: + passed:: + "$(this.promise_filename) Pass"; + !passed:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_show_classes.cf b/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_show_classes.cf new file mode 100644 index 0000000000..379f043a49 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_show_classes.cf @@ -0,0 +1,22 @@ +# Test $(sys.inputdir), $(sys.masterdir), $(sys.libdir), $(sys.bindir), $(sys.failsafe_policy_path), $(sys.update_policy_path), $(sys.local_libdir) +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent test +{ + meta: + "description" string => "Test that comments on classes are emitted in --show-evaluated-classes output."; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_output(".*test_class.*This is a comment about test_class.*", "", "$(sys.cf_agent) -Kf $(this.promise_filename).sub --show-evaluated-classes", $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_show_classes.cf.sub b/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_show_classes.cf.sub new file mode 100644 index 0000000000..9de18984d1 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_show_classes.cf.sub @@ -0,0 +1,8 @@ +bundle agent main +{ + classes: + "test_class" + expression => "any", + comment => "This is a comment about test_class", + scope => "namespace"; +} diff --git a/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_verbose.cf b/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_verbose.cf new file mode 100644 index 0000000000..32354a5f6e --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_verbose.cf @@ -0,0 +1,24 @@ +# Test $(sys.inputdir), $(sys.masterdir), $(sys.libdir), $(sys.bindir), $(sys.failsafe_policy_path), $(sys.update_policy_path), $(sys.local_libdir) +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent test +{ + meta: + "description" + string => "Test that comments on classes are emitted in verbose output.", + meta => { "CFE-2443" }; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_output(".*This is a comment about test_class.*", "", "$(sys.cf_agent) -Kvf $(this.promise_filename).sub", $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_verbose.cf.sub b/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_verbose.cf.sub new file mode 100644 index 0000000000..ea390fdc1f --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/classes_comments_emitted_in_verbose.cf.sub @@ -0,0 +1,7 @@ +bundle agent main +{ + classes: + "test_class" + expression => "any", + comment => "This is a comment about test_class"; +} diff --git a/tests/acceptance/02_classes/01_basic/classes_from_modules_available_to_subsequent_bundles_in_first_pass.cf b/tests/acceptance/02_classes/01_basic/classes_from_modules_available_to_subsequent_bundles_in_first_pass.cf new file mode 100644 index 0000000000..49a81fb141 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/classes_from_modules_available_to_subsequent_bundles_in_first_pass.cf @@ -0,0 +1,29 @@ +####################################################### +# +# Redmine#6689 - Should be able to use negative expression on class defined +# from module protocol right away +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + meta: + "test_soft_fail" + string => "any", + meta => { "redmine#6689", "zendesk#1548" }; + + methods: + "" usebundle => dcs_passif_output("should be set", + "should not be set", + "$(sys.cf_agent) -KI -f $(this.promise_filename).sub", + $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/01_basic/classes_from_modules_available_to_subsequent_bundles_in_first_pass.cf.sub b/tests/acceptance/02_classes/01_basic/classes_from_modules_available_to_subsequent_bundles_in_first_pass.cf.sub new file mode 100644 index 0000000000..1d1160579c --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/classes_from_modules_available_to_subsequent_bundles_in_first_pass.cf.sub @@ -0,0 +1,54 @@ +#!/var/cfengine/bin/cf-agent -Kf +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "main"}; +} + +bundle agent main +{ + methods: + "any" usebundle => module_stub; + "any" usebundle => test_bundle; +} + +bundle agent module_stub +{ + commands: + any:: + "$(G.echo) +test_class" + comment => "Setting test_class", + module => "true" ; +} + +bundle agent test_bundle +{ + vars: + # because this bundle runs after the module, I would expect both + # of these class statements to be false (and the vars not set). + # In practice, $(bogus_first) seems to somehow get set before + # the module runs, and $(bogus_second) - which is forced to run + # in the second pass of this bundle, is not set, which is correct. + !test_class:: + "bogus_first" + string => "should not be set", + comment => "because module_stub was activated and test_class was defined *BEFORE* $(this.bundle) was activated"; + + secondpass.!test_class:: + "bogus_second" + string => "should not be set", + comment => "because even when its the second pass, test_class was already defined."; + + !secondpass.test_class:: + "good_first" + string => "should be set"; + + classes: + "secondpass" expression => "any"; + + reports: + # I would expect *neither* of these vars to be defined + "Value of bogus_first is: $(bogus_first)"; + "Value of bogus_second is: $(bogus_second)"; + "Value of good_first is: $(good_first)"; +} diff --git a/tests/acceptance/02_classes/01_basic/classes_promises_with_classes_bodies.cf b/tests/acceptance/02_classes/01_basic/classes_promises_with_classes_bodies.cf new file mode 100644 index 0000000000..dc933ca9de --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/classes_promises_with_classes_bodies.cf @@ -0,0 +1,40 @@ +####################################################### +# +# Classes type promises should work with classes bodies +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + meta: + "test_soft_fail" + string => "any", + meta => { "redmine#7537" }; + + vars: + "classes" + slist => classesmatching("checked_for_file.*"); + + classes: + "have_file" + expression => isplain("$(this.promise_filename)"), + classes => scoped_classes_generic("bundle", "checked_for_file"); + + methods: + "pass or fail" + usebundle => dcs_passif("checked_for_file_reached", $(this.promise_filename)); + + reports: + DEBUG|EXTRA:: + "Found class: $(classes)"; + +} diff --git a/tests/acceptance/02_classes/01_basic/classes_without_expression.cf b/tests/acceptance/02_classes/01_basic/classes_without_expression.cf new file mode 100644 index 0000000000..a1c1af0e18 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/classes_without_expression.cf @@ -0,0 +1,30 @@ +####################################################### +# +# test that classes without expressions are defined +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ + classes: + "myclass1"; + "myclass2" meta => { "good" }; + "myclass3" persistence => "120"; + "myclass4" meta => { "good" }, persistence => "120"; +} +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("myclass1,myclass2,myclass3,myclass4", + "", + $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/01_basic/common_bundle_select_class_empty_list.cf b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_empty_list.cf new file mode 100644 index 0000000000..baa3b4aafb --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_empty_list.cf @@ -0,0 +1,35 @@ +# Test that select_class is skipped when no classes are given +body common control { + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ +# meta: +# "test_soft_fail" string => "any", +# meta => { "redmine7482" }; + + vars: + any:: + "common_classes" slist => { "common_1", "common_2" }; + + common_class_selected:: + "class_selected" + string => "$(common_classes)", + if => "$(common_classes)"; + + classes: + "common_class_selected" + select_class => { }; + +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_passif("any", + "$(this.promise_filename)"), + unless => "common_class_selected"; +} diff --git a/tests/acceptance/02_classes/01_basic/common_bundle_select_class_multiple_classes_as_scalar_variable.cf b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_multiple_classes_as_scalar_variable.cf new file mode 100644 index 0000000000..231fdf95d7 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_multiple_classes_as_scalar_variable.cf @@ -0,0 +1,37 @@ +# Test that select class is actuated when it has multiple classes given as a +# variable +body common control { + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ + vars: + any:: + "common_classes" slist => { "common_1", "common_2" }; + + common_class_selected:: + "class_selected" + string => "$(common_classes)", + if => "$(common_classes)"; + + classes: + # When you iterate on a list using $() it should iterate in the order of + # the list. So the first time this is actuated, the value of + # $(common_classes) should be "common_1". "common_2" should never be + # defined, because the promise will be skipped on subsequent passes since + # the "common_class_selected" is already defined. + "common_class_selected" + select_class => { $(common_classes) }; + +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_passif("common_1", + "$(this.promise_filename)"), + unless => "common_2"; +} diff --git a/tests/acceptance/02_classes/01_basic/common_bundle_select_class_single_class.cf b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_single_class.cf new file mode 100644 index 0000000000..8c38c60b10 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_single_class.cf @@ -0,0 +1,30 @@ +# Test that select class is actuated when it has only a single class given +body common control { + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ + vars: + any:: + "common_classes" slist => { }; + + common_class_selected:: + "class_selected" + string => "$(common_classes)", + if => "$(common_classes)"; + + classes: + "common_class_selected" + select_class => { "common_1" }; + +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_passif("common_1", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/02_classes/01_basic/common_bundle_select_class_single_class_as_variable.cf b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_single_class_as_variable.cf new file mode 100644 index 0000000000..602adce170 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_single_class_as_variable.cf @@ -0,0 +1,31 @@ +# Test that select class is actuated when it has only a single class given as a +# variable +body common control { + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ + vars: + any:: + "common_class" string => "common_1"; + + common_class_selected:: + "class_selected" + string => "$(common_classes)", + if => "$(common_classes)"; + + classes: + "common_class_selected" + select_class => { $(common_class) }; + +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_passif("common_1", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/02_classes/01_basic/common_bundle_select_class_variable_expands_empty_list.cf b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_variable_expands_empty_list.cf new file mode 100644 index 0000000000..8a492a3b23 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/common_bundle_select_class_variable_expands_empty_list.cf @@ -0,0 +1,31 @@ +# Test that select class is skipped when the list of classes is empty +body common control { + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test +{ + vars: + any:: + "common_classes" slist => { }; + + common_class_selected:: + "class_selected" + string => "$(common_classes)", + if => "$(common_classes)"; + + classes: + "common_class_selected" + select_class => { @(common_classes) }; + +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_passif("any", + "$(this.promise_filename)"), + unless => "common_class_selected"; +} diff --git a/tests/acceptance/02_classes/01_basic/common_class_scope.cf b/tests/acceptance/02_classes/01_basic/common_class_scope.cf new file mode 100644 index 0000000000..f5ad700922 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/common_class_scope.cf @@ -0,0 +1,45 @@ +####################################################### +# +# Check that we can use scope attributes to promise global classes +# from common bundles +# +####################################################### + +body common control { + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init { + vars: + "classes" slist => { "global_c", "local_c", "unglobal_c", "unlocal_c" }; +} + +bundle common test { + classes: + "global_c" expression => "any"; + "unglobal_c" expression => "!any"; + "local_c" expression => "any", scope => "bundle"; + "unlocal_c" expression => "!any", scope => "bundle"; + reports: + DEBUG:: + "$(init.classes) defined from test" if => "${init.classes}"; + "$(init.classes) not defined from test" if => "!${init.classes}"; +} + +bundle agent check { + classes: + global_c.!local_c.!unglobal_c.!unlocal_c:: + "ok" expression => "any"; + + reports: + DEBUG:: + "$(init.classes) defined from check" if => "${init.classes}"; + "$(init.classes) not defined from check" if => "!${init.classes}"; + + ok:: + "${this.promise_filename} Pass"; + !ok:: + "${this.promise_filename} FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/depends_on_proper_ordering.cf b/tests/acceptance/02_classes/01_basic/depends_on_proper_ordering.cf new file mode 100644 index 0000000000..24d2b8fee6 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/depends_on_proper_ordering.cf @@ -0,0 +1,50 @@ +# Redmine#5462 +# Redmine#6484 + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "states" slist => { "actual", "expected" }; + + "expected_content" string => + "second_line +first_line"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => test_insert("$(expected_content)"); + + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("first_line"), + depends_on => { "insert_second_line" }; + + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("second_line"), + handle => "insert_second_line"; +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)"; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + + diff --git a/tests/acceptance/02_classes/01_basic/depends_on_respected.cf b/tests/acceptance/02_classes/01_basic/depends_on_respected.cf new file mode 100644 index 0000000000..e6cdc286eb --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/depends_on_respected.cf @@ -0,0 +1,125 @@ +# Redmine#5462 +# Redmine#6484 +# respect depends_on restriction + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)_present_if_test_commands_false_considered_kept_or_repaired" + delete => tidy; + + "$(G.testfile)_present_if_test_commands_echo_ran" + delete => tidy; +} + +bundle agent test +{ + + files: + "$(G.testfile)_present_if_test_commands_false_considered_kept_or_repaired" + create => "true", + handle => "test_files", + classes => scoped_classes_generic("namespace", "test_commands_files_testfile"), + depends_on => { "test_commands_false", "test_commands_false2" }; + + commands: + "$(G.false)" + handle => "test_commands_false", + classes => scoped_classes_generic("namespace", "test_commands_false"); + + "$(G.false)" + handle => "test_commands_false2", + classes => scoped_classes_generic("namespace", "test_commands_false2"); + + "$(G.true)" + handle => "test_commands_true", + classes => scoped_classes_generic("namespace", "test_commands_true"), + depends_on => { "test_commands_false", "test_commands_false2" }; + + packages: + "test_packages" + package_method => mock, + handle => "test_packages", + classes => scoped_classes_generic("namespace", "test_packages"), + depends_on => { "test_commands_false", "test_commands_false2" }; + + reports: + "This promise '$(this.handle)' should have been skipped as the promise 'test_commands_false' + should never be kept or repaired" + handle => "test_reports", + classes => scoped_classes_generic("namespace", "test_reports"), + depends_on => { "test_commands_false", "test_commands_false2" }; + + +} + +bundle agent check +{ + vars: + "fail_classes" + slist => { + "test_commands_true_reached", + "test_files_reached", + "test_reports_reached", + "test_packages_reached", + }; + + classes: + "fail" or => { @(fail_classes) }; + + reports: + DEBUG:: + "'test_commands_false' was ok unexpectedly" + if => "test_commands_false_ok"; + + "'test_commands_false' was kept unexpectedly" + if => "test_commands_false_kept"; + + "'test_commands_false' was repaired unexpectedly" + if => "test_commands_false_repaired"; + + "'test_commands_false' was not_ok as expected" + if => "test_commands_false_not_ok"; + + "'test_commands_false2' was ok unexpectedly" + if => "test_commands_false2_ok"; + + "'test_commands_false2' was kept unexpectedly" + if => "test_commands_false2_kept"; + + "'test_commands_false2' was repaired unexpectedly" + if => "test_commands_false2_repaired"; + + "'test_commands_false2' was not_ok as expected" + if => "test_commands_false2_not_ok"; + + "'$(fail_classes)' erroneously thought 'test_commands_false' and or 'test_commands_false2' was ok aka (kept or repaired)." + if => "$(fail_classes)"; + + fail:: + "$(this.promise_filename) FAIL"; + + !fail:: + "$(this.promise_filename) Pass"; +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(G.echo) --list-installed"; + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.echo) --add "; + package_update_command => "$(G.echo) --update "; + package_delete_command => "$(G.echo) --delete "; + package_verify_command => "$(G.echo) --verify "; +} diff --git a/tests/acceptance/02_classes/01_basic/expected_os_classes.cf b/tests/acceptance/02_classes/01_basic/expected_os_classes.cf new file mode 100644 index 0000000000..020f4a189b --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/expected_os_classes.cf @@ -0,0 +1,44 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3608" } + string => "Make sure at least one of the expected os hard classes are defined"; + + vars: + DEBUG:: + "defined_classes" string => join("$(const.n)", classesmatching(".*")); +} + +bundle agent check +{ + classes: + "passed" or => { "debian", + "ubuntu", + "redhat", + "centos", + "fedora", + "aix", + "hpux", + "suse", + "opensuse", + "archlinux", + "windows", + "freebsd", + "macos", + "solaris" }; + + reports: + DEBUG&!passed:: + "None of the expected classes were defined."; + "Here is a list of defined classes:"; + "$(test.defined_classes)"; + passed:: + "$(this.promise_filename) Pass"; + !passed:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/from_json_booleans.cf b/tests/acceptance/02_classes/01_basic/from_json_booleans.cf new file mode 100644 index 0000000000..c9db5ed37f --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/from_json_booleans.cf @@ -0,0 +1,38 @@ +########################################################### +# +# Test expansion of JSON booleans +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle agent test +{ + classes: + "canary" expression => "any", meta => { "collect" }; # just a baseline test + "from_$(json)" expression => "$(json)", meta => { "collect" }; # iterates through non-nulls + "from_null" expression => "$(json[maybe])", meta => { "collect" }; + "from_missing" expression => "$(json[nonesuch])", meta => { "collect" }; + + vars: + "json" data => '{ "yes": true, "no": false, "maybe": null }'; + "var_$(json)" string => "$(json)"; + "collected" slist => sort(classesmatching(".*", "collect")); +} + +########################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/01_basic/from_json_booleans.cf.expected.json b/tests/acceptance/02_classes/01_basic/from_json_booleans.cf.expected.json new file mode 100644 index 0000000000..e6917c2885 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/from_json_booleans.cf.expected.json @@ -0,0 +1,13 @@ +{ + "collected": [ + "canary", + "from_true" + ], + "json": { + "maybe": null, + "no": false, + "yes": true + }, + "var_false": "false", + "var_true": "true" +} diff --git a/tests/acceptance/02_classes/01_basic/hp_ux_major.cf b/tests/acceptance/02_classes/01_basic/hp_ux_major.cf new file mode 100644 index 0000000000..5c96775a23 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/hp_ux_major.cf @@ -0,0 +1,38 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3609" } + string => "Make sure the class 'hpux_' is defined on HP-UX"; + + vars: + DEBUG:: + "defined_classes" string => join("$(const.n)", classesmatching(".*")); +} + +bundle agent check +{ + classes: + # if 'hpux': make sure 'hpux_10' or 'hpux_11' is defined + hpux:: + "passed" expression => "hpux_10|hpux_11"; + # if not 'hpux': make sure neither 'hpux_10' nor 'hpux_11' is defined + !hpux:: + "passed" expression => "!(hpux_10|hpux_11)"; + + reports: + DEBUG&hpux&!passed:: + "No class containing OS- name & version major is defined on HP-UX."; + "Here is a list of defined classes:"; + "$(test.defined_classes)"; + DEBUG&!hpux&!passed:: + "A class containing 'hpux_' was defined on a non HP-UX system"; + passed:: + "$(this.promise_filename) Pass"; + !passed:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/if_resolving_classexpression_from_datacontainer.cf b/tests/acceptance/02_classes/01_basic/if_resolving_classexpression_from_datacontainer.cf new file mode 100644 index 0000000000..7366bdc18d --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/if_resolving_classexpression_from_datacontainer.cf @@ -0,0 +1,67 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3319" } + string => "Test that if/ifvarclass can check class expressions from within a data container"; + + vars: + + "d" data => '{ + "things": [ + { + "Title": "NOPE", + "classexpr": "Something_that_wont_match" + }, + { + "Title": "ExpectedPick", + "classexpr": "$(sys.class)" + }, + { + "Title": "AlsoNOPE", + "classexpr": "AnotherSomething_that_wont_match" + } + ] + }'; + + "di" slist => getindices( "d[things]" ); + + "selected" + string => "$(d[things][$(di)][Title])", + if => "$(d[things][$(di)][classexpr])"; + + "sanity_check" + string => "You are sane", + if => "$(sys.class)"; + + reports: + EXTRA|DEBUG:: + "See iteration/expansion: $(d[things][$(di)][Title]) has classexpr $(d[things][$(di)][classexpr])"; + "See sanity_check: $(sanity_check) because $(sys.class) is a defined class"; + + "Picked: $(d[things][$(di)][Title]) has classexpr $(d[things][$(di)][classexpr])" + if => "$(d[things][$(di)][classexpr])"; +} + +####################################################### + +bundle agent check +{ + vars: + "expected_selection" string => "ExpectedPick"; + + methods: + "Pass/Fail" + usebundle => dcs_check_strcmp($(expected_selection), $(test.selected), + $(this.promise_filename), "no"); + +} + diff --git a/tests/acceptance/02_classes/01_basic/ifvarclass_common_bundles.cf b/tests/acceptance/02_classes/01_basic/ifvarclass_common_bundles.cf new file mode 100644 index 0000000000..ed27b9629e --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/ifvarclass_common_bundles.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Check that we can use ifvarclass in common bundle class expressions +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common init +{ + classes: + "initclass" expression => "any"; +} + +####################################################### + +bundle common test +{ + classes: + "shouldset" expression => "any", ifvarclass => "initclass"; + "shouldnot" expression => "any", ifvarclass => "notaclass"; +} + +####################################################### + +bundle agent check +{ + vars: + "classes" slist => { "shouldset", "shouldnot" }; + + classes: + shouldset.!shouldnot:: + "ok" expression => "any"; + + reports: + DEBUG:: + "$(classes) is set" ifvarclass => "$(classes)"; + "$(classes) is not" ifvarclass => "!$(classes)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/invalid_function_in_list.cf b/tests/acceptance/02_classes/01_basic/invalid_function_in_list.cf new file mode 100644 index 0000000000..ec463b2e3b --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/invalid_function_in_list.cf @@ -0,0 +1,21 @@ +# Testcase for Redmine 6569 + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check +{ + classes: + "PASS" or => { "any", classify( "${never}" ) }; + + reports: + PASS:: + "$(this.promise_filename) Pass"; + !PASS:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/01_basic/ipaddr_classes.cf b/tests/acceptance/02_classes/01_basic/ipaddr_classes.cf new file mode 100644 index 0000000000..7cfaa7fa4e --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/ipaddr_classes.cf @@ -0,0 +1,34 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check +{ + vars: + "ipv6_classes" slist => classesmatching("ipv6_.*"); + + classes: + "has_ipv6" expression => returnszero("ifconfig -a | grep 'inet6 [a-z0-9].*'", "useshell"); + + has_ipv6:: + "ok" expression => some(".*", "ipv6_classes"); + + !has_ipv6:: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "ipv6_classes: $(ipv6_classes)"; + + DEBUG.has_ipv6:: + "Has IPv6"; +} diff --git a/tests/acceptance/02_classes/01_basic/persistent_negate.cf b/tests/acceptance/02_classes/01_basic/persistent_negate.cf new file mode 100644 index 0000000000..5dc9e54a48 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/persistent_negate.cf @@ -0,0 +1,50 @@ +####################################################### +# +# ENT-5886 -- -N/--negate should prevent persistent classes from being defined +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dflags" string => ifelse("EXTRA", "-DDEBUG,EXTRA", "-DDEBUG"); +} + +bundle agent test +{ + meta: + "description" -> {"ENT-5886"} + string => "-N/--negate should prevent persistent classes from being defined"; + + commands: + "$(sys.cf_agent) -K $(init.dflags) -f $(this.promise_filename).sub" + classes => test_always("done_persisting"); +} + +body classes test_always(x) +{ + promise_repaired => { "$(x)" }; + promise_kept => { "$(x)" }; + repair_failed => { "$(x)" }; + repair_denied => { "$(x)" }; + repair_timeout => { "$(x)" }; +} + +bundle agent check +{ + vars: + done_persisting:: + "subout" string => execresult("$(sys.cf_agent) -N test_class -K $(init.dflags) -f $(this.promise_filename).sub2", "noshell"); + + methods: + "" usebundle => dcs_check_strcmp($(subout), "R: Pass", + $(this.promise_filename), + "no"); +} diff --git a/tests/acceptance/02_classes/01_basic/persistent_negate.cf.sub b/tests/acceptance/02_classes/01_basic/persistent_negate.cf.sub new file mode 100644 index 0000000000..58606f8f40 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/persistent_negate.cf.sub @@ -0,0 +1,12 @@ +body common control +{ + bundlesequence => { run }; +} + +bundle common run +{ + classes: + "test_class" + expression => "any", + persistence => "120"; # 2 hours. +} diff --git a/tests/acceptance/02_classes/01_basic/persistent_negate.cf.sub2 b/tests/acceptance/02_classes/01_basic/persistent_negate.cf.sub2 new file mode 100644 index 0000000000..03931d5fc8 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/persistent_negate.cf.sub2 @@ -0,0 +1,20 @@ +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + classes: + "ok" expression => "!test_class"; + + reports: + ok:: + "Pass"; + + !ok.DEBUG:: + "test_class defined"; + + !ok:: + "FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/persistent_tags.cf b/tests/acceptance/02_classes/01_basic/persistent_tags.cf new file mode 100644 index 0000000000..acc392872a --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/persistent_tags.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Redmine#5017: persistent classes should keep their tags +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dflags" string => ifelse("EXTRA", "-DDEBUG,EXTRA", "-DDEBUG"); + + commands: + "$(G.echo)" classes => init_cancel_always; +} + +body classes init_cancel_always +{ + cancel_repaired => { "myclass" }; + cancel_notkept => { "myclass" }; + cancel_kept => { "myclass" }; +} + +bundle agent test +{ + commands: + "$(sys.cf_agent) -K $(init.dflags) -f $(this.promise_filename).sub" + classes => test_always("done_persisting"); +} + +body classes test_always(x) +{ + promise_repaired => { "$(x)" }; + promise_kept => { "$(x)" }; + repair_failed => { "$(x)" }; + repair_denied => { "$(x)" }; + repair_timeout => { "$(x)" }; +} + +bundle agent check +{ + vars: + done_persisting:: + "subout" string => execresult("$(sys.cf_agent) -K $(init.dflags) -f $(this.promise_filename).sub2", "noshell"); + reports: + "$(subout)"; +} diff --git a/tests/acceptance/02_classes/01_basic/persistent_tags.cf.sub b/tests/acceptance/02_classes/01_basic/persistent_tags.cf.sub new file mode 100644 index 0000000000..20556b250b --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/persistent_tags.cf.sub @@ -0,0 +1,12 @@ +body common control +{ + bundlesequence => { run }; +} + +bundle common run +{ + classes: + "myclass" expression => "any", + meta => { "specialtag" }, + persistence => "120"; # 2 hours. +} diff --git a/tests/acceptance/02_classes/01_basic/persistent_tags.cf.sub2 b/tests/acceptance/02_classes/01_basic/persistent_tags.cf.sub2 new file mode 100644 index 0000000000..1459554798 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/persistent_tags.cf.sub2 @@ -0,0 +1,36 @@ +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + vars: + "tagged" slist => classesmatching("myclass"); + "unsorted_tags_$(tagged)" slist => getclassmetatags($(tagged)); + "tags_$(tagged)" slist => sort("unsorted_tags_$(tagged)", "lex"); + "tagged_str" string => format("%S", tagged); + "tags_$(tagged)_str" string => format("%S", "tags_$(tagged)"); + "expected_classes" string => '{ "myclass" }'; + "expected_tags" string => '{ "source=persistent", "source=promise", "specialtag" }'; + + classes: + "ok_class" expression => strcmp($(tagged_str), $(expected_classes)); + "ok_tags" expression => strcmp($(tags_myclass_str), $(expected_tags)); + "ok" and => { "ok_class", "ok_tags" }; + + reports: + !ok_class.DEBUG:: + "The list of tagged classes '$(tagged_str)' did NOT match the expected '$(expected_classes)'"; + !ok_tags.DEBUG:: + "The list of tags '$(tags_myclass_str)' did NOT match the expected '$(expected_tags)'"; + ok_class.EXTRA:: + "The list of tagged classes '$(tagged_str)' matched the expected '$(expected_classes)'"; + ok_tags.EXTRA:: + "The list of tags '$(tags_myclass_str)' matched the expected '$(expected_tags)'"; + + ok:: + "${this.promise_dirname}/persistent_tags.cf Pass"; + !ok:: + "${this.promise_dirname}/persistent_tags.cf FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/rbtree_collisions-redmine7912.cf b/tests/acceptance/02_classes/01_basic/rbtree_collisions-redmine7912.cf new file mode 100644 index 0000000000..971b2096a4 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/rbtree_collisions-redmine7912.cf @@ -0,0 +1,63 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + + classes: + + "legumes" expression => "any"; + "kumiss" expression => "any"; + "range_class_prod_ltx1_seas_groupdiscussions_indexer_4_PRODUCTVERSION__0_1_86_" expression => "any"; + + "right_classes_set" expression => "legumes.kumiss.range_class_prod_ltx1_seas_groupdiscussions_indexer_4_PRODUCTVERSION__0_1_86_"; + "wrong_classes_set" expression => "goosey|ungoverned|ipv4_10_132_60"; + + "ok" expression => "right_classes_set.!wrong_classes_set"; + + + reports: + + DEBUG.legumes:: + "Pass legumes"; + DEBUG.goosey:: + "FAIL goosey"; + + DEBUG.kumiss:: + "Pass kumiss"; + DEBUG.ungoverned:: + "FAIL ungoverned"; + + DEBUG.range_class_prod_ltx1_seas_groupdiscussions_indexer_4_PRODUCTVERSION__0_1_86_:: + "PASS range_class_prod_ltx1_seas_groupdiscussions_indexer_4_PRODUCTVERSION__0_1_86_"; + DEBUG.ipv4_10_132_60:: + "FAIL ipv4_10_132_60"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + +} diff --git a/tests/acceptance/02_classes/01_basic/simplistic_ipaddr_classes.cf b/tests/acceptance/02_classes/01_basic/simplistic_ipaddr_classes.cf new file mode 100644 index 0000000000..784225d3e9 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/simplistic_ipaddr_classes.cf @@ -0,0 +1,44 @@ +############################################################################## +# +# Test that simplistic IP-address classes are set (e.g., 192_168_56_10) +# +############################################################################## + +body common control +{ + bundlesequence => { "test", "check" }; + version => "1.0"; +} + +############################################################################## + +bundle agent test +{ + meta: + "description" -> { "ENT-2044" } + string => "Test that simplistic IP classes are set"; + + vars: + "ip_classes" + slist => maplist(canonify("$(this)"), "@(sys.ip_addresses)"); +} + +############################################################################## + +bundle agent check +{ + classes: + "ok" + and => { "@(test.ip_classes)" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "IP Classes: $(with)" + with => join(", ", "test.ip_classes"); +} diff --git a/tests/acceptance/02_classes/01_basic/staging/028.c b/tests/acceptance/02_classes/01_basic/staging/028.c new file mode 100644 index 0000000000..1540d87c46 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/staging/028.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#define SPLAY_PSEUDO_RANDOM_CONSTANT 8192 + +#define DAILY 12 * 24 +main() +{ + char s[] = "xx"; + char c, c2; + int hash, box, minblocks; + int period = DAILY - 1; + char *boxes[DAILY]; + for (box = 0; box < DAILY; box++) + { + boxes[box] = 0; + } + for (c = 'A'; c <= 'z'; c++) + { + for (c2 = 'A'; c2 <= 'z'; c2++) + { + if (!(isalnum(c) && isalnum(c2))) + continue; + s[0] = c; + s[1] = c2; + // The block algorithm is copied from evalfunction.c + hash = OatHash(s); + box = + (int) (0.5 + period * hash / (double) SPLAY_PSEUDO_RANDOM_CONSTANT); + // Back to original code + if (!boxes[box]) + { + boxes[box] = strncpy((char *) malloc(3), s, 3); + } + } + } + printf(" \"ok\" xor => {\n"); + for (box = 0; box < DAILY; box++) + { + printf("\tsplayclass(\"%s\",\"daily\"), # Box %d\n", boxes[box], box); + } + printf("\t};\n"); +} + +// This is copied from files_hashes.c +int OatHash(char *key) +{ + unsigned int hashtablesize = SPLAY_PSEUDO_RANDOM_CONSTANT; + unsigned char *p = key; + unsigned h = 0; + int i, len = strlen(key); + + for (i = 0; i < len; i++) + { + h += p[i]; + h += (h << 10); + h ^= (h >> 6); + } + + h += (h << 3); + h ^= (h >> 11); + h += (h << 15); + + return (h & (hashtablesize - 1)); +} diff --git a/tests/acceptance/02_classes/01_basic/staging/028.cf b/tests/acceptance/02_classes/01_basic/staging/028.cf new file mode 100644 index 0000000000..09461743f7 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/staging/028.cf @@ -0,0 +1,340 @@ +####################################################### +# +# Check splayclass - requires familiaity with internals +# See associated C file to generate mutually exclusive classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + classes: + "classtotest" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + # The program 028.c generates this list, and exactly 1 class will + # be True at any time of day + "ok" xor => { + splayclass("AT","daily"), # Box 0 + splayclass("AA","daily"), # Box 1 + splayclass("AH","daily"), # Box 2 + splayclass("AZ","daily"), # Box 3 + splayclass("Cd","daily"), # Box 4 + splayclass("CV","daily"), # Box 5 + splayclass("Am","daily"), # Box 6 + splayclass("EH","daily"), # Box 7 + splayclass("Bh","daily"), # Box 8 + splayclass("HF","daily"), # Box 9 + splayclass("Bz","daily"), # Box 10 + splayclass("CM","daily"), # Box 11 + splayclass("OR","daily"), # Box 12 + splayclass("FK","daily"), # Box 13 + splayclass("Ay","daily"), # Box 14 + splayclass("Cc","daily"), # Box 15 + splayclass("IF","daily"), # Box 16 + splayclass("IA","daily"), # Box 17 + splayclass("HP","daily"), # Box 18 + splayclass("Et","daily"), # Box 19 + splayclass("Ga","daily"), # Box 20 + splayclass("EF","daily"), # Box 21 + splayclass("DS","daily"), # Box 22 + splayclass("Ee","daily"), # Box 23 + splayclass("BI","daily"), # Box 24 + splayclass("CH","daily"), # Box 25 + splayclass("AS","daily"), # Box 26 + splayclass("Gd","daily"), # Box 27 + splayclass("Dm","daily"), # Box 28 + splayclass("CA","daily"), # Box 29 + splayclass("FZ","daily"), # Box 30 + splayclass("Gv","daily"), # Box 31 + splayclass("ES","daily"), # Box 32 + splayclass("Db","daily"), # Box 33 + splayclass("BS","daily"), # Box 34 + splayclass("KP","daily"), # Box 35 + splayclass("EZ","daily"), # Box 36 + splayclass("Ax","daily"), # Box 37 + splayclass("An","daily"), # Box 38 + splayclass("Mq","daily"), # Box 39 + splayclass("Bw","daily"), # Box 40 + splayclass("BL","daily"), # Box 41 + splayclass("AI","daily"), # Box 42 + splayclass("CN","daily"), # Box 43 + splayclass("Aw","daily"), # Box 44 + splayclass("Ce","daily"), # Box 45 + splayclass("Mh","daily"), # Box 46 + splayclass("Gs","daily"), # Box 47 + splayclass("He","daily"), # Box 48 + splayclass("Ex","daily"), # Box 49 + splayclass("KJ","daily"), # Box 50 + splayclass("GN","daily"), # Box 51 + splayclass("Bm","daily"), # Box 52 + splayclass("Iy","daily"), # Box 53 + splayclass("JG","daily"), # Box 54 + splayclass("Po","daily"), # Box 55 + splayclass("CF","daily"), # Box 56 + splayclass("Fg","daily"), # Box 57 + splayclass("KQ","daily"), # Box 58 + splayclass("AB","daily"), # Box 59 + splayclass("Cq","daily"), # Box 60 + splayclass("KX","daily"), # Box 61 + splayclass("ER","daily"), # Box 62 + splayclass("De","daily"), # Box 63 + splayclass("Fn","daily"), # Box 64 + splayclass("Bn","daily"), # Box 65 + splayclass("DZ","daily"), # Box 66 + splayclass("DA","daily"), # Box 67 + splayclass("Ag","daily"), # Box 68 + splayclass("Bi","daily"), # Box 69 + splayclass("Ki","daily"), # Box 70 + splayclass("Kw","daily"), # Box 71 + splayclass("Ed","daily"), # Box 72 + splayclass("In","daily"), # Box 73 + splayclass("CC","daily"), # Box 74 + splayclass("Dy","daily"), # Box 75 + splayclass("OB","daily"), # Box 76 + splayclass("DU","daily"), # Box 77 + splayclass("Cj","daily"), # Box 78 + splayclass("EC","daily"), # Box 79 + splayclass("Gr","daily"), # Box 80 + splayclass("AO","daily"), # Box 81 + splayclass("Bf","daily"), # Box 82 + splayclass("Gk","daily"), # Box 83 + splayclass("AX","daily"), # Box 84 + splayclass("Ar","daily"), # Box 85 + splayclass("Hq","daily"), # Box 86 + splayclass("Ac","daily"), # Box 87 + splayclass("BG","daily"), # Box 88 + splayclass("Dq","daily"), # Box 89 + splayclass("AY","daily"), # Box 90 + splayclass("BY","daily"), # Box 91 + splayclass("Cn","daily"), # Box 92 + splayclass("BJ","daily"), # Box 93 + splayclass("Yu","daily"), # Box 94 + splayclass("DC","daily"), # Box 95 + splayclass("Dd","daily"), # Box 96 + splayclass("Ca","daily"), # Box 97 + splayclass("Kc","daily"), # Box 98 + splayclass("Op","daily"), # Box 99 + splayclass("PV","daily"), # Box 100 + splayclass("MJ","daily"), # Box 101 + splayclass("GE","daily"), # Box 102 + splayclass("EJ","daily"), # Box 103 + splayclass("Hh","daily"), # Box 104 + splayclass("CJ","daily"), # Box 105 + splayclass("Ev","daily"), # Box 106 + splayclass("Cw","daily"), # Box 107 + splayclass("Cb","daily"), # Box 108 + splayclass("Eh","daily"), # Box 109 + splayclass("Bt","daily"), # Box 110 + splayclass("EB","daily"), # Box 111 + splayclass("Fo","daily"), # Box 112 + splayclass("Nb","daily"), # Box 113 + splayclass("HG","daily"), # Box 114 + splayclass("KD","daily"), # Box 115 + splayclass("DW","daily"), # Box 116 + splayclass("Ak","daily"), # Box 117 + splayclass("Cp","daily"), # Box 118 + splayclass("Ae","daily"), # Box 119 + splayclass("Bg","daily"), # Box 120 + splayclass("Qq","daily"), # Box 121 + splayclass("PC","daily"), # Box 122 + splayclass("CL","daily"), # Box 123 + splayclass("HA","daily"), # Box 124 + splayclass("Aq","daily"), # Box 125 + splayclass("Ef","daily"), # Box 126 + splayclass("PJ","daily"), # Box 127 + splayclass("BA","daily"), # Box 128 + splayclass("Dp","daily"), # Box 129 + splayclass("DK","daily"), # Box 130 + splayclass("Gq","daily"), # Box 131 + splayclass("GW","daily"), # Box 132 + splayclass("Hp","daily"), # Box 133 + splayclass("BK","daily"), # Box 134 + splayclass("NY","daily"), # Box 135 + splayclass("NP","daily"), # Box 136 + splayclass("CB","daily"), # Box 137 + splayclass("CI","daily"), # Box 138 + splayclass("AR","daily"), # Box 139 + splayclass("Pb","daily"), # Box 140 + splayclass("Co","daily"), # Box 141 + splayclass("Ms","daily"), # Box 142 + splayclass("Oq","daily"), # Box 143 + splayclass("Pt","daily"), # Box 144 + splayclass("RQ","daily"), # Box 145 + splayclass("AF","daily"), # Box 146 + splayclass("Fs","daily"), # Box 147 + splayclass("DO","daily"), # Box 148 + splayclass("Cu","daily"), # Box 149 + splayclass("Ds","daily"), # Box 150 + splayclass("Tv","daily"), # Box 151 + splayclass("Ys","daily"), # Box 152 + splayclass("BW","daily"), # Box 153 + splayclass("BP","daily"), # Box 154 + splayclass("cV","daily"), # Box 155 + splayclass("DE","daily"), # Box 156 + splayclass("Fz","daily"), # Box 157 + splayclass("EM","daily"), # Box 158 + splayclass("DL","daily"), # Box 159 + splayclass("cM","daily"), # Box 160 + splayclass("BD","daily"), # Box 161 + splayclass("Bd","daily"), # Box 162 + splayclass("GC","daily"), # Box 163 + splayclass("AE","daily"), # Box 164 + splayclass("Ho","daily"), # Box 165 + splayclass("JE","daily"), # Box 166 + splayclass("CZ","daily"), # Box 167 + splayclass("RG","daily"), # Box 168 + splayclass("As","daily"), # Box 169 + splayclass("Ai","daily"), # Box 170 + splayclass("IT","daily"), # Box 171 + splayclass("Cg","daily"), # Box 172 + splayclass("Cl","daily"), # Box 173 + splayclass("JS","daily"), # Box 174 + splayclass("FO","daily"), # Box 175 + splayclass("HO","daily"), # Box 176 + splayclass("Bq","daily"), # Box 177 + splayclass("OH","daily"), # Box 178 + splayclass("DN","daily"), # Box 179 + splayclass("Hx","daily"), # Box 180 + splayclass("CK","daily"), # Box 181 + splayclass("NC","daily"), # Box 182 + splayclass("PO","daily"), # Box 183 + splayclass("Dz","daily"), # Box 184 + splayclass("Eq","daily"), # Box 185 + splayclass("AG","daily"), # Box 186 + splayclass("Ke","daily"), # Box 187 + splayclass("DT","daily"), # Box 188 + splayclass("EY","daily"), # Box 189 + splayclass("BE","daily"), # Box 190 + splayclass("Br","daily"), # Box 191 + splayclass("Qk","daily"), # Box 192 + splayclass("Jw","daily"), # Box 193 + splayclass("KM","daily"), # Box 194 + splayclass("AP","daily"), # Box 195 + splayclass("DY","daily"), # Box 196 + splayclass("Jz","daily"), # Box 197 + splayclass("Hi","daily"), # Box 198 + splayclass("AM","daily"), # Box 199 + splayclass("WQ","daily"), # Box 200 + splayclass("Cv","daily"), # Box 201 + splayclass("BQ","daily"), # Box 202 + splayclass("Fu","daily"), # Box 203 + splayclass("CR","daily"), # Box 204 + splayclass("JQ","daily"), # Box 205 + splayclass("FN","daily"), # Box 206 + splayclass("QW","daily"), # Box 207 + splayclass("Fb","daily"), # Box 208 + splayclass("DB","daily"), # Box 209 + splayclass("OI","daily"), # Box 210 + splayclass("Ei","daily"), # Box 211 + splayclass("FY","daily"), # Box 212 + splayclass("Cm","daily"), # Box 213 + splayclass("IE","daily"), # Box 214 + splayclass("HM","daily"), # Box 215 + splayclass("BN","daily"), # Box 216 + splayclass("BT","daily"), # Box 217 + splayclass("HD","daily"), # Box 218 + splayclass("GK","daily"), # Box 219 + splayclass("BB","daily"), # Box 220 + splayclass("Bo","daily"), # Box 221 + splayclass("EV","daily"), # Box 222 + splayclass("Ht","daily"), # Box 223 + splayclass("BM","daily"), # Box 224 + splayclass("Lk","daily"), # Box 225 + splayclass("Er","daily"), # Box 226 + splayclass("AD","daily"), # Box 227 + splayclass("GI","daily"), # Box 228 + splayclass("SX","daily"), # Box 229 + splayclass("JH","daily"), # Box 230 + splayclass("IZ","daily"), # Box 231 + splayclass("Cs","daily"), # Box 232 + splayclass("Ym","daily"), # Box 233 + splayclass("DI","daily"), # Box 234 + splayclass("Oo","daily"), # Box 235 + splayclass("LK","daily"), # Box 236 + splayclass("DX","daily"), # Box 237 + splayclass("HR","daily"), # Box 238 + splayclass("Fj","daily"), # Box 239 + splayclass("XC","daily"), # Box 240 + splayclass("AV","daily"), # Box 241 + splayclass("AC","daily"), # Box 242 + splayclass("CW","daily"), # Box 243 + splayclass("AJ","daily"), # Box 244 + splayclass("Av","daily"), # Box 245 + splayclass("CX","daily"), # Box 246 + splayclass("ED","daily"), # Box 247 + splayclass("HE","daily"), # Box 248 + splayclass("Ao","daily"), # Box 249 + splayclass("Gm","daily"), # Box 250 + splayclass("BC","daily"), # Box 251 + splayclass("EN","daily"), # Box 252 + splayclass("EP","daily"), # Box 253 + splayclass("FQ","daily"), # Box 254 + splayclass("Dt","daily"), # Box 255 + splayclass("RC","daily"), # Box 256 + splayclass("Ib","daily"), # Box 257 + splayclass("JI","daily"), # Box 258 + splayclass("BU","daily"), # Box 259 + splayclass("BV","daily"), # Box 260 + splayclass("Xp","daily"), # Box 261 + splayclass("GA","daily"), # Box 262 + splayclass("NE","daily"), # Box 263 + splayclass("Mo","daily"), # Box 264 + splayclass("CE","daily"), # Box 265 + splayclass("AL","daily"), # Box 266 + splayclass("Es","daily"), # Box 267 + splayclass("Py","daily"), # Box 268 + splayclass("Hl","daily"), # Box 269 + splayclass("IC","daily"), # Box 270 + splayclass("Bp","daily"), # Box 271 + splayclass("Iz","daily"), # Box 272 + splayclass("HU","daily"), # Box 273 + splayclass("DQ","daily"), # Box 274 + splayclass("EI","daily"), # Box 275 + splayclass("Cf","daily"), # Box 276 + splayclass("GH","daily"), # Box 277 + splayclass("Aa","daily"), # Box 278 + splayclass("EG","daily"), # Box 279 + splayclass("Ct","daily"), # Box 280 + splayclass("Gl","daily"), # Box 281 + splayclass("AW","daily"), # Box 282 + splayclass("CP","daily"), # Box 283 + splayclass("CO","daily"), # Box 284 + splayclass("AK","daily"), # Box 285 + splayclass("BZ","daily"), # Box 286 + splayclass("Fk","daily"), # Box 287 + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/01_basic/variable_class_expressions_with_if.cf b/tests/acceptance/02_classes/01_basic/variable_class_expressions_with_if.cf new file mode 100644 index 0000000000..d22cc90389 --- /dev/null +++ b/tests/acceptance/02_classes/01_basic/variable_class_expressions_with_if.cf @@ -0,0 +1,98 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + + "description" + string => "Test that variable class expressions and the if alias work together as expected.", + meta => { "CFE-2615" }; + + vars: + "x" string => "x"; + + classes: + "DEBUG" expression => "any"; + "one$(x)" expression => "any"; + + # These expressions are here purely for illustration. The classes two and + # three are never set. + + "two" expression => "!any"; + "three" expression => "!any"; + + # Here, after the variable class expression "one$(x)":: is where all of the + # test cases are described. + + "one$(x)":: + + "FAIL_if" + expression => "any", + meta => { "failtest" }, + if => "two|three", + comment => "We expect the class 'onex' to be defined, but since neither + 'two' nor 'three' are defined this class should not be set. + This seems to have something to do with the combination of a + variable class expression AND the use of the if attribute. + Note, ifvarclass does not seem to be affected."; + + "FAIL_ifvarclass" + expression => "any", + meta => { "failtest" }, + ifvarclass => "two|three"; + + "FAIL_if_or" + expression => "any", + meta => { "failtest" }, + if => or("two", "three"); + + "FAIL_ifvarclass_or" + expression => "any", + meta => { "failtest" }, + ifvarclass => or("two", "three"); + + "FAIL_unless" + expression => "any", + meta => { "failtest" }, + unless => "!two.!three"; + + "FAIL_unless_not" + expression => "any", + meta => { "failtest" }, + unless => not("two|three"); + + vars: + "FAIL_classes" slist => classesmatching( ".*", "failtest" ); + + commands: + + "$(G.true)" + handle => "delay", + comment => "This is a benign promise that is used to delay detection of + failure classes until after normal order begins."; + + classes: + "no_FAIL_classes" + expression => none(".*", FAIL_classes), + depends_on => { "delay" }; + + "FAIL_classes" + expression => some(".*", FAIL_classes); + + + reports: + DEBUG|EXTRA:: + "CFEngine $(sys.cf_version)"; + "Found class indicating failure '$(FAIL_classes)'"; + + FAIL_classes:: + "$(this.promise_filename) FAIL"; + + no_FAIL_classes:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/02_classes/02_functions/001.cf b/tests/acceptance/02_classes/02_functions/001.cf new file mode 100644 index 0000000000..8c0c1a2b0d --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/001.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test isgreaterthan() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + isgreaterthan("1", "0"), + isgreaterthan("2.7e3", "1"), + isgreaterthan("2", "1.1"), + isgreaterthan("2.7e3", "9e1"), + isgreaterthan("3b", "2z"), + isgreaterthan("beta", "alpha"), + isgreaterthan("beta", ""), + isgreaterthan("beta", "-75"), + isgreaterthan("3", ""), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/002.cf b/tests/acceptance/02_classes/02_functions/002.cf new file mode 100644 index 0000000000..9bcdf62a71 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/002.cf @@ -0,0 +1,62 @@ +####################################################### +# +# Test islessthan() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok1" expression => islessthan("0", "1"); + "ok2" expression => islessthan("1", "2.7e3"); + "ok3" expression => islessthan("1.1", "2"); + "ok4" expression => islessthan("2.7e3", "1e9"); + "ok5" expression => islessthan("2z", "3b"); + "ok6" expression => islessthan("alpha", "beta"); + "ok7" expression => islessthan("", "beta"); + "ok8" expression => islessthan("-75", "beta"); + "ok9" expression => islessthan("", "3"); + + "ok" and => { ok1, ok2, ok3, ok4, ok5, ok6, ok7, ok8, ok9 }; + + reports: + DEBUG.ok1:: "ok1"; + DEBUG.ok2:: "ok2"; + DEBUG.ok3:: "ok3"; + DEBUG.ok4:: "ok4"; + DEBUG.ok5:: "ok5"; + DEBUG.ok6:: "ok6"; + DEBUG.ok7:: "ok7"; + DEBUG.ok8:: "ok8"; + DEBUG.ok9:: "ok9"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/003.cf b/tests/acceptance/02_classes/02_functions/003.cf new file mode 100644 index 0000000000..a719fbc467 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/003.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Test isvariable() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "present" int => "1"; + + classes: + "should_fail" or => { + isvariable("x"), + isvariable("init.x") + }; + "should_pass" not => "should_fail"; + "ok" and => { + "should_pass", + isvariable("present"), + isvariable("init.dummy") + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/004.cf b/tests/acceptance/02_classes/02_functions/004.cf new file mode 100644 index 0000000000..946c3fd07d --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/004.cf @@ -0,0 +1,58 @@ +####################################################### +# +# Test strcmp() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "null" string => ""; + "one" int => "1"; + + classes: + "should_fail" or => { + strcmp("$(null)", "$(one)"), + strcmp("01", "$(one)"), + strcmp("init.x", "$(init.x)") + }; + "should_pass" not => "should_fail"; + "ok" and => { + "should_pass", + strcmp("", "$(null)"), + strcmp("1", "$(one)"), + strcmp("dummy", "$(init.dummy)") + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/005.cf b/tests/acceptance/02_classes/02_functions/005.cf new file mode 100644 index 0000000000..e30359b74b --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/005.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Test join() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "zeros" slist => { "solo" }; + "zero" string => join("NOISE", "zeros"); + + "ones" slist => { "a", "bb", "ccc" }; + "one" string => join(":", "ones"); + + "twos" slist => { "xx", "xx", "xx" }; + "two" string => join("..", "twos"); + + "threes" slist => { "" }; + "three" string => join("", "threes"); + + "fours" slist => { "1", "", "3" }; + "four" string => join("", "fours"); + + "fives" slist => { "1", "", "3" }; + "five" string => join(",", "fives"); + + "sixs" slist => { "one ", " two ", " three" }; + "six" string => join(" and ", "sixs"); + + classes: + "ok1" expression => strcmp("solo", "$(zero)"); + "ok2" expression => strcmp("a:bb:ccc", "$(one)"); + "ok3" expression => strcmp("xx..xx..xx", "$(two)"); + "ok4" expression => strcmp("", "$(three)"); + "ok5" expression => strcmp("13", "$(four)"); + "ok6" expression => strcmp("1,,3", "$(five)"); + "ok7" expression => strcmp("one and two and three", "$(six)"); + "ok" and => { ok1, ok2, ok3, ok4, ok5, ok6, ok7 }; + + reports: + DEBUG.ok1:: "ok1"; + DEBUG.ok2:: "ok2"; + DEBUG.ok3:: "ok3"; + DEBUG.ok4:: "ok4"; + DEBUG.ok5:: "ok5"; + DEBUG.ok6:: "ok6"; + DEBUG.ok7:: "ok7"; + DEBUG:: + 'strcmp("solo", "$(zero)")'; + 'strcmp("a:bb:ccc", "$(one)")'; + 'strcmp("xx..xx..xx", "$(two)")'; + 'strcmp("", "$(three)")'; + 'strcmp("13", "$(four)")'; + 'strcmp("1,,3", "$(five)")'; + 'strcmp("one and two and three", "$(six)")'; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/006.cf b/tests/acceptance/02_classes/02_functions/006.cf new file mode 100644 index 0000000000..dfef8dd5ac --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/006.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Test grep() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "zeros" slist => { "", "a", "bb", "ccc", "dddd" }; + "zeroz" slist => grep('\d.*', "zeros"); + "zero" string => join(",", "zeroz"); + + "ones" slist => { "", "a", "bb", "ccc", "dddd" }; + "onez" slist => grep("c.*", "ones"); + "one" string => join(",", "onez"); + + "twos" slist => { "", "a", "bb", "ccc", "dddd" }; + "twoz" slist => grep(".{1,2}", "twos"); + "two" string => join(",", "twoz"); + + "threes" slist => { "", "a", "bb", "ccc", "dddd" }; + "threez" slist => grep("ccc", "threes"); + "three" string => join(",", "threez"); + + "fours" slist => { "", "a", "bb", "ccc", "dddd" }; + "fourz" slist => grep(".+", "fours"); + "four" string => join(",", "fourz"); + + "fives" slist => { "", "a", "bb", "ccc", "dddd" }; + "fivez" slist => grep(".*", "fives"); + "five" string => join(",", "fivez"); + + "sixs" slist => { "", "a", "bb", "ccc", "dddd", '' }; + "sixz" slist => grep("^$", "sixs"); + "six" string => join(",", "sixz"); + + classes: + "ok1" expression => strcmp("", "$(zero)"); + "ok2" expression => strcmp("ccc", "$(one)"); + "ok3" expression => strcmp("a,bb", "$(two)"); + "ok4" expression => strcmp("ccc", "$(three)"); + "ok5" expression => strcmp("a,bb,ccc,dddd", "$(four)"); + "ok6" expression => strcmp(",a,bb,ccc,dddd", "$(five)"); + "ok7" expression => strcmp(",", "$(six)"); + "ok" and => { ok1, ok2, ok3, ok4, ok5, ok6, ok7 }; + + reports: + DEBUG.ok1:: "ok1"; + DEBUG.ok2:: "ok2"; + DEBUG.ok3:: "ok3"; + DEBUG.ok4:: "ok4"; + DEBUG.ok5:: "ok5"; + DEBUG.ok6:: "ok6"; + DEBUG.ok7:: "ok7"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + vars: + "dummy" string => "dummy"; +} diff --git a/tests/acceptance/02_classes/02_functions/008.cf b/tests/acceptance/02_classes/02_functions/008.cf new file mode 100644 index 0000000000..0b673db355 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/008.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test userexists() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { + userexists("root"), + userexists("xxxscrabbleroot"), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/009.cf b/tests/acceptance/02_classes/02_functions/009.cf new file mode 100644 index 0000000000..327bb1f50b --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/009.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test groupexists() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { + groupexists("bin"), + groupexists("xxxscrabblebin"), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/010.cf b/tests/acceptance/02_classes/02_functions/010.cf new file mode 100644 index 0000000000..beae8b98b8 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/010.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test isdir() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { + isdir("/"), + isdir("$(G.etc_passwd)"), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/011.cf b/tests/acceptance/02_classes/02_functions/011.cf new file mode 100644 index 0000000000..e2227447f0 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/011.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test isplain() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" xor => { + isplain("$(G.etc_passwd)"), + isplain("$(G.etc)"), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/012.cf b/tests/acceptance/02_classes/02_functions/012.cf new file mode 100644 index 0000000000..474a733f55 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/012.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test isdir() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => isdir("/xxgobbleroot"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/013.cf b/tests/acceptance/02_classes/02_functions/013.cf new file mode 100644 index 0000000000..ef8939eb44 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/013.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test isplain() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => isplain("/xxgobbleroot"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/014.cf b/tests/acceptance/02_classes/02_functions/014.cf new file mode 100644 index 0000000000..a3c7b0a2f2 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/014.cf @@ -0,0 +1,43 @@ +####################################################### +# +# Test hostinnetgroup +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + classes: + "ng_host" expression => hostinnetgroup("netgroup2"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any"; # check not crashed + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/015.cf b/tests/acceptance/02_classes/02_functions/015.cf new file mode 100644 index 0000000000..a0ff132da2 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/015.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Test islink() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; + + "$(G.testfile).dir/." + create => "true"; + + "$(G.testfile).link" + link_from => test_link("$(G.testfile)"); +} + +body link_from test_link(original) +{ + source => "$(original)"; + link_type => "symlink"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine4608" }; + + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "good" and => { + islink("$(G.testfile).link"), + }; + + "bad" or => { + islink("$(G.testfile).dir"), + islink("$(G.testfile).dir/"), + islink("$(G.testfile)"), + islink("$(G.testfile).does-not-exist"), + }; + + "ok" expression => "good&!bad"; + + reports: + DEBUG.good:: + "As expected, $(G.testfile).link is a symlink to $(G.testfile)"; + DEBUG.bad:: + "One of these is detected as a symlink: $(G.testfile).dir, $(G.testfile), $(G.testfile).does-not-exist"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); +} diff --git a/tests/acceptance/02_classes/02_functions/016.cf b/tests/acceptance/02_classes/02_functions/016.cf new file mode 100644 index 0000000000..65c4241a4a --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/016.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Test isvariable() on arrays +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "a[b]" string => "c"; + "a[x][y]" string => "z"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "a[b]" string => "c"; + "a[x][y]" string => "z"; + + classes: + "should_fail" or => { + isvariable("g.a"), + isvariable("g.a[c]"), + isvariable("g.a[x]"), + isvariable("g.a[x][y][z]"), + isvariable("a"), + isvariable("a[c]"), + isvariable("a[x]"), + isvariable("a[x][y][z]"), + }; + "ok" and => { + "!should_fail", + isvariable("g.a[b]"), + isvariable("g.a[x][y]"), + isvariable("a[b]"), + isvariable("a[x][y]"), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + vars: + "dummy" string => "dummy"; +} diff --git a/tests/acceptance/02_classes/02_functions/017.cf b/tests/acceptance/02_classes/02_functions/017.cf new file mode 100644 index 0000000000..e929982de9 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/017.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Test classmatch() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + classmatch("any"), + classmatch("cfengine"), + classmatch("cf.*"), + classmatch("Hr.*"), + classmatch("Yr.*"), + classmatch("Min.*"), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/020.cf b/tests/acceptance/02_classes/02_functions/020.cf new file mode 100644 index 0000000000..fbd527f33e --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/020.cf @@ -0,0 +1,58 @@ +####################################################### +# +# Test fileexists() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "f1" string => "$(G.etc_passwd)"; + "f2" string => "$(G.etc_group)"; + "f3" string => "/etc/NoFriggin'WayThisExi5ts"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok1" expression => fileexists("$(test.f1)"); + "ok2" expression => fileexists("$(test.f2)"); + "ok3" not => fileexists("$(test.f3)"); + + "ok" and => { "ok1", "ok2", "ok3" }; + + reports: + DEBUG.ok1:: + "File $(test.f1) exists as expected"; + DEBUG.!ok1:: + "File $(test.f1) DOES NOT exist (and should)"; + DEBUG.ok2:: + "File $(test.f2) exists as expected"; + DEBUG.!ok2:: + "File $(test.f2) DOES NOT exist (and should)"; + DEBUG.ok3:: + "File $(test.f3) does not exist as expected"; + DEBUG.!ok3:: + "File $(test.f3) DOES exist (and shouldn't)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/02_functions/021.cf b/tests/acceptance/02_classes/02_functions/021.cf new file mode 100644 index 0000000000..ad5ddf3cf4 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/021.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test filesexist() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "f1" string => "$(G.etc_passwd)"; + "f2" string => "$(G.etc_group)"; + "f3" string => "/etc/NoFriggin'WayThisExi5ts"; + + "s1" slist => { "$(f1)", "$(f2)" }; + "s2" slist => { "$(f1)", "$(f2)", "$(f3)" }; + + classes: + "ok1" expression => filesexist("@(s1)"); + "ok2" not => filesexist("@(s2)"); + + "ok" and => { "ok1", "ok2" }; + + reports: + DEBUG.ok1:: + "All files in list s1 $(s1) exist as expected"; + DEBUG.!ok1:: + "All files in list s1 $(s1) DO NOT exist (and should)"; + DEBUG.ok2:: + "All files in list s2 $(s2) do not exist as expected"; + DEBUG.!ok2:: + "All files in list s2 $(s2) DO exist (and shouldn't)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/02_functions/029.cf b/tests/acceptance/02_classes/02_functions/029.cf new file mode 100644 index 0000000000..4b1959a9ba --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/029.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Test logical functions in ifvarclass +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + + "and_yes_both" + expression => "any", + ifvarclass => and("any", "any"); + "and_no_left" + expression => "any", + ifvarclass => and("any", "none"); + "and_no_right" + expression => "any", + ifvarclass => and("none", "any"); + "and_no_none" + expression => "any", + ifvarclass => and("none", "none"); + + "and_arg_fun" + expression => "any", + ifvarclass => and(isdir("/")); + "and_no_arg_fun" + expression => "any", + ifvarclass => and(isdir("/bin/sh")); + "and_two_args_fun" + expression => "any", + ifvarclass => and(isdir("/"), isdir("/")); + + "and_long" + expression => "any", + ifvarclass => and("any", "any", "any", "any", "any", "any", "any", "any"); + + "and_ok" and => { "and_yes_both", "!and_no_left", "!and_no_right", "!and_no_none", + "and_arg_fun", "!and_no_arg_fun", "and_two_args_fun", "and_long" }; + + + # + + "or_yes_both" + expression => "any", + ifvarclass => or("any", "any"); + "or_yes_left" + expression => "any", + ifvarclass => or("any", "none"); + "or_yes_right" + expression => "any", + ifvarclass => or("none", "any"); + "or_no_none" + expression => "any", + ifvarclass => or("none", "none"); + + "or_arg_fun" + expression => "any", + ifvarclass => or(isdir("/")); + "or_no_arg_fun" + expression => "any", + ifvarclass => or(isdir("/bin/sh")); + "or_two_args_fun" + expression => "any", + ifvarclass => or(isdir("/"), isexecutable("/")); + + "or_long" + expression => "any", + ifvarclass => or("any", "any", "any", "any", "any", "any", "any", "any"); + + "or_ok" and => { "or_yes_both", "or_yes_left", "or_yes_right", "!or_no_none", + "or_arg_fun", "!or_no_arg_fun", "or_two_args_fun", "or_long" }; + + # + + "not_yes_no" + expression => "any", + ifvarclass => not("none"); + "not_no_yes" + expression => "any", + ifvarclass => not("any"); + "not_arg_fun" + expression => "any", + ifvarclass => not(islink("/")); + + "not_ok" and => { "not_yes_no", "!not_no_yes", "not_arg_fun" }; + + "ok" and => { "and_ok", "or_ok", "not_ok" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/030.cf b/tests/acceptance/02_classes/02_functions/030.cf new file mode 100644 index 0000000000..907becf6db --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/030.cf @@ -0,0 +1,57 @@ +####################################################### +# +# Test concat() function +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "a" string => "a"; + "b" string => "b"; + "c" string => "c"; + "str" string => "str"; + "nil" string => ""; + + classes: + "noarg" expression => strcmp(concat(), ""); + "a" expression => strcmp(concat("$(a)"), "a"); + "a_b" expression => strcmp(concat("$(a)", "$(b)"), "ab"); + "a_b_c" expression => strcmp(concat("$(a)", "$(b)", "$(c)"), "abc"); + "nil_nil" expression => strcmp(concat("$(nil)", "$(nil)"), ""); + "str_str" expression => strcmp(concat("$(str)", "$(str)"), "strstr"); + + "ok" and => { "noarg", "a", "a_b", "a_b_c", "nil_nil", "str_str" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/classes_body_function_param.cf b/tests/acceptance/02_classes/02_functions/classes_body_function_param.cf new file mode 100644 index 0000000000..7e305e39af --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/classes_body_function_param.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test that a classes body can take a function as a parameter +# Redmine:4319 (https://cfengine.com/dev/issues/4319) +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + vars: + "filename" string => "this is a filename"; + + methods: + "test" usebundle => noop, + classes => if_repaired(canonify("define_a_class_$(filename)")); +} + +####################################################### + +bundle agent noop +{ + reports: + !any:: + "never"; +} + +bundle agent check +{ + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/02_functions/classesmatching.cf b/tests/acceptance/02_classes/02_functions/classesmatching.cf new file mode 100644 index 0000000000..abd05ea142 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/classesmatching.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test classesmatching() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "known" slist => { "cfengine", "cfengine_3", "agent", "any", $(dow), $(dow) }; + "known_str" string => join(" ", "known"); + + Monday:: + "dow" string => "Monday"; + Tuesday:: + "dow" string => "Tuesday"; + Wednesday:: + "dow" string => "Wednesday"; + Thursday:: + "dow" string => "Thursday"; + Friday:: + "dow" string => "Friday"; + Saturday:: + "dow" string => "Saturday"; + Sunday:: + "dow" string => "Sunday"; +} + +####################################################### + +bundle agent test +{ + vars: + "matched" slist => { classesmatching("^cfengine$"), # static string + # range of multiples + classesmatching("^cfengine_[0-9]$"), + # alternation/capture of single + classesmatching("^a(g)ent$"), + # range of single + classesmatching("^a[n]y$"), + # alternation of multiples + classesmatching("^(Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day$"), + # interpolation + classesmatching("^$(init.dow)$"), + }; + "matched_str" string => join(" ", "matched"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp($(init.known_str), $(test.matched_str)); + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + DEBUG.!ok:: + "$(this.promise_filename) expected '$(init.known_str)', got '$(test.matched_str)'"; +} diff --git a/tests/acceptance/02_classes/02_functions/classesmatching_inherit.cf b/tests/acceptance/02_classes/02_functions/classesmatching_inherit.cf new file mode 100644 index 0000000000..add7db3e85 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/classesmatching_inherit.cf @@ -0,0 +1,38 @@ +bundle agent __main__ +{ + classes: + "defined_in_parent" expression => "cfengine"; + + methods: + "ENT-5850" + usebundle => ENT_5850, + inherit => "true"; +} + +bundle agent ENT_5850 +{ + vars: + "c_matching" + slist => classesmatching(".*"); + "c_matching_defined_in_parent" + slist => classesmatching("defined_in_parent"); + + classes: + "defined_here" expression => "cfengine"; + + reports: + "$(this.promise_filename) Pass" + if => some( "defined_in_parent", c_matching); + + "$(this.promise_filename) FAIL $(this.promise_filename)$(const.n)Could not find class 'defined_in_parent' in classesmatching() but it's defined" + if => and( not( some( "defined_in_parent", c_matching) ), + defined_in_parent + ); + + "Classes found by classesmatching() starting with 'defined' $(with)" + with => join( ", ", classesmatching("defined.*") ); + + "'defined_here' is defined" if => "defined_here"; + "'defined_in_parent' is defined" if => "defined_in_parent"; + "Running CFEngine: $(sys.cf_version)"; +} diff --git a/tests/acceptance/02_classes/02_functions/classesmatching_inherit_2.cf b/tests/acceptance/02_classes/02_functions/classesmatching_inherit_2.cf new file mode 100644 index 0000000000..a493934021 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/classesmatching_inherit_2.cf @@ -0,0 +1,65 @@ +bundle agent common +{ + vars: + "logfile" string => "$(this.promise_dirname)$(const.dirsep)defined_classes.log"; +} + +bundle agent __main__ +{ + classes: "defined_in_main"; + methods: + "init"; + "first" inherit => "true"; + "check"; +} + +bundle agent init +{ + files: + "$(common.logfile)" + delete => tidy; +} + +bundle agent first +{ + classes: "defined_in_first"; + methods: "second" inherit => "true"; +} + +bundle agent second +{ + classes: "defined_in_second"; + methods: "third" inherit => "true"; +} + +bundle agent third +{ + vars: + "defined_classes" slist => classesmatching("defined.*"); + + reports: + "defined_classes: $(defined_classes)" + report_to_file => "$(common.logfile)"; +} + +bundle agent check +{ + vars: + "expected" string => concat("defined_classes: defined_in_main$(const.n)", + "defined_classes: defined_in_first$(const.n)", + "defined_classes: defined_in_second$(const.n)"); + "actual" string => readfile("$(common.logfile)", inf); + + reports: + "$(this.promise_filename) Pass" + if => strcmp($(expected), $(actual)); + + "$(this.promise_filename) FAIL" + if => not(strcmp($(expected), $(actual))); +} + +body delete tidy +{ + dirlinks => "delete"; + rmdirs => "true"; +} diff --git a/tests/acceptance/02_classes/02_functions/empty_list_or_data_doesnt_return_true_for_filesexist.cf b/tests/acceptance/02_classes/02_functions/empty_list_or_data_doesnt_return_true_for_filesexist.cf new file mode 100644 index 0000000000..8eba63d6dd --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/empty_list_or_data_doesnt_return_true_for_filesexist.cf @@ -0,0 +1,42 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2631" } + string => "Test that empty lists or data containers do not return true with filesexist. There are no files provided to verify!"; + + vars: + "emptylist" slist => {}; + "emptydata" data => '{}'; + + classes: + "files_in_empty_list_exist" + expression => filesexist( @(emptylist) ); + + "files_in_empty_data_exist" + expression => filesexist( @(emptydata) ); + + "pass" + expression => "!(files_in_empty_list_exist|files_in_empty_data_exist)"; + + reports: + DEBUG|EXTRA:: + "CFEngine $(sys.cf_version)"; + + (DEBUG|EXTRA).files_in_empty_list_exist:: + "files_in_empty_list_exist"; + + (DEBUG|EXTRA).files_in_empty_data_exist:: + "files_in_empty_data_exist"; + + pass:: + "$(this.promise_filename) Pass"; + + !pass:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/02_functions/function_args_flattening.cf b/tests/acceptance/02_classes/02_functions/function_args_flattening.cf new file mode 100644 index 0000000000..f9f36c0140 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/function_args_flattening.cf @@ -0,0 +1,54 @@ +####################################################### +# +# This really tests that the list returned from splitstring() +# gets flattened into the variadic function and() +# +# Test that and function can take a splitstring function call asa paramater +# Redmine:4320 (https://cfengine.com/dev/issues/4320) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common init +{ + classes: + + "one" expression => "any"; +} + +####################################################### + +bundle common test +{ + classes: + "fail_direct" expression => "any", + if => and("one", "non_existing_class"); + + "fail_split" expression => "any", + if => and(splitstring("one,non_existing_class", ",", "20")); + + "pass_direct" expression => "any", + if => or("one", "non_existing_class"); + + "pass_split" expression => "any", + if => or(splitstring("one,non_existing_class", ",", "20")); + + "pass_split1" expression => "any", + if => not(splitstring("non_existing_class", ",", "20")); +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("pass_direct,pass_split,pass_split1", "fail_direct,fail_split", $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/02_functions/getindices_containers.cf b/tests/acceptance/02_classes/02_functions/getindices_containers.cf new file mode 100644 index 0000000000..9ec292c6f6 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/getindices_containers.cf @@ -0,0 +1,47 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "obj" data => parsejson('{ "a": 1, "b": 2 }'); + "arr" data => parsejson('["a", "b"]'); +} + +####################################################### + +bundle agent test +{ + vars: + "obj_i" slist => getindices("init.obj"); + "arr_i" slist => getindices("init.arr"); + +reports: + DEBUG:: + "obj_i $(obj_i)"; + "arr_i $(arr_i)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + strcmp("a,b", join(",", "test.obj_i")), + strcmp("0,1", join(",", "test.arr_i")) + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/getindices_returns_positonal_indices_from_list.cf b/tests/acceptance/02_classes/02_functions/getindices_returns_positonal_indices_from_list.cf new file mode 100644 index 0000000000..427439ed14 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/getindices_returns_positonal_indices_from_list.cf @@ -0,0 +1,53 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + + meta: + "description" -> { "CFE-2930" } + string => "Test that getindices() returns the positional indices from a list"; + + "test_soft_fail" + string => "any", + meta => { "CFE-2930" }; + + vars: + "list" slist => { "beta", "alpha", "gamma" }; + "getindices_list" slist => getindices( list ); +} + +####################################################### +bundle agent check +{ + vars: + "expected_indices" + slist => { 0, 1, 2 }; + + "diff_getindices_vs_expected" + comment => "Expect to find no elements from getindices that differ from expected", + slist => difference( "test.getindices_list", "expected_indices" ); + + "diff_expected_vs_getindices" + comment => "Expect to find no elements from expected that differ from getindices", + slist => difference( "expected_indices", "test.getindices_list" ); + + reports: + "$(this.promise_filename) Pass" + if => strcmp( join( ", ", sort( expected_indices, lex) ), join( ", ", sort( "test.getindices_list", lex) ) ); + + "$(this.promise_filename) FAIL" + if => not( strcmp( join( ", ", sort( expected_indices, lex) ), join( ", ", sort( "test.getindices_list", lex) ) )); + + DEBUG|EXTRA:: + "diff_getindices_vs_expected"; + "$(diff_getindices_vs_expected)"; + "diff_expected_vs_getindices"; + "$(diff_expected_vs_getindices)"; +} diff --git a/tests/acceptance/02_classes/02_functions/iprange.cf b/tests/acceptance/02_classes/02_functions/iprange.cf new file mode 100644 index 0000000000..fdc991f68c --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/iprange.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Test iprange() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + classes: + "expected_missing" expression => iprange("8.8.8.8/31"), + meta => { "collect" }; + + "expected_missing" expression => iprange("8.8.8.8/24"), + meta => { "collect" }; + + "expected_missing" expression => iprange("8.8.8.8/31", $(sys.interfaces)), + meta => { "collect" }; + + "expected_missing" expression => iprange("8.8.8.8/24", $(sys.interfaces)), + meta => { "collect" }; + + "expected_missing" expression => iprange("0.0.0.0/0", "dummy"), + meta => { "collect" }; + + "all_zeroes" expression => iprange("0.0.0.0/0"), + meta => { "collect" }; + + "all_zeroes_some_interfaces" expression => iprange("0.0.0.0/0", $(sys.interfaces)), + meta => { "collect" }; + + "local_range" expression => iprange("0.0.0.0/1"), + meta => { "collect" }; + + "local_range" expression => iprange("128.0.0.0/1"), + meta => { "collect" }; + + "local_range2" expression => iprange("0.0.0.0/1", $(sys.interfaces)), + meta => { "collect" }; + + "local_range2" expression => iprange("128.0.0.0/1", $(sys.interfaces)), + meta => { "collect" }; + + "just_locals" expression => iprange("$(sys.ip_addresses)/32"), + meta => { "collect" }; + + vars: + "found" slist => classesmatching(".*", "collect"); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/02_functions/iprange.cf.expected.json b/tests/acceptance/02_classes/02_functions/iprange.cf.expected.json new file mode 100644 index 0000000000..8953e1bf4a --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/iprange.cf.expected.json @@ -0,0 +1,9 @@ +{ + "found": [ + "just_locals", + "local_range2", + "local_range", + "all_zeroes_some_interfaces", + "all_zeroes" + ] +} diff --git a/tests/acceptance/02_classes/02_functions/isipinsubnet-valid-ip.cf b/tests/acceptance/02_classes/02_functions/isipinsubnet-valid-ip.cf new file mode 100644 index 0000000000..1033e133ca --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/isipinsubnet-valid-ip.cf @@ -0,0 +1,58 @@ +####################################################### +# +# Test isipinsubnet() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3081" } + string => "Test that isipinsubnet() validates IP addresses as valid especially within the 0.0.0.0/0 range"; + + "test_soft_fail" + string => "any", + meta => { "CFE-3081"}; + vars: + + "variable_value_from_varible" string => "$(nosuch.variable)"; + + "candidates" slist => { "10.68.71.5", "10.168.171.5", "not-an-ip-address" }; + + "validIPv4[$(candidates)]" + string => "$(candidates)", + if => isipinsubnet( "0.0.0.0/0", "$(candidates)" ); + + "validIPv4_addresses" slist => getindices( validIPv4 ); +} + +bundle agent check +{ + vars: + "reg_valid_ipv4" string => "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; + + classes: + # We only pass if every one of the ip addresses within the subnet is actually a valid ipv4 string. + "pass" expression => every( $(reg_valid_ipv4), @(test.validIPv4_addresses) ); + + reports: + + pass:: + "$(this.promise_filename) Pass"; + + !pass:: + "$(this.promise_filename) FAIL"; + + (DEBUG|EXTRA).!pass:: + "Not all ips found to be valid seem to match the regular expression for ipv4 address"; + "validIPv4[$(test.candidates)] = $(test.candidates)"; +} diff --git a/tests/acceptance/02_classes/02_functions/isipinsubnet.cf b/tests/acceptance/02_classes/02_functions/isipinsubnet.cf new file mode 100644 index 0000000000..6cff4f4db0 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/isipinsubnet.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test isipinsubnet() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + classes: + "zeroes_range_match" expression => isipinsubnet("0.0.0.0/0", "1.2.3.4"), + meta => { "collect" }; + + "exact_ip_match" expression => isipinsubnet("1.2.3.4/32", "1.2.3.4"), + meta => { "collect" }; + + "two_ips_match" expression => isipinsubnet("1.2.3.4/16", "4.5.6.7", "1.2.1.2"), + meta => { "collect" }; + + "two_ips_nomatch" expression => isipinsubnet("1.2.3.4/16", "4.5.6.7", "3.4.1.2"), + meta => { "collect" }; + + "garbage_nomatch" expression => isipinsubnet("1.2.3.4/16", "blah", "blue", "bleh"), + meta => { "collect" }; + + vars: + "found" slist => classesmatching(".*", "collect"); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/02_functions/isipinsubnet.cf.expected.json b/tests/acceptance/02_classes/02_functions/isipinsubnet.cf.expected.json new file mode 100644 index 0000000000..7d6812f899 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/isipinsubnet.cf.expected.json @@ -0,0 +1,7 @@ +{ + "found": [ + "two_ips_match", + "exact_ip_match", + "zeroes_range_match" + ] +} diff --git a/tests/acceptance/02_classes/02_functions/laterthan.cf b/tests/acceptance/02_classes/02_functions/laterthan.cf new file mode 100644 index 0000000000..c6485b19a7 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/laterthan.cf @@ -0,0 +1,122 @@ +####################################################### +# +# Test laterthan() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + classes: + # On 32-bit systems, dates before 1901-12-13 aren't representable + "after_year_1902" expression => laterthan(1902,1,1,0,0,0), + scope => "namespace"; + + "after_year_1902_month" expression => laterthan(1902,12,1,0,0,0), + scope => "namespace"; + + "after_year_1902_day" expression => laterthan(1902,12,31,0,0,0), + scope => "namespace"; + + "after_year_1902_hour" expression => laterthan(1902,12,31,23,0,0), + scope => "namespace"; + + "after_year_1902_minute" expression => laterthan(1902,12,31,23,59,0), + scope => "namespace"; + + "after_year_1902_second" expression => laterthan(1902,12,31,23,59,59), + scope => "namespace"; + + # On 32-bit systems, dates after 2038-01-18 aren't representable + "after_year_2037" expression => laterthan(2037,1,1,0,0,0), + scope => "namespace"; + + "after_year_2037_month" expression => laterthan(2037,12,1,0,0,0), + scope => "namespace"; + + "after_year_2037_day" expression => laterthan(2037,12,31,0,0,0), + scope => "namespace"; + + "after_year_2037_hour" expression => laterthan(2037,12,31,23,0,0), + scope => "namespace"; + + "after_year_2037_minute" expression => laterthan(2037,12,31,23,59,0), + scope => "namespace"; + + "after_year_2037_second" expression => laterthan(2037,12,31,23,59,59), + scope => "namespace"; +} + +####################################################### + +bundle agent check +{ + vars: + DEBUG:: + # The date and time now, in laterthan-compatible format: + "present" string => execresult("$(G.date) +(%Y,%m,%d,%H,%M,%S)", "noshell"); + # NB: shell would try to sub-shell on this (...), hence noshell. + + classes: + "check_after_year_1902" and => { + "after_year_1902", + "after_year_1902_month", + "after_year_1902_day", + "after_year_1902_hour", + "after_year_1902_minute", + "after_year_1902_second" + }; + + "check_after_year_2037" or => { + "after_year_2037", + "after_year_2037_month", + "after_year_2037_day", + "after_year_2037_hour", + "after_year_2037_minute", + "after_year_2037_second" + }; + + "ok" expression => "check_after_year_1902.!check_after_year_2037"; + + reports: + DEBUG.!after_year_1902:: + "Failed laterthan(1902,1,1,0,0,0); year"; + DEBUG.!after_year_1902_month:: + "Failed laterthan(1902,12,1,0,0,0); month"; + DEBUG.!after_year_1902_day:: + "Failed laterthan(1902,12,31,0,0,0); day"; + DEBUG.!after_year_1902_hour:: + "Failed laterthan(1902,12,31,23,0,0); hour"; + DEBUG.!after_year_1902_minute:: + "Failed laterthan(1902,12,31,23,59,0); minute"; + DEBUG.!after_year_1902_second:: + "Failed laterthan(1902,12,31,23,59,59); second"; + DEBUG.after_year_2037:: + "Satisfied laterthan(2037,1,1,0,0,0); year"; + DEBUG.after_year_2037_month:: + "Satisfied laterthan(2037,12,1,0,0,0); month"; + DEBUG.after_year_2037_day:: + "Satisfied laterthan(2037,12,31,0,0,0); day"; + DEBUG.after_year_2037_hour:: + "Satisfied laterthan(2037,12,31,23,0,0); hour"; + DEBUG.after_year_2037_minute:: + "Satisfied laterthan(2037,12,31,23,59,0); minute"; + DEBUG.after_year_2037_second:: + "Satisfied laterthan(2037,12,31,23,59,59); second"; + + DEBUG.!ok:: + "The time now is '$(present)'"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/02_functions/makerule.cf b/tests/acceptance/02_classes/02_functions/makerule.cf new file mode 100644 index 0000000000..3a211efd11 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/makerule.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Test makerule() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "lold" slist => { "$(G.etc_passwd)", "$(G.etc_group)" }; + "lnew" slist => { "$(G.etc_passwd)", "$(G.testfile)" }; + "lnon" slist => { "$(G.etc_passwd)", "/etc/NoFriggin'WayThisExi5ts" }; + + files: + + "$(G.testfile)" + create => "true"; + + "$(G.testfile).nosuchfile" + + delete => tidy; +} + +####################################################### + +bundle agent check +{ + classes: + + # A new file does not need to be rebuilt from an older file (fails) + "ok1" expression => makerule("$(G.testfile)", "$(G.etc_passwd)"); + + # An older file does need to be rebuilt from a newer file + "ok2" expression => makerule("$(G.etc_passwd)", "$(G.testfile)"); + + # An older file cannot be rebuilt from a non existent file (fails) + "ok3" expression => makerule("$(G.etc_passwd)", "$(G.testfile).nosuchfile"); + + # An non existent file does need to be rebuilt from a source file + "ok4" expression => makerule("$(G.testfile).nosuchfile", "$(G.etc_passwd)"); + + # A new file does not need to be rebuilt from an older file (fails) + "ok5" expression => makerule("$(G.testfile)", "test.lold"); + + # An old file does need to be rebuilt from an newer file in a list + "ok6" expression => makerule("$(G.etc_passwd)", "test.lnew"); + + # An old file does not need to be rebuilt from sources with a missing file (fails) + "ok7" expression => makerule("$(G.etc_passwd)", "test.lnon"); + + "ok" and => { "!ok1", "ok2", "!ok3", "ok4", "!ok5", "ok6", "!ok7" }; + + reports: + DEBUG.ok1:: + "1. pass"; + DEBUG.ok2:: + "2. pass"; + DEBUG.ok3:: + "3. pass"; + DEBUG.ok4:: + "4. pass"; + DEBUG.ok5:: + "5. pass"; + DEBUG.ok6:: + "6. pass"; + DEBUG.ok7:: + "7. pass"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/02_functions/returnszero.cf b/tests/acceptance/02_classes/02_functions/returnszero.cf new file mode 100644 index 0000000000..13283a0d93 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/returnszero.cf @@ -0,0 +1,187 @@ +####################################################### +# +# Test returnszero() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; + + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine4687" }; +} + +####################################################### + +bundle agent check +{ + vars: + windows:: + "zero_rel" string => "fc $(this.promise_filename) $(this.promise_filename)"; + "one_rel" string => "fc $(this.promise_filename) $(this.promise_filename).doesnotexist"; + "nxe_rel" string => "nosuchprogramexists"; + "zero_abs" string => "c:\windows\system32\fc.exe $(this.promise_filename) $(this.promise_filename)"; + "one_abs" string => "c:\windows\system32\fc.exe $(this.promise_filename) $(this.promise_filename).doesnotexist"; + "nxe_abs" string => "c:\xbin\nosuchprogramexists"; + "zero_powershell" string => "if (diff $(this.promise_filename) $(this.promise_filename)) { exit 1 } else { exit 0 }"; + "one_powershell" string => "if (diff $(this.promise_filename) $(this.promise_filename).doesnotexit) { exit 1 } else { exit 0 }"; + "nxe_powershell" string => "nosuchprogramexists"; + !windows:: + "zero_rel" string => "grep root $(G.etc_passwd)"; + "one_rel" string => "grep gluequaggaunlikely $(G.etc_passwd)"; + "nxe_rel" string => "nosuchprogramexists"; + "zero_abs" string => "$(G.grep) root $(G.etc_passwd)"; + "one_abs" string => "$(G.grep) gluequaggaunlikely $(G.etc_passwd)"; + "nxe_abs" string => "/xbin/nosuchprogramexists"; + + classes: + "rawshell_no_prefix" expression => returnszero("ls /", "useshell"); + "rawnoshell_no_prefix" expression => returnszero("ls /", "noshell"); + + "zero_noshell_rel" expression => returnszero("$(zero_rel)", "noshell"); + "zero_useshell_rel" expression => returnszero("$(zero_rel)", "useshell"); + "zero_noshell_abs" expression => returnszero("$(zero_abs)", "noshell"); + "zero_useshell_abs" expression => returnszero("$(zero_abs)", "useshell"); + + "one_noshell_rel" expression => returnszero("$(one_rel)", "noshell"); + "one_useshell_rel" expression => returnszero("$(one_rel)", "useshell"); + "one_noshell_abs" expression => returnszero("$(one_abs)", "noshell"); + "one_useshell_abs" expression => returnszero("$(one_abs)", "useshell"); + + "nxe_noshell_rel" expression => returnszero("$(nxe_rel)", "noshell"); + "nxe_useshell_rel" expression => returnszero("$(nxe_rel)", "useshell"); + "nxe_noshell_abs" expression => returnszero("$(nxe_abs)", "noshell"); + "nxe_useshell_abs" expression => returnszero("$(nxe_abs)", "useshell"); + windows:: + "zero_powershell" expression => returnszero("$(zero_powershell)", "powershell"); + "one_powershell" expression => returnszero("$(one_powershell)", "powershell"); + "nxe_powershell" expression => returnszero("$(nxe_powershell)", "powershell"); + + any:: + "ok_common" and => { + "!zero_noshell_rel", "zero_useshell_rel", "zero_noshell_abs", "zero_useshell_abs", + "!one_noshell_rel", "!one_useshell_rel", "!one_noshell_abs", "!one_useshell_abs", + "!nxe_noshell_rel", "!nxe_useshell_rel", "!nxe_noshell_abs", "!nxe_useshell_abs", + }; + windows:: + "ok_windows" and => { + "zero_powershell", "!one_powershell", "!nxe_powershell", + }; + "ok" and => { + "ok_common", "ok_windows", + }; + + !windows:: + "ok" and => { "ok_common" }; + + reports: + DEBUG.zero_noshell_rel:: + "'$(zero_rel)' erroneously returns 0 with noshell"; + DEBUG.!zero_noshell_rel:: + "'$(zero_rel)' returns non-zero with noshell"; + + DEBUG.zero_useshell_rel:: + "'$(zero_rel)' returns 0 with useshell"; + DEBUG.!zero_useshell_rel:: + "'$(zero_rel)' erroneously returns non-zero with useshell"; + + + DEBUG.!one_noshell_rel:: + "'$(one_rel)' returns non-zero with noshell"; + DEBUG.one_noshell_rel:: + "'$(one_rel)' erroneously returns 0 with noshell"; + + DEBUG.!one_useshell_rel:: + "'$(one_rel)' returns non-zero with useshell"; + DEBUG.one_useshell_rel:: + "'$(one_rel)' erroneously returns 0 with useshell"; + + + DEBUG.!nxe_noshell_rel:: + "'$(nxe_rel)' returns non-zero with noshell"; + DEBUG.nxe_noshell_rel:: + "'$(nxe_rel)' erroneously returns 0 with noshell"; + + DEBUG.!nxe_useshell_rel:: + "'$(nxe_rel)' returns non-zero with useshell"; + DEBUG.nxe_useshell_rel:: + "'$(nxe_rel)' erroneously returns 0 with useshell"; + + DEBUG.zero_noshell_abs:: + "'$(zero_abs)' returns 0 with noshell"; + DEBUG.!zero_noshell_abs:: + "'$(zero_abs)' erroneously returns non-zero with noshell"; + + DEBUG.zero_useshell_abs:: + "'$(zero_abs)' returns 0 with useshell"; + DEBUG.!zero_useshell_abs:: + "'$(zero_abs)' erroneously returns non-zero with useshell"; + + + DEBUG.!one_noshell_abs:: + "'$(one_abs)' returns non-zero with noshell"; + DEBUG.one_noshell_abs:: + "'$(one_abs)' erroneously returns 0 with noshell"; + + DEBUG.!one_useshell_abs:: + "'$(one_abs)' returns non-zero with useshell"; + DEBUG.one_useshell_abs:: + "'$(one_abs)' erroneously returns 0 with useshell"; + + + DEBUG.!nxe_noshell_abs:: + "'$(nxe_abs)' returns non-zero with noshell"; + DEBUG.nxe_noshell_abs:: + "'$(nxe_abs)' erroneously returns 0 with noshell"; + + DEBUG.!nxe_useshell_abs:: + "'$(nxe_abs)' returns non-zero with useshell"; + DEBUG.nxe_useshell_abs:: + "'$(nxe_abs)' erroneously returns 0 with useshell"; + + windows.DEBUG.!zero_powershell:: + "'$(zero_powershell)' erroneously returns non-zero with powershell"; + windows.DEBUG.zero_powershell:: + "'$(zero_powershell)' returns 0 with powershell"; + + windows.DEBUG.!one_powershell:: + "'$(one_powershell)' returns non-zero with powershell"; + windows.DEBUG.one_powershell:: + "'$(one_powershell)' erroneously returns 0 with powershell"; + + windows.DEBUG.!nxe_powershell:: + "'$(nxe_powershell)' returns non-zero with powershell"; + windows.DEBUG.nxe_powershell:: + "'$(nxe_powershell)' erroneously returns 0 with powershell"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/02_functions/silent_returnszero.cf b/tests/acceptance/02_classes/02_functions/silent_returnszero.cf new file mode 100644 index 0000000000..047c7c0021 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/silent_returnszero.cf @@ -0,0 +1,18 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent check { + vars: + "command" string => "$(sys.cf_agent) -KIf $(this.promise_filename).sub -DAUTO"; + "silent_command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*Pass.*", "", $(command), $(this.promise_filename)); + "check" + usebundle => dcs_passif_output("", ".*Pass.*", $(silent_command), $(this.promise_filename)); +} diff --git a/tests/acceptance/02_classes/02_functions/silent_returnszero.cf.sub b/tests/acceptance/02_classes/02_functions/silent_returnszero.cf.sub new file mode 100644 index 0000000000..21e4de20ad --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/silent_returnszero.cf.sub @@ -0,0 +1,11 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "main" }; + version => "1.0"; +} + +bundle agent main { + classes: + "test" expression => returnszero("$(G.echo) Pass", "noshell"); +} diff --git a/tests/acceptance/02_classes/02_functions/splayclass_hourly.cf b/tests/acceptance/02_classes/02_functions/splayclass_hourly.cf new file mode 100644 index 0000000000..49ff896538 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/splayclass_hourly.cf @@ -0,0 +1,64 @@ +# Test that hourly splay class works as expected +body common control +{ + inputs => { "../../default.cf.sub" }; + version => "1.0"; +} + +bundle agent main +{ + meta: + "test_suppress_fail" + string => "!any", + meta => { "zendesk2548" }; + + vars: + "hourly[Min00_05]" string => "1"; + "hourly[Min05_10]" string => "2"; + "hourly[Min10_15]" string => "3"; + "hourly[Min15_20]" string => "14"; + "hourly[Min20_25]" string => "23"; + "hourly[Min25_30]" string => "4"; + "hourly[Min30_35]" string => "12"; + "hourly[Min35_40]" string => "13"; + "hourly[Min40_45]" string => "6"; + "hourly[Min45_50]" string => "7"; + "hourly[Min50_55]" string => "10"; + "hourly[Min55_00]" string => "24"; + "hourly_idx" slist => getindices(hourly); + "hourly_inputs" slist => getvalues(hourly); + + "splay_classes" slist => classesmatching("splayclass_hourly_.*"); + + classes: + + "splayclass_hourly_$(hourly_inputs)" + expression => splayclass( $(hourly_inputs), hourly); + + "ok" + and => { "$(hourly_idx)", "splayclass_hourly_$(hourly[$(hourly_idx)])"}; + + reports: + DEBUG:: + # Show the current time window + "DEBUG $(this.bundle): Current time window '$(hourly_idx)'" + if => "$(hourly_idx)"; + + # Show the currently defined splay class (found with classesmatching) + "DEBUG $(this.bundle): Defined splay class '$(splay_classes)'"; + + # Show the class expression that is satisfied + "DEBUG $(this.bundle): $(hourly_idx).splayclass_hourly_$(hourly[$(hourly_idx)])" + if => and("$(hourly_idx)", "splayclass_hourly_$(hourly[$(hourly_idx)])"); + + # Show the time with the splay class defined + "DEBUG $(this.bundle): $(sys.date) splayclass_hourly_$(hourly_inputs) defined" + if => "splayclass_hourly_$(hourly_inputs)"; + + ok:: + # We pass when the splay class is defined during the expected window + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + +} diff --git a/tests/acceptance/02_classes/02_functions/staging/017.cf b/tests/acceptance/02_classes/02_functions/staging/017.cf new file mode 100644 index 0000000000..99fd071c17 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/staging/017.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test select_class - partial test only, hard to prove persistence +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "sel1" select_class => { "one" }; + "sel2" select_class => { "alpha", "beta" }; + "sel3" select_class => { "able", "baker", "charlie" }; + "sel4" select_class => { "foo", "baz", "gar", "bletch" }; + + "ok2" xor => { "alpha", "beta" }; + "ok3" xor => { "able", "baker", "charlie" }; + "ok4" xor => { "foo", "baz", "gar", "bletch" }; + + "ok" and => { + "sel1", "one", + "sel2", "xor2", + "sel3", "xor3", + "sel4", "xor4", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + vars: + "dummy" string => "dummy"; +} diff --git a/tests/acceptance/02_classes/02_functions/staging/usemodule.cf b/tests/acceptance/02_classes/02_functions/staging/usemodule.cf new file mode 100644 index 0000000000..3a4319518d --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/staging/usemodule.cf @@ -0,0 +1,128 @@ +####################################################### +# +# Test usemodule() +# +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "script_location" string => dirname("$(this.promise_filename)"); + classes: + "ok_to_run" not => regcmp("/var/cfengine|.*\.cf-agent", "$(sys.workdir)"); + + files: + ok_to_run:: + "$(sys.workdir)/modules/usemodule.cf.bat" + create => "true", + perms => usemodule_perms, + edit_line => usemodule_lines; + + "$(sys.workdir)/modules/usemodule-neg.cf.bat" + create => "true", + perms => usemodule_perms, + edit_line => usemodule_lines_neg; +} + +bundle edit_line usemodule_lines_neg +{ + insert_lines: + !windows:: + "#!/bin/sh"; + ". \"$(init.script_location)/usemodule.cf.bat\""; + "exit 1"; + + delete_lines: + ".*"; +} + +bundle edit_line usemodule_lines +{ + insert_lines: + windows:: + "@echo off"; + "\"$(init.script_location)\usemodule.cf.bat\""; + !windows:: + "#!/bin/sh"; + ". \"$(init.script_location)/usemodule.cf.bat\""; + + delete_lines: + ".*"; +} + +body perms usemodule_perms +{ + mode => "755"; +} + +####################################################### + +bundle agent test +{ + classes: + "ok_to_run" not => regcmp("/var/cfengine|.*\.cf-agent", "$(sys.workdir)"); + + classes: + ok_to_run:: + # Should be undefined by the following script. + "undefined_class" expression => "any"; + "function_ok" expression => usemodule("usemodule.cf.bat", ""); + "function_neg_ok" + comment => "Negative test. Class set if module returns non-zero", + not => usemodule("usemodule-neg.cf.bat", ""); + "defined_variable" expression => strcmp("$(usemodule_cf_bat.defined_variable)", "yes"); + "ok" and => { "function_neg_ok", "function_ok", "defined_class", "!undefined_class", "defined_variable" }; + + vars: + ok:: + "ok" string => "yes"; + + reports: + !ok_to_run:: + "Must be run using testall script"; + ok_to_run.DEBUG.!function_ok:: + "usemodule did not execute correctly."; + ok_to_run.DEBUG.!function_neg_ok:: + "usemodule returned non-zero, class function_neg_ok was not set, but should be set."; + ok_to_run.DEBUG.!defined_class:: + "defined_class was not set, but should be."; + ok_to_run.DEBUG.undefined_class:: + "undefined_class was set, but shouldn't be."; + ok_to_run.DEBUG.!defined_variable:: + "defined_variable was not set, but should be."; + + ok_to_run.DEBUG.function_ok:: + "usemodule executed correctly."; + ok_to_run.DEBUG.function_neg_ok:: + "usemodule returned non-zero and class function_neg_ok was set."; + ok_to_run.DEBUG.defined_class:: + "defined_class was set."; + ok_to_run.DEBUG.!undefined_class:: + "undefined_class was not set."; + ok_to_run.DEBUG.defined_variable:: + "defined_variable was set."; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(test.ok)", "yes"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/usemodule-returns-nonzero.cf b/tests/acceptance/02_classes/02_functions/usemodule-returns-nonzero.cf new file mode 100644 index 0000000000..cadddca34b --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/usemodule-returns-nonzero.cf @@ -0,0 +1,92 @@ +#!/var/cfengine/bin/cf-agent -f- +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} +bundle agent init +{ + vars: + "foo_usemodule" string => +"#!/bin/sh +echo '=mystring=foo99' +echo '+class_from_classes_usemodule' +exit 1 +"; + + "foo_commands_module" string => +"#!/bin/sh +echo '=mystring=foo99' +echo '+class_from_commands_module' +exit 1 +"; + + files: + "${sys.workdir}/modules/foo-usemodule" + create => 'true', + perms => m( 700 ), + edit_defaults => empty, + edit_line => append_if_no_line( ${foo_usemodule} ); + + "${sys.workdir}/modules/foo-commands_module" + create => 'true', + perms => m( 700 ), + edit_defaults => empty, + edit_line => append_if_no_line( ${foo_commands_module} ); +} + +bundle agent test +{ + meta: + "description" -> { "CFE-942" } + string => "Test that when a module executed by usemodule() returns nonzero, it's not interpreted as successful"; + + "test_soft_fail" + string => "any", + meta => { "CFE-942" }, + comment => "usemodule seems to return true no matter if the module exists returning 0 or nonzero"; + + classes: + + # Since the module exits with non zero, we should not get this class + + "usemodule_expect_no_class_defined_because_return_nonzero" + expression => usemodule( "foo-usemodule", "" ), + scope => "namespace", + if => isexecutable( "$(sys.workdir)/foo-usemodule" ); + + # Since the module exists non zero, we should get this class + "usemodule_expect_class_defined_because_return_nonzero" + not => usemodule( "foo-usemodule", "" ), + scope => "namespace", + if => isexecutable( "$(sys.workdir)/foo-usemodule" ); + + commands: + "$(sys.workdir)/modules/foo-commands_module" + module => "true", + if => isexecutable( $(this.promiser) ); +} +bundle agent check +{ + methods: + usemodule_expect_no_class_defined_because_return_nonzero.!usemodule_expect_class_defined_because_return_nonzero:: + "FAIL" usebundle => dcs_fail( $(this.promise_filename) ); + + !usemodule_expect_class_defined_because_return_nonzero.usemodule_expect_class_defined_because_return_nonzero:: + "Pass" usebundle => dcs_pass( $(this.promise_filename) ); + + reports: + + DEBUG:: + # Since the module exists nonzero, we don't expect that it communicated things back to the agent + "The command execution executed as a module that did not return 0 defined a class (unexpectedly)" + if => "class_from_commands_module"; + + "Did not expect that a class defined as the result of a classes promise usemodule would be defined if the module used returned nonzero" + if => "usemodule_expect_no_class_defined_because_return_nonzero"; + + "expected class to be defined based on negative result from usemodule classes promise" + if => "usemodule_expect_class_defined_because_return_nonzero"; + +} diff --git a/tests/acceptance/02_classes/02_functions/usemodule.cf b/tests/acceptance/02_classes/02_functions/usemodule.cf new file mode 100644 index 0000000000..8d22d876a1 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/usemodule.cf @@ -0,0 +1,104 @@ +####################################################### +# +# Test usemodule() +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "script_location" string => dirname("$(this.promise_filename)"); + classes: + "ok_to_run" not => regcmp("/var/cfengine|.*\.cf-agent", "$(sys.workdir)"); + + files: + ok_to_run:: + "$(sys.workdir)/modules/usemodule.cf.bat" + create => "true", + perms => usemodule_perms, + edit_line => usemodule_lines; +} + +bundle edit_line usemodule_lines +{ + insert_lines: + windows:: + "@echo off"; + "\"$(init.script_location)\usemodule.cf.bat\""; + !windows:: + "#!/bin/sh"; + ". \"$(init.script_location)/usemodule.cf.bat\""; + + delete_lines: + ".*"; +} + +body perms usemodule_perms +{ + mode => "755"; +} + +####################################################### + +bundle agent test +{ + classes: + "ok_to_run" not => regcmp("/var/cfengine|.*\.cf-agent", "$(sys.workdir)"); + + classes: + ok_to_run:: + # Should be undefined by the following script. + "undefined_class" expression => "any"; + "function_ok" expression => usemodule("usemodule.cf.bat", ""); + "defined_variable" expression => strcmp("$(usemodule_cf_bat.defined_variable)", "yes"); + "ok" and => { "function_ok", "defined_class", "!undefined_class", "defined_variable" }; + + vars: + ok:: + "ok" string => "yes"; + + reports: + !ok_to_run:: + "Must be run using testall script"; + ok_to_run.DEBUG.!function_ok:: + "usemodule did not execute correctly."; + ok_to_run.DEBUG.!defined_class:: + "defined_class was not set, but should be."; + ok_to_run.DEBUG.undefined_class:: + "undefined_class was set, but shouldn't be."; + ok_to_run.DEBUG.!defined_variable:: + "defined_variable was not set, but should be."; + + ok_to_run.DEBUG.function_ok:: + "usemodule executed correctly."; + ok_to_run.DEBUG.defined_class:: + "defined_class was set."; + ok_to_run.DEBUG.!undefined_class:: + "undefined_class was not set."; + ok_to_run.DEBUG.defined_variable:: + "defined_variable was set."; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(test.ok)", "yes"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/02_functions/usemodule.cf.bat b/tests/acceptance/02_classes/02_functions/usemodule.cf.bat new file mode 100755 index 0000000000..adfc7e47e0 --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/usemodule.cf.bat @@ -0,0 +1,8 @@ +@rem ' +@echo off +@rem Careful. Batch and shell script in the same file. +@rem ' 1>/dev/null 2>/dev/null + +echo +defined_class +echo -undefined_class +echo =defined_variable=yes diff --git a/tests/acceptance/02_classes/03_os/powershell.cf b/tests/acceptance/02_classes/03_os/powershell.cf new file mode 100644 index 0000000000..9599a3b818 --- /dev/null +++ b/tests/acceptance/02_classes/03_os/powershell.cf @@ -0,0 +1,60 @@ +####################################################### +# +# Test powershell class +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!windows", "!powershell" }; + # This relies on powershell being in the path, which it should be for a standard install. + "detected_powershell" expression => regcmp(".*Succeeded.*", execresult("powershell Get-Content $(G.cwd)$(const.dirsep)text.txt", "useshell")); + "ok" and => { "windows", "powershell", "detected_powershell" }; + "ok" and => { "windows", "!powershell", "!detected_powershell" }; + + reports: + DEBUG.windows.powershell.!detected_powershell:: + "Powershell was detected by CFEngine, but not by the test."; + DEBUG.windows.!powershell.detected_powershell:: + "Powershell was detected by the test, but not by CFEngine."; + DEBUG.windows.powershell.detected_powershell:: + "Powershell was detected by CFEngine and the test."; + DEBUG.windows.!powershell.!detected_powershell:: + "Powershell was not detected by the test, nor by CFEngine."; + DEBUG.!windows.powershell:: + "Powershell was detected on a non-Windows system."; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/02_classes/03_os/text.txt b/tests/acceptance/02_classes/03_os/text.txt new file mode 100644 index 0000000000..6cc7be057d --- /dev/null +++ b/tests/acceptance/02_classes/03_os/text.txt @@ -0,0 +1 @@ +Succeeded diff --git a/tests/acceptance/02_classes/03_os/ubuntu.cf b/tests/acceptance/02_classes/03_os/ubuntu.cf new file mode 100644 index 0000000000..55c4cf1f1b --- /dev/null +++ b/tests/acceptance/02_classes/03_os/ubuntu.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test ubuntu classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + classes: + ubuntu_18_04.ubuntu_18_4:: + "not_ok"; + ubuntu_16_04.ubuntu_16_4:: + "not_ok"; + ubuntu_14_04.ubuntu_14_4:: + "not_ok"; + ubuntu_12_04.ubuntu_12_4:: + "not_ok"; + any:: + "ok" not => "not_ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/04_examples/README.org b/tests/acceptance/04_examples/README.org new file mode 100644 index 0000000000..ad0e1074b6 --- /dev/null +++ b/tests/acceptance/04_examples/README.org @@ -0,0 +1,483 @@ +#+Title: Testing Examples + +This is the place to have examples in =core/examples= tested. + +* Outputs +Before running a test the optional commands found between =#+begin_src prep= and +=#+end_src= are run to prepare the environment for the test. This is commonly +used to prepare files for the example to operate on. + +The =outputs= test checks to see that the output generated by the test is +correct as compared with the content between the =#+begin_src example_output= +and =#+end_src= tags. + +*NOTE:* Agent output should be *identical* between a normal execution and a dry +run. This means that some examples are poor candidates for automatic testing. + +=prep= sections can not handle /heredoc/, so don't try to use them to setup an +environment, instead, use echo to build up a file. + +#+CAPTION: Example error showing issue with usage of heredoc in prep +#+begin_example +Q: "...r/bin/perl /hom": processing /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/execresult.cf: Running prep 'EOF'Can't exec "EOF": No such file or directory at /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl line 227. +#+end_example + +So, don't do this: + +#+CAPTION: BAD: example using heredoc inside prep section +#+begin_example + ,#+begin_src prep + #@ ``` + #@ rm -rf /tmp/testhere + #@ mkdir -p /tmp/testhere + #@ touch /tmp/testhere/a + #@ touch /tmp/testhere/b + #@ touch /tmp/testhere/c + #@ touch /tmp/testhere/d + #@ touch /tmp/testhere/e + #@ cat << EOF >/tmp/testhere/echo-stdout-and-stderr + #@ #!/usr/bin/env sh + #@ echo stderr >&2 + #@ echo stdout + #@ EOF + #@ chmod +x /tmp/testhere/echo-stdout-and-stderr + #@ ``` + ,#+end_src +#+end_example + +Instead, do this: + +#+CAPTION: GOOD example using echo instead of heredoc during setup +#+begin_example + ,#+begin_src prep + #@ ``` + #@ rm -rf /tmp/testhere + #@ mkdir -p /tmp/testhere + #@ touch /tmp/testhere/a + #@ touch /tmp/testhere/b + #@ touch /tmp/testhere/c + #@ touch /tmp/testhere/d + #@ touch /tmp/testhere/e + #@ echo "#!/usr/bin/env sh" >/tmp/testhere/echo-stdout-and-stderr + #@ echo "echo stderr >&2" >>/tmp/testhere/echo-stdout-and-stderr + #@ echo "echo stdout" >>/tmp/testhere/echo-stdout-and-stderr + #@ chmod +x /tmp/testhere/echo-stdout-and-stderr + #@ ``` + ,#+end_src +#+end_example + +** Troubleshooting Outputs Test Failures +This section is primarily intended for CFEngine Staff as not all build output is +publicly available. + +It is *very* important for the examples output to be consistent or it will +result in a failed test. Example test failures are somewhat opaque and can be +difficult to troubleshoot. + +A failure will look like this in the build system: + +#+BEGIN_EXAMPLE +00:13:23.974 ./04_examples/outputs/check_outputs.cf FAIL (UNEXPECTED FAILURE) +#+END_EXAMPLE + +*Note:* The specific example that failed is not immediately apparent. You must +wait for the job to finish and then drill into the test results. + +Where you might be looking for something like this: + +#+BEGIN_EXAMPLE + error: Finished command related to promiser "/usr/bin/perl /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c -v /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/../../examples/multiple_outcomes.cf" -- an error occurred, returned 1 + notice: Q: "...r/bin/perl /hom": Test file: /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/../../examples/multiple_outcomes.cf +Q: "...r/bin/perl /hom": Command: "/home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" -D_cfe_output_testing -nKf /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine/multiple_outcomes.cf 2>&1 +Q: "...r/bin/perl /hom": NEW OUTPUT: [[[ warning: Warning promised, need to create file "/tmp/repaired_and_not_kept.txt" +Q: "...r/bin/perl /hom": R: My promise was not kept because the template failed to render. +Q: "...r/bin/perl /hom": R: Found class: "outcome_failed" +Q: "...r/bin/perl /hom": R: Found class: "outcome_not_kept" +Q: "...r/bin/perl /hom": R: Found class: "outcome_error" +Q: "...r/bin/perl /hom": R: Found class: "outcome_reached" +Q: "...r/bin/perl /hom": ]]] +Q: "...r/bin/perl /hom": --- /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine/multiple_outcomes.cf.a 2016-04-12 16:16:28.247136634 +0200 +Q: "...r/bin/perl /hom": +++ /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine/multiple_outcomes.cf.b 2016-04-12 16:16:28.247136634 +0200 +Q: "...r/bin/perl /hom": @@ -1,10 +1,6 @@ +Q: "...r/bin/perl /hom": -error: Template file "$(this.promsie_filename).broken_mustache" could not be opened for reading +Q: "...r/bin/perl /hom": -R: My promise was repaired because the file was created. +Q: "...r/bin/perl /hom": +warning: Warning promised, need to create file "/tmp/repaired_and_not_kept.txt" +Q: "...r/bin/perl /hom": R: My promise was not kept because the template failed to render. +Q: "...r/bin/perl /hom": -R: My promise was kept because the permissions were as desired. +Q: "...r/bin/perl /hom": R: Found class: "outcome_failed" +Q: "...r/bin/perl /hom": -R: Found class: "outcome_repaired" +Q: "...r/bin/perl /hom": -R: Found class: "outcome_error" +Q: "...r/bin/perl /hom": R: Found class: "outcome_not_kept" +Q: "...r/bin/perl /hom": +R: Found class: "outcome_error" +Q: "...r/bin/perl /hom": R: Found class: "outcome_reached" +Q: "...r/bin/perl /hom": -R: Found class: "outcome_kept" +Q: "...r/bin/perl /hom": /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/../../examples/multiple_outcomes.cf: output differs from original... + error: Method "verbose_output" failed in some repairs +R: test_example: checker "/usr/bin/perl /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/../../examples/multiple_outcomes.cf" returned error +R: test_example: ran checker "/usr/bin/perl /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/jenkins/workspace/testing-enterprise-pr_core/label/PACKAGES_x86_64_linux_redhat_6/core/tests/acceptance/../../examples/multiple_outcomes.cf" + error: Method "test_example" failed in some repairs +#+END_EXAMPLE + +Note how in the above the test failed because of differing output. In this +specific case, the differences are due to some difference in output between a +normal run and a dry-run where warnings are promised. + +#+Caption: Execute the example outputs tests with the system installed cfengine +#+NAME: test_check_outputs +#+BEGIN_SRC sh :dir ../ :results output :exports both + ./testall --jobs=4 \ + --printlog \ + --bindir=/var/cfengine/bin/ \ + ./04_examples/outputs/check_outputs.cf 2>&1 +#+END_SRC + + +#+NAME: Testing classfiltercsv() +#+CALL: test_check_outputs() + + +:DRAWER: +#+RESULTS: Example Testing classfiltercsv() +#+begin_example +====================================================================== +Testsuite started at 2019-04-13 18:40:54 +---------------------------------------------------------------------- +Total tests: 1 + + COMMON_TESTS: enabled + TIMED_TESTS: enabled + SLOW_TESTS: enabled + ERROREXIT_TESTS: enabled + SERIAL_TESTS: enabled + NETWORK_TESTS: enabled + LIBXML2_TESTS: enabled + LIBCURL_TESTS: enabled + UNSAFE_TESTS: disabled + STAGING_TESTS: disabled + +Test run is PARALLEL with MAKEFLAGS= --jobs=4 + +./04_examples/outputs/check_outputs.cf Pass + +====================================================================== +Testsuite finished at 2019-04-13 18:41:03 (9 seconds) + +Passed tests: 1 +Failed tests: 0 +Skipped tests: 0 +Soft failures: 0 +Total tests: 1 +====================================================================== +Testsuite started at 2019-04-13 18:40:54 +---------------------------------------------------------------------- +Total tests: 1 + + COMMON_TESTS: enabled + TIMED_TESTS: enabled + SLOW_TESTS: enabled + ERROREXIT_TESTS: enabled + SERIAL_TESTS: enabled + NETWORK_TESTS: enabled + LIBXML2_TESTS: enabled + LIBCURL_TESTS: enabled + UNSAFE_TESTS: disabled + STAGING_TESTS: disabled + +Test run is PARALLEL with MAKEFLAGS= --jobs=4 + +---------------------------------------------------------------------- +./04_examples/outputs/check_outputs.cf +---------------------------------------------------------------------- +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/file_hash.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/filesize.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/lsdir.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_replace.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readrealarray.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_upcase.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_tail.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getvariablemetatags.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/classfiltercsv.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/with.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/parseintrealstringarray.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_extension_multiline_json.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/ago.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/peers.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/filter.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_reverse.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/kill_process_running_wrong_user.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getvalues.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/bundlesequence.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/classmatch.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/islessthan.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/data_regextract.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/maplist.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/bundlesmatching.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/appgroups.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readfile.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_classes.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_sections_non_empty_list.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/diskfree.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/data_readstringarray.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getindices.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/returnszero.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/container_key_iteration.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/lastnode.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regarray.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/hash.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_downcase.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/filestat.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/main_library.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_extension_top.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/datastate.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getfields.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mergedata.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regline.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isnewerthan.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/augment.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readintarray.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/format.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getgid.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/strftime.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_split.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_extension_expand_key.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/sublist.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/compare.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/variablesmatching_as_data.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/reverse.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readdata.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/intersection.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regex_replace.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/quoting.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/max-min-mean-variance.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/randomint.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/islink.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/ip2host.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/execresult.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/class-automatic-canonificiation.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readstringarray.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/main.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/canonify.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/join.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/every.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/escape.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/container_iteration.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getenv.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getusers.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/maparray.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/difference.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/classesmatching.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readenvfile.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regextract.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/dirname.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/data_expand.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/userexists.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_set_delimiters.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/splitstring.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/missing_ok.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/groupexists.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getclassmetatags.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_mustache.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_sections_empty_list.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/peerleaders.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/peerleader.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_extension_compact_json.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/none.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/bundlestate.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/length.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/countlinesmatching.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/multiple_outcomes.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/nth.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/product.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isplain.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/some.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/accessedbefore.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/select_region.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/filesexist.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_sections_inverted.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/variablesmatching.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mapdata.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/grep.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isdir.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/unique.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/parsestringarrayidx.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isgreaterthan.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/main_entry_point.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readcsv.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readintrealstringlist.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/hash_to_int.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/defaults.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getuid.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isvariable.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/countclassesmatching.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_head.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/fileexists.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/registryvalue.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/findfiles.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_sections_non_false_value.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isipinsubnet.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_length.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_comments.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/translatepath.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/strcmp.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isexecutable.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/shuffle.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/sum.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/reglist.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_variables.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regcmp.cf' +R: test_example: ran checker '/usr/bin/perl /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/remake_outputs.pl --cfagent="/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/bin/cf-agent" --workdir=/home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/workdir/__04_examples_outputs_check_outputs_cf/tmp/TESTDIR.cfengine -c /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/services_default_service_bundle.cf' +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/file_hash.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/filesize.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/lsdir.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_replace.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readrealarray.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_upcase.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_tail.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getvariablemetatags.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/classfiltercsv.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/with.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/parseintrealstringarray.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_extension_multiline_json.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/ago.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/peers.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/filter.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_reverse.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/kill_process_running_wrong_user.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getvalues.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/bundlesequence.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/classmatch.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/islessthan.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/data_regextract.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/maplist.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/bundlesmatching.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/appgroups.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readfile.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_classes.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_sections_non_empty_list.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/diskfree.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/data_readstringarray.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getindices.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/returnszero.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/container_key_iteration.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/lastnode.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regarray.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/hash.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_downcase.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/filestat.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/main_library.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_extension_top.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/datastate.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getfields.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mergedata.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regline.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isnewerthan.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/augment.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readintarray.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/format.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getgid.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/strftime.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_split.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_extension_expand_key.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/sublist.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/compare.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/variablesmatching_as_data.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/reverse.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readdata.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/intersection.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regex_replace.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/quoting.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/max-min-mean-variance.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/randomint.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/islink.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/ip2host.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/execresult.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/class-automatic-canonificiation.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readstringarray.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/main.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/canonify.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/join.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/every.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/escape.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/container_iteration.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getenv.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getusers.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/maparray.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/difference.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/classesmatching.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readenvfile.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regextract.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/dirname.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/data_expand.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/userexists.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_set_delimiters.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/splitstring.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/missing_ok.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/groupexists.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getclassmetatags.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_mustache.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_sections_empty_list.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/peerleaders.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/peerleader.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_extension_compact_json.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/none.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/bundlestate.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/length.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/countlinesmatching.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/multiple_outcomes.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/nth.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/product.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isplain.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/some.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/accessedbefore.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/select_region.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/filesexist.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_sections_inverted.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/variablesmatching.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mapdata.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/grep.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isdir.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/unique.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/parsestringarrayidx.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isgreaterthan.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/main_entry_point.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readcsv.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/readintrealstringlist.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/hash_to_int.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/defaults.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/getuid.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isvariable.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/countclassesmatching.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_head.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/fileexists.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/registryvalue.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/findfiles.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_sections_non_false_value.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isipinsubnet.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/string_length.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_comments.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/translatepath.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/strcmp.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/isexecutable.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/shuffle.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/sum.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/reglist.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/mustache_variables.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/regcmp.cf +R: test: found example with output /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/../../examples/services_default_service_bundle.cf +R: /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/./04_examples/outputs/check_outputs.cf Pass + +Return code is 0. + + ==> Pass + + +====================================================================== +Testsuite finished at 2019-04-13 18:41:03 (9 seconds) + +Passed tests: 1 +Failed tests: 0 +Skipped tests: 0 +Soft failures: 0 +Total tests: 1 +#+end_example +:END: diff --git a/tests/acceptance/04_examples/mock_stdlib.cf b/tests/acceptance/04_examples/mock_stdlib.cf new file mode 100644 index 0000000000..b44f473f65 --- /dev/null +++ b/tests/acceptance/04_examples/mock_stdlib.cf @@ -0,0 +1,70 @@ +# Mock version of stdlib.cf , paths.cf etc. from masterfiles. +# This just defines some empty bodies and bundles which are necessary +# for cf-promises to run on all the examples in core. There is currently no +# way to make cf-promises validate the syntax of a single file, without +# opening inputs. +# +# NOTE: Syntax doesn't allow empty body, but body with 1 class guard is OK +# NOTE: It would be nice if this was auto-generated, but it isn't, +# just add another body / bundle if you need to use it in an example. + +body package_method yum {} +body package_method generic {} +body classes enumerate(x) {} +body perms p(user,mode) {} +body changes tripwire {} +body depth_search recurse(d) {} +body delete tidy {} +body file_select days_old(days) {} +body edit_defaults empty {} +body acl ntfs(acl) {} +body action if_elapsed(x) {} +body action warn_only {} +body classes if_ok(x) {} +body classes if_repaired(x) {} +body contain setuid(owner) {} +body copy_from local_cp(from) {} +body copy_from remote_cp(from, server) {} +body copy_from secure_cp(from,server) {} +body depth_search include_base {} +body package_method solaris(pkgname, spoolfile, adminfile) {} +body package_method zypper {} +body perms mo(mode, user) {} +body perms mog(mode, user, group) {} +body perms owner(user) {} +body rename disable {} +body rename rotate(level) {} +body rename to(file) {} +body replace_with value(x) {} +body acl strict {} +body changes detect_content {} +body classes if_else (yes, no) {} +body contain in_shell {} +body copy_from remote_dcp(from,server) {} +body package_method msi_explicit(repo) {} +body package_method msi_implicit(repo) {} + +bundle edit_line insert_lines(lines) {} +bundle edit_line append_if_no_line(lines) {} +bundle edit_line prepend_if_no_line(string) {} +bundle edit_line append_user_field(group,field,allusers) {} +bundle edit_line set_user_field(user,field,val) {} +bundle agent package_absent(package) {} +bundle agent package_present(package) {} +bundle agent package_latest(package) {} +bundle agent package_specific_present(packageorfile, package_version, package_arch) {} +bundle agent package_specific_latest(packageorfile, package_version, package_arch) {} +bundle edit_xml xml_insert_tree_nopath(treestring) {} +bundle edit_xml xml_set_value(value, xpath) {} +bundle edit_xml xml_set_attribute(attr, value, xpath) {} +bundle edit_line set_variable_values(v) {} +bundle edit_line expand_template(templatefile) {} +bundle edit_line create_solaris_admin_file {} +bundle common paths {} + +# This "test" always passes, it is meant to be included from examples: +bundle agent __main__ +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/04_examples/outputs/check_outputs.cf b/tests/acceptance/04_examples/outputs/check_outputs.cf new file mode 100644 index 0000000000..09516ef2b6 --- /dev/null +++ b/tests/acceptance/04_examples/outputs/check_outputs.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Check the example outputs +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + vars: + "basedir" string => "$(G.cwd)/../../examples"; + "checker" string => "$(basedir)/remake_outputs.pl"; + "all_examples" slist => findfiles("$(basedir)/*.cf"); + + reports: + DEBUG_LOTS:: + "$(this.bundle): will consider example $(all_examples)"; +} + +####################################################### + +bundle agent test +{ + meta: + # It's unrealistic to keep this test perfectly stable on all non-Linux platforms. + # It would require examples that quickly get very cluttered. + "test_skip_unsupported" string => "!linux"; + + vars: + "examples" slist => { @(init.all_examples) }; + "canon[$(examples)]" string => canonify($(examples)); + + classes: + "has_output_block_$(canon[$(examples)])" + expression => regline(".*example_output.*", $(examples)); + + methods: + "$(examples)" usebundle => test_example($(examples)), + if => "has_output_block_$(canon[$(examples)])"; + + reports: + DEBUG:: + "$(this.bundle): found example with output $(examples)" + if => "has_output_block_$(canon[$(examples)])"; +} + +bundle agent test_example(file) +{ + vars: + "cfile" string => canonify($(file)); + + "checker" string => "$(G.perl) $(init.checker) --cfagent=$(sys.cf_agent) --workdir=$(G.testdir) -c"; + + classes: + "failure_$(cfile)" not => returnszero("$(checker) $(file)", "noshell"), scope => "namespace"; + + methods: + "with verbose" usebundle => verbose_output("$(checker) -v $(file)"), + if => "failure_$(cfile)"; + + reports: + "$(this.bundle): checker '$(checker) $(file)' returned error" + if => "failure_$(cfile)"; + + DEBUG:: + "$(this.bundle): ran checker '$(checker) $(file)'"; +} + +bundle agent verbose_output(runme) +{ + commands: + "$(runme)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => classmatch("failure.*"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/04_examples/syntax/check_syntax.cf b/tests/acceptance/04_examples/syntax/check_syntax.cf new file mode 100644 index 0000000000..e52914c1ba --- /dev/null +++ b/tests/acceptance/04_examples/syntax/check_syntax.cf @@ -0,0 +1,166 @@ +####################################################### +# +# Check the syntax of all examples in examples directory +# +# Is this test failing? If you added an example, make sure you add the +# necessary bodies/bundles to mock_stdlib.cf +# To find out which example is failing, run in tests/acceptance: +# $ ./testall 04_examples/syntax/check_syntax.cf ; less test.log +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + vars: + "basedir" string => "$(G.cwd)/../../examples"; + "all_examples" slist => findfiles("$(basedir)/*.cf"); + "stdlib" string => "$(G.testdir)/inputs/lib/stdlib.cf"; + "mock" string => "$(this.promise_dirname)/../mock_stdlib.cf"; + + methods: + "stdlib_symlink" + usebundle => lib_maybe_symlink( + "$(mock)", "$(G.testdir)/inputs/lib/stdlib.cf"); + "paths_symlink" + usebundle => lib_maybe_symlink( + "$(mock)", "$(G.testdir)/inputs/lib/paths.cf"); + "users_symlink" + usebundle => lib_maybe_symlink( + "$(mock)", "$(G.testdir)/inputs/lib/users.cf"); + "services_symlink" + usebundle => lib_maybe_symlink( + "$(mock)", "$(G.testdir)/inputs/lib/services.cf"); + "packages_symlink" + usebundle => lib_maybe_symlink( + "$(mock)", "$(G.testdir)/inputs/lib/packages.cf"); + "commands_symlink" + usebundle => lib_maybe_symlink( + "$(mock)", "$(G.testdir)/inputs/lib/commands.cf"); + "files_symlink" + usebundle => lib_maybe_symlink( + "$(mock)", "$(G.testdir)/inputs/lib/files.cf"); + + DEBUG_LOTS:: + "$(this.bundle): will consider example $(all_examples)"; +} + +bundle agent lib_maybe_symlink(src, dst) +# Symlink a file, only if dst does not exist +# If dst already exists, assume it's correct (testing with masterfiles) +{ + classes: + "file_missing" + if => not(fileexists("$(dst)")); + methods: + file_missing:: + "symlink" + usebundle => symlink("$(src)", "$(dst)"); +} + +bundle agent symlink(src, dst) +{ + vars: + "dir" + string => dirname("$(dst)"); + + files: + "$(dir)/." + create => "true"; + + "$(dst)" + link_from => ln_s("$(src)"); +} + +body link_from ln_s(x) +{ + link_type => "symlink"; + source => "$(x)"; +} + +####################################################### + +bundle agent test +{ + meta: + # It's unrealistic to keep this test perfectly stable on all non-Linux platforms. + # It would require examples that quickly get very cluttered. + "test_skip_unsupported" + string => "!linux"; + "description" + string => "Syntax check each policy (.cf) file in examples"; + + vars: + "examples" slist => { @(init.all_examples) }; + "canon[$(examples)]" string => canonify($(examples)); + + methods: + "$(examples)" usebundle => test_example($(examples)); + + reports: + DEBUG:: + "$(this.bundle): found example with output $(examples)" + if => "has_output_block_$(canon[$(examples)])"; +} + +bundle agent test_example(file) +{ + vars: + "cfile" string => canonify($(file)); + "checker" + # -c option is omitted, because it enforces bundlesequence / main + string => "$(sys.cf_promises) --workdir=$(G.testdir)"; + + classes: + "failure_$(cfile)" + not => returnszero("$(checker) $(file)", "noshell"), + scope => "namespace"; + + reports: + DEBUG:: + "Syntax error in example: '$(file)'" + if => "failure_$(cfile)", + handle => "reported_error_$(cfile)"; + + methods: + "verbose_$(cfile)" + usebundle => run_with_output("$(checker) $(file)"), + if => "failure_$(cfile)", + depends_on => { "reported_error_$(cfile)" }; +} + +bundle agent run_with_output(runme) +# This bundle is only used in case of failure +# It reruns a command, as a commands promise, which will show the output +# from the failing cf-promises command. +{ + vars: + "c" string => canonify("$(runme)"); + reports: + "Running: $(runme)" + handle => "printed_$(c)"; + commands: + "$(runme)" + depends_on => { "printed_$(c)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => classmatch("failure_.*"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/05_processes/00_hpux_ps_setup_serial.cf b/tests/acceptance/05_processes/00_hpux_ps_setup_serial.cf new file mode 100644 index 0000000000..03daf9296b --- /dev/null +++ b/tests/acceptance/05_processes/00_hpux_ps_setup_serial.cf @@ -0,0 +1,47 @@ +# Simply makes sure that /etc/default/ps has the correct setting on HPUX. If +# DEFAULT_CMD_LINE_WIDTH is too low ps output may be truncated, leading to test +# failures. We adjust it here if we are running in unsafe mode, else we simply +# check that it is correct and fail if it isn't. + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!hpux"; + + classes: + "unsafe_mode" + expression => strcmp(getenv("UNSAFE_TESTS", "10"), "1"), + scope => "namespace"; + + files: + unsafe_mode:: + "/etc/default/ps" + edit_line => ps_edit; +} + +bundle edit_line ps_edit +{ + delete_lines: + "\s*DEFAULT_CMD_LINE_WIDTH\s*=\s*[0-9]+\s*"; + insert_lines: + "DEFAULT_CMD_LINE_WIDTH=1024"; +} + +bundle agent check +{ + classes: + "ok" expression => regline("DEFAULT_CMD_LINE_WIDTH=1024", + "/etc/default/ps"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/05_processes/01_matching/inverse_ttime_classes_body_params.cf b/tests/acceptance/05_processes/01_matching/inverse_ttime_classes_body_params.cf new file mode 100644 index 0000000000..beeea65a14 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/inverse_ttime_classes_body_params.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Set a class if a process does exist, more complex matches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + ".*" + process_select => test_select_high_users, + classes => test_set_class("found", "not_found"); +} + +body classes test_set_class(t,f) +{ + promise_kept => { "$(t)" }; + promise_repaired => { "$(t)" }; + repair_failed => { "$(f)" }; +} + +body process_select test_select_high_users +{ + ttime_range => irange(0, accumulated(9,0,0,0,0,0)); # Anything + process_result => "!ttime"; # Nothing +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "found.!not_found"; # Nothing found is "kept" + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/inverse_ttime_inverse_found.cf b/tests/acceptance/05_processes/01_matching/inverse_ttime_inverse_found.cf new file mode 100644 index 0000000000..49a79b08c0 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/inverse_ttime_inverse_found.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Set a class if a process with !any time does not exist, and +# we wanted not to (irange(0,0)) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_flakey_fail" string => "aix_7_1", + meta => { "CFE-3313" }; + + processes: + ".*" + process_count => test_range, + process_select => test_select_high_users; +} + +body process_count test_range +{ + in_range_define => { "none_found" }; + out_of_range_define => { "some_found" }; + + aix:: + # Allow one process with CPU time not between 0 seconds and 99 years on + # AIX because a process with TIME '-' sometimes appears there. + match_range => irange(0, 1); + !aix:: + match_range => irange(0, 0); +} + +body process_select test_select_high_users +{ + ttime_range => irange(0, accumulated(99,0,0,0,0,0)); # Anything + process_result => "!ttime"; # Nothing +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "none_found.!some_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/negative_restart_class.cf b/tests/acceptance/05_processes/01_matching/negative_restart_class.cf new file mode 100644 index 0000000000..212ee9c6a6 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/negative_restart_class.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Set a class if a process exists +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + processes: + "\bcf-agent\b" + restart_class => "not_ok"; + + classes: + "ok" not => "not_ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/owner.cf b/tests/acceptance/05_processes/01_matching/owner.cf new file mode 100644 index 0000000000..c0b842c747 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/owner.cf @@ -0,0 +1,91 @@ +####################################################### +# +# Run multiple similar tests for 'process_owner'. +# We test both expected success and expected failure. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "ok_list" slist => { + "pass_root_1", "pass_root_2", "pass_nouser_3", "pass_nouser_4", + "!fail_root_1", "!fail_root_2", "!fail_nouser_3", "!fail_nouser_4" + }; + + processes: + ### 'root' processes: + # finding many processes is good + ".*" + process_select => test_select_owners("root"), + process_count => test_range("2", "inf", "pass_root_1", "fail_root_1"); + + # looking for a tiny number should fail + ".*" + process_select => test_select_owners("root"), + process_count => test_range("0", "1", "fail_root_2", "pass_root_2"); + + ### A hopefully non-existent user: + # finding zero processes is good + ".*" + process_select => test_select_owners("NoSuchUserWeHope"), + process_count => test_range("0", "0", "pass_nouser_3", "fail_nouser_3"); + + # looking for one or more should fail + ".*" + process_select => test_select_owners("NoSuchUserWeHope"), + process_count => test_range("1", "inf", "fail_nouser_4", "pass_nouser_4"); + +} + +body process_count test_range(min, max, class_good, class_bad) +{ + match_range => irange("$(min)", "$(max)"); + in_range_define => { "$(class_good)" }; + out_of_range_define => { "$(class_bad)" }; +} + +body process_select test_select_owners(owner) +{ + process_owner => { "$(owner)" }; + process_result => "process_owner"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { @(test.ok_list) }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/partial_name_inverse_found.cf b/tests/acceptance/05_processes/01_matching/partial_name_inverse_found.cf new file mode 100644 index 0000000000..6a03552583 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/partial_name_inverse_found.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Set a class if a process does exist, +# but partial match name fails +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + "cf-agen$" + process_count => test_no_such; +} + +body process_count test_no_such +{ + in_range_define => { "none_found" }; + out_of_range_define => { "some_found" }; + match_range => irange(0,0); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "none_found.!some_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/partial_name_inverse_not_found.cf b/tests/acceptance/05_processes/01_matching/partial_name_inverse_not_found.cf new file mode 100644 index 0000000000..6bbd62d7fb --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/partial_name_inverse_not_found.cf @@ -0,0 +1,54 @@ +####################################################### +# +# Set a negative match class if a process does exist, +# and partial name match succeeds but we wanted zero found +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + "-agent" + process_count => test_no_such; +} + +body process_count test_no_such +{ + in_range_define => { "none_found" }; + out_of_range_define => { "some_found" }; + match_range => irange(0,0); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "!none_found.some_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/process_count_fncalls.cf b/tests/acceptance/05_processes/01_matching/process_count_fncalls.cf new file mode 100644 index 0000000000..ee6f476fb2 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/process_count_fncalls.cf @@ -0,0 +1,23 @@ +bundle agent main +{ + meta: + "description" -> { "CFE-2067" } + string => "Test that process_count body can have (failing) function calls"; + + processes: + # Test that this doesn't cause a crash / abort: + "cf-execd" + process_count => process_count_one("$(nosuchvar)"); + # Error will be printed since variable won't expand, expected behavior + + reports: + # Nothing to check, running without crashing is good enough + "$(this.promise_filename) Pass"; +} + +body process_count process_count_one(suffix) +{ + match_range => irange("1", "1"); + in_range_define => { canonify("one_$(suffix)") }; + out_of_range_define => { canonify("not_one_$(suffix)") }; +} diff --git a/tests/acceptance/05_processes/01_matching/process_count_found.cf b/tests/acceptance/05_processes/01_matching/process_count_found.cf new file mode 100644 index 0000000000..4a9aa5f9c7 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/process_count_found.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Set a class if a process does exist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + "\bcf-agent\b" + process_count => test_no_such; +} + +body process_count test_no_such +{ + in_range_define => { "found" }; + out_of_range_define => { "not_found" }; + match_range => irange(1,"inf"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "found.!not_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/process_count_inverse_found.cf b/tests/acceptance/05_processes/01_matching/process_count_inverse_found.cf new file mode 100644 index 0000000000..0b4cbf05f4 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/process_count_inverse_found.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Set a class if a process does not exist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + "There-is-NoPrOCess-with-this-name" + process_count => test_no_such; +} + +body process_count test_no_such +{ + in_range_define => { "found" }; + out_of_range_define => { "not_found" }; + match_range => irange(0,0); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "found.!not_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/process_count_inverse_not_found.cf b/tests/acceptance/05_processes/01_matching/process_count_inverse_not_found.cf new file mode 100644 index 0000000000..02d1e43ae7 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/process_count_inverse_not_found.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Set a class if a process does exist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + "\bcf-agent\b" + process_count => test_no_such; +} + +body process_count test_no_such +{ + in_range_define => { "none_found" }; + out_of_range_define => { "some_found" }; + match_range => irange(0,0); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "!none_found.some_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/process_count_not_found.cf b/tests/acceptance/05_processes/01_matching/process_count_not_found.cf new file mode 100644 index 0000000000..ec2eeb0002 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/process_count_not_found.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Set a class if a process does not exist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + "There-is-NoPrOCess-with-this-name" + process_count => test_no_such; +} + +body process_count test_no_such +{ + in_range_define => { "found" }; + out_of_range_define => { "not_found" }; + match_range => irange(1,"inf"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "!found.not_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/process_select_fncalls.cf b/tests/acceptance/05_processes/01_matching/process_select_fncalls.cf new file mode 100644 index 0000000000..6815f2028c --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/process_select_fncalls.cf @@ -0,0 +1,96 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-1968" } + string => "Test that process_select body can have (failing) function calls"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "canonified_uid" + string => canonify(getuid("nosuchuser")); + + processes: + any:: + "cf-agent" + process_select => by_owner_no_fncall("nosuchuser", "$(canonified_uid)"), + process_count => proc_found_one_or_more("nosuchuser_no_fncall"); + "cf-agent" + process_select => by_owner("nosuchuser"), + process_count => proc_found_one_or_more("nosuchuser"); + "nosuchproc" + process_count => proc_found_one_or_more("nosuchproc"); + "cf-agent" + process_count => proc_found_one_or_more("agent"); + ".*" + process_select => by_owner("root"), + process_count => proc_found_one_or_more("root"); +} + +body process_count proc_found_one_or_more(prefix) +# defines _found / _not_found if there are 1 or more / 0 matches, respectively +{ + in_range_define => { "$(prefix)_found" }; + out_of_range_define => { "$(prefix)_not_found" }; + match_range => irange(1,"inf"); +} + +body process_select by_owner(u) +# @brief Select processes owned by user `u` +# @param u The name of the user +# +# Matches processes against the given username and the given username's uid +# in case only uid is visible in process list. +# +# @note: if getuid fails (because there is no user) the canonify call will +# not resolve, and that part of the list will be skipped. +{ + process_owner => { "$(u)", canonify(getuid("$(u)")) }; + process_result => "process_owner"; +} + +body process_select by_owner_no_fncall(u, cgu) +# Compare behavior to this backwards compatible body +{ + process_owner => { "$(u)", "$(cgu)" }; + process_result => "process_owner"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" + and => { + "!nosuchuser_no_fncall_found", "nosuchuser_no_fncall_not_found", + "!nosuchuser_found", "nosuchuser_not_found", + "!nosuchproc_found", "nosuchproc_not_found", + "agent_found", "!agent_not_found", + "root_found", "!root_not_found" + }; + + reports: + DEBUG.agent_found:: + "ok - found agent"; + DEBUG.nosuchproc_found:: + "not ok - found nosuchproc"; + DEBUG.nosuchuser_found:: + "not ok - found nosuchuser"; + DEBUG.root_found:: + "ok - found root!"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/05_processes/01_matching/promiser_match_is_correct.cf b/tests/acceptance/05_processes/01_matching/promiser_match_is_correct.cf new file mode 100644 index 0000000000..73ec725cb6 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/promiser_match_is_correct.cf @@ -0,0 +1,94 @@ +# Test that process promise only matches the promiser against the command line +# not the full ps line output. Also verify that a command line longer than 80 +# characters can be matched using the promiser. Created for Redmine #7627. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "debug_info", default("$(this.promise_filename)") }; +} + +bundle common userinfo +{ + vars: + "cmd" string => "id | sed -e 's/.*uid=\([0-9][0-9]*\).*/\1/'"; + + any:: + "current_user" string => execresult($(cmd), "useshell"); + + classes: + "root_user" expression => strcmp("$(current_user)", "0"); +} + +bundle agent debug_info +{ + reports: + !root_user:: + "Need to be root user to run this. Detected user: $(userinfo.current_user)"; +} + +bundle agent init +{ + meta: + # Using background commands, this won't work correctly on Windows. + # Also, we need to be root user to test this correctly, since we need to + # match against the user in the ps output. + # And we can't use no_fds under fakeroot. + "test_skip_needs_work" string => "windows|!root_user|using_fakeroot"; + + commands: + "$(G.no_fds) --no-std $(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_underscore_marks_the_81st_character_of_this_string___STRINGBEYOND80CHARS &" + contain => in_shell; + + # give the process a chance to actually start + "$(G.sleep) 1"; +} + +body process_count at_least_one(class) +{ + match_range => irange("1", "1000"); + in_range_define => { "$(class)" }; +} + +bundle agent test +{ + processes: + "$(this.promise_filename).sub" + process_count => at_least_one("cf_agent"); + "STRINGBEYOND80CHARS" + process_count => at_least_one("string"); + # This is intended to look for a username in the process matching, which + # is wrong, we only want it to match the command string. However, "root" + # might be in the home path of the executing user, which is valid, so + # avoid that match. + "root[^/].*$(this.promise_filename).sub" + process_count => at_least_one("root"); +} + +bundle agent check +{ + vars: + "expected_classes" slist => { + "cf_agent", + "string", + "!root", + }; + + classes: + "ok" and => { @(expected_classes) }; + + commands: + # Just sleep a little to make sure our background command has finished. + "$(G.sleep) 6"; + + reports: + DEBUG:: + "expected_classes = $(expected_classes)"; + "Expression evaluated to false, should be true: $(expected_classes)" + unless => "$(expected_classes)"; + + !root_user|!ok:: + "$(this.promise_filename) FAIL"; + root_user.ok:: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/05_processes/01_matching/promiser_match_is_correct.cf.sub b/tests/acceptance/05_processes/01_matching/promiser_match_is_correct.cf.sub new file mode 100644 index 0000000000..dd1fa7831a --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/promiser_match_is_correct.cf.sub @@ -0,0 +1,11 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + commands: + "$(G.sleep) 3"; +} diff --git a/tests/acceptance/05_processes/01_matching/restart_class.cf b/tests/acceptance/05_processes/01_matching/restart_class.cf new file mode 100644 index 0000000000..3ab8f9ddaa --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/restart_class.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Set a class if a process does not exist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + processes: + "There-is-NoPrOCess-with-this-name" + restart_class => "ok"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/stime_range.cf b/tests/acceptance/05_processes/01_matching/stime_range.cf new file mode 100644 index 0000000000..02dfa16ec2 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/stime_range.cf @@ -0,0 +1,211 @@ +####################################################### +# +# Acceptance test for RedMine 4094 +# +# Where ps reports a date (or even a year) as "start time", selecting +# processes by how old they are can get wrong answers due to the +# imprecision implicit in these start times. (Our code, from the +# commit that adds this test, deals with this by using elapsed time to +# infer a start time, when appropriate.) The test below looks for +# processes whose start times look more like dates than times; it uses +# the elapsed time of such processes to find out how long they've +# actually been running and separates them into "early" and "late" +# groups, based on whether their start-times in their respective start +# dates are before or after the present time within today. If we now +# search for processes started within the number of days that their +# start date is ago, the late ones should appear within that interval +# while the early ones appear outside it. +# +# On platforms where start time doesn't fall back to a date, or on +# hosts which don't have any old processes running, this test finds no +# matching "early" or "late" processes and silently passes. If we +# have a platform on which this test fails, this may lead to the test +# not always failing (due to the host lacking relevant processes by +# which to notice the failure). +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "now_raw" string => execresult("$(G.ps) -o stime $$", "useshell"); + got_ps.got_now:: + # Get pid and parsed etime for processes whose stime isn't + # actually a time; use sed to munge ps's output into a csv + # format for ease of ingestion: + "ps_list" string => execresult("$(G.ps) -eo pid,stime,etime | sed -n -e 's/[ \t]*\([0-9][0-9]*\)[ \t][ \t]*[A-Z][a-z0-9]*[ \t][ \t]*\(\([0-9][0-9]*\)-\)\?\([0-9][0-9]*\):\([0-9][0-9]*\):\([0-9][0-9]*\)/\1,\3,\4,\5,\6/p'", "useshell"); + # Ingest the results, save in procs[i][j]: + "count" int => parsestringarrayidx("procs", "$(ps_list)", + "no comment", ",", + "1000", "100000"); + # procs[i][j] for each i describe one process; + # for j = 0, ..., 4, entries are a pid and its age in days, hrs, mins, secs + "pndx" slist => getindices("procs"); + + # Now flatten h:m:s to total seconds, for each proc-index: + "hrassec[$(pndx)]" ilist => { "$(procs[$(pndx)][2])", "3600" }; + "min2sec[$(pndx)]" ilist => { "$(procs[$(pndx)][3])", "60" }; + "hms[$(pndx)]" rlist => { product("hrassec[$(pndx)]"), + product("min2sec[$(pndx)]"), + "$(procs[$(pndx)][4])" }; + # Map pids to seconds before a whole number of days ago: + "when[$(procs[$(pndx)][0])]" string => format("%.0f", sum("hms[$(pndx)]")); + + # Likewise reduce "now" to a number of seconds into today: + "hrassec[now]" ilist => { "$(now[1])", "3600" }; + "min2sec[now]" ilist => { "$(now[2])", "60" }; + "hms[now]" rlist => { product("hrassec[now]"), + product("min2sec[now]") }; + "present" string => format("%.0f", sum("hms[now]")); + # Allow for up to 10s delay between when we asked for now_raw + # and when bundle agent test runs: + "pausing" rlist => { @(hms[now]), "10" }; + "paused" string => format("%.0f", sum("pausing")); + # Naturally, this doesn't work so well if the interval between + # present and paused straddles midnight; see bad_timing, below. + + # How many days back is each pid ? + "past[$(pndx)]" slist => { + # Number of days from elapsed time, 0 if none specified: + ifelse("got_day_$(pndx)", "$(proc_day[$(pndx)][0])", "0"), + # Plus one for those started late in their days: + ifelse("late_$(procs[$(pndx)][0])", "1", "0") }; + "days[$(procs[$(pndx)][0])]" string => format("%.0f", sum("past[$(pndx)]")); + "pid" slist => getindices("days"); + + classes: + # Give up on test unless these are set: + "got_now" expression => regextract("^(\d+):(\d+)$", "$(now_raw)", "now"); + "got_ps" expression => isexecutable("$(G.ps)"); + got_ps.got_now:: + "got_data" expression => isgreaterthan(length("pndx"), 0); + # Filtering processes + got_data:: + # Are we too close to midnight ? + "bad_timing" expression => isgreaterthan("$(paused)", 86400); # 24*60*60 + # Which procs[$(pndx)] have non-empty days, procs[$(pndx)][1] ? + "got_day_$(pndx)" + expression => regextract("(\d+)", "$(procs[$(pndx)][1])", + "proc_day[$(pndx)]"); + # A process started late in its day if less than $(present) + # seconds before a whole number of days ago: + "late_$(procs[$(pndx)][0])" + expression => isgreaterthan("$(present)", "$(when[$(procs[$(pndx)][0])])"), + scope => "namespace"; + # ... and early in its day if more than $(paused) seconds before + # a whole number of days ago: + "early_$(procs[$(pndx)][0])" + and => { isgreaterthan("$(when[$(procs[$(pndx)][0])])", "$(paused)"), + "got_day_$(pndx)" }, + scope => "namespace"; + "got_early" or => { classmatch("early_\d+") }, scope => "namespace"; + "got_late" or => { classmatch("late_\d+") }, scope => "namespace"; + + # So now a search for pid=$(pid) within the last $(days[$(pid)]) + # days should fail if early_$(pid) is set and succeed if + # late_$(pid) is set; if neither is set, it may go either way. + +# reports: +# DEBUG.got_early:: +# "Early: $(pid) from $(days[$(pid)]) days ago" if => "early_$(pid)"; +# DEBUG.got_late:: +# "Late: $(pid) from $(days[$(pid)]) days ago" if => "late_$(pid)"; +} + +####################################################### + +bundle agent test +{ + vars: + "pid" ilist => { @(init.pid) }; + + processes: + "" + process_select => pid_is_newer_than($(pid), "$(init.days[$(pid)])"), + process_count => any_procs("early_$(pid)"), + if => "early_$(pid)"; + + "" + process_select => pid_is_newer_than($(pid), "$(init.days[$(pid)])"), + process_count => any_procs("late_$(pid)"), + if => "late_$(pid)"; +} + +body process_select pid_is_newer_than(pid, days) +{ + pid => irange($(pid), $(pid)); + stime_range => irange(ago(0, 0, $(days), 0, 0, 0), now()); + process_result => "pid.stime"; +} + +body process_count any_procs(prefix) +{ + match_range => "1,1"; + in_range_define => { "$(prefix)_in" }; + out_of_range_define => { "$(prefix)_out" }; +} + +####################################################### + +bundle agent check +{ + vars: + "pid" ilist => { @(init.pid) }; + classes: + "bad_$(pid)_in" and => { "early_$(pid)", "early_$(pid)_in" }; + "good_$(pid)_out" and => { "early_$(pid)", "early_$(pid)_out" }; + "good_$(pid)_in" and => { "late_$(pid)", "late_$(pid)_in" }; + "bad_$(pid)_out" and => { "late_$(pid)", "late_$(pid)_out" }; + + "early_in" or => { classmatch("bad_\d+_in") }; + "early_out" or => { classmatch("good_\d+_out") }; + "late_in" or => { classmatch("good_\d+_in") }; + "late_out" or => { classmatch("bad_\d+_out") }; + "early_ok" or => { "!got_early", "!early_in.early_out" }; + "late_ok" or => { "!got_late", "late_in.!late_out" }; + "pass" and => { "late_ok", "early_ok" }; + "ok" or => { "pass", "bad_timing" }; + + reports: + bad_timing:: + "Ran too close to midnight to get meaningful answers; 'passing' by default."; + + DEBUG.!got_early:: + "Found no processes, early in past days, to test"; + DEBUG.!got_late:: + "Found no processes, late in past days, to test"; + + DEBUG.got_early.early_in:: + "Early processes reported as in range"; + "Process $(pid), reported within $(init.days[$(pid)]) days, is older" + if => "bad_$(pid)_in"; + DEBUG.got_early.!early_out:: + "Early processes not reported as out of range"; + "Process $(pid), not reported outside $(init.days[$(pid)]) days, is so old" + if => "early_$(pid).!early_$(pid)_out"; + DEBUG.got_late.!late_in:: + "Late processes not reported as in range"; + "Process $(pid), not reported within $(init.days[$(pid)]) days, is so new" + if => "late_$(pid).!late_$(pid)_in"; + DEBUG.got_late.late_out:: + "Late processes reported as out of range"; + "Process $(pid), reported outside $(init.days[$(pid)]) days, is newer" + if => "bad_$(pid)_out"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/timed/stime.cf b/tests/acceptance/05_processes/01_matching/timed/stime.cf new file mode 100644 index 0000000000..e1d0af8854 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/timed/stime.cf @@ -0,0 +1,66 @@ +# Test that using process cmd line matching works in a process promise. + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + meta: + # Adding the test case revealed that these platforms do not work, but we + # don't know why. + "test_skip_needs_work" string => "solaris|hpux"; + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + vars: + # Random, but unique string for this test case. + "unique" string => "Ni8Goobi"; + + commands: + test_pass_1.!windows:: + "$(G.no_fds) --no-std $(G.sh) $(this.promise_filename).bat $(unique) &" + contain => in_shell; + test_pass_1.windows:: + "$(this.promise_dirname)/../../../elevate.exe $(this.promise_filename).bat $(unique)" + contain => in_shell; +} + +bundle agent test +{ + vars: + "escaped_process" string => escape("$(this.promise_filename).bat"); + + processes: + ".*$(init.unique).*" + process_count => test_range; +} + +body process_count test_range +{ + in_range_define => { "found" }; + out_of_range_define => { "not_found" }; + match_range => irange(1,100); +} + +bundle agent check +{ + methods: + test_pass_1:: + "any" usebundle => dcs_wait("$(this.promise_filename)", 1); + + test_pass_2.(!found|not_found):: + "any" usebundle => dcs_fail("$(this.promise_filename)"); + test_pass_2.found.!not_found:: + "any" usebundle => dcs_wait("$(this.promise_filename)", 65); + + test_pass_3.(found|!not_found):: + "any" usebundle => dcs_fail("$(this.promise_filename)"); + test_pass_3.!found.not_found:: + "any" usebundle => dcs_pass("$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/timed/stime.cf.bat b/tests/acceptance/05_processes/01_matching/timed/stime.cf.bat new file mode 100644 index 0000000000..3e5c09b315 --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/timed/stime.cf.bat @@ -0,0 +1 @@ +sleep 60 diff --git a/tests/acceptance/05_processes/01_matching/ttime_classes_body_params.cf b/tests/acceptance/05_processes/01_matching/ttime_classes_body_params.cf new file mode 100644 index 0000000000..d5a72761ec --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/ttime_classes_body_params.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Set a class if a process does exist, more complex matches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + ".*" + process_select => test_select_high_users, + classes => test_set_class("found", "not_found"); +} + +body classes test_set_class(t,f) +{ + promise_kept => { "$(t)" }; + promise_repaired => { "$(t)" }; + repair_failed => { "$(f)" }; +} + +body process_select test_select_high_users +{ + ttime_range => irange(0, accumulated(9,0,0,0,0,0)); # Anything + process_result => "ttime"; # Everything +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "found.!not_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/ttime_inverse_found.cf b/tests/acceptance/05_processes/01_matching/ttime_inverse_found.cf new file mode 100644 index 0000000000..8329dcecff --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/ttime_inverse_found.cf @@ -0,0 +1,62 @@ +####################################################### +# +# Set a class if a process does exist but +# we wanted not to (irange(0,0)), more complex matches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + processes: + ".*" + process_count => test_range, + process_select => test_select_high_users; +} + +body process_count test_range +{ + in_range_define => { "none_found" }; + out_of_range_define => { "some_found" }; + match_range => irange(0,0); +} + +body process_select test_select_high_users +{ + ttime_range => irange(0, accumulated(9,0,0,0,0,0)); # Anything + process_result => "ttime"; # Everything +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "!none_found.some_found"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/01_matching/tty.cf b/tests/acceptance/05_processes/01_matching/tty.cf new file mode 100644 index 0000000000..c363f76fbd --- /dev/null +++ b/tests/acceptance/05_processes/01_matching/tty.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Basic checking of 'tty' selector. +# +# The name pattern of ttys varies across operating systems. +# While the example in the documentation (e.g. "pts/[0-9]+") +# is typical and illustrative, this basic-level test needs to be +# reliably portable. +# +# The possible meta "test_skip_needs_work" would allow graceful handling +# of platforms that have not yet been tested. But aim to minimise +# any reliance on this. +# +# It assumes that the host on which the test is being run will have at least +# one tty connection active. This is clearly the case if it is being run +# by a human from their command line. But it might not be true in a +# CI/CD environment; in such a case the meta/skip may be useful. +# +# Tests: +# +# 1. Negative: using a highly unlikely name. +# +# 2. Positive: using a known-good name pattern; allow to differ across OSes. +# +# David Lee, 2019 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_needs_work" -> {"CFE-3025"} + string => "!linux.!hpux", + comment => "TTY-based process filtering doesn't work well on other platforms"; + + vars: + # Maintenance note: Aim to cover as many OSes as possible + # and mimimise the ".+" catch-all. + linux:: + "dev_pattern" string => "(pts/[0-9]+)|(ttyS?[0-9]+)"; + !linux:: + "dev_pattern" string => ".+"; + + processes: + ### Expect zero processes on a tty with a highly unlikely name. + ".*" + handle => "expect_none", + process_select => test_select_tty("No-Such-Major/No-Such-Minor"), + process_count => test_range("0", "0", "pass_bad_dev", "fail_bad_dev"); + + ### Expect to find one or more processes on these ttys. + ".*" + handle => "expect_some", + process_select => test_select_tty("$(dev_pattern)"), + process_count => test_range("1", "inf", "pass_good_dev", "fail_good_dev"); + +} + +body process_count test_range(min, max, class_good, class_bad) +{ + match_range => irange("$(min)", "$(max)"); + in_range_define => { "$(class_good)" }; + out_of_range_define => { "$(class_bad)" }; +} + +body process_select test_select_tty(tty_pattern) +{ + tty => "$(tty_pattern)"; + process_result => "tty"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + "pass_good_dev", "pass_bad_dev", + "!fail_good_dev", "!fail_bad_dev", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 30 diff --git a/tests/acceptance/05_processes/02_functions/findprocesses.cf b/tests/acceptance/05_processes/02_functions/findprocesses.cf new file mode 100644 index 0000000000..97647414a9 --- /dev/null +++ b/tests/acceptance/05_processes/02_functions/findprocesses.cf @@ -0,0 +1,37 @@ +########################################################### +# +# Test findprocesses() +# +# Note: On HP-UX this depends on the +# 00_hpux_ps_setup_serial.cf setup test having run first. +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle common test +{ + classes: + # this will be set only if we find our own exact PID + "descartes" expression => strcmp($(found_pids), $(this.promiser_pid)); + vars: + # find our own PID, using \b to make sure we match whole words + "found" data => findprocesses("\bfindprocesses\.cf\b"); + # pluck the "pid" field out into a list + "found_pids" data => mapdata("none", "$(found[$(this.k)][pid])", found); +} + +########################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("descartes", "", $(this.promise_filename)); +} diff --git a/tests/acceptance/05_processes/02_functions/processexists.cf b/tests/acceptance/05_processes/02_functions/processexists.cf new file mode 100644 index 0000000000..77a48b60c0 --- /dev/null +++ b/tests/acceptance/05_processes/02_functions/processexists.cf @@ -0,0 +1,31 @@ +########################################################### +# +# Test processexists() +# +# Note: On HP-UX this depends on the +# 00_hpux_ps_setup_serial.cf setup test having run first. +# +########################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; + version => "1.0"; +} + +########################################################### + +bundle common test +{ + classes: + "descartes" expression => processexists(".+agent.+"); +} + +########################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_expected("descartes", "", $(this.promise_filename)); +} diff --git a/tests/acceptance/05_processes/process_stop.cf b/tests/acceptance/05_processes/process_stop.cf new file mode 100644 index 0000000000..b4935cbd01 --- /dev/null +++ b/tests/acceptance/05_processes/process_stop.cf @@ -0,0 +1,150 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-4988" } + string => "Test some basic expectations when using process_stop in processes type promises"; + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + processes: + + # The policy file itself is not expected to be executable, this promise is + # expected to fail and be notkept. + + "." + process_stop => "$(this.promise_filename)", + handle => "process_stop_not_executable_expect_failed", + classes => explicit_results( "namespace", "$(this.handle)_is" ); + + # G.true is expected to return true, and the promise is expected to be + # repaired. Note: At the time of authorship there is no validation that + # the selected pids were killed by process_stop. We are only using the + # return code. + + "." + process_stop => "$(G.true)", + handle => "process_stop_return_zero_expect_repaired", + classes => explicit_results( "namespace", "$(this.handle)_is" ); + + # G.false is expected to return false, and the promise is expected to be + # repaired. Note: At the time of authorship there is no validation that + # the selected pids were killed by process_stop. We are only using the + # return code. + + "." + process_stop => "$(G.false)", + handle => "process_stop_return_nonzero_expect_failed", + classes => explicit_results( "namespace", "$(this.handle)_is" ); + + # G.echo is expected to return true, and the promise is expected to be + # repaired. Note: At the time of authorship there is no validation that + # the selected pids were killed by process_stop. We are only using the + # return code. + + "." + process_stop => "$(G.echo) pretend stop servicename", + handle => "process_stop_with_args_return_nonzero_expect_repaired", + classes => explicit_results( "namespace", "$(this.handle)_is" ); +} + +bundle agent check +{ + vars: + + "expected_classes" slist => { + "process_stop_with_args_return_nonzero_expect_repaired_is_repaired", + "process_stop_return_nonzero_expect_failed_is_failed", + "process_stop_return_zero_expect_repaired_is_repaired", + "process_stop_not_executable_expect_failed_is_failed", + }; + + + DEBUG:: + "found_classes" slist => classesmatching( "process_stop_.*"); + "difference" slist => difference( found_classes, expected_classes ); + + classes: + "ok" and => { @(expected_classes) }; + + reports: + DEBUG:: + "Found unexpected class: $(difference)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body classes explicit_results(scope, class_prefix) +# @brief Define classes prefixed with `class_prefix` and suffixed with +# appropriate outcomes: _kept, _repaired, _failed, _denied, _timeout +# +# @param scope The scope in which the class should be defined (`bundle` or `namespace`) +# @param class_prefix The prefix for the classes defined +# +# This body can be applied to any promise and sets global +# (`namespace`) or local (`bundle`) classes based on its outcome. For +# instance, with `class_prefix` set to `abc`: +# +# This body is a simpler, more consistent version of the body `results`. The key +# difference is that fewer classes are defined, and only for explicit outcomes +# that we can know. For example this body does not define "OK/not OK" outcome +# classes, since a promise can be both kept and failed at the same time. +# +# It's important to understand that promises may do multiple things, +# so a promise is not simply "OK" or "not OK." The best way to +# understand what will happen when your specific promises get this +# body is to test it in all the possible combinations. +# +# **Suffix Notes:** +# +# * `_kept` indicates some aspect of the promise was kept +# +# * `_repaired` indicates some aspect of the promise was repaired +# +# * `_failed` indicates the promise failed +# +# * `_denied` indicates the promise repair was denied +# +# * `_timeout` indicates the promise timed out +# +# **Example:** +# +# ```cf3 +# bundle agent example +# { +# commands: +# "/bin/true" +# classes => results("bundle", "my_class_prefix"); +# +# reports: +# my_class_prefix_kept:: +# "My promise was kept"; +# +# my_class_prefix_repaired:: +# "My promise was repaired"; +# } +# ``` +# +# **See also:** `scope`, `scoped_classes_generic`, `classes_generic` +{ + scope => "$(scope)"; + + promise_kept => { "$(class_prefix)_kept" }; + + promise_repaired => { "$(class_prefix)_repaired" }; + + repair_failed => { "$(class_prefix)_failed" }; + + repair_denied => { "$(class_prefix)_denied" }; + + repair_timeout => { "$(class_prefix)_timeout" }; +} + diff --git a/tests/acceptance/06_storage/01_local/001.cf b/tests/acceptance/06_storage/01_local/001.cf new file mode 100644 index 0000000000..e3bc8bca5a --- /dev/null +++ b/tests/acceptance/06_storage/01_local/001.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test simple storage promises - free space in range +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "sunos_5_11|sunos_5_10", + meta => { "redmine5234" }; + + vars: + windows:: + "path" string => "C:\\"; + !windows:: + "path" string => "/var"; + + storage: + "$(path)" + volume => test_volume, + classes => test_set_class("pass","fail"); +} + +body volume test_volume +{ + freespace => "1k"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "pass.!fail"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 31 diff --git a/tests/acceptance/06_storage/01_local/staging/002.cf b/tests/acceptance/06_storage/01_local/staging/002.cf new file mode 100644 index 0000000000..38047d6e53 --- /dev/null +++ b/tests/acceptance/06_storage/01_local/staging/002.cf @@ -0,0 +1,56 @@ +####################################################### +# +# Test simple storage promises - free space not in range +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + storage: + "/var" + volume => test_volume, + classes => test_set_class("fail","pass"); +} + +body volume test_volume +{ + freespace => "1000G"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "pass.!fail"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 31 diff --git a/tests/acceptance/07_packages/001.cf b/tests/acceptance/07_packages/001.cf new file mode 100644 index 0000000000..a0a1e55c04 --- /dev/null +++ b/tests/acceptance/07_packages/001.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Test add a package +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "G", "g", default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +bundle common g +{ + classes: + "mpm_declared" not => strcmp(getenv("MOCK_PACKAGE_MANAGER", "65535"), ""); + + vars: + mpm_declared:: + "pm" string => getenv("MOCK_PACKAGE_MANAGER", "65535"); + + !mpm_declared:: + "pm" string => "$(G.mock_package_manager)"; +} + +####################################################### + +bundle agent init +{ + commands: + "$(g.pm) --clear-installed"; + "$(g.pm) --clear-available"; + "$(g.pm) --populate-available imagisoft:1.0i:teapot"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "name" string => "imagisoft"; + "version" string => "1.0i"; + "arch" string => "teapot"; + + packages: + "$(name):$(version):$(arch)" + package_policy => "add", + package_method => mock, + classes => test_set_class("pass","fail"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(g.pm) --list-installed"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(g.pm) --add "; + package_update_command => "$(g.pm) --update "; + package_delete_command => "$(g.pm) --delete "; + package_verify_command => "$(g.pm) --verify "; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "pkgs" string => execresult("$(g.pm) --list-installed", "noshell"); + + classes: + "has_pkg" expression => regcmp("imagisoft:1\.0i:teapot", "$(pkgs)"); + + "ok" expression => "pass.!fail.has_pkg"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body classes successfully_executed(class) +{ + kept_returncodes => { "0" }; + promise_kept => { "$(class)" }; +} + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/002.cf b/tests/acceptance/07_packages/002.cf new file mode 100644 index 0000000000..983f5eb156 --- /dev/null +++ b/tests/acceptance/07_packages/002.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Test add a package using a "default" architecture +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "G", "g", default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +bundle common g +{ + classes: + "mpm_declared" not => strcmp(getenv("MOCK_PACKAGE_MANAGER", "65535"), ""); + + vars: + mpm_declared:: + "pm" string => getenv("MOCK_PACKAGE_MANAGER", "65535"); + + !mpm_declared:: + "pm" string => "$(G.mock_package_manager)"; +} + +####################################################### + +bundle agent init +{ + commands: + "$(g.pm) --clear-installed"; + "$(g.pm) --clear-available"; + "$(g.pm) --populate-available imagisoft:1.0i:x666"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "name" string => "imagisoft"; + "version" string => "1.0i"; + + packages: + "$(name):$(version)" + package_policy => "add", + package_method => mock, + classes => test_set_class("pass","fail"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(g.pm) --list-installed"; + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(g.pm) --add "; + package_update_command => "$(g.pm) --update "; + package_delete_command => "$(g.pm) --delete "; + package_verify_command => "$(g.pm) --verify "; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "pkgs" string => execresult("$(g.pm) --list-installed", "noshell"); + + classes: + "has_pkg" expression => regcmp("imagisoft:1\.0i:x666", "$(pkgs)"); + + "ok" expression => "pass.!fail.has_pkg"; + + reports: + DEBUG.pass:: + "pass"; + DEBUG.has_pkg:: + "has_pkg"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body classes successfully_executed(class) +{ + kept_returncodes => { "0" }; + promise_kept => { "$(class)" }; +} + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/003.cf b/tests/acceptance/07_packages/003.cf new file mode 100644 index 0000000000..1497178bdb --- /dev/null +++ b/tests/acceptance/07_packages/003.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Test adding a package using separate package_version attribute +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "G", "g", default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +bundle common g +{ + classes: + "mpm_declared" not => strcmp(getenv("MOCK_PACKAGE_MANAGER", "65535"), ""); + + vars: + mpm_declared:: + "pm" string => getenv("MOCK_PACKAGE_MANAGER", "65535"); + + !mpm_declared:: + "pm" string => "$(G.mock_package_manager)"; +} + +####################################################### + +bundle agent init +{ + commands: + "$(g.pm) --clear-installed"; + "$(g.pm) --clear-available"; + "$(g.pm) --populate-available imagisoft:1.0i:x666"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "name" string => "imagisoft"; + + packages: + "$(name)" + package_policy => "add", + package_method => mock, + package_version => "1.0i", + classes => test_set_class("pass","fail"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(g.pm) --list-installed"; + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(g.pm) --add "; + package_update_command => "$(g.pm) --update "; + package_delete_command => "$(g.pm) --delete "; + package_verify_command => "$(g.pm) --verify "; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "pkgs" string => execresult("$(g.pm) --list-installed", "noshell"); + + classes: + "has_pkg" expression => regcmp("imagisoft:1\.0i:x666", "$(pkgs)"); + + "ok" expression => "pass.!fail.has_pkg"; + + reports: + DEBUG.pass:: + "pass"; + DEBUG.has_pkg:: + "has_pkg"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body classes successfully_executed(class) +{ + kept_returncodes => { "0" }; + promise_kept => { "$(class)" }; +} + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/004.cf b/tests/acceptance/07_packages/004.cf new file mode 100644 index 0000000000..4baf261f30 --- /dev/null +++ b/tests/acceptance/07_packages/004.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Test adding *second* package +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "G", "g", default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +bundle common g +{ + classes: + "mpm_declared" not => strcmp(getenv("MOCK_PACKAGE_MANAGER", "65535"), ""); + + vars: + mpm_declared:: + "pm" string => getenv("MOCK_PACKAGE_MANAGER", "65535"); + + !mpm_declared:: + "pm" string => "$(G.mock_package_manager)"; +} + +####################################################### + +bundle agent init +{ + commands: + "$(g.pm) --clear-installed"; + "$(g.pm) --clear-available"; + "$(g.pm) --populate-available imagisoft:1.0i:x666"; + "$(g.pm) --populate-available bluesky:3.1:x666"; + "$(g.pm) --add bluesky:*:*"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "name" string => "imagisoft"; + + packages: + "$(name)" + package_policy => "add", + package_method => mock, + package_version => "1.0i", + classes => test_set_class("pass","fail"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(g.pm) --list-installed"; + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(g.pm) --add "; + package_update_command => "$(g.pm) --update "; + package_delete_command => "$(g.pm) --delete "; + package_verify_command => "$(g.pm) --verify "; + +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "pkgs" string => execresult("$(g.pm) --list-installed", "noshell"); + + classes: + "has_pkg" expression => regcmp(".*imagisoft:1\.0i:x666.*", "$(pkgs)"); + + "ok" expression => "pass.!fail.has_pkg"; + + reports: + DEBUG.pass:: + "pass"; + DEBUG.has_pkg:: + "has_pkg"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body classes successfully_executed(class) +{ + kept_returncodes => { "0" }; + promise_kept => { "$(class)" }; +} + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/005.cf b/tests/acceptance/07_packages/005.cf new file mode 100644 index 0000000000..9147b167d2 --- /dev/null +++ b/tests/acceptance/07_packages/005.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Test trying to actually specify versions of packages +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "G", "g", default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +bundle common g +{ + classes: + "mpm_declared" not => strcmp(getenv("MOCK_PACKAGE_MANAGER", "65535"), ""); + + vars: + mpm_declared:: + "pm" string => getenv("MOCK_PACKAGE_MANAGER", "65535"); + + !mpm_declared:: + "pm" string => "$(G.mock_package_manager)"; +} + +####################################################### + +bundle agent init +{ + commands: + "$(g.pm) --clear-installed"; + "$(g.pm) --clear-available"; + "$(g.pm) --populate-available imagisoft:1.0i:x666"; + "$(g.pm) --populate-available bluesky:3.1:x666"; + "$(g.pm) --add bluesky:*:*"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "name" string => "imagisoft"; + + packages: + "$(name)" + package_policy => "add", + package_method => mock, + package_version => "1.0i", + classes => test_set_class("pass","fail"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(g.pm) --list-installed"; + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(g.pm) --add "; + package_update_command => "$(g.pm) --update "; + package_delete_command => "$(g.pm) --delete "; + package_verify_command => "$(g.pm) --verify "; + + package_name_convention => "$(name):$(version):$(arch)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "pkgs" string => execresult("$(g.pm) --list-installed", "noshell"); + + classes: + "has_pkg" expression => regcmp(".*imagisoft:1\.0i:x666.*", "$(pkgs)"); + + "ok" expression => "pass.!fail.has_pkg"; + + reports: + DEBUG.pass:: + "pass"; + DEBUG.has_pkg:: + "has_pkg"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body classes successfully_executed(class) +{ + kept_returncodes => { "0" }; + promise_kept => { "$(class)" }; +} + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/006.cf b/tests/acceptance/07_packages/006.cf new file mode 100644 index 0000000000..8d171f03fc --- /dev/null +++ b/tests/acceptance/07_packages/006.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Test trying to avoid parsing promiser +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "G", "g", default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +bundle common g +{ + classes: + "mpm_declared" not => strcmp(getenv("MOCK_PACKAGE_MANAGER", "65535"), ""); + + vars: + mpm_declared:: + "pm" string => getenv("MOCK_PACKAGE_MANAGER", "65535"); + + !mpm_declared:: + "pm" string => "$(G.mock_package_manager)"; +} + +####################################################### + +bundle agent init +{ + commands: + "$(g.pm) --clear-installed"; + "$(g.pm) --clear-available"; + "$(g.pm) --populate-available imagisoft:1.0i:x666"; + "$(g.pm) --populate-available bluesky:3.1:x666"; + "$(g.pm) --add bluesky:*:*"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "name" string => "imagisoft"; + + packages: + "$(name)" + package_policy => "add", + package_method => mock, + package_version => "1.0i", + classes => test_set_class("pass","fail"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(g.pm) --list-installed"; + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(g.pm) --add "; + package_update_command => "$(g.pm) --update "; + package_delete_command => "$(g.pm) --delete "; + package_verify_command => "$(g.pm) --verify "; + + package_name_convention => "$(name):$(version):$(arch)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "pkgs" string => execresult("$(g.pm) --list-installed", "noshell"); + + classes: + "has_pkg" expression => regcmp(".*imagisoft:1\.0i:x666.*", "$(pkgs)"); + + "ok" expression => "pass.!fail.has_pkg"; + + reports: + DEBUG.pass:: + "pass"; + DEBUG.has_pkg:: + "has_pkg"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body classes successfully_executed(class) +{ + kept_returncodes => { "0" }; + promise_kept => { "$(class)" }; +} + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/800.cf b/tests/acceptance/07_packages/800.cf new file mode 100644 index 0000000000..a3e29e608a --- /dev/null +++ b/tests/acceptance/07_packages/800.cf @@ -0,0 +1,65 @@ +####################################################### +# Test that classes are set on individual installation (Mantis #829) +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + "a:b:c" + package_policy => "add", + package_method => mock, + if => "!a", + classes => ok("a"); + + "d:e:f" + package_policy => "add", + package_method => mock, + if => "!d", + classes => ok("d"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(G.true)"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes ok(classname) +{ + promise_repaired => { "$(classname)" }; + promise_kept => { "$(classname)" }; +} + +bundle agent check +{ + reports: + a.d:: + "$(this.promise_filename) Pass"; + !a|!d:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/900.cf b/tests/acceptance/07_packages/900.cf new file mode 100644 index 0000000000..ad7ea787c8 --- /dev/null +++ b/tests/acceptance/07_packages/900.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test no-op works +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "a:b:c" + package_policy => "add", + package_method => mock; +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(G.true)"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +####################################################### + +bundle agent check +{ + reports: + cfengine_3:: + "$(this.promise_filename) Pass"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/901.cf b/tests/acceptance/07_packages/901.cf new file mode 100644 index 0000000000..ab364ad562 --- /dev/null +++ b/tests/acceptance/07_packages/901.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test missing version regex fails +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + # Windows has its own mechanism to list packages, and doesn't use the package body. + "test_skip_unsupported" string => "windows"; + + packages: + "a:b:c" + package_policy => "add", + package_method => mock, + classes => test_set_class("fail","ok"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(G.true)"; + + package_list_name_regex => "^[^:]*"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok.!fail:: + "$(this.promise_filename) Pass"; + !ok|fail:: + "$(this.promise_filename) FAIL"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/902.cf b/tests/acceptance/07_packages/902.cf new file mode 100644 index 0000000000..d64550fd1a --- /dev/null +++ b/tests/acceptance/07_packages/902.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test missing name regex fails +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + # Windows has its own mechanism to list packages, and doesn't use the package body. + "test_skip_unsupported" string => "windows"; + + packages: + "a:b:c" + package_policy => "add", + package_method => mock, + classes => test_set_class("fail","ok"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(G.true)"; + + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok.!fail:: + "$(this.promise_filename) Pass"; + !ok|fail:: + "$(this.promise_filename) FAIL"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/903.cf b/tests/acceptance/07_packages/903.cf new file mode 100644 index 0000000000..058203b1ef --- /dev/null +++ b/tests/acceptance/07_packages/903.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test missing package listing fails +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + # Windows has its own mechanism to list packages, and doesn't use the package body. + "test_skip_unsupported" string => "windows"; + + packages: + "a:b:c" + package_policy => "add", + package_method => mock, + classes => test_set_class("fail","ok"); +} + +body package_method mock +{ + package_changes => "individual"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok.!fail:: + "$(this.promise_filename) Pass"; + !ok|fail:: + "$(this.promise_filename) FAIL"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/904.cf b/tests/acceptance/07_packages/904.cf new file mode 100644 index 0000000000..7509106bf8 --- /dev/null +++ b/tests/acceptance/07_packages/904.cf @@ -0,0 +1,68 @@ +####################################################### +# +# Test missing basic package commands +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "a:b:c" + package_policy => "add", + package_method => mock, + classes => test_set_class("fail","ok"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(G.true)"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok.!fail:: + "$(this.promise_filename) Pass"; + !ok|fail:: + "$(this.promise_filename) FAIL"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/905.cf b/tests/acceptance/07_packages/905.cf new file mode 100644 index 0000000000..c537e9de79 --- /dev/null +++ b/tests/acceptance/07_packages/905.cf @@ -0,0 +1,68 @@ +####################################################### +# +# Test missing "installed" regex +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "a:b:c" + package_policy => "add", + package_method => mock, + classes => test_set_class("fail","ok"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "$(G.true)"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok.!fail:: + "$(this.promise_filename) Pass"; + !ok|fail:: + "$(this.promise_filename) FAIL"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/906.cf b/tests/acceptance/07_packages/906.cf new file mode 100644 index 0000000000..1a54b37abc --- /dev/null +++ b/tests/acceptance/07_packages/906.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test too long repository directory +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "a:b:c" + package_policy => "add", + package_method => mock, + classes => test_set_class("fail","ok"); +} + +body package_method mock +{ + package_changes => "individual"; + package_file_repositories => { "/tmp", "/fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/" }; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok.!fail:: + "$(this.promise_filename) Pass"; + !ok|fail:: + "$(this.promise_filename) FAIL"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/907.cf b/tests/acceptance/07_packages/907.cf new file mode 100644 index 0000000000..8d83361ced --- /dev/null +++ b/tests/acceptance/07_packages/907.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test no-op with with "package_repositories" works +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "a:b:c" + package_policy => "add", + package_method => mock; +} + +body package_method mock +{ + package_changes => "individual"; + package_file_repositories => { "/tmp" }; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +####################################################### + +bundle agent check +{ + reports: + cfengine_3:: + "$(this.promise_filename) Pass"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/908.cf b/tests/acceptance/07_packages/908.cf new file mode 100644 index 0000000000..93fc806120 --- /dev/null +++ b/tests/acceptance/07_packages/908.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test broken list_command +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "a:b:c" + package_policy => "add", + package_method => mock, + classes => test_set_class("fail","ok"); +} + +body package_method mock +{ + package_changes => "individual"; + package_list_command => "/nonexisting"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok.!fail:: + "$(this.promise_filename) Pass"; + !ok|fail:: + "$(this.promise_filename) FAIL"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/contradicting-bodies-and-promises.cf b/tests/acceptance/07_packages/contradicting-bodies-and-promises.cf new file mode 100644 index 0000000000..39b11d839d --- /dev/null +++ b/tests/acceptance/07_packages/contradicting-bodies-and-promises.cf @@ -0,0 +1,155 @@ +####################################################### +# Test that specifying two incompatible ways to get package version is detected +# and reported +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "bash-1.0-amd64" + package_policy => "add", + package_method => mock, + package_version => "2.0", + package_architectures => { "amd64" }, + classes => test_set_class("overlapping_version_arch_regex_succ", + "overlapping_version_arch_regex_fail"); + + "bash-1.0-i386" + package_policy => "add", + package_method => mock_no_name_regex_but_version_regex, + classes => test_set_class("no_name_regex_version_succ", "no_name_regex_version_fail"); + + "bash-1.0-i386" + package_policy => "add", + package_method => mock_no_name_regex_but_arch_regex, + classes => test_set_class("no_name_regex_arch_succ", "no_name_regex_arch_fail"); + + "bash-1.0-sparc" + package_policy => "add", + package_method => mock_overlapping_version_regex, + package_version => "2.0", + classes => test_set_class("overlapping_version_regex_succ", + "overlapping_version_regex_fail"); +} + +body package_method mock_overlapping_version_regex +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.true) $(this.name)-$(this.version):$(this.arch)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body package_method mock_no_name_regex_but_arch_regex +{ + package_changes => "individual"; + + package_arch_regex => ".*"; + # but no package_name_regex! + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body package_method mock_no_name_regex_but_version_regex +{ + package_changes => "individual"; + + package_version_regex => ".*"; + # but no package_name_regex! + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body package_method mock +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!no_name_regex_version_succ", "no_name_regex_version_fail", + "!no_name_regex_arch_succ", "no_name_regex_arch_fail", + "!overlapping_version_arch_regex_succ", "overlapping_version_arch_regex_fail", + "!overlapping_version_regex_succ", "overlapping_version_regex_fail"}; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/custom-version-compare.cf b/tests/acceptance/07_packages/custom-version-compare.cf new file mode 100644 index 0000000000..ef717b50bc --- /dev/null +++ b/tests/acceptance/07_packages/custom-version-compare.cf @@ -0,0 +1,109 @@ +####################################################### +# Test custom version comparator +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + # Windows has its own mechanism to list packages, and doesn't use the package body. + "test_skip_needs_work" string => "windows"; + + packages: + # Test that overriding 'equal' works. Installed is 01, requested is 1. Overridden eq compares numeric values. + "bash-1-amd64" + package_policy => "addupdate", + package_method => mock_zeroone_installed, + classes => test_set_class("custom_eq_succ", "custom_eq_fail"); + + "bash-2-amd64" + package_policy => "addupdate", + package_method => mock_zeroone_installed_can_install, + classes => test_set_class("custom_eq_2_succ", "custom_eq_2_fail"); +} + +body package_method mock_zeroone_installed +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-01-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; + + package_version_less_command => "test $(v1) -lt $(v2)"; + package_version_equal_command => "test $(v1) -eq $(v2)"; +} + +body package_method mock_zeroone_installed_can_install +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-01-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; + + package_version_less_command => "test $(v1) -lt $(v2)"; + package_version_equal_command => "test $(v1) -eq $(v2)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "custom_eq_succ", "!custom_eq_fail", + "custom_eq_2_succ", "!custom_eq_2_fail" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/default-list-architectures-different.cf b/tests/acceptance/07_packages/default-list-architectures-different.cf new file mode 100644 index 0000000000..dd42c96b26 --- /dev/null +++ b/tests/acceptance/07_packages/default-list-architectures-different.cf @@ -0,0 +1,113 @@ +# +# Test matching packages with explicit default architecture set and the same +# architecture in list of installed packages. Should work similar to the case +# there are no architectures at all. +# +# List of installed packages contains a single package with the default +# architecture specified. +# + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + + # + # Test that package is matched by a promise with no package_architectures + # constraint, as it means "any architecture" + # + + "foobar" + package_version => "1", + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_1"); + + # + # Test that specifying default architecture does not match the + # + + "foobar" + package_version => "1", + package_architectures => { "setun" }, + package_policy => "addupdate", + package_method => mock, + classes => fail("ok_2"); + + # + # Test that specifying proper architecture explicitly matches the package. + # + + "foobar" + package_version => "1", + package_architectures => { "besm6" }, + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_3"); + + # + # Test that specifying wrong architecture explicitly does not match the package. + # + + "foobar" + package_version => "1", + package_architectures => { "aldan" }, + package_policy => "addupdate", + package_method => mock, + classes => fail("ok_4"); +} + +body package_method mock +{ + package_changes => "individual"; + + package_default_arch_command => "$(G.printf) 'setun'"; + + package_list_command => "$(G.printf) 'foobar-1-besm6'"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-([\S]+)"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body classes kept(classname) +{ + promise_kept => { "$(classname)" }; +} + +body classes fail(classname) +{ + repair_failed => { "$(classname)" }; +} + +bundle agent check +{ + classes: + "ok" and => { "ok_1", "ok_2", "ok_3", "ok_4"}; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/default-list-architectures-same.cf b/tests/acceptance/07_packages/default-list-architectures-same.cf new file mode 100644 index 0000000000..af6a5c6092 --- /dev/null +++ b/tests/acceptance/07_packages/default-list-architectures-same.cf @@ -0,0 +1,102 @@ +# +# Test matching packages with explicit default architecture set and the same +# architecture in list of installed packages. Should work similar to the case +# there are no architectures at all. +# +# List of installed packages contains a single package with the default +# architecture specified. +# + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + + # + # Test that package is matched by a promise with no package_architectures + # constraint, as it means "any architecture" + # + + "foobar" + package_version => "1", + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_1"); + + # + # Test that specifying proper architecture explicitly matches the package. + # + + "foobar" + package_version => "1", + package_architectures => { "setun" }, + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_2"); + + # + # Test that specifying wrong architecture explicitly does not match the package. + # + + "foobar" + package_version => "1", + package_architectures => { "aldan" }, + package_policy => "addupdate", + package_method => mock, + classes => fail("ok_3"); +} + +body package_method mock +{ + package_changes => "individual"; + + package_default_arch_command => "$(G.printf) 'setun'"; + + package_list_command => "$(G.printf) 'foobar-1-setun'"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-([\S]+)"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body classes kept(classname) +{ + promise_kept => { "$(classname)" }; +} + +body classes fail(classname) +{ + repair_failed => { "$(classname)" }; +} + +bundle agent check +{ + classes: + "ok" and => { "ok_1", "ok_2", "ok_3" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/dependency-checks-package_delete_command.cf b/tests/acceptance/07_packages/dependency-checks-package_delete_command.cf new file mode 100644 index 0000000000..7f2f077fae --- /dev/null +++ b/tests/acceptance/07_packages/dependency-checks-package_delete_command.cf @@ -0,0 +1,78 @@ +####################################################### +# Test for presence of "main" attribute, given the presence of "dependent" +# attributes, which do not make sense without the "main" attribute. +# +# main attribute: package_delete_command +# dependent attributes: +# package_delete_convention +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + "bash-1.0-amd64" + package_policy => "add", + package_method => mock_no_delete_command_delete_convention, + classes => test_set_class("no_delete_command_delete_convention_succ", + "no_delete_command_delete_convention_fail"); +} + +body package_method mock_no_delete_command_delete_convention +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + # package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; + + package_delete_convention => "$(name)"; + +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!no_delete_command_delete_convention_succ", "no_delete_command_delete_convention_fail", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/dependency-checks-package_list_command.cf b/tests/acceptance/07_packages/dependency-checks-package_list_command.cf new file mode 100644 index 0000000000..e7ab3be084 --- /dev/null +++ b/tests/acceptance/07_packages/dependency-checks-package_list_command.cf @@ -0,0 +1,180 @@ +####################################################### +# Test for presence of "main" attribute, given the presence of "dependent" +# attributes, which do not make sense without the "main" attribute. +# +# main attribute: package_list_command +# dependent attributes: +# package_installed_regex +# package_list_arch_regex +# package_list_name_regex +# package_list_version_regex +# package_multiline_start +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + meta: + # Windows has its own mechanism to list packages, and doesn't use the package body. + "test_skip_unsupported" string => "windows"; + + packages: + "bash-1.0-amd64" + package_policy => "add", + package_method => mock_no_list_command_package_installed_regex, + classes => test_set_class("no_list_command_package_installed_regex_succ", + "no_list_command_package_installed_regex_fail"); + + "bash-2.0-amd64" + package_policy => "add", + package_method => mock_no_list_command_package_arch_regex, + classes => test_set_class("no_list_command_package_arch_regex_succ", + "no_list_command_package_arch_regex_fail"); + + "bash-1.0-i386" + package_policy => "add", + package_method => mock_no_list_command_package_name_regex, + classes => test_set_class("no_list_command_package_name_regex_succ", + "no_list_command_package_name_regex_fail"); + + "bash-1.0-sparc" + package_policy => "add", + package_method => mock_no_list_command_package_version_regex, + classes => test_set_class("no_list_command_package_version_regex_succ", + "no_list_command_package_version_regex_fail"); +} + +body package_method mock_no_list_command_package_version_regex +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_file_repositories => { "/package/repos1", "package/repos2" }; + # but no package_list_command! + # package_installed_regex => ".*"; + # package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + # package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + # package_multiline_start => + + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body package_method mock_no_list_command_package_name_regex +{ + package_changes => "individual"; + + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_file_repositories => { "/package/repos1", "package/repos2" }; + # but no package_list_command! + # package_installed_regex => ".*"; + # package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + package_list_name_regex => "^([^-]+)"; + # package_list_version_regex => "^[^-]+-([^-]+)"; + # package_multiline_start => + + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body package_method mock_no_list_command_package_arch_regex +{ + package_changes => "individual"; + + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_file_repositories => { "/package/repos1", "package/repos2" }; + # but no package_list_command! + # package_installed_regex => ".*"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + # package_list_name_regex => "^([^-]+)"; + # package_list_version_regex => "^[^-]+-([^-]+)"; + # package_multiline_start => + + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body package_method mock_no_list_command_package_installed_regex +{ + package_changes => "individual"; + + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_file_repositories => { "/package/repos1", "package/repos2" }; + # but no package_list_command! + package_installed_regex => ".*"; + # package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + # package_list_name_regex => "^([^-]+)"; + # package_list_version_regex => "^[^-]+-([^-]+)"; + # package_multiline_start => + + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!no_list_command_package_installed_regex_succ", "no_list_command_package_installed_regex_fail", + "!no_list_command_package_arch_regex_succ", "no_list_command_package_arch_regex_fail", + "!no_list_command_package_name_regex_succ", "no_list_command_package_name_regex_fail", + "!no_list_command_package_version_regex_succ", "no_list_command_package_version_regex_fail", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/dependency-checks-package_patch_command.cf b/tests/acceptance/07_packages/dependency-checks-package_patch_command.cf new file mode 100644 index 0000000000..f35025adbc --- /dev/null +++ b/tests/acceptance/07_packages/dependency-checks-package_patch_command.cf @@ -0,0 +1,137 @@ +####################################################### +# Test for presence of "main" attribute, given the presence of "dependent" +# attributes, which do not make sense without the "main" attribute. +# +# main attribute: package_patch_command +# dependent attributes: +# package_patch_arch_regex +# package_patch_name_regex +# package_patch_version_regex +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + "bash-1.0-amd64" + package_policy => "add", + package_method => mock_no_patch_command_patch_arch_regex, + classes => test_set_class("no_patch_command_patch_arch_regex_succ", + "no_patch_command_patch_arch_regex_fail"); + + "bash-1.0-i386" + package_policy => "add", + package_method => mock_no_patch_command_patch_name_regex, + classes => test_set_class("no_patch_command_patch_name_regex_succ", + "no_patch_command_patch_name_regex_fail"); + + "bash-1.0-sparc" + package_policy => "add", + package_method => mock_no_patch_command_patch_version_regex, + classes => test_set_class("no_patch_command_patch_version_regex_succ", + "no_patch_command_patch_version_regex_fail"); +} + +body package_method mock_no_patch_command_patch_arch_regex +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; + + package_patch_arch_regex => ""; +} + +body package_method mock_no_patch_command_patch_name_regex +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; + + package_patch_name_regex => ""; +} + +body package_method mock_no_patch_command_patch_version_regex +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; + + package_patch_version_regex => ""; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!no_patch_command_patch_arch_regex_succ", "no_patch_command_patch_arch_regex_fail", + "!no_patch_command_patch_name_regex_succ", "no_patch_command_patch_name_regex_fail", + "!no_patch_command_patch_version_regex_succ", "no_patch_command_patch_version_regex_fail", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/dependency-checks-package_patch_list_command.cf b/tests/acceptance/07_packages/dependency-checks-package_patch_list_command.cf new file mode 100644 index 0000000000..b37689cc4f --- /dev/null +++ b/tests/acceptance/07_packages/dependency-checks-package_patch_list_command.cf @@ -0,0 +1,77 @@ +####################################################### +# Test for presence of "main" attribute, given the presence of "dependent" +# attributes, which do not make sense without the "main" attribute. +# +# main attribute: package_patch_list_command +# dependent attributes: +# package_patch_installed_regex +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + "bash-1.0-amd64" + package_policy => "add", + package_method => mock_no_patch_list_command_patch_installed_regex, + classes => test_set_class("no_patch_list_command_patch_installed_regex_succ", + "no_patch_list_command_patch_installed_regex_fail"); +} + +body package_method mock_no_patch_list_command_patch_installed_regex +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; + + package_patch_installed_regex => ""; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!no_patch_list_command_patch_installed_regex_succ", "no_patch_list_command_patch_installed_regex_fail", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/dependency-checks-package_verify_command.cf b/tests/acceptance/07_packages/dependency-checks-package_verify_command.cf new file mode 100644 index 0000000000..7ce789d662 --- /dev/null +++ b/tests/acceptance/07_packages/dependency-checks-package_verify_command.cf @@ -0,0 +1,111 @@ +####################################################### +# Test for presence of "main" attribute, given the presence of "dependent" +# attributes, which do not make sense without the "main" attribute. +# +# main attribute: package_verify_command +# dependent attributes: +# package_noverify_regex +# package_noverify_returncode +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "bash-1.0-amd64" + package_policy => "add", + package_method => mock_no_verify_command_noverify_regex, + classes => test_set_class("no_verify_command_noverify_regex_succ", + "no_verify_command_noverify_regex_fail"); + + "bash-1.0-i386" + package_policy => "add", + package_method => mock_no_verify_command_noverify_returncode, + classes => test_set_class("no_verify_command_noverify_returncode_succ", + "no_verify_command_noverify_returncode_fail"); +} + +body package_method mock_no_verify_command_noverify_regex +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + # package_verify_command => "$(G.true)"; + + package_noverify_regex => "."; +} + +body package_method mock_no_verify_command_noverify_returncode +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "$(G.printf) 'bash-1.0-amd64'"; + package_installed_regex => ".*"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + # package_verify_command => "$(G.true)"; + + package_noverify_returncode => "-1"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!no_verify_command_noverify_regex_succ", "no_verify_command_noverify_regex_fail", + "!no_verify_command_noverify_returncode_succ", "no_verify_command_noverify_returncode_fail", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/dry-run-install.cf b/tests/acceptance/07_packages/dry-run-install.cf new file mode 100644 index 0000000000..6be330f4a3 --- /dev/null +++ b/tests/acceptance/07_packages/dry-run-install.cf @@ -0,0 +1,36 @@ +# Test that if the action => 'warn' is specified, no packages are installed. + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kv -f $(this.promise_filename).sub | $(G.grep) should-not-be-called", "useshell"); +} + +bundle agent check +{ + classes: + "ok" not => regcmp(".*should-not-be-called.*", "$(test.subout)"); + + reports: + DEBUG:: + "$(test.subout)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/dry-run-install.cf.sub b/tests/acceptance/07_packages/dry-run-install.cf.sub new file mode 100644 index 0000000000..5b8adf9433 --- /dev/null +++ b/tests/acceptance/07_packages/dry-run-install.cf.sub @@ -0,0 +1,41 @@ +# Test that if the action => 'warn' is specified, no packages are installed. + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "test" }; + version => "1.0"; +} + +bundle agent test +{ +packages: + "bash" + package_policy => "add", + package_method => mock_do_not_install_pkg, + package_select => ">=", + package_version => "1.0", + action => dryrun; +} + +body action dryrun +{ + action_policy => "warn"; +} + +body package_method mock_do_not_install_pkg +{ + package_changes => "individual"; + + package_list_command => "$(G.true)"; + + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + package_installed_regex => ".*"; + + package_add_command => "/bin/should-not-be-called"; + package_update_command => "/bin/should-not-be-called"; + package_delete_command => "/bin/should-not-be-called"; + package_verify_command => "/bin/should-not-be-called"; +} diff --git a/tests/acceptance/07_packages/has-default-no-list-architectures.cf b/tests/acceptance/07_packages/has-default-no-list-architectures.cf new file mode 100644 index 0000000000..c137eeefc2 --- /dev/null +++ b/tests/acceptance/07_packages/has-default-no-list-architectures.cf @@ -0,0 +1,101 @@ +# +# Test matching packages with explicit default architecture set and no +# architecture in list of installed packages. Should work similar to the case +# there are no architectures at all. +# +# List of installed packages contains a single package with no architecture +# specified. +# + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + + # + # Test that package is matched by a promise with no package_architectures + # constraint, as it means "any architecture" + # + + "foobar" + package_version => "1", + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_1"); + + # + # Test that specifying proper architecture explicitly matches the package. + # + + "foobar" + package_version => "1", + package_architectures => { "setun" }, + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_2"); + + # + # Test that specifying wrong architecture explicitly does not match the package. + # + + "foobar" + package_version => "1", + package_architectures => { "aldan" }, + package_policy => "addupdate", + package_method => mock, + classes => fail("ok_3"); +} + +body package_method mock +{ + package_changes => "individual"; + + package_default_arch_command => "$(G.printf) 'setun'"; + + package_list_command => "$(G.printf) 'foobar-1'"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([\S+])"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body classes kept(classname) +{ + promise_kept => { "$(classname)" }; +} + +body classes fail(classname) +{ + repair_failed => { "$(classname)" }; +} + +bundle agent check +{ + classes: + "ok" and => { "ok_1", "ok_2", "ok_3" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/list-architecture.cf b/tests/acceptance/07_packages/list-architecture.cf new file mode 100644 index 0000000000..03e5cd2af9 --- /dev/null +++ b/tests/acceptance/07_packages/list-architecture.cf @@ -0,0 +1,98 @@ +# +# Test matching packages with no explicit default architecture set, but with +# architecture reported by the list of installed packages. +# +# List of installed packages contains a single package with explicit architecture set. +# + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + + # + # Test that package with explicit architecture is matched by promise with no + # package_architectures constraint, as no constraint means "any architecture". + # + + "foobar" + package_version => "1", + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_1"); + + # + # Test that specifying architecture explicitly matches. + # + + "foobar" + package_version => "1", + package_architectures => { "besm6" }, + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_2"); + + # + # Test that specifying wrong architecture does not match. + # + + "foobar" + package_version => "1", + package_architectures => { "aldan" }, + package_policy => "addupdate", + package_method => mock, + classes => fail("ok_3"); +} + +body package_method mock +{ + package_changes => "individual"; + + package_list_command => "$(G.printf) 'foobar-1-besm6'"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-([\S]+)"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body classes kept(classname) +{ + promise_kept => { "$(classname)" }; +} + +body classes fail(classname) +{ + repair_failed => { "$(classname)" }; +} + +bundle agent check +{ + classes: + "ok" and => { "ok_1", "ok_2", "ok_3" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/no-architectures.cf b/tests/acceptance/07_packages/no-architectures.cf new file mode 100644 index 0000000000..222bbe8057 --- /dev/null +++ b/tests/acceptance/07_packages/no-architectures.cf @@ -0,0 +1,88 @@ +# +# Test matching packages with no explicit default architecture set and no +# architectures in list of installed packages. +# +# List of installed packages contains a single package with no architecture +# specified. +# + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + packages: + + # + # Test that package is matched by a promise with no package_architectures + # constraint. + # + + "foobar" + package_version => "1", + package_policy => "addupdate", + package_method => mock, + classes => kept("ok_1"); + + # + # Test that specifying architecture explicitly does not match the package with + # default architecture. + # + + "foobar" + package_version => "1", + package_architectures => { "aldan" }, + package_policy => "addupdate", + package_method => mock, + classes => fail("ok_2"); +} + +body package_method mock +{ + package_changes => "individual"; + + package_list_command => "$(G.printf) 'foobar-1'"; + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([\S+])"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.false)"; + package_verify_command => "$(G.false)"; +} + +body classes kept(classname) +{ + promise_kept => { "$(classname)" }; +} + +body classes fail(classname) +{ + repair_failed => { "$(classname)" }; +} + +bundle agent check +{ + classes: + "ok" and => { "ok_1", "ok_2" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/package_commands_useshell.cf b/tests/acceptance/07_packages/package_commands_useshell.cf new file mode 100644 index 0000000000..265a1ad620 --- /dev/null +++ b/tests/acceptance/07_packages/package_commands_useshell.cf @@ -0,0 +1,141 @@ +####################################################### +# +# Test package_commands_useshell works +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + meta: + # Windows has its own mechanism to list packages, and doesn't use the package body. + "test_skip_needs_work" string => "windows"; + + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "bash-1.0-amd64" + package_policy => "add", + package_method => mock_useshell_true, + classes => test_set_class("useshell_true_succ", + "useshell_true_fail"); + + "bash-1.0-i386" + package_policy => "add", + package_method => mock_useshell_false, + classes => test_set_class("useshell_false_succ", + "useshell_false_fail"); + + "bash-1.0-spark" + package_policy => "add", + package_method => mock_useshell_unset, + classes => test_set_class("useshell_unset_succ", + "useshell_unset_fail"); +} + +body package_method mock_useshell_true +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + + package_list_command => "if true; then true; else false; fi"; + + package_list_name_regex => ".*"; + package_list_version_regex => ".*"; + package_list_arch_regex => ".*"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; + + package_commands_useshell => "true"; +} + +body package_method mock_useshell_false +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "if true; then true; else false; fi"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; + + package_commands_useshell => "false"; +} + +body package_method mock_useshell_unset +{ + package_changes => "individual"; + + package_name_regex => "^([^-]+)"; + package_version_regex => "^[^-]+-([^-]+)"; + package_arch_regex => "^[^-]+-[^-]+-(.*)"; + + package_list_command => "echo 'a:b:c\n'"; + + package_list_name_regex => "^[^:]*"; + package_list_version_regex => ":(?<=:).*(?=:)"; + package_list_arch_regex => "[^:]\w+$"; + package_installed_regex => "^[^:]*"; + + package_add_command => "$(G.true)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.true)"; +} + +body classes test_set_class(ok_class,notok_class) +{ + promise_kept => { "$(ok_class)" }; + promise_repaired => { "$(ok_class)" }; + repair_failed => { "$(notok_class)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "useshell_true_succ", "!useshell_true_fail", + "!useshell_false_succ", "useshell_false_fail", + "useshell_unset_succ", "!useshell_unset_fail", + }; + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/package_module_interpreter.cf b/tests/acceptance/07_packages/package_module_interpreter.cf new file mode 100644 index 0000000000..86ef4c5259 --- /dev/null +++ b/tests/acceptance/07_packages/package_module_interpreter.cf @@ -0,0 +1,50 @@ +# Based on 00_basics/ifelapsed_and_expireafter/timed/package_lock.cf + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + files: + "$(sys.workdir)/modules/packages/." + create => "true"; + "$(sys.workdir)/modules/packages/test_module" + copy_from => local_cp("$(this.promise_filename).module"); +} + +body package_module test_module +{ + query_installed_ifelapsed => "60"; + query_updates_ifelapsed => "14400"; + default_options => { "$(G.testfile)" }; + interpreter => "$(G.sh)"; # /bin/sh; NOTE: no hashbang in the test_module script +} + +bundle agent test +{ + meta: + "description" + string => "Test that the interpreter for the package module script can be set", + meta => { "CFE-2880" }; + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + packages: + "first_pkg" + policy => "present", + package_module => test_module; + "second_pkg" + policy => "present", + package_module => test_module; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff($(G.testfile), + "$(this.promise_filename).expected", + $(this.promise_filename)); +} diff --git a/tests/acceptance/07_packages/package_module_interpreter.cf.expected b/tests/acceptance/07_packages/package_module_interpreter.cf.expected new file mode 100644 index 0000000000..835519d05b --- /dev/null +++ b/tests/acceptance/07_packages/package_module_interpreter.cf.expected @@ -0,0 +1,6 @@ +Name=first_pkg +Version=1.0 +Architecture=generic +Name=second_pkg +Version=1.0 +Architecture=generic diff --git a/tests/acceptance/07_packages/package_module_interpreter.cf.module b/tests/acceptance/07_packages/package_module_interpreter.cf.module new file mode 100644 index 0000000000..f05930bae2 --- /dev/null +++ b/tests/acceptance/07_packages/package_module_interpreter.cf.module @@ -0,0 +1,67 @@ +set -e + +remove_prefix() +{ + echo "$1" | sed "s/$2//" +} + +case "$1" in + supports-api-version) + echo 1 + ;; + get-package-data) + while read line; do + case "$line" in + File=*) + echo PackageType=repo + echo Name=`remove_prefix "${line}" "File="` + ;; + *) + true + ;; + esac + done + ;; + list-installed) + while read line; do + case "$line" in + options=*) + OUTPUT=`remove_prefix "${line}" "options="` + ;; + *) + exit 1 + ;; + esac + done + if [ -f "$OUTPUT" ]; then + cat "$OUTPUT" + fi + ;; + list-*) + # Drain input. + cat > /dev/null + ;; + repo-install) + while read line; do + case "$line" in + options=*) + OUTPUT=`remove_prefix "${line}" "options="` + ;; + Name=*) + NAME=`remove_prefix "${line}" "Name="` + ;; + *) + exit 1 + ;; + esac + done + echo "Name=$NAME" >> "$OUTPUT" + echo "Version=1.0" >> "$OUTPUT" + echo "Architecture=generic" >> "$OUTPUT" + ;; + *) + exit 1 + ;; +esac + +exit 0 diff --git a/tests/acceptance/07_packages/package_module_path.cf b/tests/acceptance/07_packages/package_module_path.cf new file mode 100644 index 0000000000..7590bd64fb --- /dev/null +++ b/tests/acceptance/07_packages/package_module_path.cf @@ -0,0 +1,51 @@ +# Based on 00_basics/ifelapsed_and_expireafter/timed/package_lock.cf + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + files: + "$(sys.workdir)/modules/packages/." + create => "true"; + "$(sys.workdir)/modules/packages/test_module_script.sh" + copy_from => local_cp("$(this.promise_filename).module"), + perms => m("ugo+x"); +} + +body package_module test_module +{ + query_installed_ifelapsed => "60"; + query_updates_ifelapsed => "14400"; + default_options => { "$(G.testfile)" }; + module_path => "$(sys.workdir)/modules/packages/test_module_script.sh"; +} + +bundle agent test +{ + meta: + "description" + string => "Test that the interpreter for the package module script can be set", + meta => { "CFE-2880" }; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + packages: + "first_pkg" + policy => "present", + package_module => test_module; + "second_pkg" + policy => "present", + package_module => test_module; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff($(G.testfile), + "$(this.promise_filename).expected", + $(this.promise_filename)); +} diff --git a/tests/acceptance/07_packages/package_module_path.cf.expected b/tests/acceptance/07_packages/package_module_path.cf.expected new file mode 100644 index 0000000000..835519d05b --- /dev/null +++ b/tests/acceptance/07_packages/package_module_path.cf.expected @@ -0,0 +1,6 @@ +Name=first_pkg +Version=1.0 +Architecture=generic +Name=second_pkg +Version=1.0 +Architecture=generic diff --git a/tests/acceptance/07_packages/package_module_path.cf.module b/tests/acceptance/07_packages/package_module_path.cf.module new file mode 100644 index 0000000000..4d185fe7f6 --- /dev/null +++ b/tests/acceptance/07_packages/package_module_path.cf.module @@ -0,0 +1,69 @@ +#!/bin/sh + +set -e + +remove_prefix() +{ + echo "$1" | sed "s/$2//" +} + +case "$1" in + supports-api-version) + echo 1 + ;; + get-package-data) + while read line; do + case "$line" in + File=*) + echo PackageType=repo + echo Name=`remove_prefix "${line}" "File="` + ;; + *) + true + ;; + esac + done + ;; + list-installed) + while read line; do + case "$line" in + options=*) + OUTPUT=`remove_prefix "${line}" "options="` + ;; + *) + exit 1 + ;; + esac + done + if [ -f "$OUTPUT" ]; then + cat "$OUTPUT" + fi + ;; + list-*) + # Drain input. + cat > /dev/null + ;; + repo-install) + while read line; do + case "$line" in + options=*) + OUTPUT=`remove_prefix "${line}" "options="` + ;; + Name=*) + NAME=`remove_prefix "${line}" "Name="` + ;; + *) + exit 1 + ;; + esac + done + echo "Name=$NAME" >> "$OUTPUT" + echo "Version=1.0" >> "$OUTPUT" + echo "Architecture=generic" >> "$OUTPUT" + ;; + *) + exit 1 + ;; +esac + +exit 0 diff --git a/tests/acceptance/07_packages/repositories.cf b/tests/acceptance/07_packages/repositories.cf new file mode 100644 index 0000000000..0691813510 --- /dev/null +++ b/tests/acceptance/07_packages/repositories.cf @@ -0,0 +1,55 @@ +# Tests that package upgrade works properly + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +bundle agent init +{ + vars: + "d" string => "$(const.dirsep)"; + # Due to shell rules, windows includes certain characters on certain commands, + # while Unix does not. + windows:: + "q" string => '"'; + "s" string => ' '; + !windows:: + "q" string => ''; + "s" string => ''; + + methods: + "make" usebundle => file_make("$(G.testfile).expected", + " +$(s)filerepo DELETE delete-exact-version-2.2.3.i386.rpm +$(s)filerepo ADD $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)install-greaterthan-version-2.2.3.i386.rpm$(q) +$(s)filerepo ADD $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)install-greaterorequal-version-2.2.3.i386.rpm$(q) +$(s)filerepo ADD $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)install-lessthan-version-2.2.3.i386.rpm$(q) +$(s)filerepo ADD $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)install-lessorequal-version-2.2.3.i386.rpm$(q) +$(s)filerepo ADD $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)install-exact-version-2.2.3.i386.rpm$(q) +$(s)filerepo UPDATE $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)update-to-greaterthan-version-already-greater-installed-2.2.3.i386.rpm$(q) +$(s)filerepo UPDATE $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)update-to-greaterthan-version-2.2.3.i386.rpm$(q) +$(s)filerepo UPDATE $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)update-to-greaterorequal-version-already-greater-installed-2.2.3.i386.rpm$(q) +$(s)filerepo UPDATE $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)update-to-greaterorequal-version-2.2.3.i386.rpm$(q) +$(s)filerepo UPDATE $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)update-to-lessthan-version-2.2.3.i386.rpm$(q) +$(s)filerepo UPDATE $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)update-to-lessorequal-version-2.2.3.i386.rpm$(q) +$(s)filerepo UPDATE $(q)$(G.cwd)$(d).$(d)07_packages$(d)test_repository$(d)update-to-exact-version-2.2.3.i386.rpm$(q)"); + +} + +bundle agent test +{ + methods: + "run" usebundle => dcs_runagent("$(this.promise_filename).sub"); +} + +bundle agent check +{ + methods: + "any" usebundle => sorted_check_diff("$(G.testfile)", "$(G.testfile).expected", "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/repositories.cf.sub b/tests/acceptance/07_packages/repositories.cf.sub new file mode 100644 index 0000000000..d68e64102a --- /dev/null +++ b/tests/acceptance/07_packages/repositories.cf.sub @@ -0,0 +1,279 @@ +# Tests that package upgrade works properly + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "init", "test" }; + version => "1.0"; + +} + +bundle agent init +{ + methods: + "make filerepo" usebundle => file_make($(G.testfile), + ""); +} + +body package_method test_method_filerepo(currentdir) +{ + package_changes => "individual"; + package_list_command => "$(G.cat) $(currentdir)/test_repository/installed.txt | $(G.grep) 'i '"; + package_installed_regex => "i .*"; + package_list_name_regex => "^[ia] (\S+?)\s\S+?\s\S+$"; + package_list_version_regex => "^[ia] \S+?\s(\S+?)\s\S+$"; + package_list_arch_regex => "^[ia] \S+?\s\S+?\s(\S+)$"; + + # Normally one would use $(G.echo), but it messes up redirection when you have arguments + # following the redirected filename on Windows, so use the stock "echo" in this case. + package_add_command => "echo >>$(G.testfile) filerepo ADD"; + package_update_command => "echo >>$(G.testfile) filerepo UPDATE"; + package_delete_command => "echo >>$(G.testfile) filerepo DELETE"; + + package_file_repositories => { translatepath("$(currentdir)/test_repository") }; + package_name_convention => "$(name)-$(version).$(arch).rpm"; +} + +bundle agent test +{ + vars: + "currentdir" string => translatepath("$(this.promise_dirname)"); + packages: + "install-exact-version" + package_policy => "addupdate", + package_select => "==", + package_version => "2.2.3", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "update-to-exact-version" + package_policy => "addupdate", + package_select => "==", + package_version => "2.2.3", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "delete-exact-version" + package_policy => "delete", + package_select => "==", + package_version => "2.2.3", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "install-lessorequal-version" + package_policy => "addupdate", + package_select => "<=", + package_version => "2.4.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "update-to-lessorequal-version" + package_policy => "addupdate", + package_select => "<=", + package_version => "2.4.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "install-lessthan-version" + package_policy => "addupdate", + package_select => "<", + package_version => "2.4.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "update-to-lessthan-version" + package_policy => "addupdate", + package_select => "<", + package_version => "2.4.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "install-greaterorequal-version" + package_policy => "addupdate", + package_select => ">=", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "update-to-greaterorequal-version" + package_policy => "addupdate", + package_select => ">=", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "update-to-greaterorequal-version-already-greater-installed" + package_policy => "addupdate", + package_select => ">=", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "install-greaterthan-version" + package_policy => "addupdate", + package_select => ">", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "update-to-greaterthan-version" + package_policy => "addupdate", + package_select => ">", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "update-to-greaterthan-version-already-greater-installed" + package_policy => "addupdate", + package_select => ">", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-install-exact-version" + package_policy => "addupdate", + package_select => "==", + package_version => "2.1.3", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-update-to-exact-version" + package_policy => "addupdate", + package_select => "==", + package_version => "2.1.3", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-delete-exact-version" + package_policy => "delete", + package_select => "==", + package_version => "2.1.3", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-install-lessorequal-version" + package_policy => "addupdate", + package_select => "<=", + package_version => "2.1.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-update-to-lessorequal-version" + package_policy => "addupdate", + package_select => "<=", + package_version => "2.1.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-install-lessthan-version" + package_policy => "addupdate", + package_select => "<", + package_version => "2.1.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-update-to-lessthan-version" + package_policy => "addupdate", + package_select => "<", + package_version => "2.1.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-install-greaterorequal-version" + package_policy => "addupdate", + package_select => ">=", + package_version => "2.4.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-update-to-greaterorequal-version" + package_policy => "addupdate", + package_select => ">=", + package_version => "2.4.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-install-greaterthan-version" + package_policy => "addupdate", + package_select => ">", + package_version => "2.4.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "mismatch-update-to-greaterthan-version" + package_policy => "addupdate", + package_select => ">", + package_version => "2.4.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-install-exact-version" + package_policy => "addupdate", + package_select => "==", + package_version => "2.2.3", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-update-to-exact-version" + package_policy => "addupdate", + package_select => "==", + package_version => "2.2.3", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-install-lessorequal-version" + package_policy => "addupdate", + package_select => "<=", + package_version => "2.4.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-update-to-lessorequal-version" + package_policy => "addupdate", + package_select => "<=", + package_version => "2.4.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-install-lessthan-version" + package_policy => "addupdate", + package_select => "<", + package_version => "2.4.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-update-to-lessthan-version" + package_policy => "addupdate", + package_select => "<", + package_version => "2.4.5", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-install-greaterorequal-version" + package_policy => "addupdate", + package_select => ">=", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-update-to-greaterorequal-version" + package_policy => "addupdate", + package_select => ">=", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-install-greaterthan-version" + package_policy => "addupdate", + package_select => ">", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); + + "missing-update-to-greaterthan-version" + package_policy => "addupdate", + package_select => ">", + package_version => "2.0.1", + package_architectures => { "i386" }, + package_method => test_method_filerepo($(currentdir)); +} diff --git a/tests/acceptance/07_packages/test_repository/install-exact-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/install-exact-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/install-greaterorequal-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/install-greaterorequal-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/install-greaterthan-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/install-greaterthan-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/install-lessorequal-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/install-lessorequal-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/install-lessthan-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/install-lessthan-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/installed.txt b/tests/acceptance/07_packages/test_repository/installed.txt new file mode 100644 index 0000000000..4daa492d6f --- /dev/null +++ b/tests/acceptance/07_packages/test_repository/installed.txt @@ -0,0 +1,41 @@ +i update-to-exact-version 1.4.3 i386 +i update-to-lessorequal-version 1.4.3 i386 +i update-to-lessthan-version 1.4.3 i386 +i update-to-greaterorequal-version 1.4.3 i386 +i update-to-greaterthan-version 1.4.3 i386 +i update-to-greaterorequal-version-already-greater-installed 2.2.2 i386 +i update-to-greaterthan-version-already-greater-installed 2.2.2 i386 +i mismatch-update-to-exact-version 1.4.3 i386 +i mismatch-update-to-lessorequal-version 1.4.3 i386 +i mismatch-update-to-lessthan-version 1.4.3 i386 +i mismatch-update-to-greaterorequal-version 1.4.3 i386 +i mismatch-update-to-greaterthan-version 1.4.3 i386 +i missing-update-to-exact-version 1.4.3 i386 +i missing-update-to-lessorequal-version 1.4.3 i386 +i missing-update-to-lessthan-version 1.4.3 i386 +i missing-update-to-greaterorequal-version 1.4.3 i386 +i missing-update-to-greaterthan-version 1.4.3 i386 +i delete-exact-version 2.2.3 i386 +i mismatch-delete-exact-version 2.2.3 i386 +a install-exact-version-2.2.3.i386.rpm +a install-greaterorequal-version-2.2.3.i386.rpm +a install-greaterthan-version-2.2.3.i386.rpm +a install-lessorequal-version-2.2.3.i386.rpm +a install-lessthan-version-2.2.3.i386.rpm +a mismatch-install-exact-version-2.2.3.i386.rpm +a mismatch-install-greaterorequal-version-2.2.3.i386.rpm +a mismatch-install-greaterthan-version-2.2.3.i386.rpm +a mismatch-install-lessorequal-version-2.2.3.i386.rpm +a mismatch-install-lessthan-version-2.2.3.i386.rpm +a mismatch-update-to-exact-version-2.2.3.i386.rpm +a mismatch-update-to-greaterorequal-version-2.2.3.i386.rpm +a mismatch-update-to-greaterthan-version-2.2.3.i386.rpm +a mismatch-update-to-lessorequal-version-2.2.3.i386.rpm +a mismatch-update-to-lessthan-version-2.2.3.i386.rpm +a update-to-exact-version-2.2.3.i386.rpm +a update-to-greaterorequal-version-2.2.3.i386.rpm +a update-to-greaterorequal-version-already-greater-installed-2.2.3.i386.rpm +a update-to-greaterthan-version-2.2.3.i386.rpm +a update-to-greaterthan-version-already-greater-installed-2.2.3.i386.rpm +a update-to-lessorequal-version-2.2.3.i386.rpm +a update-to-lessthan-version-2.2.3.i386.rpm diff --git a/tests/acceptance/07_packages/test_repository/mismatch-install-exact-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-install-exact-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-install-greaterorequal-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-install-greaterorequal-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-install-greaterthan-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-install-greaterthan-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-install-lessorequal-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-install-lessorequal-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-install-lessthan-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-install-lessthan-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-update-to-exact-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-update-to-exact-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-update-to-greaterorequal-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-update-to-greaterorequal-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-update-to-greaterthan-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-update-to-greaterthan-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-update-to-lessorequal-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-update-to-lessorequal-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/mismatch-update-to-lessthan-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/mismatch-update-to-lessthan-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/update-to-exact-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/update-to-exact-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/update-to-greaterorequal-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/update-to-greaterorequal-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/update-to-greaterorequal-version-already-greater-installed-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/update-to-greaterorequal-version-already-greater-installed-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/update-to-greaterthan-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/update-to-greaterthan-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/update-to-greaterthan-version-already-greater-installed-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/update-to-greaterthan-version-already-greater-installed-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/update-to-lessorequal-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/update-to-lessorequal-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/test_repository/update-to-lessthan-version-2.2.3.i386.rpm b/tests/acceptance/07_packages/test_repository/update-to-lessthan-version-2.2.3.i386.rpm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/07_packages/upgrade-package-version.cf b/tests/acceptance/07_packages/upgrade-package-version.cf new file mode 100644 index 0000000000..e9fa7c334d --- /dev/null +++ b/tests/acceptance/07_packages/upgrade-package-version.cf @@ -0,0 +1,57 @@ +# Tests that package upgrade works properly with "more than" version + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +bundle agent init +{ + meta: + # Windows has its own mechanism to list packages, and doesn't use the package body. + "test_skip_needs_work" string => "windows"; + + vars: + "dummy" string => "dummy"; +} + +body package_method yum_rpm +{ + package_changes => "individual"; + package_list_command => "$(G.printf) 'which 1 i386'"; + + package_list_name_regex => "^(\S+?)\s\S+?\s\S+$"; + package_list_version_regex => "^\S+?\s(\S+?)\s\S+$"; + package_list_arch_regex => "^\S+?\s\S+?\s(\S+)$"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.false)"; +} + +bundle agent test +{ + packages: + "which" + package_policy => "addupdate", + package_select => ">", + package_version => "2", + package_method => yum_rpm, + classes => if_repaired("whichrepaired"); +} + +bundle agent check +{ + reports: + whichrepaired:: + "$(this.promise_filename) Pass"; + !whichrepaired:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/upgrade-package.cf b/tests/acceptance/07_packages/upgrade-package.cf new file mode 100644 index 0000000000..5cda789ee3 --- /dev/null +++ b/tests/acceptance/07_packages/upgrade-package.cf @@ -0,0 +1,57 @@ +# Tests that package upgrade works properly + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +bundle agent init +{ + meta: + # Windows has its own mechanism to list packages, and doesn't use the package body. + "test_skip_needs_work" string => "windows"; + + vars: + "dummy" string => "dummy"; +} + +body package_method yum_rpm +{ + package_changes => "individual"; + package_list_command => "$(G.printf) 'which 1 i386'"; + + package_list_name_regex => "^(\S+?)\s\S+?\s\S+$"; + package_list_version_regex => "^\S+?\s(\S+?)\s\S+$"; + package_list_arch_regex => "^\S+?\s\S+?\s(\S+)$"; + + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.true)"; + package_delete_command => "$(G.false)"; +} + +bundle agent test +{ + packages: + "which" + package_policy => "addupdate", + package_select => "==", + package_version => "2", + package_method => yum_rpm, + classes => if_repaired("whichrepaired"); +} + +bundle agent check +{ + reports: + whichrepaired:: + "$(this.promise_filename) Pass"; + !whichrepaired:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/warn-package-delete-non-existing.cf b/tests/acceptance/07_packages/warn-package-delete-non-existing.cf new file mode 100644 index 0000000000..0d71f09fc0 --- /dev/null +++ b/tests/acceptance/07_packages/warn-package-delete-non-existing.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test delete a package that does not exist +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + packages: + "nosuchpackageinstalled" + package_method => mock_warn_delete_pkg, + package_policy => "delete", + classes => kept_repaired_notkept("kept", "repaired", "notkept"); +} + +body package_method mock_warn_delete_pkg +{ + package_changes => "individual"; + + package_list_command => "$(G.printf) 'bash-4.2.8-x86_64'"; + + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.false)"; +} + +body classes kept_repaired_notkept(kept, repaired, notkept) +{ + promise_kept => { "$(kept)" }; + promise_repaired => { "$(repaired)" }; + repair_failed => { "$(notkept)" }; +} + +####################################################### + +bundle agent check +{ + + reports: + kept:: + "$(this.promise_filename) Pass"; + !kept:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/warn-package-delete.cf b/tests/acceptance/07_packages/warn-package-delete.cf new file mode 100644 index 0000000000..421cd8a18b --- /dev/null +++ b/tests/acceptance/07_packages/warn-package-delete.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test delete a package +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -KI -f $(this.promise_filename).sub --dry-run", "useshell"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => regcmp(".*Deleting.*", "$(test.subout)"); + + reports: + # DEBUG:: + # "$(test.subout)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 29 diff --git a/tests/acceptance/07_packages/warn-package-delete.cf.sub b/tests/acceptance/07_packages/warn-package-delete.cf.sub new file mode 100644 index 0000000000..4671f656c2 --- /dev/null +++ b/tests/acceptance/07_packages/warn-package-delete.cf.sub @@ -0,0 +1,42 @@ +####################################################### +# +# Test delete a package +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { "test_warn_delete_pkg" }; + version => "1.0"; +} + +####################################################### + +bundle agent test_warn_delete_pkg +{ +packages: + "bash" + package_policy => "delete", + package_method => mock_warn_delete_pkg, + package_select => ">=", + package_version => "4.2.8", + package_architectures => {"x86_64"}; +} + +body package_method mock_warn_delete_pkg +{ + package_changes => "individual"; + + package_list_command => "$(G.printf) 'bash-4.2.8-x86_64'"; + + package_list_name_regex => "^([^-]+)"; + package_list_version_regex => "^[^-]+-([^-]+)"; + package_list_arch_regex => "^[^-]+-[^-]+-(.*)"; + package_installed_regex => ".*"; + + package_add_command => "$(G.false)"; + package_update_command => "$(G.false)"; + package_delete_command => "$(G.true)"; + package_verify_command => "$(G.false)"; +} diff --git a/tests/acceptance/08_commands/01_modules/001.cf b/tests/acceptance/08_commands/01_modules/001.cf new file mode 100644 index 0000000000..3680b4e43a --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/001.cf @@ -0,0 +1,62 @@ +####################################################### +# +# Test command modules +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "script_name" string => "$(this.promise_filename).txt"; +} + +####################################################### + +bundle agent test +{ + vars: + !windows:: + "cat_prefix" string => "cat"; + windows:: + "cat_prefix" string => "cat_exe"; + + commands: + "$(G.cat) $(init.script_name)" + contain => in_shell, + module => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + + CLASSTOBEDEFINED.!UNDEFINEDCLASS:: + "classok" expression => "any"; + + any:: + "varok" expression => strcmp("${$(test.cat_prefix).answer}", "42"); + + "ok" and => { "classok", "varok" }; + + reports: + DEBUG:: + "${$(test.cat_prefix).answer} =?= 42"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/001.cf.txt b/tests/acceptance/08_commands/01_modules/001.cf.txt new file mode 100644 index 0000000000..071218f5dd --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/001.cf.txt @@ -0,0 +1,3 @@ ++CLASSTOBEDEFINED +-UNDEFINEDCLASS +=answer=42 diff --git a/tests/acceptance/08_commands/01_modules/002.cf b/tests/acceptance/08_commands/01_modules/002.cf new file mode 100644 index 0000000000..772f943580 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/002.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test setting associative array in module (Issue 714) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle agent init +{ + vars: + "script_name" string => "$(this.promise_filename).txt"; +} + +####################################################### + +bundle agent test +{ + vars: + !windows:: + "cat_prefix" string => "cat"; + windows:: + "cat_prefix" string => "cat_exe"; + + commands: + "$(G.cat) $(init.script_name)" + contain => in_shell, + module => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "generated_indices" slist => getindices("$(test.cat_prefix).answer"); + + classes: + + "ok" and => { strcmp("${$(test.cat_prefix).answer[42]}", "Yes"), + strcmp("${$(test.cat_prefix).answer[41]}", "No") }; + + reports: + DEBUG:: + "${$(test.cat_prefix).answer[${generated_indices}]}"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/002.cf.txt b/tests/acceptance/08_commands/01_modules/002.cf.txt new file mode 100644 index 0000000000..d6af1e54c7 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/002.cf.txt @@ -0,0 +1,2 @@ +=answer[42]=Yes +=answer[41]=No diff --git a/tests/acceptance/08_commands/01_modules/003.cf b/tests/acceptance/08_commands/01_modules/003.cf new file mode 100644 index 0000000000..1b1b782ab7 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/003.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Test command modules +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "script_name" string => "$(this.promise_filename).txt"; +} + +####################################################### + +bundle agent test +{ + vars: + !windows:: + "cat_prefix" string => "cat"; + windows:: + "cat_prefix" string => "cat_exe"; + + commands: + "$(G.cat) $(init.script_name)" + contain => in_shell, + module => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("$($(test.cat_prefix).myvar[dot.dash-test])", "42"); + + reports: + DEBUG:: + "${$(test.cat_prefix).myvar[dot.dash-test]} =?= 42"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL "; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/003.cf.txt b/tests/acceptance/08_commands/01_modules/003.cf.txt new file mode 100644 index 0000000000..6f674d7fda --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/003.cf.txt @@ -0,0 +1 @@ +=myvar[dot.dash-test]=42 diff --git a/tests/acceptance/08_commands/01_modules/004.cf b/tests/acceptance/08_commands/01_modules/004.cf new file mode 100644 index 0000000000..26582d5652 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/004.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Test command modules +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "script_name" string => "$(this.promise_filename).txt"; + +} + +####################################################### + +bundle agent test +{ + vars: + !windows:: + "cat_prefix" string => "cat"; + windows:: + "cat_prefix" string => "cat_exe"; + + commands: + "$(G.cat) $(init.script_name)" + contain => in_shell, + module => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "list0" slist => {"abc", "def", "ghi"}; + "list1" slist => {"{{abc}}", " ' def}", "ghi'''"}; + "list2" slist => {'{{a,bc}}', ' " de,f}', 'gh,,i"""'}; + "list3" slist => {"{{a'bc',,}}", ' ",, d"ef}', "ghi,},'''"}; + + "actual0" string => join(":", "list0"); + "actual1" string => join(":", "list1"); + "actual2" string => join(":", "list2"); + "actual3" string => join(":", "list3"); + + "joined0" string => join(":", "$(test.cat_prefix).mylist"); + "joined1" string => join(":", "$(test.cat_prefix).myalist"); + "joined2" string => join(":", "$(test.cat_prefix).myblist"); + "joined3" string => join(":", "$(test.cat_prefix).myclist"); + + classes: + + any:: + "var0ok" expression => strcmp("${this.joined0}" , "${this.actual0}"); + "var1ok" expression => strcmp("${this.joined1}" , "${this.actual1}"); + "var2ok" expression => strcmp("${this.joined2}" , "${this.actual2}"); + "var3ok" expression => strcmp("${this.joined3}" , "${this.actual3}"); + + "ok" and => { "var0ok", "var1ok", "var2ok", "var3ok" }; + + reports: + !DEBUG:: + "joined3 = [${this.joined3}] vs actual3 = [${this.actual3}]"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/004.cf.txt b/tests/acceptance/08_commands/01_modules/004.cf.txt new file mode 100644 index 0000000000..e9b1e0cbd9 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/004.cf.txt @@ -0,0 +1,4 @@ +@mylist={"abc", "def", "ghi"} +@myalist={"{{abc}}", " ' def}", "ghi'''"} +@myblist={'{{a,bc}}', ' " de,f}', 'gh,,i"""'} +@myclist={"{{a'bc',,}}", ' ",, d"ef}', "ghi,},'''"} diff --git a/tests/acceptance/08_commands/01_modules/classes-automatically-canonified.cf b/tests/acceptance/08_commands/01_modules/classes-automatically-canonified.cf new file mode 100644 index 0000000000..e94b027d63 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/classes-automatically-canonified.cf @@ -0,0 +1,49 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + + "description" + string => "Test that classes defined via the module protocol are + automatically canonified."; + + classes: + "my-invalid-class" + expression => "any", + scope => "namespace"; + + commands: + "$(G.echo) +invalid-class@module" + module => "true"; + + reports: + (EXTRA|DEBUG).invalid_class_module:: + "Class defined from module automatically canonified as expected"; + + (EXTRA|DEBUG).!invalid_class_module:: + "Class defined from module NOT automatically canonified"; + + (EXTRA|DEBUG).my_invalid_class:: + "My class was automatically canonified as expected"; + + (EXTRA|DEBUG).!my_invalid_class:: + "My class was NOT automatically canonified"; +} +bundle agent check +{ + classes: + "pass" and => { "my_invalid_class", "invalid_class_module" }; + + reports: + pass:: + "$(this.promise_filename) Pass"; + + !pass:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/08_commands/01_modules/long-module-lines.cf b/tests/acceptance/08_commands/01_modules/long-module-lines.cf new file mode 100644 index 0000000000..b0535649a3 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/long-module-lines.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Test command modules with long lines +# Redmine#4170: segfault on long lines +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "script_name" string => "$(G.cat) $(this.promise_filename).txt"; + +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-1623" }; + + commands: + "$(init.script_name)" module => "true"; + +} + +####################################################### + +bundle agent check +{ + vars: + "scalars" slist => { "x4095", "x4094", "x4093" }; + "lists" slist => { "zshort", "z4095" }; + + "bytes1024" string => "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + "bytes1000" string => "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567"; + + "expected[x4095]" string => concat($(bytes1024),$(bytes1024),$(bytes1024),$(bytes1000),"89abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789x4095"); + "expected[x4094]" string => concat($(bytes1024),$(bytes1024),$(bytes1024),$(bytes1000),"89abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678x4094"); + "expected[x4093]" string => concat($(bytes1024),$(bytes1024),$(bytes1024),$(bytes1000),"89abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567x4093"); + + "longclass" string => "class678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012"; + + "expected_length[zshort]" int => "227"; + "expected_length[z4095]" int => "293"; + "actual_length[$(lists)]" int => length("cat.$(lists)"); + + "longclass_length" int => string_length($(longclass)); + "expected_length[$(scalars)]" int => string_length("$(expected[$(scalars)])"); + "actual_length[$(scalars)]" int => string_length("$(cat.$(scalars))"); + "tail[$(scalars)]" string => string_tail("$(cat.$(scalars))", 256); + + classes: + "match_$(scalars)" expression => strcmp("$(expected[$(scalars)])", + "$(cat.$(scalars))"); + "match_length_$(scalars)" expression => strcmp("$(expected_length[$(scalars)])", + "$(actual_length[$(scalars)])"); + + "match_length_$(lists)" expression => strcmp("$(expected_length[$(lists)])", + "$(actual_length[$(lists)])"); + "ok" and => { "$(longclass)", + "match_x4095", "match_length_x4095", + "match_x4094", "match_length_x4094", + "match_x4093", "match_length_x4093", + "match_length_zshort", "match_length_z4095", + }; + + reports: + DEBUG:: + "tail(256) of $(scalars) = $(tail[$(scalars)])"; + + "Got the long class, $(longclass_length) bytes" + if => "$(longclass)"; + + "Did NOT get the long class, $(longclass_length) bytes" + if => "!$(longclass)"; + + "Got the length for list $(lists): $(actual_length[$(lists)])" + if => "match_length_$(lists)"; + + "Did NOT get the length for list $(lists): actual $(actual_length[$(lists)]) vs expected $(expected_length[$(lists)])" + if => "!match_length_$(lists)"; + + "Got the length for scalar $(scalars): $(actual_length[$(scalars)])" + if => "match_length_$(scalars)"; + + "Did NOT get the length for scalar $(scalars): actual $(actual_length[$(scalars)]) vs expected $(expected_length[$(scalars)])" + if => "!match_length_$(scalars)"; + + "Got the long $(scalars), actual $(actual_length[$(scalars)]) bytes, actual tail($(scalars), 256) = $(tail[$(scalars)])" + if => "match_$(scalars)"; + + "Did NOT get the long $(scalars), actual $(actual_length[$(scalars)]) bytes, actual tail($(scalars), 256) = $(tail[$(scalars)])" + if => "!match_$(scalars)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/long-module-lines.cf.txt b/tests/acceptance/08_commands/01_modules/long-module-lines.cf.txt new file mode 100644 index 0000000000..2916a3b2ba --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/long-module-lines.cf.txt @@ -0,0 +1,41 @@ ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345 + ++1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234 + ++123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123 + ++class678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012 + ++123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + +-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + +-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345 + +-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234 + +-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123 + +-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012 + +=x=123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + +=x=1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234 + +=x4095=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789x4095 + +=x4094=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678x4094 + +=x4093=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567x4093 + +@y={ "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" } + +@z={ "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890" } + +@zshort={ "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890" } + +@z4096={ "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "12" } + +@z4095={ "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "" } + +%json=[ "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890", "1234567890" ] diff --git a/tests/acceptance/08_commands/01_modules/module-array-allows-at.cf b/tests/acceptance/08_commands/01_modules/module-array-allows-at.cf new file mode 100644 index 0000000000..d3fa859c39 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module-array-allows-at.cf @@ -0,0 +1,64 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3099" } + string => "Test that arrays defined by modules can contain @ just like classic arrays."; + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + commands: + + # Define a simple classic array using the module protocol. + + "$(G.echo)" + args => "=array[key]=array_key_value", + module => "true"; + + # Define a classic array where the key contains an @ using the module protocol. + + "$(G.echo)" + args => "=array[key@1]=array_path_key_value", + module => "true"; + + vars: + + # Define a classic array where the key contains an @. + + "array[key@1]" string => "another_array_path_key_value"; + + #Find all the array entries defined by the module commands. + "v" slist => variablesmatching("default:echo\.*array.*"); + + reports: + DEBUG:: + "Found variable '$(v)'"; +} + +####################################################### + +bundle agent check +{ + vars: + # Construct strings from the array values for comparison and test pass/fail + "expected" string => "array_key_value array_path_key_value another_array_path_key_value"; + "actual" string => "$(echo.array[key]) $(echo.array[key@1]) $(test.array[key@1])"; + + methods: + "" usebundle => dcs_check_strcmp($(expected), + $(actual), + $(this.promise_filename), + "no"); + + reports: + DEBUG:: + "module array[key] = '$(echo.array[key])'"; + "module array[key@1] = '$(echo.array[key@1])'"; + "classic array[key@1] = '$(test.array[key@1])'"; +} diff --git a/tests/acceptance/08_commands/01_modules/module-array-allows-slash.cf b/tests/acceptance/08_commands/01_modules/module-array-allows-slash.cf new file mode 100644 index 0000000000..dfe1a565ed --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module-array-allows-slash.cf @@ -0,0 +1,64 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-2768" } + string => "Test that arrays defined by modules can contain slashes just like classic arrays."; + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + commands: + + # Define a simple classic array using the module protocol. + + "$(G.echo)" + args => "=array[key]=array_key_value", + module => "true"; + + # Define a classic array where the key contains a backslash using the module protocol. + + "$(G.echo)" + args => "=array[/path/key]=array_path_key_value", + module => "true"; + + vars: + + # Define a classic array where the key contains a backslash. + + "array[/path/key]" string => "another_array_path_key_value"; + + #Find all the array entries defined by the module commands. + "v" slist => variablesmatching("default:echo\.*array.*"); + + reports: + DEBUG:: + "Found variable '$(v)'"; +} + +####################################################### + +bundle agent check +{ + vars: + # Construct strings from the array values for comparison and test pass/fail + "expected" string => "array_key_value array_path_key_value another_array_path_key_value"; + "actual" string => "$(echo.array[key]) $(echo.array[/path/key]) $(test.array[/path/key])"; + + methods: + "" usebundle => dcs_check_strcmp($(expected), + $(actual), + $(this.promise_filename), + "no"); + + reports: + DEBUG:: + "module array[key] = '$(echo.array[key])'"; + "module array[/path/key] = '$(echo.array[/path/key])'"; + "classic array[/path/key] = '$(test.array[/path/key])'"; +} diff --git a/tests/acceptance/08_commands/01_modules/module-array-indexing.cf b/tests/acceptance/08_commands/01_modules/module-array-indexing.cf new file mode 100644 index 0000000000..9082c2e564 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module-array-indexing.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Zendesk 1312 +# Test command modules with given array indices +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + commands: + "$(G.cat) $(this.promise_filename).txt" module => "true"; + + reports: + EXTRA:: + "Got foo_idx = $(arrays.foo_idx)"; + "Got foo[$(arrays.foo_idx)] = $(arrays.foo[$(arrays.foo_idx)])"; +} + +####################################################### + +bundle agent check +{ + vars: + "foo_$(arrays.foo_idx)" string => "$(arrays.foo[$(arrays.foo_idx)])"; + "merged_foo" data => mergedata("arrays.foo"); + "sfoo" string => "foo_a=$(foo_a), foo_b=$(foo_b)"; + "actual" string => format("foo = %S, foo_idx = %S, sfoo = %s", merged_foo, "arrays.foo_idx", $(sfoo)); + "expected" string => 'foo = {"a":"a_foo","b":"b_foo"}, foo_idx = { "a", "b" }, sfoo = foo_a=a_foo, foo_b=b_foo'; + + methods: + "" usebundle => dcs_check_strcmp($(expected), + $(actual), + $(this.promise_filename), + "no"); + +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/module-array-indexing.cf.txt b/tests/acceptance/08_commands/01_modules/module-array-indexing.cf.txt new file mode 100644 index 0000000000..3944667d07 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module-array-indexing.cf.txt @@ -0,0 +1,5 @@ +^context=arrays +=foo[a]=a_foo +=foo[b]=b_foo +@foo_idx={"a","b"} ++arrays_loaded diff --git a/tests/acceptance/08_commands/01_modules/module_allows_empty_lists.cf b/tests/acceptance/08_commands/01_modules/module_allows_empty_lists.cf new file mode 100644 index 0000000000..531f318e0a --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module_allows_empty_lists.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Test that modules are allowed to emit empty lists +# Redmine: 7577 +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "any", + meta => { "redmine7577" }; + + commands: + "$(G.cat)" + args => "$(this.promise_filename).txt", + module => "true", + comment => "Module output found in this file"; +} + +####################################################### + +bundle agent check +{ + vars: + "length_of_list_from_module" + int => length("default:cat.list_from_module"); + + classes: + "have_list_from_module" + expression => isvariable("default:cat.list_from_module"), + comment => "Modules should be able to define empty/null lists."; + + "list_from_module_length_0" + expression => strcmp( 0, $(length_of_list_from_module) ); + + "ok" + and => { "have_list_from_module", "list_from_module_length_0" }, + comment => "OK since we can both define an empty list, and its length is 0"; + + reports: + DEBUG|DEBUG_check:: + "DEBUG $(this.bundle): Module defined 'default:cat.list_from_module'" + if => "have_list_from_module"; + + "DEBUG $(this.bundle): Module did not define the expected list 'default:cat.list_from_module'" + unless => "have_list_from_module"; + + + "DEBUG $(this.bundle): The list defined from the module had the expected length of 0" + if => "list_from_module_length_0"; + + "DEBUG $(this.bundle): The the list defined from the module did not have the expected length or was undefined" + unless => "list_from_module_length_0"; + + + "DEBUG $(this.bundle): length of list from module '$(length_of_list_from_module)'"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/module_allows_empty_lists.cf.txt b/tests/acceptance/08_commands/01_modules/module_allows_empty_lists.cf.txt new file mode 100644 index 0000000000..1bbec5282f --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module_allows_empty_lists.cf.txt @@ -0,0 +1 @@ +@list_from_module={ } diff --git a/tests/acceptance/08_commands/01_modules/module_allows_trailing_comma.cf b/tests/acceptance/08_commands/01_modules/module_allows_trailing_comma.cf new file mode 100644 index 0000000000..e430904f31 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module_allows_trailing_comma.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Test that modules are allowed to define lists with trailing commas +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "any", + meta => { "redmine7578" }; + + commands: + "$(G.cat)" + args => "$(this.promise_filename).txt", + module => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "length_of_list_from_module" + int => length("default:cat.list_from_module"); + + classes: + "have_list_from_module" + expression => isvariable("default:cat.list_from_module"), + comment => "Modules should be able to define empty/null lists."; + + "list_from_module_length_1" + expression => strcmp(1, $(length_of_list_from_module)); + + "ok" + and => { "have_list_from_module", "list_from_module_length_1" }, + comment => "OK since we can both define a list with a trailing comma, and + its length is 1 as expected"; + + reports: + DEBUG|DEBUG_check:: + "DEBUG $(this.bundle): Module defined 'default:cat.list_from_module'" + if => "have_list_from_module"; + + "DEBUG $(this.bundle): The list defined from the module had the expected length of 0" + if => "list_from_module_length_1"; + + "DEBUG $(this.bundle): length of list from module '$(length_of_list_from_module)'"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/module_allows_trailing_comma.cf.txt b/tests/acceptance/08_commands/01_modules/module_allows_trailing_comma.cf.txt new file mode 100644 index 0000000000..e4d530a2e5 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module_allows_trailing_comma.cf.txt @@ -0,0 +1 @@ +@list_from_module={ "list_element_with_trailing_comma", } diff --git a/tests/acceptance/08_commands/01_modules/module_file_test.cf b/tests/acceptance/08_commands/01_modules/module_file_test.cf new file mode 100644 index 0000000000..958dd7868e --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module_file_test.cf @@ -0,0 +1,53 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + any:: + "$(G.testfile).json" + create => "true", + edit_line => init_insert_json; +} + +bundle edit_line init_insert_json +{ + insert_lines: + '{ "hello": "world" }'; +} + + +bundle agent test +{ + meta: + "description" + string => "Test module file protocol"; + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + commands: + any:: + "$(G.echo) &data=$(G.testfile).json" + module => "true"; + + reports: + DEBUG:: + "hello has value: $(echo.data[hello])"; +} + +bundle agent check +{ + classes: + "pass" expression => strcmp("$(echo.data[hello])", "world"); + + reports: + pass:: + "$(this.promise_filename) Pass"; + + !pass:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/08_commands/01_modules/module_with_no_output.cf b/tests/acceptance/08_commands/01_modules/module_with_no_output.cf new file mode 100644 index 0000000000..751bdb58f9 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module_with_no_output.cf @@ -0,0 +1,36 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent init +{ + methods: + "init" usebundle => file_make("$(G.testdir)/module.output.txt", + "=myvar=good_output_1"); + + "init" usebundle => file_make("$(G.testdir)/module.no_output.txt", + "=myvar=bad_output"); + + "init" usebundle => file_make("$(G.testdir)/module.default.txt", + "=myvar=bad_output"); + + "init" usebundle => file_make("$(G.testdir)/no_module.output.txt", + "=myvar=good_output_2"); + + "init" usebundle => file_make("$(G.testdir)/no_module.no_output.txt", + "=myvar=bad_output"); + + "init" usebundle => file_make("$(G.testdir)/no_module.default.txt", + "=myvar=good_output_3"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_passif_output(".*good_output_1.*good_output_2.*good_output_3.*", + ".*bad_output.*", + "$(sys.cf_agent) -D AUTO -Kf $(this.promise_filename).sub", + $(this.promise_filename)); +} diff --git a/tests/acceptance/08_commands/01_modules/module_with_no_output.cf.sub b/tests/acceptance/08_commands/01_modules/module_with_no_output.cf.sub new file mode 100644 index 0000000000..c7cd6ffae1 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/module_with_no_output.cf.sub @@ -0,0 +1,33 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent test +{ + commands: + "$(G.cat) $(G.testdir)/module.output.txt" + module => "true", + contain => test_contain("false"); + + "$(G.cat) $(G.testdir)/module.no_output.txt" + module => "true", + contain => test_contain("true"); + + "$(G.cat) $(G.testdir)/module.default.txt" + module => "true"; + + "$(G.cat) $(G.testdir)/no_module.output.txt" + contain => test_contain("false"); + + "$(G.cat) $(G.testdir)/no_module.no_output.txt" + contain => test_contain("true"); + + "$(G.cat) $(G.testdir)/no_module.default.txt"; +} + +body contain test_contain(no_output) +{ + no_output => "$(no_output)"; +} diff --git a/tests/acceptance/08_commands/01_modules/set-context.cf b/tests/acceptance/08_commands/01_modules/set-context.cf new file mode 100644 index 0000000000..ac60385c3a --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/set-context.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Test command modules +# Redmine#3991: support data containers +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "script_name" string => "$(this.promise_filename).txt"; + +} + +####################################################### + +bundle agent test +{ + commands: + "$(G.cat) $(init.script_name)" + contain => in_shell, + module => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "list0" slist => {"abc", "def", "ghi"}; + "list1" slist => {"{{abc}}", " ' def}", "ghi'''"}; + "list2" slist => {'{{a,bc}}', ' " de,f}', 'gh,,i"""'}; + "list3" slist => {"{{a'bc',,}}", ' ",, d"ef}', "ghi,},'''"}; + + "actual0" string => join(":", "list0"); + "actual1" string => join(":", "list1"); + "actual2" string => join(":", "list2"); + "actual3" string => join(":", "list3"); + + "joined0" string => join(":", "xyz_123___456.mylist"); + "joined1" string => join(":", "xyz_123___456.myalist"); + "joined2" string => join(":", "xyz_123___456.myblist"); + "joined3" string => join(":", "xyz_123___456.myclist"); + + "c" slist => { "1", "2", "3", "4", "5", "6" }; + "c$(c)" string => format("%S", "xyz_123___456.mycontainer$(c)"); + + "e1" string => '{"x":[456,789]}'; + "e5" string => '["read","some","strings"]'; + "e6" string => '{"mix":["match"],"nothing":null,"paddle":true,"ping":"pong"}'; + + "hello_contexts" slist => { "xyz_123___456", "_456", "456", "_", "a__456__", "__a__", }; + + classes: + + any:: + "var0ok" expression => strcmp("${this.joined0}" , "${this.actual0}"); + "var1ok" expression => strcmp("${this.joined1}" , "${this.actual1}"); + "var2ok" expression => strcmp("${this.joined2}" , "${this.actual2}"); + "var3ok" expression => strcmp("${this.joined3}" , "${this.actual3}"); + "var4ok" expression => strcmp("hello there" , "${xyz_123___456.myvar}"); + + "hello_$(hello_contexts)_ok" expression => strcmp("hello there" , "${$(hello_contexts).myvar}"); + + "ok_c1" expression => strcmp($(e1) , $(c1)); + "ok_c2" not => isvariable("c2"); + "ok_c3" not => isvariable("c3"); + "ok_c4" not => isvariable("c4"); + "ok_c5" expression => strcmp($(e5) , $(c5)); + "ok_c6" expression => strcmp($(e6) , $(c6)); + + "ok_injected_var" -> { "CFE-1915" } + expression => strcmp($(test.myvar), "hello there"), + comment => "This remote variable injection should work, BUT IS A DIRTY HACK!"; + + "ok" and => { "myclass", "var0ok", "var1ok", "var2ok", "var3ok", "var4ok", + "hello_xyz_123___456_ok", "hello__456_ok", "hello_456_ok", "hello___ok", "hello_a__456___ok", "hello___a___ok", + "ok_c1", "ok_c2", "ok_c3", "ok_c4", "ok_c5", "ok_c6", + "ok_injected_var" }; + + reports: + DEBUG:: + "container $(c): $(e$(c)) != $(c$(c))" + if => "!ok_c$(c)"; + + "context $(hello_contexts) failed" if => "!hello_$(hello_contexts)_ok"; + + "test.myvar = $(test.myvar)" + if => "!ok_injected_var"; + + EXTRA:: + "context $(hello_contexts) worked" if => "hello_$(hello_contexts)_ok"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/set-context.cf.txt b/tests/acceptance/08_commands/01_modules/set-context.cf.txt new file mode 100644 index 0000000000..77c1a6010e --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/set-context.cf.txt @@ -0,0 +1,25 @@ +^context=xyz_123___456 ++myclass +=myvar=hello there +@mylist={"abc", "def", "ghi"} +@myalist={"{{abc}}", " ' def}", "ghi'''"} +@myblist={'{{a,bc}}', ' " de,f}', 'gh,,i"""'} +@myclist={"{{a'bc',,}}", ' ",, d"ef}', "ghi,},'''"} +%mycontainer1={"x":[456,789]} +%mycontainer2={"x":"y" +%mycontainer3="long string here" +%mycontainer4=null +%mycontainer5=["read", "some", "strings"] +%mycontainer6={"mix": ["match"], "ping": "pong", "paddle": true, "nothing": null} +^context=_456 +=myvar=hello there +^context=456 +=myvar=hello there +^context=_ +=myvar=hello there +^context=a__456__ +=myvar=hello there +^context=__a__ +=myvar=hello there +^context=test +=myvar=hello there diff --git a/tests/acceptance/08_commands/01_modules/set-persistent-class.cf b/tests/acceptance/08_commands/01_modules/set-persistent-class.cf new file mode 100644 index 0000000000..07de575b34 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/set-persistent-class.cf @@ -0,0 +1,38 @@ +####################################################### +# +# Test command modules +# Redmine#7302: support persistent classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; +} + +bundle agent check +{ + vars: + "command" string => "($(sys.cf_agent) -Kvf $(this.promise_filename).sub; $(sys.cf_promises) -v)| $(G.grep) 'x4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP'"; + + methods: + # sample output + + # verbose: Module set persistent class 'Cx4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP' for 20 minutes + # verbose: Adding persistent class 'Cx4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP' to heap + + # note we check that Ax4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP and Bx4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP are not set persistently! + "check" usebundle => dcs_passif_output(".*Module set persistent class 'Cx4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP' for 20 minutes.*", ".*Module set persistent class '[AB]x4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP'.*", $(command), $(this.promise_filename)); + +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/08_commands/01_modules/set-persistent-class.cf.sub b/tests/acceptance/08_commands/01_modules/set-persistent-class.cf.sub new file mode 100644 index 0000000000..d61095c635 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/set-persistent-class.cf.sub @@ -0,0 +1,17 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; +} + +bundle agent main +{ + commands: + "$(G.echo) '^persistence=20$(const.n)+Cx4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP'" + module => "true"; + + "$(G.echo) '^persistence=$(const.n)+Bx4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP'" + module => "true"; + + "$(G.echo) '+Ax4eXfWrHT0zsSbrAXh5jFnRnDLKqNsP'" + module => "true"; +} diff --git a/tests/acceptance/08_commands/01_modules/set-tags.cf b/tests/acceptance/08_commands/01_modules/set-tags.cf new file mode 100644 index 0000000000..530c1c84d4 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/set-tags.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Test the ^meta module protocol extension +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common init +{ + vars: + "script_name" string => "$(this.promise_filename).txt"; + +} + +####################################################### + +bundle agent test +{ + meta: + "test_flakey_fail" string => "windows", + meta => { "ENT-10257" }; + commands: + "$(G.cat) $(init.script_name)" + contain => in_shell, + module => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "xyzvars" slist => variablesmatching("default:xyz.*"); + + "list0" slist => {"abc", "def", "ghi"}; + "list1" slist => {"{{abc}}", " ' def}", "ghi'''"}; + "list2" slist => {'{{a,bc}}', ' " de,f}', 'gh,,i"""'}; + "list3" slist => {"{{a'bc',,}}", ' ",, d"ef}', "ghi,},'''"}; + + "actual0" string => join(":", "list0"); + "actual1" string => join(":", "list1"); + "actual2" string => join(":", "list2"); + "actual3" string => join(":", "list3"); + + "joined0" string => join(":", "xyz.mylist"); + "joined1" string => join(":", "xyz.myalist"); + "joined2" string => join(":", "xyz.myblist"); + "joined3" string => join(":", "xyz.myclist"); + + "tags0" slist => getvariablemetatags("xyz.mylist"); + "tags1" slist => getvariablemetatags("xyz.myalist"); + "tags2" slist => getvariablemetatags("xyz.myblist"); + "tags3" slist => getclassmetatags("mycclass"); + + "jtags0" string => join(",", "tags0"); + "jtags1" string => join(",", "tags1"); + "jtags2" string => join(",", "tags2"); + "jtags3" string => join(",", "tags3"); + + "etags0" string => "xyz,abc=def,,\?\?what is this\?\?,source=module,derived_from=.*"; + "etags1" string => "xyz,abc=def,,\?\?what is this\?\?,source=module,derived_from=.*"; + "etags2" string => "1,2,3,source=module,derived_from=.*"; + "etags3" string => "a,b,c,source=module,derived_from=.*"; + + classes: + + any:: + "var0ok" expression => strcmp("${this.joined0}" , "${this.actual0}"); + "var1ok" expression => strcmp("${this.joined1}" , "${this.actual1}"); + "var2ok" expression => strcmp("${this.joined2}" , "${this.actual2}"); + "var3ok" expression => strcmp("${this.joined3}" , "${this.actual3}"); + "var4ok" expression => strcmp("hello there" , "${xyz.myvar}"); + + "tags0ok" expression => regcmp($(etags0), $(jtags0)); + "tags1ok" expression => regcmp($(etags1), $(jtags1)); + "tags2ok" expression => regcmp($(etags2), $(jtags2)); + "tags3ok" expression => regcmp($(etags3), $(jtags3)); + + "ok" and => { "myclass", "var0ok", "var1ok", "var2ok", "var3ok", "var4ok", + "tags0ok", "tags1ok", "tags2ok", "tags3ok", }; + + reports: + DEBUG:: + "xyzvars = $(xyzvars)"; + + "tags0ok => regcmp('$(etags0)', '$(jtags0)')" if => "tags0ok"; + "tags1ok => regcmp('$(etags1)', '$(jtags1)')" if => "tags1ok"; + "tags2ok => regcmp('$(etags2)', '$(jtags2)')" if => "tags2ok"; + "tags3ok => regcmp('$(etags3)', '$(jtags3)')" if => "tags3ok"; + + "tags0 NOT ok => regcmp('$(etags0)', '$(jtags0)')" if => "!tags0ok"; + "tags1 NOT ok => regcmp('$(etags1)', '$(jtags1)')" if => "!tags1ok"; + "tags2 NOT ok => regcmp('$(etags2)', '$(jtags2)')" if => "!tags2ok"; + "tags3 NOT ok => regcmp('$(etags3)', '$(jtags3)')" if => "!tags3ok"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/01_modules/set-tags.cf.txt b/tests/acceptance/08_commands/01_modules/set-tags.cf.txt new file mode 100644 index 0000000000..79f9025085 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/set-tags.cf.txt @@ -0,0 +1,12 @@ +^context=xyz +^meta=xyz,abc=def,,??what is this?? ++myclass +=myvar=hello there +@mylist={"abc", "def", "ghi"} +@myalist={"{{abc}}", " ' def}", "ghi'''"} +^meta=1,2,3 +@myblist={'{{a,bc}}', ' " de,f}', 'gh,,i"""'} ++mybclass +^meta=a,b,c +@myclist={"{{a'bc',,}}", ' ",, d"ef}', "ghi,},'''"} ++mycclass diff --git a/tests/acceptance/08_commands/01_modules/vars-from-module-have-source-and-derived_from-tags.cf b/tests/acceptance/08_commands/01_modules/vars-from-module-have-source-and-derived_from-tags.cf new file mode 100644 index 0000000000..79746b5d24 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/vars-from-module-have-source-and-derived_from-tags.cf @@ -0,0 +1,48 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-7725" } + string => "Test that vars defined by modules without explicit tags still have automatic tags identifying the source"; + + "test_soft_fail" + string => "windows", + meta => { "ENT-10217" }, + comment => "The subtest policy uses /bin/echo"; + + commands: + "/bin/echo" + args => "=my_var_from_module= my val from module", + module => "true"; +} + +bundle agent check +{ + vars: + "my_var_from_module_tags" + slist => getvariablemetatags( "echo.my_var_from_module" ); + + reports: + + # Every variable should have a source=SOMETHING tag + "$(this.promise_filename) Pass" + if => and( + reglist( @(my_var_from_module_tags), "source=.*" ), + reglist( @(my_var_from_module_tags), "derived_from=.*" ) + ); + "$(this.promise_filename) FAIL" + unless => and( + reglist( @(my_var_from_module_tags), "source=.*" ), + reglist( @(my_var_from_module_tags), "derived_from=.*" ) + ); + + EXTRA|DEBUG:: + "my_var_from_module_tags == $(my_var_from_module_tags)"; + "echo.my_var_from_module == $(echo.my_var_from_module)"; +} diff --git a/tests/acceptance/08_commands/01_modules/vars-without-explicit-tags-do-not-segfault.cf b/tests/acceptance/08_commands/01_modules/vars-without-explicit-tags-do-not-segfault.cf new file mode 100644 index 0000000000..ec07555744 --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/vars-without-explicit-tags-do-not-segfault.cf @@ -0,0 +1,33 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-7724", "ENT-7678" } + string => "Test that vars defined by modules without explicit tags does not crash the agent when there are vars defined by the module protocol that do not have explicit tags defined."; + + "test_skip_unsupported" + string => "windows", + comment => "The subtest policy uses /bin/echo"; + + commands: + "$(sys.cf_agent) -Kvf $(this.promise_filename).sub" + classes => results( "namespace", "sub_agent"); + +} +bundle agent check +{ + methods: + "Pass/Fail" + usebundle => dcs_passif_expected( "sub_agent_repaired", "sub_agent_not_kept", + "$(this.promise_filename)"); + + reports: + EXTRA|DEBUG:: + "sub_agent_.* classes: $(with)" with => join( ",", classesmatching( "sub_agent_.*" )); +} diff --git a/tests/acceptance/08_commands/01_modules/vars-without-explicit-tags-do-not-segfault.cf.sub b/tests/acceptance/08_commands/01_modules/vars-without-explicit-tags-do-not-segfault.cf.sub new file mode 100644 index 0000000000..a3c30c3e1f --- /dev/null +++ b/tests/acceptance/08_commands/01_modules/vars-without-explicit-tags-do-not-segfault.cf.sub @@ -0,0 +1,11 @@ +bundle agent main +{ + vars: + "my_var_in_policy" string => "my value in policy"; + "tagged" slist => variablesmatching(".*", "my_tag"); + + commands: + "/bin/echo" + args => "=my_var_from_module= my val from module", + module => "true"; +} \ No newline at end of file diff --git a/tests/acceptance/08_commands/02_syntax/arglist.cf b/tests/acceptance/08_commands/02_syntax/arglist.cf new file mode 100644 index 0000000000..7ac630f5fb --- /dev/null +++ b/tests/acceptance/08_commands/02_syntax/arglist.cf @@ -0,0 +1,35 @@ +####################################################### +# +# Test that arglist works properly +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + commands: + "$(G.echo)" arglist => { "=abc=2'3\"4" }, module => "true"; + # this will generate the command `echo =def=567 =ghi=8'9"a` + "$(G.echo)" args => "=def=567", arglist => { "=ghi=8'9\"a" }, module => "true"; +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(echo, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/08_commands/02_syntax/arglist.cf.expected.json b/tests/acceptance/08_commands/02_syntax/arglist.cf.expected.json new file mode 100644 index 0000000000..6d0b0f435b --- /dev/null +++ b/tests/acceptance/08_commands/02_syntax/arglist.cf.expected.json @@ -0,0 +1,4 @@ +{ + "abc": "2'3\"4", + "def": "567 =ghi=8'9\"a" +} diff --git a/tests/acceptance/08_commands/02_syntax/staging/001.cf b/tests/acceptance/08_commands/02_syntax/staging/001.cf new file mode 100644 index 0000000000..b12ec95474 --- /dev/null +++ b/tests/acceptance/08_commands/02_syntax/staging/001.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Test that backslashes are correctly passed to commands (Issue 471) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + edit_defaults => empty, + edit_line => setup_lines; +} + +body edit_defaults empty +{ + empty_file_before_editing => "true"; +} + +bundle edit_line setup_lines +{ + insert_lines: + "a test"; + "a.test"; + "nottest"; +} + +####################################################### + +bundle agent test +{ + vars: + "all_lines" string => execresult("$(G.egrep) -c '.' $(G.testfile)", "useshell"); + "re_lines" string => execresult("$(G.egrep) -c 'a.test' $(G.testfile)", "useshell"); + "lit_lines" string => execresult("$(G.egrep) -c 'a\.test' $(G.testfile)", "useshell"); + "doubleslash_lit_lines" string => execresult("$(G.egrep) -c 'a\\.test' $(G.testfile)", "useshell"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { + strcmp("$(test.all_lines)", "3"), + strcmp("$(test.re_lines)", "2"), + strcmp("$(test.lit_lines)", "1"), + strcmp("$(test.doubleslash_lit_lines)", "1") + }; + + reports: + DEBUG:: + "all_lines: $(test.all_lines) =?= 3"; + "re_lines: $(test.re_lines) =?= 2"; + "lit_lines: $(test.lit_lines) =?= 1"; + "doubleslash_lit_lines: $(test.doubleslash_lit_lines) =?= 1"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/03_shells/shelltypes.cf b/tests/acceptance/08_commands/03_shells/shelltypes.cf new file mode 100644 index 0000000000..2e5f28255a --- /dev/null +++ b/tests/acceptance/08_commands/03_shells/shelltypes.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test command useshell parameters +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "origtestdir" string => dirname("$(this.promise_filename)"); + + "shelltypes" slist => { "useshell_true", + "useshell_false", + "useshell_yes", + "useshell_no", + "useshell_on", + "useshell_off", + "useshell_noshell", + "useshell_useshell", + "useshell_powershell" + }; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + classes: + "$(init.shelltypes)" expression => regcmp(".*(Succeeded|Powershell is only supported on Windows).*", execresult("$(sys.cf_agent) -D $(init.shelltypes) -Kf $(init.origtestdir)$(const.dirsep)tryshell.cf.sub", "noshell")); + + "ok" and => { "@(init.shelltypes)" }; + + vars: + ok:: + "ok" string => "ok"; + + reports: + DEBUG:: + "$(init.shelltypes) test failed" + if => "!$(init.shelltypes)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("$(test.ok)", "ok"); + + reports: + DEBUG:: + "Tests powershell in commands promises."; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL "; +} + +### PROJECT_ID: core +### CATEGORY_ID: 26 diff --git a/tests/acceptance/08_commands/03_shells/text.txt b/tests/acceptance/08_commands/03_shells/text.txt new file mode 100644 index 0000000000..6cc7be057d --- /dev/null +++ b/tests/acceptance/08_commands/03_shells/text.txt @@ -0,0 +1 @@ +Succeeded diff --git a/tests/acceptance/08_commands/03_shells/tryshell.cf.sub b/tests/acceptance/08_commands/03_shells/tryshell.cf.sub new file mode 100644 index 0000000000..6edc1c9b39 --- /dev/null +++ b/tests/acceptance/08_commands/03_shells/tryshell.cf.sub @@ -0,0 +1,78 @@ +####################################################### +# +# Test command useshell parameters +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "init", "test" }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "origtestdir" string => dirname("$(this.promise_filename)"); +} + +####################################################### + +bundle agent test +{ + classes: + "shelltype_noshell" or => { "useshell_noshell", "useshell_false", "useshell_no", "useshell_off" }; + "shelltype_useshell" or => { "useshell_useshell", "useshell_true", "useshell_yes", "useshell_on" }; + "shelltype_powershell" or => { "useshell_powershell" }; + + vars: + useshell_true:: + "shelltype" string => "true"; + useshell_false:: + "shelltype" string => "false"; + useshell_yes:: + "shelltype" string => "yes"; + useshell_no:: + "shelltype" string => "no"; + useshell_on:: + "shelltype" string => "on"; + useshell_off:: + "shelltype" string => "off"; + useshell_noshell:: + "shelltype" string => "noshell"; + useshell_useshell:: + "shelltype" string => "useshell"; + useshell_powershell:: + "shelltype" string => "powershell"; + + shelltype_noshell.windows:: + "shellcmd" string => "c:\windows\system32\cmd.exe /C type"; + shelltype_noshell.!windows:: + "shellcmd" string => "/bin/cat"; + shelltype_useshell.windows:: + "shellcmd" string => "type"; + shelltype_useshell.!windows:: + "shellcmd" string => "cat"; + shelltype_powershell:: + "shellcmd" string => "Get-Content"; + + classes: + "shell_specified" or => { "shelltype_noshell", "shelltype_useshell", "shelltype_powershell" }; + + commands: + shell_specified:: + "$(shellcmd) $(init.origtestdir)$(const.dirsep)text.txt" + contain => shelltype; + + reports: + !shell_specified:: + "No shell type specified!"; +} + +body contain shelltype +{ + useshell => "$(test.shelltype)"; +} diff --git a/tests/acceptance/08_commands/arglist.cf b/tests/acceptance/08_commands/arglist.cf new file mode 100644 index 0000000000..8b344e1321 --- /dev/null +++ b/tests/acceptance/08_commands/arglist.cf @@ -0,0 +1,67 @@ +############################################################################## +# +# CFE-2724: Test that arglist attribute preserves white-spaces +# +############################################################################## + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +############################################################################## + +bundle agent init +{ + files: + "$(G.testfile).actual" + delete => tidy; + "$(G.testfile).sh" + perms => mog("700", "root", "root"), + content => '#!/bin/sh +for arg in "$(const.dollar)$(const.at)" +do + echo "$(const.dollar)arg" >> $(G.testfile).actual +done'; +} + +############################################################################## + +bundle agent test +{ + meta: + "description" -> { "CFE-2724" } + string => "Test that arglist attribute preserves white-spaces"; + "test_skip_unsupported" + string => "windows", + comment => "See ticket CFE-4294"; + + commands: + "$(G.testfile).sh" + args => "one two three", + arglist => { "four", "five six", " seven$(const.t)" }; +} + +############################################################################## + +bundle agent check +{ + vars: + "actual" + string => readfile("$(G.testfile).actual"); + "expected" + string => "one +two +three +four +five six + seven$(const.t) +"; + + methods: + "any" + usebundle => dcs_check_strcmp("$(actual)", "$(expected)", + "$(this.promise_filename)", "no"); +} diff --git a/tests/acceptance/08_commands/staging/default_failed_returncodes.cf b/tests/acceptance/08_commands/staging/default_failed_returncodes.cf new file mode 100644 index 0000000000..4b43f9035d --- /dev/null +++ b/tests/acceptance/08_commands/staging/default_failed_returncodes.cf @@ -0,0 +1,40 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + commands: + + "/bin/false" + classes => scoped_classes_generic("namespace", "default"), + comment => "Test that by default non zero commands return promise failure"; + + "/bin/false" + classes => scoped_classes_generic_kept_0("namespace", "specify_kept"), + comment => "Test that when specifying kept returncodes that failures are still registered"; +} + +bundle agent check +{ + methods: + "report" + usebundle => dcs_passif_expected("default_not_kept,specify_kept_not_kept", "", "$(this.promise_filename)") + inherit => "true"; +} + +body classes scoped_classes_generic_kept_0(scope, x) +{ + scope => "$(scope)"; + promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" }; + repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" }; + repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" }; + repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" }; + promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_not_repaired", "$(x)_reached" }; + kept_returncodes => { "0" }; + #repaired_returncodes => { "2" }; + #failed_returncodes => { "1" }; +} + diff --git a/tests/acceptance/09_services/custom.cf b/tests/acceptance/09_services/custom.cf new file mode 100644 index 0000000000..0830031e72 --- /dev/null +++ b/tests/acceptance/09_services/custom.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test services custom keyword +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "CFE-2402" }; + + services: + "myservice" + service_policy => "custom", + service_method => service_test; +} + +body service_method service_test { + service_bundle => test_services("$(this.promiser)","$(this.service_policy)"); +} + +bundle agent test_services(service, state) { + vars: + "service_state" string => "$(state)"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("custom", "$(test_services.service_state)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 39 diff --git a/tests/acceptance/09_services/disable.cf b/tests/acceptance/09_services/disable.cf new file mode 100644 index 0000000000..ad8d97aa1d --- /dev/null +++ b/tests/acceptance/09_services/disable.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test services disable keyword +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "CFE-2402" }; + + services: + "myservice" + service_policy => "disable", + service_method => service_test; +} + +body service_method service_test { + service_bundle => test_services("$(this.promiser)","$(this.service_policy)"); +} + +bundle agent test_services(service, state) { + vars: + "service_state" string => "$(state)"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("disable", "$(test_services.service_state)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 39 diff --git a/tests/acceptance/09_services/enable.cf b/tests/acceptance/09_services/enable.cf new file mode 100644 index 0000000000..86445a1963 --- /dev/null +++ b/tests/acceptance/09_services/enable.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test services enable keyword +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "CFE-2402" }; + + services: + "myservice" + service_policy => "enable", + service_method => service_test; +} + +body service_method service_test { + service_bundle => test_services("$(this.promiser)","$(this.service_policy)"); +} + +bundle agent test_services(service, state) { + vars: + "service_state" string => "$(state)"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("enable", "$(test_services.service_state)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 39 diff --git a/tests/acceptance/09_services/outcomes.cf b/tests/acceptance/09_services/outcomes.cf new file mode 100644 index 0000000000..fca336d67a --- /dev/null +++ b/tests/acceptance/09_services/outcomes.cf @@ -0,0 +1,53 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-2161" }; + + vars: + "services" slist => { "single" }; + "policies" slist => { "enable", "disable", "start", "stop", "restart", "reload", "custom" }; + + services: + "$(services)" + service_policy => "$(policies)", + classes => scoped_classes_generic("namespace", "$(services)_$(policies)"); +} + +bundle agent standard_services(service, state) +{ + reports: + EXTRA:: + "$(this.bundle): passthrough for $(service) -> $(state)"; +} + +bundle agent check +{ + vars: + "s" slist => { @(test.services) }; + "p" slist => { @(test.policies) }; + + methods: + "collect" usebundle => dcs_all_classes_to_string("$(s)_$(p)"), + inherit => "true", + useresult => "$(s)_$(p)"; + + EXTRA:: + "" usebundle => dcs_report_generic_classes("$(s)_$(p)"), + inherit => "true"; + + any:: + "report" usebundle => dcs_passif_expected('single_enable_kept,single_disable_kept,single_start_kept,single_stop_kept,single_restart_kept,single_reload_kept,single_custom_kept', + 'single_enable_repaired,single_enable_failed,single_enable_denied,single_enable_timeout,single_disable_repaired,single_disable_failed,single_disable_denied,single_disable_timeout,single_start_repaired,single_start_failed,single_start_denied,single_start_timeout,single_stop_repaired,single_stop_failed,single_stop_denied,single_stop_timeout,single_restart_repaired,single_restart_failed,single_restart_denied,single_restart_timeout,single_reload_repaired,single_reload_failed,single_reload_denied,single_reload_timeout,single_custom_repaired,single_custom_failed,single_custom_denied,single_custom_timeout', + $(this.promise_filename)); + + reports: + EXTRA:: + "Class strings for service $(s), policy $(p): $($(s)_$(p)[str])"; +} diff --git a/tests/acceptance/09_services/reload.cf b/tests/acceptance/09_services/reload.cf new file mode 100644 index 0000000000..058b011fa8 --- /dev/null +++ b/tests/acceptance/09_services/reload.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test services reload keyword +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "redmine4772,ENT-2161" }; + + services: + "myservice" + service_policy => "reload", + service_method => service_test; +} + +body service_method service_test { + service_bundle => test_services("$(this.promiser)","$(this.service_policy)"); +} + +bundle agent test_services(service, state) { + vars: + "service_state" string => "$(state)"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("reload", "$(test_services.service_state)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 39 diff --git a/tests/acceptance/09_services/restart.cf b/tests/acceptance/09_services/restart.cf new file mode 100644 index 0000000000..167557cd91 --- /dev/null +++ b/tests/acceptance/09_services/restart.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test services restart keyword +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "redmine4772,ENT-2161" }; + + services: + "myservice" + service_policy => "restart", + service_method => service_test; +} + +body service_method service_test { + service_bundle => test_services("$(this.promiser)","$(this.service_policy)"); +} + +bundle agent test_services(service, state) { + vars: + "service_state" string => "$(state)"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("restart", "$(test_services.service_state)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 39 diff --git a/tests/acceptance/09_services/service_cannot_be_resolved.cf b/tests/acceptance/09_services/service_cannot_be_resolved.cf new file mode 100644 index 0000000000..48161dd1d9 --- /dev/null +++ b/tests/acceptance/09_services/service_cannot_be_resolved.cf @@ -0,0 +1,57 @@ +####################################################### +# +# catch "Service ... cannot be resolved" inform message +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +bundle common test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "redmine4772,ENT-2161" }; + + classes: + "resolution_warning" expression => returnszero("$(command) Service | $(G.grep) 'cannot be resolved' 2>&1", "useshell"); + "myservice_found" expression => returnszero("$(command) 4441a73c9b58ff7f2285c018ee7449f35ec89712 2>&1", "useshell"); + + vars: + "command" string => "$(sys.cf_agent) -KI -f $(this.promise_filename).sub | $(G.grep)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!resolution_warning", "myservice_found" }; + + reports: + DEBUG.resolution_warning:: + "failure: service bundle 'cannot be resolved' error WAS found"; + DEBUG.!resolution_warning:: + "success: service bundle 'cannot be resolved' error was not found"; + DEBUG.myservice_found:: + "success: myservice report 4441a73c9b58ff7f2285c018ee7449f35ec89712 was found"; + DEBUG.!myservice_found:: + "failure: myservice report 4441a73c9b58ff7f2285c018ee7449f35ec89712 was NOT found"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/09_services/service_cannot_be_resolved.cf.sub b/tests/acceptance/09_services/service_cannot_be_resolved.cf.sub new file mode 100644 index 0000000000..a975643633 --- /dev/null +++ b/tests/acceptance/09_services/service_cannot_be_resolved.cf.sub @@ -0,0 +1,17 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { test }; +} + +bundle agent test +{ + services: + "myservice" service_policy => "start"; +} + +bundle agent standard_services(service, state) +{ + reports: + "$(this.bundle): bringing $(service) into desired state $(state) 4441a73c9b58ff7f2285c018ee7449f35ec89712"; +} diff --git a/tests/acceptance/09_services/standard_services-from-non-default-namespace.cf b/tests/acceptance/09_services/standard_services-from-non-default-namespace.cf new file mode 100644 index 0000000000..015b0a2ced --- /dev/null +++ b/tests/acceptance/09_services/standard_services-from-non-default-namespace.cf @@ -0,0 +1,60 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "ENT-5406" } + string => "Test that standard_services can be used from non-default namespace"; + + "test_soft_fail" string => "windows", + meta => { "ENT-2161" }; + + methods: + "Test" usebundle => ENT_5406:ns_test; +} + +bundle agent standard_services(service, state) +# @brief Mock implementation of standard_services. +# NOTE The stdlib in the MPF (masterfiles policy framework) contains a bundle of +# the same name, this mock implementation is used becasue we don't really want +# to test starting and stopping services, we only want to test that the correct +# bundle is selected. +{ + + files: + "$(G.testfile)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists( $(G.testfile) ); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body file control +{ + namespace => "ENT_5406"; +} + +bundle agent ns_test +{ + services: + "myservice" + service_policy => "start"; +} diff --git a/tests/acceptance/09_services/start.cf b/tests/acceptance/09_services/start.cf new file mode 100644 index 0000000000..9a9d9b7b86 --- /dev/null +++ b/tests/acceptance/09_services/start.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test services start keyword +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "redmine4772,ENT-2161" }; + + services: + "myservice" + service_policy => "start", + service_method => service_test; +} + +body service_method service_test { + service_bundle => test_services("$(this.promiser)","$(this.service_policy)"); +} + +bundle agent test_services(service, state) { + vars: + "service_state" string => "$(state)"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("start", "$(test_services.service_state)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 39 diff --git a/tests/acceptance/09_services/stop.cf b/tests/acceptance/09_services/stop.cf new file mode 100644 index 0000000000..212be2b2ff --- /dev/null +++ b/tests/acceptance/09_services/stop.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Test services stop keyword +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "redmine4772,ENT-2161" }; + + services: + "myservice" + service_policy => "stop", + service_method => service_test; +} + +body service_method service_test { + service_bundle => test_services("$(this.promiser)","$(this.service_policy)"); +} + +bundle agent test_services(service, state) { + vars: + "service_state" string => "$(state)"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("stop", "$(test_services.service_state)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 39 diff --git a/tests/acceptance/10_files/01_create/001.cf b/tests/acceptance/10_files/01_create/001.cf new file mode 100644 index 0000000000..9f7f7a0c51 --- /dev/null +++ b/tests/acceptance/10_files/01_create/001.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Create a file, check defaults +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + # We want default 0600, 1 link, zero size + !windows:: + "expect[permoct]" string => "600"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/002.cf b/tests/acceptance/10_files/01_create/002.cf new file mode 100644 index 0000000000..a933c790d5 --- /dev/null +++ b/tests/acceptance/10_files/01_create/002.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Create a file, check initial permissions (predictable mode) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0641"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + mode => "$(test.mode)"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/003.cf b/tests/acceptance/10_files/01_create/003.cf new file mode 100644 index 0000000000..502159a1cf --- /dev/null +++ b/tests/acceptance/10_files/01_create/003.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Create a file, check initial permissions (odd mode) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0146"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + mode => "$(test.mode)"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/004.cf b/tests/acceptance/10_files/01_create/004.cf new file mode 100644 index 0000000000..4f0015787d --- /dev/null +++ b/tests/acceptance/10_files/01_create/004.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Create a file, check initial permissions (strange mode) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "01010"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + mode => "$(test.mode)"; +} + +####################################################### + +bundle agent check +{ + vars: + !freebsd.!solaris.!windows:: + "expect[modeoct]" string => "[0-9]+$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + # + # OS X refuses to open(2) file you have no permissions for (which is required for safe_chmod), + # so this test is temporarily disabled there. + # + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok|darwin:: + "$(this.promise_filename) Pass"; + not_ok&!darwin:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/005.cf b/tests/acceptance/10_files/01_create/005.cf new file mode 100644 index 0000000000..cadc51e1af --- /dev/null +++ b/tests/acceptance/10_files/01_create/005.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Create a file, check initial permissions (strange setgid mode) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "02001"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + mode => "$(test.mode)"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "[0-9]+$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + # + # OS X refuses to open(2) file you have no permissions for (which is required for safe_chmod), + # so this test is temporarily disabled there. + # + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok|darwin:: + "$(this.promise_filename) Pass"; + not_ok&!darwin:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/006.cf b/tests/acceptance/10_files/01_create/006.cf new file mode 100644 index 0000000000..801b1298c7 --- /dev/null +++ b/tests/acceptance/10_files/01_create/006.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Create a file, check initial permissions (strange setuid mode) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "04777"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + mode => "$(test.mode)"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "[0-9]+$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/007.cf b/tests/acceptance/10_files/01_create/007.cf new file mode 100644 index 0000000000..dbccba68ad --- /dev/null +++ b/tests/acceptance/10_files/01_create/007.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Create a file, check owners/mode +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0644"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)); +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/008.cf b/tests/acceptance/10_files/01_create/008.cf new file mode 100644 index 0000000000..9a17e517fb --- /dev/null +++ b/tests/acceptance/10_files/01_create/008.cf @@ -0,0 +1,80 @@ +####################################################### +# +# Create a file, check owners/mode, different order - assumption, this is run +# by root (see notes below) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0644"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)); +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/009.cf b/tests/acceptance/10_files/01_create/009.cf new file mode 100644 index 0000000000..cc45ed8152 --- /dev/null +++ b/tests/acceptance/10_files/01_create/009.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Create a file, check owners/mode, different order - assumption, this is run +# by root (see notes below) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0644"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + mode => "$(test.mode)"; + # In the POSIX model, one cannot create a file for another user, one can only + # create a file as you, and then change owners. This test file will be created + # by root, but then changed to sys - and thus the promise to be owned by sys + # is kept. + owners => { "3", "1" }; + groups => { "3", "1" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/010.cf b/tests/acceptance/10_files/01_create/010.cf new file mode 100644 index 0000000000..3600663df7 --- /dev/null +++ b/tests/acceptance/10_files/01_create/010.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Create a file, check owner/group (single choice) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0644"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + mode => "$(test.mode)"; + owners => { "3" }; + groups => { "3" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/011.cf b/tests/acceptance/10_files/01_create/011.cf new file mode 100644 index 0000000000..aa20fc59e1 --- /dev/null +++ b/tests/acceptance/10_files/01_create/011.cf @@ -0,0 +1,63 @@ +####################################################### +# +# Create a fifo, check defaults +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +bundle agent test +{ + vars: + "mode" int => "0644"; + + files: + "$(G.testfile)" + create => "true", + file_type => "fifo"; +} + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "600"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/01_create/012.cf b/tests/acceptance/10_files/01_create/012.cf new file mode 100644 index 0000000000..ec9f49d929 --- /dev/null +++ b/tests/acceptance/10_files/01_create/012.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Create a file, change perms +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +bundle agent test +{ + vars: + "mode" int => "0644"; + + files: + "$(G.testfile)" + create => "true", + file_type => "fifo", + perms => test_perms; +} + +body perms test_perms +{ + mode => "$(test.mode)"; + owners => { "3" }; + groups => { "3" }; +} + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + any:: + "expect[nlink]" string => "1"; + "expect[uid]" string => "\d+"; + "expect[gid]" string => "\d+"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/01_create/cfengine_create_by_default.cf b/tests/acceptance/10_files/01_create/cfengine_create_by_default.cf new file mode 100644 index 0000000000..7c0ac6e174 --- /dev/null +++ b/tests/acceptance/10_files/01_create/cfengine_create_by_default.cf @@ -0,0 +1,129 @@ +############################################################################## +# +# template_method cfengine should create files by default, unless +# `create => "false"` is specified. +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +############################################################################## + +bundle agent init +{ + vars: + "range" + slist => { expandrange("[1-4]", 1) }; + + files: + "$(G.testfile).test_$(range)" + delete => tidy; + + "$(G.testfile).test_5" + create => "true"; + + "$(G.testfile).valid_template" + content => "[%CFEngine BEGIN %] +I have $(const.dollar)(sys.cpus) CPU's! +[%CFEngine END %] +"; + + "$(G.testfile).invalid_template" + content => "[%CFEngine BEGIN %] +I have $(const.dollar)(sys.cpus) CPU's! +[%CFEngine END %) +"; +} + +############################################################################## + +bundle agent test +{ + meta: + "description" -> { "CFE-3955" } + string => "template_method cfengine creates promiser by default"; + + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + files: + # File should be created by default and rendered with content + "$(G.testfile).test_1" + template_method => "cfengine", + edit_template => "$(G.testfile).valid_template", + if => fileexists("$(G.testfile).valid_template"); + + # File should not be created due to invalid template + "$(G.testfile).test_2" + template_method => "cfengine", + edit_template => "$(G.testfile).invalid_template", + if => fileexists("$(G.testfile).invalid_template"); + + # File should be created even though template is invalid + "$(G.testfile).test_3" + create => "true", + template_method => "cfengine", + edit_template => "$(G.testfile).invalid_template", + if => fileexists("$(G.testfile).invalid_template"); + + # File should not be created even though template is valid + "$(G.testfile).test_4" + create => "false", + template_method => "cfengine", + edit_template => "$(G.testfile).valid_template", + if => fileexists("$(G.testfile).valid_template"); + + # File should be rendered with content, since it already exists + "$(G.testfile).test_5" + create => "false", + template_method => "cfengine", + edit_template => "$(G.testfile).valid_template", + if => fileexists("$(G.testfile).valid_template"); +} + +############################################################################## + +bundle agent check +{ + vars: + # Get lists of successful / failed checks so we can report them + "checks" + slist => { expandrange("check_[1-5]", 1) }; + "successful_checks" + slist => sort(classesmatching("check_[1-5]"), "lex"); + "failed_checks" + slist => sort(difference("checks", "successful_checks"), "lex"); + + classes: + "check_1" + expression => regcmp("I have [0-9]+ CPU's!", + readfile("$(G.testfile).test_1")), + if => fileexists("$(G.testfile).test_1"); + "check_2" + expression => not(fileexists("$(G.testfile).test_2")); + "check_3" + expression => fileexists("$(G.testfile).test_3"); + "check_4" + expression => not(fileexists("$(G.testfile).test_4")); + "check_5" + expression => regcmp("I have [0-9]+ CPU's!", + readfile("$(G.testfile).test_5")), + if => fileexists("$(G.testfile).test_5"); + "ok" + expression => and("check_1", "check_2", "check_3", "check_4", + "check_5"); + + reports: + DEBUG:: + "'$(successful_checks)' succeded!"; + "'$(failed_checks)' failed!"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/01_create/content_create_by_default.cf b/tests/acceptance/10_files/01_create/content_create_by_default.cf new file mode 100644 index 0000000000..3d9ddb3dc8 --- /dev/null +++ b/tests/acceptance/10_files/01_create/content_create_by_default.cf @@ -0,0 +1,110 @@ +############################################################################## +# +# content files promises shall create files by default unless `create => +# "false"` is specified. +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +############################################################################## + +bundle agent init +{ + vars: + "delete_range" + slist => { expandrange("[1-3]", 1) }; + "create_range" + slist => { expandrange("[4-6]", 1) }; + + files: + "$(G.testfile)_$(delete_range)" + delete => tidy; + "$(G.testfile)_$(create_range)" + create => "true"; +} + +############################################################################## + +bundle agent test +{ + meta: + "description" -> { "CFE-3916" } + string => "files with content attribute shall create promiser by default"; + + files: + # File should be created by default + "$(G.testfile)_1" + content => "Hello World!"; + + "$(G.testfile)_2" + create => "true", + content => "Hello World!"; + + "$(G.testfile)_3" + create => "false", + content => "Hello World!"; + + "$(G.testfile)_4" + content => "Hello World!"; + + "$(G.testfile)_5" + create => "true", + content => "Hello World!"; + + "$(G.testfile)_6" + create => "false", + content => "Hello World!"; +} + +############################################################################## + +bundle agent check +{ + vars: + # Get lists of successful / failed checks so we can report them + "checks" + slist => { expandrange("check_[1-6]", 1) }; + "successful_checks" + slist => sort(classesmatching("check_[1-6]"), "lex"); + "failed_checks" + slist => sort(difference("checks", "successful_checks"), "lex"); + + classes: + "check_1" + expression => strcmp(readfile("$(G.testfile)_1"), "Hello World!"), + if => fileexists("$(G.testfile)_1"); + "check_2" + expression => strcmp(readfile("$(G.testfile)_2"), "Hello World!"), + if => fileexists("$(G.testfile)_2"); + "check_3" + expression => not(fileexists("$(G.testfile)_3")); + "check_4" + expression => strcmp(readfile("$(G.testfile)_4"), "Hello World!"), + if => fileexists("$(G.testfile)_4"); + "check_5" + expression => strcmp(readfile("$(G.testfile)_5"), "Hello World!"), + if => fileexists("$(G.testfile)_5"); + "check_6" + expression => strcmp(readfile("$(G.testfile)_6"), "Hello World!"), + if => fileexists("$(G.testfile)_6"); + "ok" + expression => and("check_1", "check_2", "check_3", "check_4", + "check_5", "check_6"); + + methods: + "Pass/Fail" + usebundle => dcs_passif("ok", $(this.promise_filename)), + inherit => "true"; # We want dcs_passif to inhert bundle scoped classes from our check bundle + + reports: + DEBUG:: + "'$(successful_checks)' succeded!"; + "'$(failed_checks)' failed!"; + +} diff --git a/tests/acceptance/10_files/01_create/copy_and_purge.cf b/tests/acceptance/10_files/01_create/copy_and_purge.cf new file mode 100644 index 0000000000..d184a285b0 --- /dev/null +++ b/tests/acceptance/10_files/01_create/copy_and_purge.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Redmine #3429: copy and purge files and directories should purge empty directories too +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ +} + +####################################################### + +bundle agent init +{ + methods: + "1" usebundle => init1; + "2" usebundle => init2; +} + +bundle agent init1 +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +bundle agent init2 +{ + files: + "$(G.testfile)/source/1/2/3/." + create => "true"; + + "$(G.testfile)/dest/1/2/should-be-removed/3/." + create => "true"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)/dest/." + create => "true", + depth_search => test_recurse("inf"), + action => test_immediate, + copy_from => test_sync("$(G.testfile)/source"); +} + +body depth_search test_recurse(d) + +{ + depth => "$(d)"; + xdev => "true"; +} + +body action test_immediate +{ + ifelapsed => "0"; +} + +body copy_from test_sync(from) +{ + source => "$(from)"; + purge => "true"; + preserve => "false"; + type_check => "false"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok_copy" expression => fileexists("$(G.testfile)/dest/1/2/3"); + "ok_purge" not => fileexists("$(G.testfile)/dest/1/2/should-be-removed"); + "bad_copy" not => fileexists("$(G.testfile)/dest/1/2/3"); + "bad_purge" expression => fileexists("$(G.testfile)/dest/1/2/should-be-removed"); + + "ok" and => { ok_copy, ok_purge }; + + reports: + DEBUG:: + "The copy worked" if => "ok_copy"; + "The purge worked" if => "ok_purge"; + "The copy failed" if => "bad_copy"; + "The purge failed" if => "bad_purge"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/directory_with_trailing_slash.cf b/tests/acceptance/10_files/01_create/directory_with_trailing_slash.cf new file mode 100644 index 0000000000..2a6b9751d4 --- /dev/null +++ b/tests/acceptance/10_files/01_create/directory_with_trailing_slash.cf @@ -0,0 +1,85 @@ +####################################################### +# +# Redmine#3905: Create a directory, set permissions using a trailing slash +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "mode" string => "751"; + + methods: + "1" usebundle => test1; # create the directory + "1" usebundle => test2($(mode)); # promise the directory with a trailing slash +} + +bundle agent test1 +{ + files: + "$(G.testfile)/." + create => "true", + perms => test_perms("700"); +} + +bundle agent test2(mode) +{ + files: + "$(G.testfile)/" + perms => test_perms($(mode)); +} + +body perms test_perms(m) +{ + mode => "$(m)"; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat($(G.testfile), "permoct"); + + classes: + "ok" expression => strcmp($(test.mode), "$(result)"); + + reports: + DEBUG:: + "expected: '$(test.mode)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/01_create/mustache_create_by_default.cf b/tests/acceptance/10_files/01_create/mustache_create_by_default.cf new file mode 100644 index 0000000000..90af9518a8 --- /dev/null +++ b/tests/acceptance/10_files/01_create/mustache_create_by_default.cf @@ -0,0 +1,151 @@ +############################################################################## +# +# template_method mustache and inline_mustache should create files if they are +# absent by default. But file should not be created in the cases where promise +# is not kept. +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +############################################################################## + +bundle agent init +{ + vars: + "range" + slist => { expandrange("[1-8]", 1) }; + + files: + "$(G.testfile).test_$(range)" + delete => tidy; + + "$(G.testfile).valid_template" + content => "Hello {{{name}}}"; + + "$(G.testfile).invalid_template" + content => "Hello {{{name"; +} + +############################################################################## + +bundle agent test +{ + meta: + "description" -> { "ENT-4792" } + string => "mustache and inline_mustache creates promiser by default"; + + vars: + "d" + data => '{ "name": "Lars" }'; + + files: + # File should be created by default + "$(G.testfile).test_1" + template_method => "mustache", + edit_template => "$(G.testfile).valid_template", + if => fileexists("$(G.testfile).valid_template"), + template_data => @(d); + + # File should not be created due to invalid template + "$(G.testfile).test_2" + template_method => "mustache", + edit_template => "$(G.testfile).invalid_template", + if => fileexists("$(G.testfile).invalid_template"), + template_data => @(d); + + # File should be created even though template is invalid + "$(G.testfile).test_3" + create => "true", + template_method => "mustache", + edit_template => "$(G.testfile).invalid_template", + if => fileexists("$(G.testfile).invalid_template"), + template_data => @(d); + + # File should not be created even though template is valid + "$(G.testfile).test_4" + create => "false", + template_method => "mustache", + edit_template => "$(G.testfile).valid_template", + if => fileexists("$(G.testfile).valid_template"), + template_data => @(d); + + # File should be created by default + "$(G.testfile).test_5" + template_method => "inline_mustache", + edit_template_string => readfile("$(G.testfile).valid_template"), + if => fileexists("$(G.testfile).valid_template"), + template_data => @(d); + + # File should not be created due to invalid template + "$(G.testfile).test_6" + template_method => "inline_mustache", + edit_template_string => readfile("$(G.testfile).invalid_template"), + if => fileexists("$(G.testfile).invalid_template"), + template_data => @(d); + + # File should be created even though template is invalid + "$(G.testfile).test_7" + create => "true", + template_method => "inline_mustache", + edit_template_string => readfile("$(G.testfile).invalid_template"), + if => fileexists("$(G.testfile).invalid_template"), + template_data => @(d); + + # File should not be created even though template is valid + "$(G.testfile).test_8" + create => "false", + template_method => "inline_mustache", + edit_template_string => readfile("$(G.testfile).valid_template"), + if => fileexists("$(G.testfile).valid_template"), + template_data => @(d); +} + +############################################################################## + +bundle agent check +{ + vars: + # Get lists of successful / failed checks so we can report them + "checks" + slist => { expandrange("check_[1-8]", 1) }; + "successful_checks" + slist => sort(classesmatching("check_[1-8]"), "lex"); + "failed_checks" + slist => sort(difference("checks", "successful_checks"), "lex"); + + classes: + "check_1" + expression => fileexists("$(G.testfile).test_1"); + "check_2" + expression => not(fileexists("$(G.testfile).test_2")); + "check_3" + expression => fileexists("$(G.testfile).test_3"); + "check_4" + expression => not(fileexists("$(G.testfile).test_4")); + "check_5" + expression => fileexists("$(G.testfile).test_5"); + "check_6" + expression => not(fileexists("$(G.testfile).test_6")); + "check_7" + expression => fileexists("$(G.testfile).test_7"); + "check_8" + expression => not(fileexists("$(G.testfile).test_8")); + "ok" + expression => and("check_1", "check_2", "check_3", "check_4", + "check_5", "check_6", "check_7", "check_8"); + + reports: + DEBUG:: + "'$(successful_checks)' succeded!"; + "'$(failed_checks)' failed!"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/01_create/perms-mode.cf b/tests/acceptance/10_files/01_create/perms-mode.cf new file mode 100644 index 0000000000..d86cdb8594 --- /dev/null +++ b/tests/acceptance/10_files/01_create/perms-mode.cf @@ -0,0 +1,122 @@ +####################################################### +# +# Redmine #4791: body perms mode +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + vars: + freebsd|solaris:: + "modes" slist => { "1", "333", "100", "200", "777", "o=x", "ugo=wx", "u=x", "u=w", "ugo=rwx" }; + !freebsd.!solaris:: + "modes" slist => { "1", "333", "100", "200", "777", "1111", "1010", "13400", "22222", "o=x", "ugo=wx", "u=x", "u=w", "ugo=rwx" }; + + files: + "$(G.testdir)/$(modes)" + create => "true", + perms => test_mode($(modes)); +} + +body perms test_mode(m) +{ + mode => "$(m)"; +} + +bundle agent check +{ + vars: + "modes" slist => { @(test.modes) }; + windows:: + "win_r" string => "100444"; + "win_w" string => "100666"; + "expected" data => parsejson(' +{ + "1": "$(win_r)", + "333": "$(win_w)", + "100": "$(win_r)", + "200": "$(win_w)", + "777": "$(win_w)", + "1111": "$(win_r)", + "1010": "$(win_r)", + "13400": "$(win_r)", + "22222": "$(win_r)", + "o=x": "$(win_r)", + "ugo=wx": "$(win_w)", + "u=x": "$(win_r)", + "u=w": "$(win_w)", + "ugo=rwx": "$(win_w)", +}'); + + freebsd|solaris:: + "expected" data => parsejson(' +{ + "1": "100001", + "333": "100333", + "100": "100100", + "200": "100200", + "777": "100777", + "o=x": "100001", + "ugo=wx": "100333", + "u=x": "100100", + "u=w": "100200", + "ugo=rwx": "100777", +}'); + + !freebsd.!solaris.!windows:: + "expected" data => parsejson(' +{ + "1": "100001", + "333": "100333", + "100": "100100", + "200": "100200", + "777": "100777", + "1111": "101111", + "1010": "101010", + "13400": "103400", + "22222": "102222", + "o=x": "100001", + "ugo=wx": "100333", + "u=x": "100100", + "u=w": "100200", + "ugo=rwx": "100777", +}'); + + any:: + "actual[$(modes)]" string => filestat("$(G.testdir)/$(modes)", "modeoct"); + "canonified[$(modes)]" string => canonify("$(modes)"); + + classes: + "ok_$(canonified[$(modes)])" expression => strcmp("$(expected[$(modes)])", "$(actual[$(modes)])"); + + freebsd|solaris:: + "ok" and => { "ok_1", "ok_333", "ok_100", "ok_200", "ok_777", classify("ok_o=x"), classify("ok_ugo=wx"), classify("ok_u=x"), classify("ok_u=w"), classify("ok_ugo=rwx") }; + + !freebsd.!solaris:: + "ok" and => { "ok_1", "ok_333", "ok_100", "ok_200", "ok_777", "ok_1111", "ok_1010", "ok_13400", "ok_22222", classify("ok_o=x"), classify("ok_ugo=wx"), classify("ok_u=x"), classify("ok_u=w"), classify("ok_ugo=rwx") }; + + reports: + DEBUG:: + "Permission $(modes) worked, got $(expected[$(modes)])" if => "ok_$(canonified[$(modes)])"; + "Permission $(modes) failed, expected $(expected[$(modes)]) != actual $(actual[$(modes)])" if => "!ok_$(canonified[$(modes)])"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/001.cf b/tests/acceptance/10_files/02_maintain/001.cf new file mode 100644 index 0000000000..ae1482af9c --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/001.cf @@ -0,0 +1,85 @@ +####################################################### +# +# Create a file, expect simultaneous copy with default compare to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)), + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_group)"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/002.cf b/tests/acceptance/10_files/02_maintain/002.cf new file mode 100644 index 0000000000..67a1f9ebd0 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/002.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Create a file, expect simultaneous copy with mtime compare to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)), + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_group)"; + compare => "mtime"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/003.cf b/tests/acceptance/10_files/02_maintain/003.cf new file mode 100644 index 0000000000..ef4bbcff78 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/003.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Create a file, expect simultaneous copy with ctime compare to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)), + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_group)"; + compare => "ctime"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/004.cf b/tests/acceptance/10_files/02_maintain/004.cf new file mode 100644 index 0000000000..77523864ad --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/004.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Create a file, expect simultaneous copy with atime compare to succeed +# but not change owner/group +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" string => filestat("$(G.etc_group)", "modeoct"); + + files: + "$(G.testfile)" + create => "true", + perms => test_og, + copy_from => test_copy; +} + +body perms test_og +{ + owners => { "3" }; + groups => { "3" }; +} + +body copy_from test_copy +{ + preserve => "true"; + source => "$(G.etc_group)"; + compare => "atime"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "$(test.mode)"; + "expect[uid]" string => "3"; + "expect[gid]" string => "3"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/005.cf b/tests/acceptance/10_files/02_maintain/005.cf new file mode 100644 index 0000000000..d7854d438f --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/005.cf @@ -0,0 +1,85 @@ +####################################################### +# +# Create a file, expect simultaneous copy with hash compare to succeed +# but not change owner/group +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" string => filestat("$(G.etc_group)", "modeoct"); + + files: + "$(G.testfile)" + create => "true", + perms => test_og, + copy_from => test_copy; +} + +body perms test_og +{ + owners => { "3" }; + groups => { "3" }; +} + +body copy_from test_copy +{ + preserve => "true"; + source => "$(G.etc_group)"; + compare => "hash"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "$(test.mode)"; + "expect[uid]" string => "3"; + "expect[gid]" string => "3"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/006.cf b/tests/acceptance/10_files/02_maintain/006.cf new file mode 100644 index 0000000000..0cb520202b --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/006.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Create a file, expect simultaneous copy with digest compare to succeed +# but not change owner/group +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" string => filestat("$(G.etc_group)", "modeoct"); + + files: + "$(G.testfile)" + create => "true", + perms => test_og, + copy_from => test_copy; +} + +body perms test_og +{ + owners => { "3" }; + groups => { "3" }; +} + +body copy_from test_copy +{ + preserve => "true"; + source => "$(G.etc_group)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "$(test.mode)"; + "expect[uid]" string => "3"; + "expect[gid]" string => "3"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/007.cf b/tests/acceptance/10_files/02_maintain/007.cf new file mode 100644 index 0000000000..9752a459aa --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/007.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Create a file, expect simultaneous copy with binary compare to succeed +# but not change owner/group +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + "$(G.testdir)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" string => filestat("$(G.etc_group)", "modeoct"); + + files: + "$(G.testfile)" + create => "true", + perms => test_og, + copy_from => test_copy; +} + +body perms test_og +{ + owners => { "3" }; + groups => { "3" }; +} + +body copy_from test_copy +{ + source => "$(G.etc_group)"; + compare => "binary"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "$(test.mode)"; + "expect[uid]" string => "3"; + "expect[gid]" string => "3"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testdir)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/008.cf b/tests/acceptance/10_files/02_maintain/008.cf new file mode 100644 index 0000000000..f23fd4d742 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/008.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Create a file, expect simultaneous copy with exists compare to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)), + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_group)"; + compare => "exists"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/009.cf b/tests/acceptance/10_files/02_maintain/009.cf new file mode 100644 index 0000000000..7985665dda --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/009.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Test that copying file by relative symlink works +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/linkdir/" + comment => "Create a directory."; + "$(G.testdir)/linkdir/another/" + comment => "Create another directory."; + "$(G.testdir)/linkdir/another/target" + comment => "A target file.", + create => "true"; + "$(G.testdir)/linkdir/link" + comment => "Create a relative link to the target.", + link_from => ln_s("$(G.testdir)/linkdir/another/target"); +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testdir)/copy_file" + comment => "Copy the file behind the link.", + perms => test_perms($(mode)), + copy_from => cp_2_file("$(G.testdir)/linkdir/link"); +} + +body link_from ln_s(x) { + link_type => "relative"; + source => "$(x)"; + when_no_source => "nop"; +} + +body copy_from cp_2_file(x) { + source => "$(x)"; + compare => "binary"; + copy_backup => "false"; + copylink_patterns => { ".*" }; +} + +body perms test_perms(m) { + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "\d+$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)$(const.dirsep)copy_file", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/010.cf b/tests/acceptance/10_files/02_maintain/010.cf new file mode 100644 index 0000000000..1bac08e030 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/010.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Test that symlinks are set aside while copying trees of directories (Issue 569) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/srcdir/foo" + create => "true"; + "$(G.testdir)/realdestdir/" + create => "true"; + "$(G.testdir)/destdir" + comment => "Create a relative link to the target.", + link_from => ln_s("$(G.testdir)/realdestdir"); +} + + +body link_from ln_s(x) { + link_type => "relative"; + source => "$(x)"; + when_no_source => "nop"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testdir)/destdir/" + copy_from => test_sync_cp("$(G.testdir)/srcdir"), + depth_search => recurse("inf"), + move_obstructions => "true", + action => if_elapsed(0); +} + +body copy_from test_sync_cp(from) +{ + source => "$(from)"; + purge => "true"; + preserve => "true"; + type_check => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "directory"; + "result" string => filestat("$(G.testdir)/destdir", "type"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/011.cf b/tests/acceptance/10_files/02_maintain/011.cf new file mode 100644 index 0000000000..4528327df2 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/011.cf @@ -0,0 +1,75 @@ +####################################################### +# +# Test that symlinks outside of copying root are NOT set aside while copying trees of directorise (Issue 585) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/srcdir/" + create => "true"; + "$(G.testdir)/srcdir/foo" + create => "true"; + "$(G.testdir)/realdestdir/" + create => "true"; + "$(G.testdir)/destdir" + comment => "Create a relative link to the target.", + link_from => ln_s("$(G.testdir)/realdestdir"); +} + + +body link_from ln_s(x) { + link_type => "relative"; + source => "$(x)"; + when_no_source => "nop"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testdir)/destdir/foo" + copy_from => test_sync_cp("$(G.testdir)/srcdir/foo"); +} + +body copy_from test_sync_cp(from) +{ + source => "$(from)"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "symlink"; + "result" string => filestat("$(G.testdir)/destdir", "type"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/012.cf b/tests/acceptance/10_files/02_maintain/012.cf new file mode 100644 index 0000000000..cff3d2e61c --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/012.cf @@ -0,0 +1,80 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/srcdir/" + create => "true"; + "$(G.testdir)/srcdir/intermediate/" + create => "true"; + "$(G.testdir)/srcdir/intermediate/intermediate2/" + create => "true"; + "$(G.testdir)/srcdir/intermediate/intermediate2/foo" + create => "true"; + + "$(G.testdir)/realdestdir/" + create => "true"; + "$(G.testdir)/destdir" + comment => "Create a relative link to the target.", + link_from => ln_s("$(G.testdir)/realdestdir"); +} + + +body link_from ln_s(x) { + link_type => "relative"; + source => "$(x)"; + when_no_source => "nop"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testdir)/destdir/intermediate/intermediate2/foo" + copy_from => test_sync_cp("$(G.testdir)/srcdir/intermediate/intermediate2/foo"); +} + +body copy_from test_sync_cp(from) +{ + source => "$(from)"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "symlink"; + "result" string => filestat("$(G.testdir)/destdir", "type"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/013.cf b/tests/acceptance/10_files/02_maintain/013.cf new file mode 100644 index 0000000000..6351ed1a16 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/013.cf @@ -0,0 +1,83 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/srcdir/" + create => "true"; + "$(G.testdir)/srcdir/intermediate/" + create => "true"; + "$(G.testdir)/srcdir/intermediate/intermediate2/" + create => "true"; + "$(G.testdir)/srcdir/intermediate/intermediate2/foo" + create => "true"; + + "$(G.testdir)/realdestdir/" + create => "true"; + "$(G.testdir)/destdir" + comment => "Create a relative link to the target.", + link_from => ln_s("$(G.testdir)/realdestdir"); +} + + +body link_from ln_s(x) { + link_type => "relative"; + source => "$(x)"; + when_no_source => "nop"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testdir)/destdir" + copy_from => test_sync_cp("$(G.testdir)/srcdir"), + depth_search => recurse("inf"); +} + +body copy_from test_sync_cp(from) +{ + source => "$(from)"; + purge => "true"; + preserve => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "symlink"; + "result" string => filestat("$(G.testdir)/destdir", "type"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/015.cf b/tests/acceptance/10_files/02_maintain/015.cf new file mode 100644 index 0000000000..c7b4c517db --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/015.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Test that we can supply relative filename to link_from +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destdir" + link_from => sync_cp("./srcdirrr"); +} + +body link_from sync_cp(from) +{ + source => "$(from)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/016.cf b/tests/acceptance/10_files/02_maintain/016.cf new file mode 100644 index 0000000000..3bacebe06d --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/016.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Test that timestamped copying adds .cfsaved suffix (Issue 666) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/afile" + create => "true"; + + # We can't use files promise here as it will remember we have created the file + # and will not generate backup file which we are trying to get. + commands: + "$(G.echo) LimpBizkit > $(G.testdir)/destfile" + contain => shell; +} + +body contain shell +{ + useshell => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + files: + "$(G.testdir)/destfile" + copy_from => cp_2_file("$(G.testdir)/afile"); +} + +body copy_from cp_2_file(x) { + source => "$(x)"; + copy_backup => "timestamp"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + files: + "$(G.testdir)/destfile.*\.cfsaved" + touch => "true", + classes => if_repaired("ok"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/017.cf b/tests/acceptance/10_files/02_maintain/017.cf new file mode 100644 index 0000000000..45646e761c --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/017.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test that copy_from body with source does not crash (Issue 687) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/." + create => "true", + move_obstructions => "true", + copy_from => update_nobackup; +} + +body copy_from update_nobackup() +{ + copy_backup => "false"; +} + +####################################################### + +bundle agent check +{ + reports: + cfengine_3:: + "$(this.promise_filename) Pass"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/018.cf b/tests/acceptance/10_files/02_maintain/018.cf new file mode 100644 index 0000000000..fbfd023f95 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/018.cf @@ -0,0 +1,64 @@ +####################################################### +# +# Test that disable_mode => "000" in body rename works (Issue 688) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testfile)" + rename => disable_file; +} + +body rename disable_file { + disable_mode => "000"; + disable => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect[permoct]" string => "0"; + "expect[nlink]" string => "1"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile).cfdisabled", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} +## PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/019.cf b/tests/acceptance/10_files/02_maintain/019.cf new file mode 100644 index 0000000000..93b5865a87 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/019.cf @@ -0,0 +1,64 @@ +####################################################### +# +# Test that disable_mode => "333" in body rename works (Issue 688) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testfile)" + rename => disable_file; +} + +body rename disable_file { + disable_mode => "333"; + disable => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect[permoct]" string => "333"; + "expect[nlink]" string => "1"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile).cfdisabled", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/101.cf b/tests/acceptance/10_files/02_maintain/101.cf new file mode 100644 index 0000000000..f182e6caf4 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/101.cf @@ -0,0 +1,85 @@ +####################################################### +# +# Create a file, expect simultaneous default link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)), + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/102.cf b/tests/acceptance/10_files/02_maintain/102.cf new file mode 100644 index 0000000000..f44438d8fc --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/102.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Create a file, expect simultaneous hard link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)), + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "hardlink"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/103.cf b/tests/acceptance/10_files/02_maintain/103.cf new file mode 100644 index 0000000000..4cf7b9acd6 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/103.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Create a file, expect simultaneous symbolic link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)), + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "symlink"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + "expect[permoct]" string => "$(test.mode)"; + "expect[nlink]" string => "1"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/104.cf b/tests/acceptance/10_files/02_maintain/104.cf new file mode 100644 index 0000000000..b8752f3661 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/104.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Create a file, expect simultaneous relative symbolic link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(mode)), + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "relative"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + "expect[permoct]" string => "$(test.mode)"; + "expect[nlink]" string => "1"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/105.cf b/tests/acceptance/10_files/02_maintain/105.cf new file mode 100644 index 0000000000..e70c9957ee --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/105.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Create a file, expect simultaneous absolute symbolic link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile).SOURCE" + create => "true"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms($(test.mode)), + link_from => test_link; +} + +body link_from test_link +{ + link_type => "absolute"; + source => "$(G.testfile).SOURCE"; +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + "expect[permoct]" string => "$(test.mode)"; + "expect[nlink]" string => "1"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/106.x.cf b/tests/acceptance/10_files/02_maintain/106.x.cf new file mode 100644 index 0000000000..4c92238e6f --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/106.x.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Test wrong type of link_type. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "none"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect[permoct]" string => "$(test.mode)"; + "expect[nlink]" string => "1"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/111.x.cf b/tests/acceptance/10_files/02_maintain/111.x.cf new file mode 100644 index 0000000000..f4192ced8d --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/111.x.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Create a file, expect simultaneous none no-filename link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + link_from => test_link; +} + +body link_from test_link +{ + link_type => "none"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/112.cf b/tests/acceptance/10_files/02_maintain/112.cf new file mode 100644 index 0000000000..4e7bf560c3 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/112.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Create a file, expect simultaneous link with default and move_obstructions +# to succeed +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "inode" string => filestat("$(G.etc_group)", "ino"); + + files: + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat(filestat("$(G.testfile)", "linktarget"), "ino"); + + classes: + "ok" expression => strcmp("$(test.inode)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(test.inode)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/113.cf b/tests/acceptance/10_files/02_maintain/113.cf new file mode 100644 index 0000000000..07cef282b9 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/113.cf @@ -0,0 +1,75 @@ +####################################################### +# +# Create a file, expect simultaneous link with hardlink and move_obstructions +# to succeed +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + + "$(G.testfile).SOURCE" + create => "true"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.testfile).SOURCE"; + link_type => "hardlink"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => filestat("$(G.testfile).SOURCE", "ino"); + "result" string => filestat("$(G.testfile)", "ino"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/114.cf b/tests/acceptance/10_files/02_maintain/114.cf new file mode 100644 index 0000000000..a2542fbd0d --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/114.cf @@ -0,0 +1,76 @@ +####################################################### +# +# Create a file, expect simultaneous link with symlink and move_obstructions +# to succeed +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "inode" string => filestat("$(G.etc_group)", "ino"); + + files: + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "symlink"; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat(filestat("$(G.testfile)", "linktarget"), "ino"); + "Lresult" string => filestat("$(G.testfile)", "ino"); + + classes: + "okL" not => strcmp("$(test.inode)", "$(Lresult)"); + "ok" and => { "okL", strcmp("$(test.inode)", "$(result)") }; + + reports: + DEBUG:: + "expected: '$(test.inode)'"; + "got: '$(Lresult)' => '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/201.cf b/tests/acceptance/10_files/02_maintain/201.cf new file mode 100644 index 0000000000..204831cf52 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/201.cf @@ -0,0 +1,104 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + any:: + "mode" int => "01751"; + + pass2.!windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body perms init_perms(m) +{ + mode => "$(m)"; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + + files: + "$(G.testfile)" + create => "true", + perms => test_perms("$(init.mode)"); +} + +body perms test_perms(m) +{ + mode => "$(m)"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "ok" expression => strcmp("$(init.result)", "$(result)"); + "not_ok" not => regcmp("$(init.result[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(init.result[$(fields)])'"; + "got: $(fields) = '$(check.result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/202.cf b/tests/acceptance/10_files/02_maintain/202.cf new file mode 100644 index 0000000000..3a4744a9c3 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/202.cf @@ -0,0 +1,120 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size, owner, group +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + any:: + "mode" int => "01751"; + linux:: + "owner" string => "sys"; + "group" string => "sys"; + freebsd:: + "owner" string => "bin"; + "group" string => "sys"; + !(linux|freebsd):: + "owner" string => "undefined-please-fix"; + "group" string => "undefined-please-fix"; + + pass2.!windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)", "$(owner)", "$(group)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body perms init_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + + files: + "$(G.testfile)" + create => "true", + perms => test_perms("$(init.mode)", "$(init.owner)", "$(init.group)"); +} + +body perms test_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)", "root" }; + linux:: + groups => { "$(g)", "root" }; + freebsd:: + groups => { "$(g)", "wheel" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "ok" expression => strcmp("$(init.result)", "$(result)"); + "not_ok" not => regcmp("$(init.result[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(init.result[$(fields)])'"; + "got: $(fields) = '$(check.result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/203.cf b/tests/acceptance/10_files/02_maintain/203.cf new file mode 100644 index 0000000000..e5b587d209 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/203.cf @@ -0,0 +1,120 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size, owner, group (different ordering of groups) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + any:: + "mode" int => "01751"; + linux:: + "owner" string => "sys"; + "group" string => "sys"; + freebsd:: + "owner" string => "bin"; + "group" string => "sys"; + !(linux|freebsd):: + "owner" string => "undefined-please-fix"; + "group" string => "undefined-please-fix"; + + pass2.!windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)", "$(owner)", "$(group)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body perms init_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + + files: + "$(G.testfile)" + create => "true", + perms => test_perms("$(init.mode)", "$(init.owner)", "$(init.group)"); +} + +body perms test_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "root", "$(o)" }; + linux:: + groups => { "root", "$(g)" }; + freebsd:: + groups => { "wheel", "$(g)" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "ok" expression => strcmp("$(init.result)", "$(result)"); + "not_ok" not => regcmp("$(init.result[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(init.result[$(fields)])'"; + "got: $(fields) = '$(check.result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/204.cf b/tests/acceptance/10_files/02_maintain/204.cf new file mode 100644 index 0000000000..50c18c986e --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/204.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size or change owner/group (different ordering of groups) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + any:: + "mode" int => "01751"; + linux:: + "owner" string => "sys"; + "group" string => "sys"; + freebsd:: + "owner" string => "bin"; + "group" string => "sys"; + !(linux|freebsd):: + "owner" string => "undefined-please-fix"; + "group" string => "undefined-please-fix"; + + pass2.!windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)", "$(owner)", "$(group)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body perms init_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + + files: + "$(G.testfile)" + create => "true", + perms => test_perms("$(init.mode)"); +} + +body perms test_perms(m) +{ + mode => "$(m)"; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[permoct]" string => "$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "ok" expression => strcmp("$(init.result)", "$(result)"); + "not_ok" not => regcmp("$(init.result[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(init.result[$(fields)])'"; + "got: $(fields) = '$(check.result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/205.cf b/tests/acceptance/10_files/02_maintain/205.cf new file mode 100644 index 0000000000..91f5991a47 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/205.cf @@ -0,0 +1,133 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size, but DOES change owner, group (symbolic IDs) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +####################################################### + +bundle agent init +{ + vars: + freebsd|solaris:: + "mode" int => "04751"; + !freebsd.!solaris:: + "mode" int => "01751"; + linux:: + "owner" string => "sys"; + "group" string => "sys"; + freebsd:: + "owner" string => "bin"; + "group" string => "sys"; + !(linux|freebsd):: + "owner" string => "undefined-please-fix"; + "group" string => "undefined-please-fix"; + + pass2.(freebsd|solaris):: + "expect[modeoct]" string => "104755"; + + pass2.!(freebsd|solaris|windows):: + "expect[modeoct]" string => "101755"; + + pass2.!windows:: + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + "fields" slist => getindices("expect"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)", "$(owner)", "$(group)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body perms init_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + perms => test_perms("a+r"); +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "root" }; + linux|hpux|solaris|qnx:: + groups => { "root" }; + freebsd|netbsd|openbsd|darwin|dragonfly:: + groups => { "wheel" }; + aix:: + groups => { "system" }; + cray:: + groups => { "sys" }; + # Still need unix_sv, sco, vmware +} + +####################################################### + +bundle agent check +{ + vars: + freebsd|solaris:: + "expect[modeoct]" string => "104755"; + !freebsd.!solaris.!windows:: + "expect[modeoct]" string => "101755"; + !windows:: + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "result: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/206.cf b/tests/acceptance/10_files/02_maintain/206.cf new file mode 100644 index 0000000000..4faabe7e19 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/206.cf @@ -0,0 +1,126 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size, but DOES change owner, group - numeric UID/GID +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +####################################################### + +bundle agent init +{ + vars: + freebsd|solaris:: + "mode" int => "04751"; + !freebsd.!solaris:: + "mode" int => "01751"; + linux:: + "owner" string => "sys"; + "group" string => "sys"; + freebsd:: + "owner" string => "bin"; + "group" string => "sys"; + !(linux|freebsd):: + "owner" string => "undefined-please-fix"; + "group" string => "undefined-please-fix"; + + pass2.(freebsd|solaris):: + "expect[modeoct]" string => "104755"; + + pass2.!(freebsd|solaris|windows):: + "expect[modeoct]" string => "101755"; + + pass2.!windows:: + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + "fields" slist => getindices("expect"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)", "$(owner)", "$(group)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body perms init_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + perms => test_perms("a+r"); +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "456" }; + groups => { "567" }; +} + +####################################################### + +bundle agent check +{ + vars: + freebsd|solaris:: + "expect[modeoct]" string => "104755"; + !freebsd.!solaris.!windows:: + "expect[modeoct]" string => "101755"; + !windows:: + "expect[uid]" string => "456"; + "expect[gid]" string => "567"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "result: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/207.cf b/tests/acceptance/10_files/02_maintain/207.cf new file mode 100644 index 0000000000..e6139d1e8b --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/207.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size, but DOES change owner, group (numeric IDs) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +####################################################### + +bundle agent init +{ + vars: + freebsd|solaris:: + "mode" int => "04751"; + !freebsd.!solaris:: + "mode" int => "01751"; + linux:: + "owner" string => "sys"; + "group" string => "sys"; + freebsd:: + "owner" string => "bin"; + "group" string => "sys"; + !(linux|freebsd):: + "owner" string => "undefined-please-fix"; + "group" string => "undefined-please-fix"; + + pass2.(freebsd|solaris):: + "expect[modeoct]" string => "104755"; + + pass2.!(freebsd|solaris|windows):: + "expect[modeoct]" string => "101755"; + + pass2.!windows:: + "expect[uid]" string => "333"; + "expect[gid]" string => "444"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + "fields" slist => getindices("expect"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)", "$(owner)", "$(group)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body perms init_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + perms => test_perms("a+r"); +} + +body perms test_perms(m) +{ + mode => "$(m)"; + owners => { "333" }; + groups => { "444" }; +} + +####################################################### + +bundle agent check +{ + vars: + freebsd|solaris:: + "expect[modeoct]" string => "104755"; + !freebsd.!solaris.!windows:: + "expect[modeoct]" string => "101755"; + !windows:: + "expect[uid]" string => "333"; + "expect[gid]" string => "444"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "result: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/208.cf b/tests/acceptance/10_files/02_maintain/208.cf new file mode 100644 index 0000000000..4c18272c3b --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/208.cf @@ -0,0 +1,114 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size, group but DOES change owner, because +# we start with a non-registered user/group name. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +####################################################### + +bundle agent init +{ + vars: + freebsd|solaris:: + "mode" int => "04751"; + !freebsd.!solaris:: + "mode" int => "01751"; + any:: + "owner" string => "786756"; + "group" string => "786756"; + + pass2.!windows:: + "expect[modeoct]" string => "\d+$(init.mode)"; + "expect[uid]" string => "$(init.owner)"; + "expect[gid]" string => "$(init.group)"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + "fields" slist => getindices("expect"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)", "$(owner)", "$(group)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; + compare => "mtime"; +} + +body perms init_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + # Mode is unspecified - should not change anything + owners => { "12349", "none" }; # Should change to 12349 + # Group is unspecified - should not change anything +} + + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "\d+$(init.mode)"; + "expect[uid]" string => "12349"; + "expect[gid]" string => "$(init.group)"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "result: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/209.cf b/tests/acceptance/10_files/02_maintain/209.cf new file mode 100644 index 0000000000..4bbeea15ea --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/209.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Copy a file, then ensure that subsequent create=true doesn't +# overwrite mode, size, but DOES change owner, group, because +# we start with a non-registered user/group name. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +####################################################### + +bundle agent init +{ + vars: + freebsd|solaris:: + "mode" int => "04751"; + !freebsd.!solaris:: + "mode" int => "01751"; + any:: + "owner" string => "786756"; + "group" string => "786756"; + + pass2.!windows:: + "expect[modeoct]" string => "\d+$(init.mode)"; + "expect[uid]" string => "$(init.owner)"; + "expect[gid]" string => "$(init.group)"; + + pass2.any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + "fields" slist => getindices("expect"); + + files: + "$(G.testfile)" + copy_from => init_copy("$(G.etc_group)"), + perms => init_perms("$(mode)", "$(owner)", "$(group)"), + classes => init_set_class("pass2"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; + compare => "mtime"; +} + +body perms init_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + perms => test_perms; +} + +body perms test_perms +{ + # Mode, owner is unspecified - should not change anything + groups => { "23459", "none" }; # Should change to 23459 +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "\d+$(init.mode)"; + "expect[uid]" string => "$(init.owner)"; + "expect[gid]" string => "23459"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => filestat("$(G.etc_group)", "size"); + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "result: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/301.cf b/tests/acceptance/10_files/02_maintain/301.cf new file mode 100644 index 0000000000..215b8af264 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/301.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Create a file using copy, expect second copy to have "promise_kept" +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + classes: + "cxl_succ" expression => "any"; + "cxl_fail" expression => "any"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + perms => init_mog, + copy_from => init_copy("$(G.etc_group)"); +} + +body perms init_mog +{ + mode => "751"; + owners => { "0" }; + groups => { "0" }; +} + +body copy_from init_copy(fn) +{ + source => "$(fn)"; + compare => "digest"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + perms => init_mog, + copy_from => init_copy("$(G.etc_group)"), + classes => test_classes("success", "failure", "failure", "failure", "cxl_succ", "cxl_fail", "cxl_fail"); +} + +body classes test_classes(kep, rep, fai, xxx, cxl_kep, cxl_rep, cxl_nkp) +{ + promise_kept => { "$(kep)" }; + promise_repaired => { "$(rep)" }; + repair_failed => { "$(fai)" }; + repair_denied => { "$(fai)" }; + repair_timeout => { "$(fai)" }; + cancel_kept => { "$(cxl_kep)" }; + cancel_repaired => { "$(cxl_rep)" }; + cancel_notkept => { "$(cxl_nkp)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "success", "!cxl_succ", "!failure", "cxl_fail" }; + + reports: + DEBUG.success:: + "class 'success' was set (should be)"; + DEBUG.!success:: + "class 'success' was not set (should be)"; + DEBUG.cxl_succ:: + "class 'cxl_succ' was still set (should not be)"; + DEBUG.!cxl_succ:: + "class 'cxl_succ' was not still set (should not be)"; + DEBUG.failure:: + "class 'failure' was set (should not be)"; + DEBUG.!failure:: + "class 'failure' was not set (should not be)"; + DEBUG.cxl_fail:: + "class 'cxl_fail' was still set (should be)"; + DEBUG.!cxl_fail:: + "class 'cxl_fail' was not still set (should not be)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/403.x.cf b/tests/acceptance/10_files/02_maintain/403.x.cf new file mode 100644 index 0000000000..bbc33a385c --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/403.x.cf @@ -0,0 +1,77 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + default_repository => "non-absolute-path"; # Intentionally wrong +} + +bundle agent init +{ + files: + "$(g.repofile).*" + delete => init_delete; + + "$(G.testfile).*" + delete => init_delete; + + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; + compare => "digest"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + move_obstructions => "true", + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_motd)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "test" not => fileexists("$(G.testfile).cfsaved"); + "repo" expression => fileexists("$(g.repofile).cfsaved"); + "ok" and => { "test", "repo" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/404.x.cf b/tests/acceptance/10_files/02_maintain/404.x.cf new file mode 100644 index 0000000000..1735f31ba7 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/404.x.cf @@ -0,0 +1,93 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + default_repository => "tmp"; # Intentionally wrong +} + +bundle common g +{ + vars: + "repofile" string => "/var/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(g.repofile).*" + delete => init_delete; + + "$(G.testfile).*" + delete => init_delete; + + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; + compare => "digest"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + move_obstructions => "true", + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_motd)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "test" not => fileexists("$(G.testfile).cfsaved"); + "repo" expression => fileexists("$(g.repofile).cfsaved"); + "ok" and => { "test", "repo" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(g.repofile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/406.x.cf b/tests/acceptance/10_files/02_maintain/406.x.cf new file mode 100644 index 0000000000..9bf2008f20 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/406.x.cf @@ -0,0 +1,73 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(g.repofile).*" + delete => init_delete; + + "$(G.testfile).*" + delete => init_delete; + + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; + compare => "digest"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + repository => "non-absolute-path", # Intentionally wrong + move_obstructions => "true", + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_motd)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "test" not => fileexists("$(G.testfile).cfsaved"); + "repo" expression => fileexists("$(g.repofile).cfsaved"); + "ok" and => { "test", "repo" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/407.x.cf b/tests/acceptance/10_files/02_maintain/407.x.cf new file mode 100644 index 0000000000..8f4a246287 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/407.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; + "repofile" string => "/var/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(g.repofile).*" + delete => init_delete; + + "$(G.testfile).*" + delete => init_delete; + + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; + compare => "digest"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + repository => "tmp", # Intentionally wrong + move_obstructions => "true", + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_motd)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "test" not => fileexists("$(G.testfile).cfsaved"); + "repo" expression => fileexists("$(g.repofile).cfsaved"); + "ok" and => { "test", "repo" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(g.repofile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/408.cf b/tests/acceptance/10_files/02_maintain/408.cf new file mode 100644 index 0000000000..0e33dc3fb5 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/408.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Copy symlink over file, test that file is correctly saved aside. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle common g +{ + vars: + "srcfile" string => "$(G.testfile).source"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; + + "$(g.srcfile)" + move_obstructions => "true", + link_from => init_link; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; + compare => "digest"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +body link_from init_link +{ + source => "$(G.true)"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testfile)" + move_obstructions => "true", + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(g.srcfile)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile).cfsaved"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/409.cf b/tests/acceptance/10_files/02_maintain/409.cf new file mode 100644 index 0000000000..e2b41f1879 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/409.cf @@ -0,0 +1,64 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename => disable (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_null)"; + compare => "digest"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + rename => disable, + action => warn_only; +} + +body rename disable +{ + disable => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "filestillthere" expression => fileexists("$(G.testfile)"); + "fileisnotdisabled" not => fileexists("$(G.testfile).cfdisabled"); + + "ok" and => { "filestillthere", "fileisnotdisabled" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/410.cf b/tests/acceptance/10_files/02_maintain/410.cf new file mode 100644 index 0000000000..b2703b8601 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/410.cf @@ -0,0 +1,64 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename { rotate => 1 } (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_null)"; + compare => "digest"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + rename => rotate, + action => warn_only; +} + +body rename rotate +{ + rotate => "1"; +} + +####################################################### + +bundle agent check +{ + classes: + "filestillthere" expression => fileexists("$(G.testfile)"); + "fileisnotrotated" not => fileexists("$(G.testfile).1"); + + "ok" and => { "filestillthere", "fileisnotrotated" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/411.cf b/tests/acceptance/10_files/02_maintain/411.cf new file mode 100644 index 0000000000..af795fb9d2 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/411.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename { rotate => 0 } (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => "someline"; + "expected" string => "someline"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + rename => rotate, + action => warn_only; +} + +body rename rotate +{ + rotate => "0"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/412.cf b/tests/acceptance/10_files/02_maintain/412.cf new file mode 100644 index 0000000000..f3a58cad4a --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/412.cf @@ -0,0 +1,52 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename { rotate => 1 } (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent init +{ + commands: + "$(G.touch) $(G.testfile)"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + commands: + "$(cmd)"; + "$(cmd) -D superfluous"; +} +####################################################### + +bundle agent check +{ + classes: + "filestillthere" expression => fileexists("$(G.testfile)"); + "fileisrotated" expression => fileexists("$(G.testfile).cf-before-edit.1"); + "nounnamedbackup" not => fileexists("$(G.testfile).cf-before-edit"); + + "ok" and => { "filestillthere", "fileisrotated", "nounnamedbackup" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/412.cf.sub b/tests/acceptance/10_files/02_maintain/412.cf.sub new file mode 100644 index 0000000000..312a5ea4b4 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/412.cf.sub @@ -0,0 +1,37 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename { rotate => 1 } (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "test" }; +} + +####################################################### + +bundle agent test +{ +files: + "$(G.testfile)" + edit_line => append_nonsense, + edit_defaults => rotate; +} + +body edit_defaults rotate +{ + edit_backup => "rotate"; + rotate => "4"; + max_file_size => "300000"; +} + +bundle edit_line append_nonsense +{ +vars: + "ns" string => execresult("$(G.date) +%N", "noshell"); +insert_lines: + "$(ns)"; +} + diff --git a/tests/acceptance/10_files/02_maintain/413.cf b/tests/acceptance/10_files/02_maintain/413.cf new file mode 100644 index 0000000000..d3c5a91bc2 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/413.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename { rotate => 1 } (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent init +{ + commands: + "$(G.touch) $(G.testfile)"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + commands: + "$(cmd) -D useless0"; + "$(cmd) -D useless1"; + "$(cmd) -D useless2"; + "$(cmd) -D useless3"; + "$(cmd) -D useless4"; +} +####################################################### + +bundle agent check +{ + classes: + "filestillthere" expression => fileexists("$(G.testfile)"); + "fileisrotated" expression => fileexists("$(G.testfile).cf-before-edit.4"); + "nooverflow" not => fileexists("$(G.testfile).cf-before-edit.5"); + + "ok" and => { "filestillthere", "fileisrotated", "nooverflow" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/413.cf.sub b/tests/acceptance/10_files/02_maintain/413.cf.sub new file mode 100644 index 0000000000..db94d6a8ad --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/413.cf.sub @@ -0,0 +1,48 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename { rotate => 1 } (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "test" }; +} + +####################################################### + +bundle agent test +{ +files: + "$(G.testfile)" + edit_line => append_nonsense, + edit_defaults => rotate; +} + +body edit_defaults rotate +{ + edit_backup => "rotate"; + rotate => "4"; + max_file_size => "300000"; +} + +bundle edit_line append_nonsense +{ +vars: + useless0:: + "contents" string => "0"; + useless1:: + "contents" string => "1"; + useless2:: + "contents" string => "2"; + useless3:: + "contents" string => "3"; + useless4:: + "contents" string => "4"; + +insert_lines: + any:: + "$(contents)"; +} + diff --git a/tests/acceptance/10_files/02_maintain/414.cf b/tests/acceptance/10_files/02_maintain/414.cf new file mode 100644 index 0000000000..c0960b3e0b --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/414.cf @@ -0,0 +1,55 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename { rotate => 1 } (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent init +{ + commands: + "$(G.touch) $(G.testfile)"; +} + +####################################################### + +bundle agent test +{ + vars: + "cmd" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + commands: + "$(cmd)"; + "$(cmd) -D useless1"; + "$(cmd) -D useless2"; + "$(cmd) -D useless3"; + "$(cmd) -D useless4"; +} +####################################################### + +bundle agent check +{ + classes: + "filestillthere" expression => fileexists("$(G.testfile)"); + "fileisrotated" expression => fileexists("$(G.testfile).cf-before-edit.1"); + "nooverflow" not => fileexists("$(G.testfile).cf-before-edit.2"); + + "ok" and => { "filestillthere", "fileisrotated", "nooverflow" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/414.cf.sub b/tests/acceptance/10_files/02_maintain/414.cf.sub new file mode 100644 index 0000000000..b0864052c6 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/414.cf.sub @@ -0,0 +1,36 @@ +####################################################### +# +# Test that action => "warn" works correctly for rename { rotate => 1 } (Issue 841) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "test" }; +} + +####################################################### + +bundle agent test +{ +files: + "$(G.testfile)" + edit_line => append_nonsense, + edit_defaults => rotate; +} + +body edit_defaults rotate +{ + edit_backup => "rotate"; + max_file_size => "300000"; +} + +bundle edit_line append_nonsense +{ +vars: + "ns" string => execresult("$(G.date) +%N", "noshell"); +insert_lines: + "$(ns)"; +} + diff --git a/tests/acceptance/10_files/02_maintain/RM5411.cf b/tests/acceptance/10_files/02_maintain/RM5411.cf new file mode 100644 index 0000000000..7529797949 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/RM5411.cf @@ -0,0 +1,31 @@ +# RedMine 5411: segfaults. -*- Mode: cfengine -*- +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testdir)/src" create => "true"; +} + +bundle agent test +{ + files: + "$(G.testdir)/dst" copy_from => chained("$(G.testdir)/src"); +} + +body copy_from chained(sourcedir) +{ + source => "$(sourcedir)"; + servers => { "x", "localhost" }; +} + +bundle agent check +{ + reports: # If we made it this far, we didn't segfault. + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/10_files/02_maintain/absolute_link_and_move_obstructions.cf b/tests/acceptance/10_files/02_maintain/absolute_link_and_move_obstructions.cf new file mode 100644 index 0000000000..9827ecfae7 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/absolute_link_and_move_obstructions.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Create a file, expect simultaneous link with absolute and move_obstructions +# to succeed +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "inode" string => filestat("$(G.etc_group)", "ino"); + + files: + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "absolute"; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat(filestat("$(G.testfile)", "linktarget"), "ino"); + "Lresult" string => filestat("$(G.testfile)", "ino"); + + # This tells us where the link points + "link_target" string => filestat("$(G.testfile)", "linktarget"); + + classes: + "okL" not => strcmp("$(test.inode)", "$(Lresult)"); + "ok" and => { "okL", + strcmp("$(test.inode)", "$(result)"), + # Might be slightly different from link_target because of redundant '../../'. + regcmp("/.*/group", "$(link_target)") + }; + + reports: + DEBUG:: + "expected: '$(test.inode)'"; + "got: '$(Lresult)' => '$(result)'"; + "got this too: '$(G.etc_group)' => '$(link_target)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/absolute_link_and_no_move_obstructions.cf b/tests/acceptance/10_files/02_maintain/absolute_link_and_no_move_obstructions.cf new file mode 100644 index 0000000000..9c196413db --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/absolute_link_and_no_move_obstructions.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Create an relative link to a file, expect absolute link promise to want +# to change it to an absolute link (but fail to) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => init_link; +} + +body link_from init_link +{ + source => "$(G.etc_group)"; + link_type => "relative"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + "test_suppress_fail" string => "freebsd", + meta => { "redmine5261" }; + + + vars: + "inode" string => filestat("$(G.etc_group)", "ino"); + + files: + "$(G.testfile)" + classes => init_if_failed("test_ok"), + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "absolute"; +} + +body classes init_if_failed(c) +{ + repair_failed => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat(filestat("$(G.testfile)", "linktarget"), "ino"); + "Lresult" string => filestat("$(G.testfile)", "ino"); + + # Tell us where the link points, but don't follow it + "link_target" string => filestat("$(G.testfile)", "linktarget_shallow"); + + classes: + "okL" not => strcmp("$(test.inode)", "$(Lresult)"); + "okT" not => strcmp("$(link_target)", "$(G.etc_group)"); + "ok" and => { "test_ok", "okL", "okT", + strcmp("$(test.inode)", "$(result)"), + # Test that the symlink target starts with dot + regcmp("\..*", "$(link_target)") + }; + + reports: + DEBUG:: + "expected: '$(test.inode)'"; + "got: '$(Lresult)' => '$(result)'"; + "got this too: '$(G.etc_group)' => '$(link_target)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/backup_repository.cf b/tests/acceptance/10_files/02_maintain/backup_repository.cf new file mode 100644 index 0000000000..1795b22757 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/backup_repository.cf @@ -0,0 +1,41 @@ +# Test that repository contains backup of file after updating it. + +body common control +{ + bundlesequence => { default("$(this.promise_filename)") }; + inputs => { "../../default.cf.sub" }; +} + +body agent control +{ + default_repository => $(init.repodir); +} + +bundle agent init +{ + vars: + "repodir" string => "$(sys.workdir)/repository"; + + methods: + "" usebundle => dcs_fini($(repodir)); + "" usebundle => file_make("$(G.testfile).orig", "Original content"); + "" usebundle => file_make("$(G.testfile).new", "Updated content"); + "" usebundle => file_make("$(G.testfile).copy", "Original content"); +} + +bundle agent test +{ + methods: + "" usebundle => file_copy("$(G.testfile).new", "$(G.testfile).copy"); +} + +bundle agent check +{ + vars: + "backups" slist => findfiles("$(init.repodir)/*cfsaved"); + + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).orig", + $(backups), + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/10_files/02_maintain/changes_depth_search.cf b/tests/acceptance/10_files/02_maintain/changes_depth_search.cf new file mode 100644 index 0000000000..61de4fcd03 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/changes_depth_search.cf @@ -0,0 +1,118 @@ +# Checks whether a whole directory is monitored correctly with +# a file changes promise. + +body common control +{ + inputs => { "../../dcs.cf.sub", + "../../plucked.cf.sub", + "check_file_changes_log.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => setup_files; +} + +bundle agent setup_files +{ + methods: + # Note: sorted, not in actual order, due to file system order sensitivity. + "any" usebundle => file_make("$(G.testfile).expected", "file.content,C,Content changed +file.content,C,Content changed +file.content,C,Content changed +file.content,C,Content changed +file.content,C,Content changed +file.content,N,New file found +file.content.cf-before-edit,N,New file found +file.content.cf-before-edit,N,New file found +file.content.cf-before-edit,N,New file found +file.content.cf-before-edit,R,File removed +file.content.cf-before-edit,R,File removed +file.created,N,New file found +file.created,N,New file found +file.created,N,New file found +file.created,R,File removed +file.created,R,File removed +file.new-ignored.content,N,New file found +file.new-ignored.content,N,New file found +file.new-ignored.content,N,New file found +file.new-ignored.content,R,File removed +file.new-ignored.content,R,File removed +file.new-ignored.content,R,File removed +file.new-ignored.removed,N,New file found +file.new-ignored.removed,N,New file found +file.new-ignored.removed,N,New file found +file.new-ignored.removed,R,File removed +file.new-ignored.removed,R,File removed +file.new-ignored.removed,R,File removed +file.new-ignored.same,N,New file found +file.new-ignored.same,N,New file found +file.new-ignored.same,N,New file found +file.new-ignored.same,R,File removed +file.new-ignored.same,R,File removed +file.new-ignored.same,R,File removed +file.removed,N,New file found +file.removed,N,New file found +file.removed,N,New file found +file.removed,R,File removed +file.removed,R,File removed +file.removed,R,File removed +file.same,N,New file found +subdir,N,New file found +subfile.content,C,Content changed +subfile.content,C,Content changed +subfile.content,C,Content changed +subfile.content,C,Content changed +subfile.content,C,Content changed +subfile.content,N,New file found +subfile.content.cf-before-edit,N,New file found +subfile.content.cf-before-edit,N,New file found +subfile.content.cf-before-edit,N,New file found +subfile.content.cf-before-edit,R,File removed +subfile.content.cf-before-edit,R,File removed +subfile.created,N,New file found +subfile.created,N,New file found +subfile.created,N,New file found +subfile.created,R,File removed +subfile.created,R,File removed +subfile.removed,N,New file found +subfile.removed,N,New file found +subfile.removed,N,New file found +subfile.removed,R,File removed +subfile.removed,R,File removed +subfile.removed,R,File removed +subfile.same,N,New file found"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + commands: + "$(sys.cf_agent) -Dpass1 -Kf $(this.promise_filename).sub"; + "$(sys.cf_agent) -Dpass2 -Kf $(this.promise_filename).sub"; + "$(sys.cf_agent) -Dpass3 -Kf $(this.promise_filename).sub"; +} + +bundle agent check +{ + methods: + "any" usebundle => check_file_changes_log("$(G.testfile).expected", "test_changes_log_ok", + "test_changes_log_fail", "sorted"); + + classes: + "ok" and => { "test_changes_log_ok", + "!test_changes_log_fail", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/02_maintain/changes_depth_search.cf.sub b/tests/acceptance/10_files/02_maintain/changes_depth_search.cf.sub new file mode 100644 index 0000000000..ee5faef623 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/changes_depth_search.cf.sub @@ -0,0 +1,104 @@ +body common control +{ + inputs => { "../../dcs.cf.sub", + "../../plucked.cf.sub", + }; + bundlesequence => { "init", "test" }; +} + +bundle agent init +{ + files: + "$(G.testdir)" + depth_search => recurse("inf"), + file_select => all, + delete => tidy; + + methods: + "any" usebundle => setup_files; + "any" usebundle => init_monitoring; +} + +bundle agent setup_files +{ + methods: + "any" usebundle => file_make("$(G.testdir)/file.same", "same"); + "any" usebundle => file_make("$(G.testdir)/file.removed", "removed"); + #"any" usebundle => file_make("$(G.testdir)/file.created", "created"); + "any" usebundle => file_make("$(G.testdir)/file.content", "old content"); + "any" usebundle => file_make("$(G.testdir)/file.always-ignored.same", "same"); + "any" usebundle => file_make("$(G.testdir)/file.always-ignored.removed", "removed"); + #"any" usebundle => file_make("$(G.testdir)/file.always-ignored.created", "created"); + "any" usebundle => file_make("$(G.testdir)/file.always-ignored.content", "old content"); + "any" usebundle => file_make("$(G.testdir)/file.new-ignored.same", "same"); + "any" usebundle => file_make("$(G.testdir)/file.new-ignored.removed", "removed"); + #"any" usebundle => file_make("$(G.testdir)/file.new-ignored.created", "created"); + "any" usebundle => file_make("$(G.testdir)/file.new-ignored.content", "old content"); + "any" usebundle => file_make("$(G.testdir)/subdir/subfile.same", "same"); + #"any" usebundle => file_make("$(G.testdir)/subdir/subfile.created", "subfile created"); + "any" usebundle => file_make("$(G.testdir)/subdir/subfile.removed", "subfile removed"); + "any" usebundle => file_make("$(G.testdir)/subdir/subfile.content", "subfile old content"); +} + +bundle agent init_monitoring +{ + files: + "$(G.testdir)" + depth_search => recurse("inf"), + file_select => init_select, + changes => changes_body; +} + +body file_select init_select +{ + leaf_name => { ".*always-ignored.*" }; + file_result => "!leaf_name"; +} + +body changes changes_body +{ + report_changes => "all"; + update_hashes => "true"; +} + +bundle agent test +{ + vars: + "remove" slist => { "file.removed", + "file.always-ignored.removed", + "file.new-ignored.removed", + "subdir/subfile.removed", + }; + "create" slist => { "file.created", + "file.always-ignored.created", + "file.new-ignored.created", + "subdir/subfile.created", + }; + "modify" slist => { "file.content", + "file.always-ignored.content", + "file.new-ignored.content", + "subdir/subfile.content", + }; + + files: + "$(G.testdir)/$(remove)" + delete => tidy; + + "$(G.testdir)/$(create)" + create => "true"; + + "$(G.testdir)/$(modify)" + edit_line => insert_lines("extra content"); + + files: + "$(G.testdir)" + depth_search => recurse("inf"), + file_select => test_select, + changes => changes_body; +} + +body file_select test_select +{ + leaf_name => { ".*ignored.*" }; + file_result => "!leaf_name"; +} diff --git a/tests/acceptance/10_files/02_maintain/changes_depth_search_last_file.cf b/tests/acceptance/10_files/02_maintain/changes_depth_search_last_file.cf new file mode 100644 index 0000000000..02b7b474ba --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/changes_depth_search_last_file.cf @@ -0,0 +1,57 @@ +# Checks whether a whole directory is monitored correctly with +# a file changes promise even when all files are removed. + +body common control +{ + inputs => { "../../dcs.cf.sub", + "../../plucked.cf.sub", + "check_file_changes_log.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => setup_files; +} + +bundle agent setup_files +{ + methods: + "any" usebundle => file_make("$(G.testfile).expected", "file,N,New file found +file,R,File removed +file,N,New file found +file,R,File removed"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + commands: + "$(sys.cf_agent) -Dpass1 -Kf $(this.promise_filename).sub"; + "$(sys.cf_agent) -Dpass2 -Kf $(this.promise_filename).sub"; + "$(sys.cf_agent) -Dpass3 -Kf $(this.promise_filename).sub"; + "$(sys.cf_agent) -Dpass4 -Kf $(this.promise_filename).sub"; +} + +bundle agent check +{ + methods: + "any" usebundle => check_file_changes_log("$(G.testfile).expected", "test_changes_log_ok", + "test_changes_log_fail", ""); + + classes: + "ok" and => { "test_changes_log_ok", + "!test_changes_log_fail", + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/02_maintain/changes_depth_search_last_file.cf.sub b/tests/acceptance/10_files/02_maintain/changes_depth_search_last_file.cf.sub new file mode 100644 index 0000000000..7f1d749039 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/changes_depth_search_last_file.cf.sub @@ -0,0 +1,33 @@ +body common control +{ + inputs => { "../../dcs.cf.sub", + "../../plucked.cf.sub", + }; + bundlesequence => { "init", "test" }; +} + +bundle agent init +{ + methods: + pass1|pass3:: + "any" usebundle => file_make("$(G.testdir)/file", "content"); + + files: + pass2|pass4:: + "$(G.testdir)/file" + delete => tidy; +} + +body changes changes_body +{ + report_changes => "all"; + update_hashes => "true"; +} + +bundle agent test +{ + files: + "$(G.testdir)" + depth_search => recurse("inf"), + changes => changes_body; +} diff --git a/tests/acceptance/10_files/02_maintain/changes_promise_status.cf b/tests/acceptance/10_files/02_maintain/changes_promise_status.cf new file mode 100644 index 0000000000..17a73109df --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/changes_promise_status.cf @@ -0,0 +1,90 @@ +# Test that monitoring file status gives the right promise status + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + files: + "$(G.testdir)/existingfile" + create => "true", + changes => test_changes; +} + +bundle agent test +{ + files: + "$(G.testdir)/nosuchfile" + changes => test_changes, + classes => kept_repaired_notkept("nosuchfile_kept", "nosuchfile_repaired", "nosuchfile_notkept"); + "$(G.testdir)/newfile" + create => "true", + changes => test_changes, + classes => kept_repaired_notkept("newfile_kept", "newfile_repaired", "newfile_notkept"); + "$(G.testdir)/existingfile" + changes => test_changes, + classes => kept_repaired_notkept("existingfile_kept", "existingfile_repaired", "existingfile_notkept"); +} + +body changes test_changes +{ + hash => "sha256"; + report_changes => "all"; + update_hashes => "yes"; +} + +body classes kept_repaired_notkept(kept, repaired, notkept) +{ + promise_kept => { "$(kept)" }; + promise_repaired => { "$(repaired)" }; + repair_failed => { "$(notkept)" }; + repair_denied => { "$(notkept)" }; + repair_timeout => { "$(notkept)" }; +} + +bundle agent check +{ + + classes: + "ok" and => { "!nosuchfile_kept", + "!nosuchfile_repaired", + "nosuchfile_notkept", + # Kept state can be either set or unset depending on number of passes. + # Let's not depend on either. + #"!newfile_kept", + "newfile_repaired", + "!newfile_notkept", + "existingfile_kept", + "!existingfile_repaired", + "!existingfile_notkept", + }; + + reports: + DEBUG.nosuchfile_kept:: + "nosuchfile_kept is set, but shouldn't be."; + DEBUG.nosuchfile_repaired:: + "nosuchfile_repaired is set, but shouldn't be."; + DEBUG.!nosuchfile_notkept:: + "nosuchfile_notkept is not set, but should be."; + DEBUG.newfile_kept:: + "newfile_kept is set, but shouldn't be. (tolerated, for now)"; + DEBUG.!newfile_repaired:: + "newfile_repaired is not set, but should be."; + DEBUG.newfile_notkept:: + "newfile_notkept is set, but shouldn't be."; + DEBUG.!existingfile_kept:: + "existingfile_kept is not set, but should be."; + DEBUG.existingfile_repaired:: + "existingfile_repaired is set, but shouldn't be."; + DEBUG.existingfile_notkept:: + "existingfile_notkept is set, but shouldn't be."; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/changes_update_hashes.cf b/tests/acceptance/10_files/02_maintain/changes_update_hashes.cf new file mode 100644 index 0000000000..a2f9479a79 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/changes_update_hashes.cf @@ -0,0 +1,244 @@ +# Test that monitoring file status of an updated file gives right promises +# status. + +body common control +{ + inputs => { "../../default.cf.sub", "check_file_changes_log.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + files: + # Not in database. + "$(G.testfile).update.new" + create => "true"; + # Should be in database. + "$(G.testfile).update.same" + create => "true", + changes => test_changes_update; + "$(G.testfile).update.updated" + create => "true", + changes => test_changes_update; + + # Not in database. + "$(G.testfile).noupdate.new" + create => "true"; + # Should be in database. + "$(G.testfile).noupdate.same" + create => "true", + changes => test_changes_nohasheupdate_report_changes_all; + "$(G.testfile).noupdate.updated" + create => "true", + changes => test_changes_nohasheupdate_report_changes_all; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + files: + "$(G.testfile).update.updated" + edit_line => insert_text; + "$(G.testfile).noupdate.updated" + edit_line => insert_text; + + methods: + # Update each file twice. We cannot reuse the bundles because CFEngine + # will skip bundles it has done before. + "test_update_new" usebundle => test_update_new; + "test_update_same" usebundle => test_update_same; + "test_update_updated" usebundle => test_update_updated; + "test_update_new_again" usebundle => test_update_new_again; + "test_update_same_again" usebundle => test_update_same_again; + "test_update_updated_again" usebundle => test_update_updated_again; + + "test_noupdate_new" usebundle => test_noupdate_new; + "test_noupdate_same" usebundle => test_noupdate_same; + "test_noupdate_updated" usebundle => test_noupdate_updated; + "test_noupdate_new_again" usebundle => test_noupdate_new_again; + "test_noupdate_same_again" usebundle => test_noupdate_same_again; + "test_noupdate_updated_again" usebundle => test_noupdate_updated_again; +} + +bundle edit_line insert_text +{ + insert_lines: + "Text"; +} + +bundle agent test_update_new +{ + files: + "$(G.testfile).update.new" + changes => test_changes_update, + classes => kept_repaired_notkept("test_update_new_kept", "test_update_new_repaired", "test_update_new_notkept"); +} + +bundle agent test_update_same +{ + files: + "$(G.testfile).update.same" + changes => test_changes_update, + classes => kept_repaired_notkept("test_update_same_kept", "test_update_same_repaired", "test_update_same_notkept"); +} + +bundle agent test_update_updated +{ + files: + "$(G.testfile).update.updated" + changes => test_changes_update, + classes => kept_repaired_notkept("test_update_updated_kept", "test_update_updated_repaired", "test_update_updated_notkept"); +} + +bundle agent test_update_new_again +{ + files: + "$(G.testfile).update.new" + changes => test_changes_update, + classes => kept_repaired_notkept("test_update_new_kept", "test_update_new_repaired", "test_update_new_notkept"); +} + +bundle agent test_update_same_again +{ + files: + "$(G.testfile).update.same" + changes => test_changes_update, + classes => kept_repaired_notkept("test_update_same_kept", "test_update_same_repaired", "test_update_same_notkept"); +} + +bundle agent test_update_updated_again +{ + files: + "$(G.testfile).update.updated" + changes => test_changes_update, + classes => kept_repaired_notkept("test_update_updated_kept", "test_update_updated_repaired", "test_update_updated_notkept"); +} + +bundle agent test_noupdate_new +{ + files: + "$(G.testfile).noupdate.new" + changes => test_changes_nohasheupdate_report_changes_all, + classes => kept_repaired_notkept("test_noupdate_new_kept", "test_noupdate_new_repaired", "test_noupdate_new_notkept"); +} + +bundle agent test_noupdate_same +{ + files: + "$(G.testfile).noupdate.same" + changes => test_changes_nohasheupdate_report_changes_all, + classes => kept_repaired_notkept("test_noupdate_same_kept", "test_noupdate_same_repaired", "test_noupdate_same_notkept"); +} + +bundle agent test_noupdate_updated +{ + files: + "$(G.testfile).noupdate.updated" + changes => test_changes_nohasheupdate_report_changes_all, + classes => kept_repaired_notkept("test_noupdate_updated_kept", "test_noupdate_updated_repaired", "test_noupdate_updated_notkept"); +} + +bundle agent test_noupdate_new_again +{ + files: + "$(G.testfile).noupdate.new" + changes => test_changes_nohasheupdate_report_changes_all, + classes => kept_repaired_notkept("test_noupdate_new_kept", "test_noupdate_new_repaired", "test_noupdate_new_notkept"); +} + +bundle agent test_noupdate_same_again +{ + files: + "$(G.testfile).noupdate.same" + changes => test_changes_nohasheupdate_report_changes_all, + classes => kept_repaired_notkept("test_noupdate_same_kept", "test_noupdate_same_repaired", "test_noupdate_same_notkept"); +} + +bundle agent test_noupdate_updated_again +{ + files: + "$(G.testfile).noupdate.updated" + changes => test_changes_nohasheupdate_report_changes_all, + classes => kept_repaired_notkept("test_noupdate_updated_kept", "test_noupdate_updated_repaired", "test_noupdate_updated_notkept"); +} + +body changes test_changes_update +{ + hash => "sha256"; + report_changes => "all"; + update_hashes => "yes"; +} + +body changes test_changes_nohasheupdate_report_changes_all +{ + hash => "sha256"; + report_changes => "all"; + update_hashes => "no"; +} + +body classes kept_repaired_notkept(kept, repaired, notkept) +{ + promise_kept => { "$(kept)" }; + promise_repaired => { "$(repaired)" }; + repair_failed => { "$(notkept)" }; + repair_denied => { "$(notkept)" }; + repair_timeout => { "$(notkept)" }; +} + +bundle agent check +{ + vars: + "classes_set" slist => classesmatching("test_.*"); + + methods: + # Seems like CFEngine does two passes over files where we don't update + # hash, so the last four entries should really be two entries. + # If the number of "Content changed" lines is wrong, this is the first + # place to look. + "any" usebundle => file_make("$(G.testfile).file_changes_log", "TEST.cfengine.update.updated,C,Content changed +TEST.cfengine.noupdate.updated,C,Content changed +TEST.cfengine.noupdate.updated,C,Content changed +TEST.cfengine.noupdate.updated,C,Content changed +TEST.cfengine.noupdate.updated,C,Content changed"); + + "any" usebundle => check_file_changes_log("$(G.testfile).file_changes_log", "test_changes_log_ok", + "test_changes_log_fail", ""); + + classes: + "ok" and => { + "checksum_alerts", + "test_update_new_repaired", + "!test_update_new_notkept", + "!test_update_same_repaired", + "test_update_same_kept", + "!test_update_same_notkept", + "test_update_updated_repaired", + "!test_update_updated_notkept", + "test_noupdate_new_repaired", + "!test_noupdate_new_notkept", + "!test_noupdate_same_repaired", + "test_noupdate_same_kept", + "!test_noupdate_same_notkept", + "test_noupdate_updated_repaired", # because changes are recorded in the changes log + "test_noupdate_updated_notkept", # because hashes are not updated + "test_changes_log_ok", + "!test_changes_log_fail", + }; + + reports: + DEBUG:: + "Set classes: $(classes_set)"; + !checksum_alerts.DEBUG:: + "The checksum_alerts class was NOT set as expected."; + checksum_alerts.EXTRA:: + "The checksum_alerts class was set as expected."; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/check_file_changes_log.cf.sub b/tests/acceptance/10_files/02_maintain/check_file_changes_log.cf.sub new file mode 100644 index 0000000000..6a5ef14fda --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/check_file_changes_log.cf.sub @@ -0,0 +1,60 @@ +# Checks whether the given content matches the content in +# /var/cfengine/state/file_changes.log, but removes "unstable" +# fields first (timestamp, promise ID, absolute path). +# +# The checks argument is a comma separated list of the following +# options to check. +# +# - check_mtime +# - sorted +# +# If the "check_*" flags are not given, those fields are filtered out +# before comparing, since they are typically unstable. +# +# The "sorted" flag is to sort the log before comparing. This is +# necessary for tests that depend on file system order, but the test +# won't be as accurate. + +body file control +{ + inputs => { "../../plucked.cf.sub" }; +} + +bundle agent check_file_changes_log(file, pass_class, fail_class, checks) +{ + vars: + "classes_to_define" slist => splitstring($(checks), ",", 10); + + classes: + "$(classes_to_define)" expression => "any"; + + "actual_exists" expression => fileexists("$(file).actual"); + + files: + !actual_exists:: + "$(file).actual" + copy_from => local_cp("$(sys.workdir)/state/file_changes.log"); + any:: + "$(file).actual" + edit_line => remove_unstable_fields($(file)); + + methods: + sorted:: + "any" usebundle => dcs_sort("$(file).actual", "$(file).actual.sort"); + "any" usebundle => dcs_if_diff("$(file).actual.sort", "$(file)", + "$(pass_class)", "$(fail_class)"); + !sorted:: + "any" usebundle => dcs_if_diff("$(file).actual", "$(file)", + "$(pass_class)", "$(fail_class)"); +} + +bundle edit_line remove_unstable_fields(file) +{ + replace_patterns: + # Get rid of timestamp, promise ID and absolute path. + "^[0-9]+,[^,]*,[^,]+/" + replace_with => value(""); + delete_lines: + !check_mtime:: + ".*,S,Modified time:.*"; +} diff --git a/tests/acceptance/10_files/02_maintain/classes_on_delete.cf b/tests/acceptance/10_files/02_maintain/classes_on_delete.cf new file mode 100644 index 0000000000..eee8e1365c --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/classes_on_delete.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Redmine#6509: repairs from delete bodies should not be seen as kept +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testdir)/redmine_6509_1/." + create => "true"; + + "$(G.testdir)/redmine_6509_2" + delete => tidy; +} + + +bundle agent test +{ + files: + "$(G.testdir)/redmine_6509_1" + classes => mygeneric("dir1_absent"), + delete => tidy; + + "$(G.testdir)/redmine_6509_2" + classes => mygeneric("dir2_absent"), + delete => tidy; +} + +bundle agent check +{ + vars: + "expected_ok" + slist => { + "dir1_absent_ok", + "dir1_absent_repaired", + "dir1_absent_reached", + "dir2_absent_ok", + "dir2_absent_kept", + "dir2_absent_reached", + }; + + "seen_fail" + slist => { + "dir1_absent_error", + "dir1_absent_repair_failed", + "dir1_absent_repair_denied", + "dir1_absent_repair_timeout", + "dir1_absent_kept", + }; + + classes: + "ok" and => { @(expected_ok) }; + + "fail" or => { @(seen_fail) }; + + reports: + DEBUG.fail:: + "Found $(expected_ok) defined as expected" + if => "$(expected_ok)"; + + "Found $(seen_fail) which is not expected and causes the test to fail" + if => "$(seen_fail)"; + + "Missing $(expected_ok) which is expected and not being defined causes the test to fail" + if => "!$(expected_ok)"; + + !fail.ok:: + "$(this.promise_filename) Pass"; + + fail:: + "$(this.promise_filename) FAIL"; + +} + +body classes mygeneric(x) +{ + promise_kept => { "$(x)_ok", "$(x)_kept", "$(x)_reached" }; + promise_repaired => { "$(x)_ok", "$(x)_repaired", "$(x)_reached" }; + repair_failed => { "$(x)_error", "$(x)_repair_failed", "$(x)_reached" }; + repair_denied => { "$(x)_error", "$(x)_repair_denied", "$(x)_reached" }; + repair_timeout => { "$(x)_error", "$(x)_repair_timeout", "$(x)_reached" }; +} diff --git a/tests/acceptance/10_files/02_maintain/copy-relative-link-existing.cf b/tests/acceptance/10_files/02_maintain/copy-relative-link-existing.cf new file mode 100644 index 0000000000..1ee5f67d40 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/copy-relative-link-existing.cf @@ -0,0 +1,63 @@ +# Assume the following file-structure: +# dest +# link -> dest +# link-currdir -> ./dest +# +# When verifying "link-currdir" is copy of "link", it is ok +# to correct it (to be link-currdir -> dest), but after this, the promise must be kept. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + commands: + "$(G.mkdir) -p $(G.testdir)"; + "$(G.touch) $(G.testdir)/dest"; + "$(G.ln) -s dest link" + contain => in_dir("$(G.testdir)"); + "$(G.ln) -s ./dest link-currdir" + contain => in_dir("$(G.testdir)"); +} + +bundle agent test +{ + files: + "$(G.testdir)/link-currdir" + copy_from => local_cp("$(G.testdir)/link"), + move_obstructions => "true"; + + "$(G.testdir)/link-currdir" + copy_from => local_cp("$(G.testdir)/link"), + classes => if_kept("copy_link_kept"); +} + +bundle agent check +{ + classes: + "ok" or => { "copy_link_kept" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + + +body contain in_dir(s) +{ + chdir => "$(s)"; +} + +body classes if_kept(x) +{ + promise_kept => { "$(x)" }; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/copy-relative-link.cf b/tests/acceptance/10_files/02_maintain/copy-relative-link.cf new file mode 100644 index 0000000000..4736242837 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/copy-relative-link.cf @@ -0,0 +1,49 @@ +# Test that copying relative link does not mangle it (Mantis #1117) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + commands: + "$(G.mkdir) -p $(G.testdir)/a"; + "$(G.touch) $(G.testdir)/a/dest"; + "$(G.ln) -s dest $(G.testdir)/a/link"; +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testdir)/b" + copy_from => local_cp("$(G.testdir)/a"), + depth_search => recurse("inf"); +} + +bundle agent check +{ + vars: + "expect" string => "dest"; + "result" string => filestat("$(G.testdir)/b/link", "linktarget_shallow"); + + classes: + "ok" expression => strcmp("$(result)", "$(expect)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/copy_from_depth_search.cf b/tests/acceptance/10_files/02_maintain/copy_from_depth_search.cf new file mode 100644 index 0000000000..2fc05589df --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/copy_from_depth_search.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Copy a directory structure, mixing create and depth_search +# Redmine 6027 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "source_dirs" slist => { ".", "a.dir/.", "a.dir/b.dir/." }; + "source_files" slist => { "a.file", "a.dir/b.file" }; + files: + "$(G.testdir)_source/$(source_dirs)" + create => "true"; + + "$(G.testdir)_source/$(source_files)" + create => "true"; +} + +bundle agent test +{ + files: + "$(G.testdir)_destination" + create => "true", + copy_from => local_cp("$(G.testdir)_source"), + depth_search => recurse("inf"); + + "$(G.testdir)_destination_dir/." + create => "true", + copy_from => local_cp("$(G.testdir)_source"), + depth_search => recurse("inf"); +} + +bundle agent check +{ + # test that selected destination file system entries exist with the correct type + classes: + "files_ok" and => { strcmp(filestat("$(G.testdir)_destination/a.dir/b.file", "type"), "regular file"), + strcmp(filestat("$(G.testdir)_destination_dir/a.dir/b.file", "type"), "regular file") }; + "dirs_ok" and => { strcmp(filestat("$(G.testdir)_destination", "type"), "directory"), + strcmp(filestat("$(G.testdir)_destination/a.dir/b.dir", "type"), "directory"), + strcmp(filestat("$(G.testdir)_destination_dir/a.dir/b.dir", "type"), "directory") }; + + "ok" and => { "files_ok", "dirs_ok" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/02_maintain/copy_from_translatepath.cf b/tests/acceptance/10_files/02_maintain/copy_from_translatepath.cf new file mode 100644 index 0000000000..9c072bc2e5 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/copy_from_translatepath.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test that copying a file with double directory +# separators work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + vars: + "regex" string => escape("$(const.dirsep)"); + "orig_path" string => translatepath("$(this.promise_filename)"); + "path_list" slist => splitstring("$(orig_path)", "$(regex)", 100); + "path" string => join("$(const.dirsep)$(const.dirsep)", "path_list"); + + files: + "$(G.testfile)" + copy_from => copy_body("$(path)"); +} + +body copy_from copy_body(file) +{ + source => "$(file)"; +} + +bundle agent check +{ + reports: + DEBUG:: + "Tested pathname: '$(test.path)'"; + + methods: + "check" usebundle => dcs_check_diff("$(this.promise_filename)", + "$(G.testfile)", "$(this.promise_filename)"); +} diff --git a/tests/acceptance/10_files/02_maintain/copy_linkpattern_from_contains_dots.cf b/tests/acceptance/10_files/02_maintain/copy_linkpattern_from_contains_dots.cf new file mode 100644 index 0000000000..8c3643f8cc --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/copy_linkpattern_from_contains_dots.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Test copylink_pattern can handle source symlink that contains '/../' +# CFE-2960 +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testdir)/linkdir/" + comment => "Create a directory."; + "$(G.testdir)/linkdir/another/" + comment => "Create another directory."; + "$(G.testdir)/linkdir/another/target" + comment => "A target file.", + create => "true"; + "$(G.testdir)/linkdir/link" + comment => "Create a relative link to the target.", + link_from => ln_s("$(G.testdir)/linkdir/another/target"); +} + +####################################################### + +bundle agent test +{ + meta: + "description" + string => "Test copylink_pattern can handle source symlink that contains'/../'", + meta => { "CFE-2960" }; + + vars: + "mode" int => "0600"; + "from" string => "$(G.testdir)/linkdir/../linkdir/link"; + + files: + "$(G.testdir)/copy_file" + comment => "Copy the file behind the link.", + perms => test_perms($(mode)), + copy_from => cp_2_file("$(from)"); +} + +body link_from ln_s(x) { + link_type => "relative"; + source => "$(x)"; + when_no_source => "nop"; +} + +body copy_from cp_2_file(x) { + source => "$(x)"; + compare => "binary"; + copy_backup => "false"; + copylink_patterns => { ".*" }; +} + +body perms test_perms(m) { + mode => "$(m)"; + owners => { "0" }; + groups => { "0" }; +} + +####################################################### + +bundle agent check +{ + vars: + !windows:: + "expect[modeoct]" string => "\d+$(test.mode)"; + "expect[uid]" string => "0"; + "expect[gid]" string => "0"; + any:: + "expect[nlink]" string => "1"; + "expect[size]" string => "0"; + + "fields" slist => getindices("expect"); + "result[$(fields)]" string => filestat("$(G.testfile)$(const.dirsep)copy_file", "$(fields)"); + + classes: + "not_ok" not => regcmp("$(expect[$(fields)])", "$(result[$(fields)])"); + + reports: + DEBUG:: + "expected: $(fields) = '$(expect[$(fields)])'"; + "got: $(fields) = '$(result[$(fields)])'"; + !not_ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/02_maintain/fifos.cf b/tests/acceptance/10_files/02_maintain/fifos.cf new file mode 100644 index 0000000000..7e1fe09ab1 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/fifos.cf @@ -0,0 +1,44 @@ +# https://dev.cfengine.com/issues/7030 +# + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "test_fifo" string => "$(G.testfile).fifo"; + + commands: + "$(G.mkfifo) $(test_fifo)"; +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!has_mkfifo|windows"; + + files: + "$(init.test_fifo)" + create => "false", + perms => m("0700"); +} + +bundle agent check +{ + classes: + "ok" expression => isexecutable("$(init.test_fifo)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + +} + + diff --git a/tests/acceptance/10_files/02_maintain/line_truncation_at_4096_chars.cf b/tests/acceptance/10_files/02_maintain/line_truncation_at_4096_chars.cf new file mode 100644 index 0000000000..79c4cc81cf --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/line_truncation_at_4096_chars.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Check if the lines in a file get truncated after 4096 +# chars during file editing. +# +# Ticket: https://cfengine.com/dev/issues/3882 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("${this.promise_filename}") }; + version => "1.0"; + cache_system_functions => "off"; +} + +####################################################### + +bundle agent init +{ + vars: + any:: + "file_mode" int => "0644"; + "char_number" int => "6144"; + "line_to_add" string => "This is a nice short line."; + + files: + "${G.testfile}" + create => "true", + perms => m("${file_mode}"); + + # Using system command instead of CFEngine itself to + # prevent the bug to be here at file creation too. + # It would create a false negative. + commands: + "${G.perl}" + args => "-e 'print \"#\" x ${char_number}; print \"ENDMARK\n\"' > ${G.testfile}", + contain => in_shell; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "${G.testfile}" + create => "false", + edit_line => insert_lines("${init.line_to_add}"); +} + +####################################################### + +bundle agent check +{ + + classes: + !solaris:: + # ok is defined if the file still contains ENDMARK, meaning the end of the sample very + # long line did not get truncated during file editing + "ok" expression => returnszero("${G.grep} ENDMARK ${G.testfile}", "noshell"); + solaris:: + # Bug in xpg4 version of Solaris. grep internally has a limit of 4096 chars per line, + # which is the very thing we're testing. Use /bin/grep. + "ok" expression => returnszero("/bin/grep ENDMARK ${G.testfile}", "noshell"); + + reports: + DEBUG.!ok:: + "${this.promise_filename} FAILed as the generated file got at least a line truncated to 4096 characters"; + ok:: + "${this.promise_filename} Pass"; + !ok:: + "${this.promise_filename} FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("${G.testfile}"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/perms_recurse.cf b/tests/acceptance/10_files/02_maintain/perms_recurse.cf new file mode 100644 index 0000000000..ef2dca9ba2 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/perms_recurse.cf @@ -0,0 +1,98 @@ +# https://dev.cfengine.com/issues/7808 +# + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + meta: + # Permissions test doesn't work with fakeroot. + # It also doesn't work on non-Linux due to the use of GNU specific find + # options. + "test_skip_needs_work" string => "using_fakeroot|!linux"; + + vars: + "directory" string => "$(G.testdir)"; + + "mode" string => "750"; + "owner" string => "bin"; + "group" string => "bin"; + + files: + "$(directory)/." + perms => mog("000", "root", "0"), + create => "true"; + + "$(directory)/dir1/." + perms => mog("000", "root", "0"), + create => "true"; + + "$(directory)/dir2/." + perms => mog("000", "root", "0"), + create => "true"; + +} + +bundle agent test +{ + files: + "$(init.directory)" + create => "false", + perms => mog("${init.mode}", "${init.owner}", "${init.group}"), + depth_search => recurse_with_base("inf"), + file_select => dirs; +} + +body file_select dirs +# @brief Select directories +{ + file_types => { "dir" }; + file_result => "file_types"; +} + + +bundle agent check +{ + + vars: + "permissions_test_mode" string => "/usr/bin/test \"`/usr/bin/find ${init.directory} -perm ${init.mode} | wc -l`\" = \"3\""; + "permissions_test_owner" string => "/usr/bin/test \"`/usr/bin/find ${init.directory} -user ${init.owner} | wc -l`\" = \"3\""; + "permissions_test_group" string => "/usr/bin/test \"`/usr/bin/find ${init.directory} -group ${init.group} | wc -l`\" = \"3\""; + + commands: + "${permissions_test_mode}" + contain => in_shell, + classes => ok("permissions_test_mode_ok"); + "${permissions_test_owner}" + contain => in_shell, + classes => ok("permissions_test_owner_ok"); + "${permissions_test_group}" + contain => in_shell, + classes => ok("permissions_test_group_ok"); + + reports: + DEBUG.!permissions_test_mode_ok:: + "Didn't find 3 files with mode ${init.mode}"; + DEBUG.!permissions_test_owner_ok:: + "Didn't find 3 files with owner${init.owner}"; + DEBUG.!permissions_test_group_ok:: + "Didn't find 3 files with group ${init.group}"; + permissions_test_mode_ok.permissions_test_owner_ok.permissions_test_group_ok:: + "$(this.promise_filename) Pass"; + !(permissions_test_mode_ok.permissions_test_owner_ok.permissions_test_group_ok):: + "$(this.promise_filename) FAIL"; + +} + +body classes ok(classname) +{ + promise_repaired => { "$(classname)" }; + promise_kept => { "$(classname)" }; +} + + diff --git a/tests/acceptance/10_files/02_maintain/relative_link_and_move_obstructions.cf b/tests/acceptance/10_files/02_maintain/relative_link_and_move_obstructions.cf new file mode 100644 index 0000000000..11bdce04a8 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/relative_link_and_move_obstructions.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Create a file, expect simultaneous link with relative and move_obstructions +# to succeed +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + "test_suppress_fail" string => "freebsd", + meta => { "redmine5261" }; + + vars: + "inode" string => filestat("$(G.etc_group)", "ino"); + + files: + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "relative"; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat(filestat("$(G.testfile)", "linktarget"), "ino"); + "Lresult" string => filestat("$(G.testfile)", "ino"); + + # This tells us where the link points, but don't follow it + "link_target" string => filestat("$(G.testfile)", "linktarget_shallow"); + + classes: + "okL" not => strcmp("$(test.inode)", "$(Lresult)"); + "okT" not => strcmp("$(link_target)", "$(G.etc_group)"); + "ok" and => { "okL", "okT", + strcmp("$(test.inode)", "$(result)"), + # Test that the symlink target starts with dot + regcmp("\..*", "$(link_target)") + }; + + reports: + DEBUG:: + "expected: '$(test.inode)'"; + "got: '$(Lresult)' => '$(result)'"; + "got this too: '$(G.etc_group)' => '$(link_target)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/relative_link_and_no_move_obstructions.cf b/tests/acceptance/10_files/02_maintain/relative_link_and_no_move_obstructions.cf new file mode 100644 index 0000000000..82e70a436a --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/relative_link_and_no_move_obstructions.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Create an absolute link to a file, expect relative link promise to want +# to change it to an relative link (but fail to) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => init_link; +} + +body link_from init_link +{ + source => "$(G.etc_group)"; + link_type => "absolute"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "inode" string => filestat("$(G.etc_group)", "ino"); + + files: + "$(G.testfile)" + classes => init_if_failed("test_ok"), + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "relative"; +} + +body classes init_if_failed(c) +{ + repair_failed => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat(filestat("$(G.testfile)", "linktarget"), "ino"); + "Lresult" string => filestat("$(G.testfile)", "ino"); + + # This tells us where the link points + "link_target" string => filestat("$(G.testfile)", "linktarget"); + + classes: + "okL" not => strcmp("$(test.inode)", "$(Lresult)"); + "ok" and => { "test_ok", "okL", + strcmp("$(test.inode)", "$(result)"), + # Might be slightly different from link_target because of redundant '../../'. + regcmp("/.*/group", "$(link_target)") + }; + + reports: + DEBUG:: + "expected: '$(test.inode)'"; + "got: '$(Lresult)' => '$(result)'"; + "got this too: '$(G.etc_group)' => '$(link_target)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/107.x.cf b/tests/acceptance/10_files/02_maintain/staging/107.x.cf new file mode 100644 index 0000000000..1c771937e7 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/107.x.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Create a file, expect simultaneous hard no-filename link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; + + # This extracts the octal mode, and decimal nlink, uid, gid, size + "command" string => 'printf "%o" . " %d" x 4, (stat("$(G.testfile)"))[2]&07777, (stat(_))[3..5,7]'; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + link_from => test_link; +} + +body link_from test_link +{ + link_type => "hardlink"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "$(test.mode) 1 0 0 0"; + + "result" string => execresult( + "$(G.perl) -le '$(g.command)'", + "noshell"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/108.x.cf b/tests/acceptance/10_files/02_maintain/staging/108.x.cf new file mode 100644 index 0000000000..47687d333a --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/108.x.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Create a file, expect simultaneous symbolic no-filename link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; + + # This extracts the octal mode, and decimal nlink, uid, gid, size + "command" string => 'printf "%o" . " %d" x 4, (stat("$(G.testfile)"))[2]&07777, (stat(_))[3..5,7]'; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + link_from => test_link("$(baz)"); +} + +body link_from test_link(foo) +{ + link_type => "$(foo)"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "$(test.mode) 1 0 0 0"; + + "result" string => execresult( + "$(G.perl) -le '$(g.command)'", + "noshell"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/109.x.cf b/tests/acceptance/10_files/02_maintain/staging/109.x.cf new file mode 100644 index 0000000000..111a3b20f4 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/109.x.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Create a file, expect simultaneous relative symbolic no-filename link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; + + # This extracts the octal mode, and decimal nlink, uid, gid, size + "command" string => 'printf "%o" . " %d" x 4, (stat("$(G.testfile)"))[2]&07777, (stat(_))[3..5,7]'; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + link_from => test_link; +} + +body link_from test_link +{ + link_type => "relative"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "$(test.mode) 1 0 0 0"; + + "result" string => execresult( + "$(G.perl) -le '$(g.command)'", + "noshell"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/110.x.cf b/tests/acceptance/10_files/02_maintain/staging/110.x.cf new file mode 100644 index 0000000000..f943f0caf9 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/110.x.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Create a file, expect simultaneous absolute symbolic no-filename link to fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; + + # This extracts the octal mode, and decimal nlink, uid, gid, size + "command" string => 'printf "%o" . " %d" x 4, (stat("$(G.testfile)"))[2]&07777, (stat(_))[3..5,7]'; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "mode" int => "0600"; + + files: + "$(G.testfile)" + create => "true", + link_from => test_link; +} + +body link_from test_link +{ + link_type => "absolute"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "$(test.mode) 1 0 0 0"; + + "result" string => execresult( + "$(G.perl) -le '$(g.command)'", + "noshell"); + + classes: + "ok" expression => strcmp("$(expect)", "$(result)"); + + reports: + DEBUG:: + "expected: '$(expect)'"; + "got: '$(result)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/302.cf b/tests/acceptance/10_files/02_maintain/staging/302.cf new file mode 100644 index 0000000000..235b3672a6 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/302.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Create a file using copy, expect second copy of different file to +# have "promise_repaired" +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; + + classes: + "cxl_succ" expression => "any"; + "cxl_fail" expression => "any"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + perms => init_mog, + copy_from => init_copy("$(G.etc_group)"); +} + +body perms init_mog +{ + mode => "751"; + owners => { "bin" }; + groups => { "bin" }; +} + +body copy_from init_copy(fn) +{ + source => "$(fn)"; + compare => "digest"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + perms => init_mog, + copy_from => init_copy("$(G.etc_passwd)"), + classes => test_classes("failure", "success", "failure", "failure", "cxl_fail", "cxl_succ", "cxl_fail"); +} + +body classes test_classes(kep, rep, fai, xxx, cxl_kep, cxl_rep, cxl_nkp) +{ + promise_kept => { "$(kep)" }; + promise_repaired => { "$(rep)" }; + repair_failed => { "$(fai)" }; + repair_denied => { "$(fai)" }; + repair_timeout => { "$(fai)" }; + cancel_kept => { "$(cxl_kep)" }; + cancel_repaired => { "$(cxl_rep)" }; + cancel_notkept => { "$(cxl_nkp)" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "success", "!cxl_succ", "!failure", "cxl_fail" }; + + reports: + DEBUG.success:: + "class 'success' was set (should be)"; + DEBUG.!success:: + "class 'success' was not set (should be)"; + DEBUG.cxl_succ:: + "class 'cxl_succ' was still set (should not be)"; + DEBUG.!cxl_succ:: + "class 'cxl_succ' was not still set (should not be)"; + DEBUG.failure:: + "class 'failure' was set (should not be)"; + DEBUG.!failure:: + "class 'failure' was not set (should not be)"; + DEBUG.cxl_fail:: + "class 'cxl_fail' was still set (should be)"; + DEBUG.!cxl_fail:: + "class 'cxl_fail' was not still set (should not be)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/401.cf b/tests/acceptance/10_files/02_maintain/staging/401.cf new file mode 100644 index 0000000000..ff0272f8a8 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/401.cf @@ -0,0 +1,84 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).*" + delete => init_delete; + + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; + compare => "digest"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + move_obstructions => "true", + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_motd)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile).cfsaved"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/402.cf b/tests/acceptance/10_files/02_maintain/staging/402.cf new file mode 100644 index 0000000000..4503c06b51 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/402.cf @@ -0,0 +1,91 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + default_repository => "/var/tmp"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; + "repofile" string => "_tmp_TEST.cfengine"; + "repo" string => "/var/tmp"; +} + +####################################################### + +bundle agent init +{ + files: + "$(g.repofile).*" + delete => init_delete; + + "$(G.testfile).*" + delete => init_delete; + + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; + compare => "digest"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + move_obstructions => "true", + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_motd)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "test" not => fileexists("$(G.testfile).cfsaved"); + "repo" expression => fileexists("$(g.repo)/$(g.repofile).cfsaved"); + "ok" and => { "test", "repo" }; + + reports: + DEBUG:: + "testfile = $(G.testfile)"; + "repo = $(g.repo)"; + "repofile = $(g.repofile)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/405.cf b/tests/acceptance/10_files/02_maintain/staging/405.cf new file mode 100644 index 0000000000..80fdf7501d --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/405.cf @@ -0,0 +1,96 @@ +####################################################### +# +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; + "repofile" string => "_tmp_TEST.cfengine"; + "repo" string => "/var/tmp"; +} + +####################################################### + +bundle agent init +{ + files: + "$(g.repo)/$(g.repofile).*" + delete => init_delete; + + "$(G.testfile).*" + delete => init_delete; + + "$(G.testfile)" + move_obstructions => "true", + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; + compare => "digest"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + move_obstructions => "true", + repository => "$(g.repo)", + copy_from => test_copy; +} + +body copy_from test_copy +{ + source => "$(G.etc_motd)"; + compare => "digest"; +} + +####################################################### + +bundle agent check +{ + classes: + "test" not => fileexists("$(G.testfile).cfsaved"); + "repo" expression => fileexists("$(g.repo)/$(g.repofile).cfsaved"); + "ok" and => { "test", "repo" }; + + reports: + DEBUG:: + "testfile = $(G.testfile)"; + "repo = $(g.repo)"; + "repofile = $(g.repofile)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); + "any" usebundle => dcs_fini("$(g.repo)/$(g.repofile)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/copy-select-link-dereference.cf b/tests/acceptance/10_files/02_maintain/staging/copy-select-link-dereference.cf new file mode 100644 index 0000000000..c0bf4a72f0 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/copy-select-link-dereference.cf @@ -0,0 +1,93 @@ +# Assume the following file-structure: +# dir1/file1 +# dir2/file2 +# link -> dir1 +# +# We want to copy only what link points to, without explicitly specifying the link name. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + commands: + "$(G.mkdir) -p $(G.testdir)/from/dir1"; + "$(G.mkdir) -p $(G.testdir)/from/dir2"; + "$(G.echo) 'indir1' > $(G.testdir)/from/dir1/file1" + contain => in_shell; + "$(G.echo) 'indir2' > $(G.testdir)/from/dir2/file2" + contain => in_shell; + "$(G.ln) -s dir1 link", + contain => in_dir("$(G.testdir)/from"); +} + + +bundle agent test +{ + vars: + "regular_dirs" slist => { ".*dir1",".*dir2" }; + + files: + + "$(G.testdir)/to" + copy_from => dereference_all("$(G.testdir)/from"), + depth_search => recurse("inf"), + file_select => links_to( "@(regular_dirs)" ); +} + + +bundle agent check +{ + vars: + "file1_contents" string => readfile("$(G.testdir)/to/dir1/file1", 100); + + classes: + "file1_contents_correct" expression => strcmp("$(file1_contents)", "indir1"); + "file2_exists" expression => fileexists("$(G.testdir)/to/dir2/file2"); + + "ok" expression => "file1_contents_correct.(!file2_exists)"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + +} + + +body contain in_dir(s) +{ + chdir => "$(s)"; +} + +body contain in_shell +{ + useshell => "true"; +} + +body copy_from dereference_all(from) +{ + source => "$(from)"; + copylink_patterns => { ".*" }; +} + +body file_select links_to(list) +{ + issymlinkto => { @(list) }; + file_result => "issymlinkto"; +} + +body depth_search recurse(d) +{ + depth => "$(d)"; + xdev => "true"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/staging/edit_immutable.cf b/tests/acceptance/10_files/02_maintain/staging/edit_immutable.cf new file mode 100644 index 0000000000..0d8c719a56 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/edit_immutable.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Redmine#3184: Test editing of an immutable file +# Redmine#2100: Test chmod of an immutable file +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle common chattr +{ + vars: + "chattr" string => "/usr/bin/chattr"; + + classes: + "have_chattr" expression => fileexists($(chattr)); +} + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent test +{ + methods: + "1" usebundle => test_setup($(G.testfile)); + "2" usebundle => test_edit($(G.testfile)); + "2" usebundle => test_chmod($(G.testfile)); + "3" usebundle => test_teardown($(G.testfile)); +} + +bundle agent test_setup(file) +{ + commands: + have_chattr:: + "$(chattr.chattr)" args => "+i $(file)", + classes => scoped_classes_generic("namespace", "chattr"); +} + +bundle agent test_edit(file) +{ + files: + have_chattr:: + "$(file)" + edit_line => test_edit_line(), + classes => scoped_classes_generic("namespace", "test"); +} + +bundle agent test_chmod(file) +{ + files: + have_chattr:: + "$(file)" + perms => m("755"), + classes => scoped_classes_generic("namespace", "chmod"); +} + +body perms m(mode) +{ + mode => "$(mode)"; +} + +bundle agent test_teardown(file) +{ + commands: + have_chattr:: + "$(chattr.chattr)" args => "-i $(file)", + classes => scoped_classes_generic("namespace", "chattr"); +} + +bundle edit_line test_edit_line() +{ + insert_lines: + "blah"; +} + +####################################################### + +bundle agent check +{ + classes: + # we're OK if: + # - we don't have chattr + # - chattr failed to DTRT (e.g. we're not root) + # - the test failed to edit + # - the test denied the chmod (TODO: should it be "denied" or "failed"? + "ok" expression => "!have_chattr|chattr_failed|(test_failed.!test_kept.!test_denied.!test_timeout.!test_repaired.!chmod_failed.!chmod_kept.chmod_denied.!chmod_timeout.!chmod_repaired)"; + + methods: + "report" usebundle => dcs_report_generic_classes("test"); + "report" usebundle => dcs_report_generic_classes("chmod"); + "report" usebundle => dcs_report_generic_classes("chattr"); + + reports: + DEBUG.!have_chattr:: + "Without $(chattr.chattr), the test will always pass"; + DEBUG.chattr_failed:: + "Since you can't run $(chattr.chattr) to set attributes, the test will always pass"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/02_maintain/staging/line_truncation_at_4096_chars.cf b/tests/acceptance/10_files/02_maintain/staging/line_truncation_at_4096_chars.cf new file mode 100644 index 0000000000..c946ab5ed5 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/staging/line_truncation_at_4096_chars.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Check if the lines in a file get truncated after 4096 +# chars during file editing. +# +# Ticket: https://cfengine.com/dev/issues/3882 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("${this.promise_filename}") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + any:: + "file_mode" int => "0644"; + "char_number" int => "6144"; + "line_to_add" string => "This is a nice short line."; + + files: + "${G.testfile}" + create => "true", + perms => m("${file_mode}"); + + # Using system command instead of CFEngine itself to + # prevent the bug to be here at file creation too. + # It would create a false negative. + commands: + "${G.perl}" + args => "-e 'print \"#\" x ${char_number}; print \"ENDMARK\n\"' > ${G.testfile}" + contain => in_shell; +} + +body perms m(mode) +{ + mode => "${mode}"; +} + +body contain in_shell +{ + useshell => "true"; # canonical "useshell" but this is backwards-compatible +} + +####################################################### + +bundle agent test +{ + + files: + "${G.testfile}" + create => "false", + edit_line => insert_lines("${init.line_to_add}"); +} + +bundle edit_line insert_lines(lines) +{ +insert_lines: + + "${lines}" + comment => "Append lines if they don't exist"; +} + +####################################################### + +bundle agent check +{ + + classes: + # ok is defined if the file still contains ENDMARK, meaning the end of the sample very + # long line did not get truncated during file editing + "ok" expression => returnszero("${G.grep} -q ENDMARK ${G.testfile}", "noshell"); + + reports: + DEBUG.!ok:: + "${this.promise_filename} FAILed as the generated file got at least a line truncated to 4096 characters" + ok:: + "${this.promise_filename} Pass"; + !ok:: + "${this.promise_filename} FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("${G.testfile}"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/symlink_link_and_no_move_obstructions.cf b/tests/acceptance/10_files/02_maintain/symlink_link_and_no_move_obstructions.cf new file mode 100644 index 0000000000..e6a1baf108 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/symlink_link_and_no_move_obstructions.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Create an relative link to a file, expect subsequent symlink promise to +# be unhappy with that (symlink is supposed to be synonymous with absolute) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => init_link; +} + +body link_from init_link +{ + source => "$(G.etc_group)"; + link_type => "relative"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + "test_suppress_fail" string => "freebsd", + meta => { "redmine5261" }; + + vars: + "inode" string => filestat("$(G.etc_group)", "ino"); + + files: + "$(G.testfile)" + classes => test_if_ok("test_ok"), + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.etc_group)"; + link_type => "symlink"; +} + +body classes test_if_ok(c) +{ + repair_failed => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat(filestat("$(G.testfile)", "linktarget"), "ino"); + "Lresult" string => filestat("$(G.testfile)", "ino"); + + # This tells us where the link points + "link_target" string => filestat("$(G.testfile)", "linktarget_shallow"); + + classes: + "okL" not => strcmp("$(test.inode)", "$(Lresult)"); + "okT" not => strcmp("$(link_target)", "$(G.etc_group)"); + "ok" and => { "test_ok", "okL", "okT", + strcmp("$(test.inode)", "$(result)"), + # Test that the symlink target starts with dot + regcmp("\..*", "$(link_target)") + }; + + reports: + DEBUG:: + "expected: '$(test.inode)'"; + "got: '$(Lresult)' => '$(result)'"; + "got this too: '$(G.etc_group)' => '$(link_target)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/symlink_link_kept.cf b/tests/acceptance/10_files/02_maintain/symlink_link_kept.cf new file mode 100644 index 0000000000..ba112ae5ad --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/symlink_link_kept.cf @@ -0,0 +1,91 @@ +####################################################### +# +# Create an absolute link to a file, expect subsequent symlink promise to +# be happy with that +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).linked_to" + create => "true"; + "$(G.testfile)" + create => "true", + move_obstructions => "true", + link_from => init_link; +} + +body link_from init_link +{ + source => "$(G.testfile).linked_to"; + link_type => "absolute"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + vars: + "inode" string => filestat("$(G.testfile).linked_to", "ino"); + + files: + "$(G.testfile)" + classes => test_if_ok("test_ok"), + link_from => test_link; +} + +body link_from test_link +{ + source => "$(G.testfile).linked_to"; + link_type => "symlink"; +} + +body classes test_if_ok(c) +{ + promise_kept => { "$(c)" }; +} + +####################################################### + +bundle agent check +{ + vars: + "result" string => filestat(filestat("$(G.testfile)", "linktarget"), "ino"); + "Lresult" string => filestat("$(G.testfile)", "ino"); + + # This tells us where the link points + "link_target" string => filestat("$(G.testfile)", "linktarget"); + + classes: + "okL" not => strcmp("$(test.inode)", "$(Lresult)"); + "ok" and => { "test_ok", "okL", + strcmp("$(test.inode)", "$(result)"), + strcmp("$(link_target)", "$(G.testfile).linked_to") + }; + + reports: + DEBUG:: + "expected: '$(test.inode)'"; + "got: '$(Lresult)' => '$(result)'"; + "got this too: '$(G.testfile).linked_to' => '$(link_target)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/02_maintain/touch.cf b/tests/acceptance/10_files/02_maintain/touch.cf new file mode 100644 index 0000000000..eb4d8fadb6 --- /dev/null +++ b/tests/acceptance/10_files/02_maintain/touch.cf @@ -0,0 +1,56 @@ +####################################################### +# +# Test that action => "warn" works correctly for touch +# Redmine 3172 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent test +{ + files: + "$(G.testfile)" + touch => "true", + action => warn_only, + classes => if_repaired("wrong_class_set"); + + "$(G.testfile)_not_there" + touch => "true", + action => warn_only; + + "$(G.testfile)_there" + touch => "true"; +} + +bundle agent check +{ + vars: + "wrong_classes" slist => classesmatching("wrong_.*"); + classes: + "wrong_file_there" expression => fileexists("$(G.testfile)_not_there"); + "right_file_there" expression => fileexists("$(G.testfile)_there"); + + "ok" and => { "right_file_there", "!wrong_file_there", "!wrong_class_set" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG.!ok:: + "Unwanted class: $(wrong_classes)"; +} diff --git a/tests/acceptance/10_files/03_transform/001.cf b/tests/acceptance/10_files/03_transform/001.cf new file mode 100644 index 0000000000..0adfc0681f --- /dev/null +++ b/tests/acceptance/10_files/03_transform/001.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Ensure that the transformer runs for every file in a recursive tree +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + any:: + "files" slist => { "1", "2", "3" }; + + files: + "$(G.testdir)/." + create => "true"; + + "$(G.testdir)/$(files)" + copy_from => init_copy("$(G.etc_group)"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)" + transformer => "$(G.gzip) $(this.promiser)", + file_select => test_plain, + depth_search => test_recurse; +} + +body file_select test_plain +{ + file_types => { "plain" }; + file_result => "file_types"; +} + +body depth_search test_recurse +{ + depth => "inf"; +} + +####################################################### + +bundle agent check +{ + vars: + "files" slist => { @{init.files} }; + + classes: + "ok$(files)" expression => fileexists("$(G.testdir)/$(files).gz"); + "ok" and => { "ok1", "ok2", "ok3" }; + + reports: + DEBUG:: + "$(G.testdir)/$(files).gz exists as expected" + if => "ok$(files)"; + "$(G.testdir)/$(files).gz was not created!" + if => "!ok$(files)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/03_transform/002.cf b/tests/acceptance/10_files/03_transform/002.cf new file mode 100644 index 0000000000..b49e79bf33 --- /dev/null +++ b/tests/acceptance/10_files/03_transform/002.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Ensure that the transformer does not run when --dry-run is used +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + any:: + "files" slist => { "1", "2", "3" }; + + files: + "$(G.testdir)/." + create => "true"; + + "$(G.testdir)/$(files)" + copy_from => init_copy("$(G.etc_group)"); +} + +body copy_from init_copy(file) +{ + source => "$(file)"; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + commands: + "$(sys.cf_agent) -f $(this.promise_filename) -D AUTO -K -b init,test_dry_run --dry-run" + contain => start_from_cwd; +} + +body contain start_from_cwd +{ + chdir => "$(G.cwd)"; +} + +bundle agent test_dry_run +{ + files: + "$(G.testdir)" + transformer => "$(G.gzip) $(this.promiser)", + file_select => test_plain, + depth_search => test_recurse; + + reports: + !opt_dry_run:: + "$(this.promise_filename) --dry-run FAIL"; +} + +body file_select test_plain +{ + file_types => { "plain" }; + file_result => "file_types"; +} + +body depth_search test_recurse +{ + depth => "inf"; +} + +####################################################### + +bundle agent check +{ + vars: + "files" slist => { @{init.files} }; + + classes: + "ok$(files)" expression => fileexists("$(G.testdir)/$(files)"); + "no$(files)" expression => fileexists("$(G.testdir)/$(files).gz"); + "ok" and => { + "ok1", "ok2", "ok3", + "!no1", "!no2", "!no3", + }; + + reports: + DEBUG:: + "$(G.testdir)/$(files) exists with no $(G.testdir)/$(files).gz" + if => "ok$(files).!no$(files)"; + "$(G.testdir)/$(files).gz was created during --dry-run!" + if => "no$(files)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/03_transform/003.cf b/tests/acceptance/10_files/03_transform/003.cf new file mode 100644 index 0000000000..589549e641 --- /dev/null +++ b/tests/acceptance/10_files/03_transform/003.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Ensure that the transformer runs only once for every file in a recursive tree +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "off"; # for execresult in check bundle +} + +####################################################### + +bundle agent init +{ + vars: + "files" slist => { "1", "2", "3" }; + + files: + "$(G.testdir)/files/." + create => "true"; + + "$(G.testdir)/files/$(files)" + copy_from => init_copy; +} + +body copy_from init_copy +{ + source => "$(G.etc_group)"; +} + +body classes init_set_class(class) +{ + promise_kept => { "$(class)" }; + promise_repaired => { "$(class)" }; +} + +####################################################### + +bundle agent test +{ + vars: + "counter" string => "$(G.testdir)/counter"; + + files: + windows:: + "$(G.testdir)/files" + transformer => "$(G.echo) $(this.promiser) >> $(counter)", + file_select => test_plain, + depth_search => test_recurse; + !windows:: + "$(G.testdir)/files" + transformer => "/bin/sh -c 'echo $(this.promiser) >> $(counter)'", + file_select => test_plain, + depth_search => test_recurse; +} + +body file_select test_plain +{ + file_types => { "plain" }; + file_result => "file_types"; +} + +body depth_search test_recurse +{ + depth => "inf"; +} + +####################################################### + +bundle agent check +{ + vars: + "count" string => execresult("$(G.wc) -l $(test.counter)", "noshell"); + DEBUG:: + "list" string => execresult("$(G.ls) -1 $(G.testdir)/files", "noshell"); + + classes: + "ok" expression => regcmp("^\s*3\s.*", "$(count)"); + + reports: + DEBUG.!ok:: + "3 transformations expected, saw (wc): '$(count)'"; + "3 copies of $(G.etc_group) expected, saw (ls): '$(list)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/001.cf b/tests/acceptance/10_files/04_match/001.cf new file mode 100644 index 0000000000..d59b27840d --- /dev/null +++ b/tests/acceptance/10_files/04_match/001.cf @@ -0,0 +1,54 @@ +####################################################### +# +# Create a file and delete it - hoping that it matches (issue 365) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +####################################################### + +bundle agent test +{ + + files: + "$(G.testfile)" + delete => test_delete; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/004.cf b/tests/acceptance/10_files/04_match/004.cf new file mode 100644 index 0000000000..9f60b58a54 --- /dev/null +++ b/tests/acceptance/10_files/04_match/004.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Test exec_program on file_select (issue 294) - test negative match on existing file +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "find_this" string => "bibbetygargle"; + files: + "$(G.testfile)" + create => "true", + edit_line => init_data; +} + +bundle edit_line init_data +{ + insert_lines: + "Gabba gabba hey!"; + "yibbity $(find_this) bletch"; + "torchecol bum adder"; +} + +####################################################### + +bundle agent test +{ + vars: + linux:: + "prefix" string => ""; + freebsd:: + "prefix" string => "/usr"; + !(linux|freebsd):: + "prefix" string => "FIX ME"; + + files: + "$(G.testroot)" + file_select => test_hunt("$(prefix)"), + delete => test_delete; +} + +body file_select test_hunt(prefix) +{ + exec_program => "$(G.grep) XX$(init.find_this)XX $(this.promiser)"; + file_result => "exec_program"; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => fileexists("$(G.testfile)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/008.cf b/tests/acceptance/10_files/04_match/008.cf new file mode 100644 index 0000000000..6866a78cc3 --- /dev/null +++ b/tests/acceptance/10_files/04_match/008.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Delete a file in a nonexistent directory. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "1"; +} + +####################################################### + +bundle agent test +{ + vars: + "agent" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO 2>&1", "useshell"); +} + +####################################################### + +bundle agent check +{ + classes: + "chdir" expression => regcmp(".*chdir.*", $(test.agent)); + "checkpoint" expression => regcmp(".*CHECKPOINT.*", $(test.agent)); + "ok" and => { "!chdir", "checkpoint" }; + + reports: + DEBUG:: + "This should only pass if $(this.promise_filename).sub does not output anything about failing to chdir"; + "Output from $(this.promise_filename).sub: $(test.agent)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/008.cf.sub b/tests/acceptance/10_files/04_match/008.cf.sub new file mode 100644 index 0000000000..e890288b66 --- /dev/null +++ b/tests/acceptance/10_files/04_match/008.cf.sub @@ -0,0 +1,29 @@ +####################################################### +# +# Delete a file in a nonexistent directory. +# +####################################################### + +body common control +{ + bundlesequence => { "delete" }; + version => "1.0"; +} + +####################################################### + +bundle agent delete +{ + files: + "/abc/nofile" + delete => test_delete; + + reports: + "CHECKPOINT"; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} diff --git a/tests/acceptance/10_files/04_match/directory_can_not_readlink.cf b/tests/acceptance/10_files/04_match/directory_can_not_readlink.cf new file mode 100644 index 0000000000..96febd5204 --- /dev/null +++ b/tests/acceptance/10_files/04_match/directory_can_not_readlink.cf @@ -0,0 +1,56 @@ +####################################################### +# +# Delete a file in a nonexistent directory. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + cache_system_functions => "false"; +} + +####################################################### + +bundle agent init +{ + vars: + "checkdir" string => "$(G.testdir)/should-not-show-up-in-output"; + + files: + "$(checkdir)/." create => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "agent" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub -D AUTO 2>&1", "useshell"); +} + +####################################################### + +bundle agent check +{ + classes: + "checkpoint_fail" not => regcmp(".*CHECKPOINT.*", $(test.agent)); + "fail" or => { checkpoint_fail, + regcmp(".*Unable to read link.*", $(test.agent)), + regcmp(".*$(init.checkdir).*", $(test.agent)) }; + + reports: + DEBUG:: + "This should only pass if $(this.promise_filename).sub does not output anything about failing to readlink"; + "Output from $(this.promise_filename).sub: $(test.agent)"; + DEBUG.checkpoint_fail:: + "The secondary policy in $(this.promise_filename).sub did not checkpoint correctly"; + !fail:: + "$(this.promise_filename) Pass"; + fail:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/directory_can_not_readlink.cf.sub b/tests/acceptance/10_files/04_match/directory_can_not_readlink.cf.sub new file mode 100644 index 0000000000..796f0c9b38 --- /dev/null +++ b/tests/acceptance/10_files/04_match/directory_can_not_readlink.cf.sub @@ -0,0 +1,47 @@ +####################################################### +# +# Find a directory when expecting a symlink +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { symlinks }; + version => "1.0"; +} + +####################################################### + +bundle agent symlinks +{ + vars: + "checkdir" string => "$(G.testdir)/should-not-show-up-in-output"; + + classes: + "checkdir_exists" expression => fileexists($(checkdir)); + + files: + "$(checkdir)" + handle => "delete_checkdir_link", + comment => "Delete checkdir symlink", + file_select => select_link("/home"), + delete => nodir, + pathtype => "literal"; + + reports: + checkdir_exists:: + "CHECKPOINT"; +} + +body file_select select_link(name) +{ + file_types => { "symlink" }; + issymlinkto => { "$(name)$" }; + file_result => "issymlinkto"; +} + +body delete nodir +{ + rmdirs => "false"; +} diff --git a/tests/acceptance/10_files/04_match/kept_promise.cf b/tests/acceptance/10_files/04_match/kept_promise.cf new file mode 100644 index 0000000000..5f86c87bfd --- /dev/null +++ b/tests/acceptance/10_files/04_match/kept_promise.cf @@ -0,0 +1,82 @@ +####################################################### +# +# Check that already inserted lines trigger promise +# kept. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + vars: + "array[1]" string => "one"; + "array[2]" string => "two"; + "array[3]" string => "three"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_edit; +} + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + edit_line => test_edit; +} + +bundle edit_line init_edit +{ + vars: + "index" slist => getindices("init.array"); + + insert_lines: + "${init.array[${index}]}"; +} + +bundle edit_line test_edit +{ + vars: + "index" slist => getindices("init.array"); + + insert_lines: + "${init.array[${index}]}" + classes => kept_if_else("line_${index}_kept", "line_${index}_added", "line_${index}_failed"); +} + +bundle agent check +{ + classes: + "ok" and => { "line_1_kept", "line_2_kept", "line_3_kept" }; + + reports: + DEBUG.!line_1_kept:: + "Line 1 promise should have been kept, but was not."; + DEBUG.!line_2_kept:: + "Line 2 promise should have been kept, but was not."; + DEBUG.!line_3_kept:: + "Line 3 promise should have been kept, but was not."; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +body classes kept_if_else(kept, yes, no) + +{ + promise_kept => { "${kept}" }; + promise_repaired => { "${yes}" }; + repair_failed => { "${no}" }; + repair_denied => { "${no}" }; + repair_timeout => { "${no}" }; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/match_scope.cf b/tests/acceptance/10_files/04_match/match_scope.cf new file mode 100644 index 0000000000..5ce45b60fc --- /dev/null +++ b/tests/acceptance/10_files/04_match/match_scope.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Check that $(match.1) works +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "testfile" string => concat("$(G.testdir)", "/dummy_file"); + + files: + "$(testfile)" + create => "true"; + + reports: + DEBUG:: + "Creating file $(testfile)"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + vars: + "foo" string => "foo"; + + files: + "$(G.testdir)/([^_]*)_(.*)" + delete => test_delete, + classes => test_classes; +} + +body classes test_classes +{ + promise_repaired => { "$(match.1)", "$(match.2)" }; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "dummy", "file" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/not_selected_dirs_are_skipped.cf b/tests/acceptance/10_files/04_match/not_selected_dirs_are_skipped.cf new file mode 100644 index 0000000000..e4a6395e2d --- /dev/null +++ b/tests/acceptance/10_files/04_match/not_selected_dirs_are_skipped.cf @@ -0,0 +1,114 @@ +######################################################################## +# +# Make sure that a directory is skipped when body_select does not match +# it, even when depth_search is off. +# +######################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body perms perms1 +{ + mode => "732"; +} + +body depth_search recurse_nobasedir(x) +{ + depth => "$(x)"; + include_basedir => "false"; +} + +body delete del +{ + rmdirs => "true"; +} + +body file_select by_name(names) +{ + leaf_name => { @(names) }; + file_result => "leaf_name"; +} + +bundle agent init +{ + files: + # Create a directory and some files in there + "$(G.testroot)/srcdir/." + create => "true"; + "$(G.testroot)/srcdir/testfile1" + create => "true"; + "$(G.testroot)/srcdir/testfile2" + create => "true"; + + # The destination should already exist and have specific + # permissions, so that it's excluded by the body files_select later + "$(G.testroot)/destdir/." + perms => perms1, + create => "true"; + + # Make sure all of its contents are cleaned up + "$(G.testroot)/destdir" + depth_search => recurse_nobasedir("inf"), + file_select => by_name(".*"), + delete => del; +} + +####################################################### + +body file_select test_select +{ + # Select will fail because the dir is mode 732 + search_mode => { "444" }; + file_result => "mode"; +} + +body link_from ln_s_children(x) +{ + link_type => "symlink"; + link_children => "true"; + source => "$(x)"; +} + +bundle agent test +{ + + # We promise that if destdir has specific permissions, then it must + # contain links to all children from srcdir. + + files: + "$(G.testroot)/destdir" + file_select => test_select, + link_from => ln_s_children("$(G.testroot)/srcdir"); + +} + +####################################################### + +bundle agent check +{ + + # Since body select fails, the files in target directory should *not* + # be created + + classes: + "files_created" and => { + fileexists("$(G.testroot)/destdir/testfile1"), + fileexists("$(G.testroot)/destdir/testfile2") + }; + + reports: + !files_created:: + "$(this.promise_filename) Pass"; + + files_created:: + "$(this.promise_filename) FAIL"; + +} + diff --git a/tests/acceptance/10_files/04_match/staging/002.cf b/tests/acceptance/10_files/04_match/staging/002.cf new file mode 100644 index 0000000000..20f6ef4eb2 --- /dev/null +++ b/tests/acceptance/10_files/04_match/staging/002.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Create a file and delete it - hoping that it matches (issue 365) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true"; +} + +####################################################### + +bundle agent test +{ + + files: + "/tmp/TEST\.cfengine" + delete => test_delete; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/staging/003.cf b/tests/acceptance/10_files/04_match/staging/003.cf new file mode 100644 index 0000000000..177886e5e0 --- /dev/null +++ b/tests/acceptance/10_files/04_match/staging/003.cf @@ -0,0 +1,76 @@ +####################################################### +# +# Test exec_program on file_select (issue 294) - test positive match on existing file +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "find_this" string => "bibbetygargle"; + files: + "$(G.testfile)" + create => "true", + edit_line => init_data; +} + +bundle edit_line init_data +{ + insert_lines: + "Gabba gabba hey!"; + "yibbity $(find_this) bletch"; + "torchecol bum adder"; +} + +####################################################### + +bundle agent test +{ + vars: + linux:: + "prefix" string => ""; + freebsd:: + "prefix" string => "/usr"; + !(linux|freebsd):: + "prefix" string => "FIX ME"; + + files: + "/tmp" + file_select => test_hunt("$(prefix)"), + delete => test_delete; +} + +body file_select test_hunt(prefix) +{ + exec_program => "$(G.grep) $(init.find_this) $(this.promiser)"; + file_result => "exec_program"; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/staging/005.cf b/tests/acceptance/10_files/04_match/staging/005.cf new file mode 100644 index 0000000000..08c65d3829 --- /dev/null +++ b/tests/acceptance/10_files/04_match/staging/005.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test exec_program on file_select (issue 294) - test positive match on existing file +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "find_this" string => "bibbetygargle"; + files: + "$(G.testfile)" + create => "true", + edit_line => init_data; +} + +bundle edit_line init_data +{ + insert_lines: + "Gabba gabba hey!"; + "yibbity $(find_this) bletch"; + "torchecol bum adder"; +} + +####################################################### + +bundle agent test +{ + files: + "/tmp" + file_select => test_hunt, + delete => test_delete; +} + +body file_select test_hunt +{ + exec_regex => ".*$(test.find_this).*"; + exec_program => "/bin/cat $(this.promiser)"; + file_result => "exec_program.exec_regex"; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/staging/006.cf b/tests/acceptance/10_files/04_match/staging/006.cf new file mode 100644 index 0000000000..8a2b284297 --- /dev/null +++ b/tests/acceptance/10_files/04_match/staging/006.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test exec_program on file_select (issue 294) - test negative match on existing file +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "find_this" string => "bibbetygargle"; + files: + "$(G.testfile)" + create => "true", + edit_line => init_data; +} + +bundle edit_line init_data +{ + insert_lines: + "Gabba gabba hey!"; + "yibbity $(find_this) bletch"; + "torchecol bum adder"; +} + +####################################################### + +bundle agent test +{ + files: + "/tmp" + file_select => test_hunt, + delete => test_delete; +} + +body file_select test_hunt +{ + exec_regex => ".*XX$(test.find_this)XX.*"; + exec_program => "/bin/cat $(this.promiser)"; + file_result => "exec_program.exec_regex"; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/04_match/staging/007.cf b/tests/acceptance/10_files/04_match/staging/007.cf new file mode 100644 index 0000000000..1ec5288d09 --- /dev/null +++ b/tests/acceptance/10_files/04_match/staging/007.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Test exec_program on file_select (issue 294) - test negative match on existing file +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "find_this" string => "bibbetygargle"; + files: + "$(G.testfile)" + create => "true", + edit_line => init_data; +} + +bundle edit_line init_data +{ + insert_lines: + "Gabba gabba hey!"; + "yibbity $(find_this) bletch"; + "torchecol bum adder"; +} + +####################################################### + +bundle agent test +{ + files: + "/tmp" + file_select => test_hunt, + delete => test_delete; +} + +body file_select test_hunt +{ + exec_regex => "$(test.find_this)"; # Partial regex shouldn't match + exec_program => "/bin/cat $(this.promiser)"; + file_result => "exec_program.exec_regex"; +} + +body delete test_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => fileexists("$(G.testfile)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/001.cf b/tests/acceptance/10_files/05_classes/001.cf new file mode 100644 index 0000000000..b1a2c132f2 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/001.cf @@ -0,0 +1,96 @@ +####################################################### +# +# Delete a line, ensure that a promise_repaired class gets set +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "keep this +and this +but delete one line +keep this too"; + + "expected" string => + "keep this +and this +keep this too"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => ".*delete.*"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => full_set; +} + +body classes full_set +{ + promise_kept => { "fail" }; + promise_repaired => { "pass" }; + repair_failed => { "fail" }; + repair_denied => { "fail" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + methods: + pass.!fail:: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); + + reports: + !pass|fail:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/002.cf b/tests/acceptance/10_files/05_classes/002.cf new file mode 100644 index 0000000000..6119f84cab --- /dev/null +++ b/tests/acceptance/10_files/05_classes/002.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Delete a line that isn't there, ensure that a promise_kept class gets set +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "keep this +and this +keep this too"; + + "expected" string => + "keep this +and this +keep this too"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => ".*delete.*"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => full_set; +} + +body classes full_set +{ + promise_kept => { "pass" }; + promise_repaired => { "fail" }; + repair_failed => { "fail" }; + repair_denied => { "fail" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + methods: + pass.!fail:: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); + + reports: + !pass|fail:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/004.cf b/tests/acceptance/10_files/05_classes/004.cf new file mode 100644 index 0000000000..fb727640a3 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/004.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a line that isn't there, action_policy=>"warn", ensure that a promise_kept class gets set +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "keep this +and this +keep this too"; + + "expected" string => + "keep this +and this +keep this too"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => ".*delete.*"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + action => test_warn_only, + classes => full_set; +} + +body action test_warn_only +{ + action_policy => "warn"; +} + +body classes full_set +{ + promise_kept => { "pass" }; + promise_repaired => { "fail" }; + repair_failed => { "fail" }; + repair_denied => { "fail" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + methods: + pass.!fail:: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); + + reports: + !pass|fail:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/006.cf b/tests/acceptance/10_files/05_classes/006.cf new file mode 100644 index 0000000000..4dfc0a20e3 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/006.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a line that isn't there, action_policy=>"nop", ensure that a promise_kept class gets set +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "keep this +and this +keep this too"; + + "expected" string => + "keep this +and this +keep this too"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => ".*delete.*"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + action => test_warn_only, + classes => full_set; +} + +body action test_warn_only +{ + action_policy => "nop"; +} + +body classes full_set +{ + promise_kept => { "pass" }; + promise_repaired => { "fail" }; + repair_failed => { "fail" }; + repair_denied => { "fail" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + methods: + pass.!fail:: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); + + reports: + !pass|fail:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/102.cf b/tests/acceptance/10_files/05_classes/102.cf new file mode 100644 index 0000000000..eded9dc159 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/102.cf @@ -0,0 +1,128 @@ +####################################################### +# +# Test multiple promise_repaired and cancel_repaired (contrived example) +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_repaired => { "promise_repaired", "p2_repaired" }; + cancel_repaired => { "cancel_repaired", "cancel_kept", "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[p2_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "p2_repaired", + "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/003.cf b/tests/acceptance/10_files/05_classes/staging/003.cf new file mode 100644 index 0000000000..5b11b5289d --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/003.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Try to delete a line, action_policy=>"warn", ensure that a repair_denied class gets set (issue 441) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "keep this +and this +but delete one line +keep this too"; + + "expected" string => + "keep this +and this +but delete one line +keep this too"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => ".*delete.*"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => full_set; +} + +body classes full_set +{ + promise_kept => { "fail" }; + promise_repaired => { "fail" }; + repair_failed => { "fail" }; + repair_denied => { "pass" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + classes: + "no_difference" expression => returnszero( + "$(G.diff) -q $(G.testfile).actual $(G.testfile).expected", + "noshell"); + "ok" and => { "pass", "!fail", "no_difference" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/005.cf b/tests/acceptance/10_files/05_classes/staging/005.cf new file mode 100644 index 0000000000..cacf046b79 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/005.cf @@ -0,0 +1,118 @@ +####################################################### +# +# Attempt to delete a line, action_policy=>"nop", ensure that a repair_denied class gets set (issue 441) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "keep this +and this +but delete one line +keep this too"; + + "expected" string => + "keep this +and this +but delete one line +keep this too"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => ".*delete.*"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + action => warn_only, + classes => full_set; +} + +body action warn_only +{ + action_policy => "nop"; +} + +body classes full_set +{ + promise_kept => { "fail" }; + promise_repaired => { "fail" }; + repair_failed => { "fail" }; + repair_denied => { "pass" }; + repair_timeout => { "fail" }; +} + + +####################################################### + +bundle agent check +{ + classes: + "no_difference" expression => returnszero( + "$(G.diff) -q $(G.testfile).actual $(G.testfile).expected", + "noshell"); + "ok" and => { "pass", "!fail", "no_difference" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +####################################################### + +bundle agent fini +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).*"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/007.cf b/tests/acceptance/10_files/05_classes/staging/007.cf new file mode 100644 index 0000000000..36d1fdd0d0 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/007.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Delete a line that isn't there, action_policy=>"nop", ensure that a promise_kept class gets set +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "testfile" string => "/tmp/TEST.cfengine"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + perms => test_mode("0644"); + + "$(G.testfile).link" + link_from => init_link("$(G.testfile)"); +} + +body link_from init_link(src) +{ + source => "$(src)"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).link" + perms => test_mode("0600"), + classes => fail_check; + + "$(G.testfile)" + perms => test_mode("0644"), + classes => kept_check; +} + +body perms test_mode(m) +{ + mode => "$(m)"; +} + +body classes fail_check +{ + promise_kept => { "fail1" }; + promise_repaired => { "fail1" }; + repair_failed => { "pass1" }; # Cannot chmod a sylink! + repair_denied => { "fail1" }; + repair_timeout => { "fail1" }; +} + +body classes kept_check +{ + promise_kept => { "pass2" }; # Original mode should not have changed + promise_repaired => { "fail2" }; + repair_failed => { "fail2" }; + repair_denied => { "fail2" }; + repair_timeout => { "fail2" }; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "pass1", "pass2", "!fail1", "!fail2" }; + + reports: + DEBUG.!pass1:: + "Trying to chmod a symlink did not fail like it should"; + DEBUG.fail1:: + "Trying to chmod a symlink gave a return other than repair_failed"; + DEBUG.!pass2:: + "Something was funky with the target of a symlink"; + DEBUG.fail2:: + "Doing a chmod on a symlink changed the target file instead!"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/101.cf b/tests/acceptance/10_files/05_classes/staging/101.cf new file mode 100644 index 0000000000..ee58cc089e --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/101.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/103.cf b/tests/acceptance/10_files/05_classes/staging/103.cf new file mode 100644 index 0000000000..aba20c05f4 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/103.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test promise_repaired and cancel_repaired (different location in edit_line) +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/104.cf b/tests/acceptance/10_files/05_classes/staging/104.cf new file mode 100644 index 0000000000..0588292759 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/104.cf @@ -0,0 +1,133 @@ +####################################################### +# +# Test promise_kept and cancel_kept +# Insert lines to a file, then verify that insert promise is kept +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + edit_line => init_insert("$(init.body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/105.cf b/tests/acceptance/10_files/05_classes/staging/105.cf new file mode 100644 index 0000000000..e9722006ec --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/105.cf @@ -0,0 +1,135 @@ +####################################################### +# +# Test promise_kept+repaired and cancel_kept+repaired (broken multiple promises set classes, see comments) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + edit_line => init_insert("$(init.body)"), + edit_defaults => init_empty; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and test, it is only executed ONCE, so it only + # sets the repaired promises. + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/106.cf b/tests/acceptance/10_files/05_classes/staging/106.cf new file mode 100644 index 0000000000..f087686134 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/106.cf @@ -0,0 +1,145 @@ +####################################################### +# +# Same as 105.cf, but two separate edit_line promises (incorrect test, see comments) +# Test promise_kept+repaired and cancel_kept+repaired (multiple promises set classes) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + edit_line => test_insert("$(init.body)"), + edit_defaults => init_empty; +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and we call test_insert from test, the promises are + # the same, and are only executed ONCE, so it only sets the repaired + # promises. + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/107.cf b/tests/acceptance/10_files/05_classes/staging/107.cf new file mode 100644 index 0000000000..e088aade88 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/107.cf @@ -0,0 +1,152 @@ +####################################################### +# +# Same as 105.cf, but two separate edit_line promises (still incorrectly done!) +# Test promise_kept+repaired and cancel_kept+repaired (multiple promises set classes) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + "body2" string => + "BEGIN + One potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + edit_line => test_insert, + edit_defaults => init_empty; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(init.body2)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and we call test_insert from test, the promises are + # the same, and are only executed ONCE, so it only sets the repaired + # promises. + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/108.cf b/tests/acceptance/10_files/05_classes/staging/108.cf new file mode 100644 index 0000000000..6b9d273f74 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/108.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Same as 105.cf, but two separate edit_line promises (second promise is not kept because files are different) +# Test promise_kept+repaired and cancel_kept+repaired (multiple promises set classes) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + "body2" string => + "BEGIN + One potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + edit_line => test_insert, + edit_defaults => init_empty; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(init.body2)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and we call test_insert from test, the promises are + # the same, and are only executed ONCE, so it only sets the repaired + # promises. + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/109.cf b/tests/acceptance/10_files/05_classes/staging/109.cf new file mode 100644 index 0000000000..122d0acc80 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/109.cf @@ -0,0 +1,153 @@ +####################################################### +# +# Same as 105.cf, but two separate edit_line promises (correctly done!) +# Test promise_kept+repaired and cancel_kept+repaired (multiple promises set classes) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + "body2" string => + "BEGIN + One potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(init.body2)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and we call test_insert from test, the promises are + # the same, and are only executed ONCE, so it only sets the repaired + # promises. + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/110.cf b/tests/acceptance/10_files/05_classes/staging/110.cf new file mode 100644 index 0000000000..0b7b9af437 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/110.cf @@ -0,0 +1,150 @@ +####################################################### +# +# Same as 105.cf, but two separate edit_line promises (correctly done!) +# Test promise_kept+repaired and cancel_kept+repaired (multiple promises set classes) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + "body2" string => + "BEGIN + One potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(init.body2)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and we call test_insert from test, the promises are + # the same, and are only executed ONCE, so it only sets the repaired + # promises. + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/111.cf b/tests/acceptance/10_files/05_classes/staging/111.cf new file mode 100644 index 0000000000..3d761ea9ab --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/111.cf @@ -0,0 +1,151 @@ +####################################################### +# +# Same as 105.cf, but two separate edit_line promises (second promise is not kept because files are different) +# Test promise_kept+repaired and cancel_kept+repaired (multiple promises set classes) +# Insert lines to a file (setting/clearing classes), verify that insert +# promise is kept (which sets/clears more classes) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + "body2" string => + "BEGIN + One potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + create => "true", + edit_line => init_insert("$(body)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)" + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + edit_line => test_insert, + edit_defaults => init_empty; +} + +bundle edit_line test_insert +{ + insert_lines: + "$(init.body2)" + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + # NOTE: bundle edit_line init_insert(str) is CACHED, so even though it + # is mentioned in init and we call test_insert from test, the promises are + # the same, and are only executed ONCE, so it only sets the repaired + # promises. + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/112.cf b/tests/acceptance/10_files/05_classes/staging/112.cf new file mode 100644 index 0000000000..c801c1118d --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/112.cf @@ -0,0 +1,132 @@ +####################################################### +# +# Test repair_failed and cancel_notkept +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + # create => "true", # file will not exist! + edit_line => init_insert("$(body)"), + edit_defaults => init_empty, + classes => all_classes; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => "ON"; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => ""; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/113.cf b/tests/acceptance/10_files/05_classes/staging/113.cf new file mode 100644 index 0000000000..cf76c16545 --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/113.cf @@ -0,0 +1,133 @@ +####################################################### +# +# Test repair_failed and cancel_notkept (different location and results) +# Insert lines to a file, verify that insert promise is repaired +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "body" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + files: + "$(G.testfile)" + # create => "true", # file will not exist! + edit_line => init_insert("$(body)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)", + # Promise never executed, so classes not set! + classes => all_classes; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => ""; + "expect[promise_repaired]" string => ""; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => "ON"; + "expect[cancel_repaired]" string => "ON"; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/05_classes/staging/114.cf b/tests/acceptance/10_files/05_classes/staging/114.cf new file mode 100644 index 0000000000..3ae3395f8c --- /dev/null +++ b/tests/acceptance/10_files/05_classes/staging/114.cf @@ -0,0 +1,126 @@ +####################################################### +# +# Test setting two classes in one promise +# Copy file with one mode, then copy again with different mode +# +# Present in both 00_basics/03_bodies and 10_files/05_classes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; + + files: + "$(G.testfile)" + copy_from => local("$(G.etc_group)"), + perms => mode("666"); +} + +body copy_from local(f) +{ + source => "$(f)"; +} + +body perms mode(m) +{ + mode => "$(m)"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes all_classes +{ + promise_kept => { "promise_kept" }; + promise_repaired => { "promise_repaired" }; + repair_failed => { "repair_failed" }; + repair_denied => { "repair_denied" }; + repair_timeout => { "repair_timeout" }; + cancel_kept => { "cancel_kept" }; + cancel_repaired => { "cancel_repaired" }; + cancel_notkept => { "cancel_notkept" }; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + copy_from => local("$(G.etc_group)"), # Same file + perms => mode("644"), # Different mode + classes => all_classes; +} + +####################################################### + +bundle agent check +{ + # These variables determine the conditions being tested + vars: + "expect[promise_kept]" string => "ON"; + "expect[promise_repaired]" string => "ON"; + "expect[repair_failed]" string => ""; + "expect[repair_denied]" string => ""; + "expect[repair_timeout]" string => ""; + "expect[cancel_kept]" string => ""; + "expect[cancel_repaired]" string => ""; + "expect[cancel_notkept]" string => "ON"; + + # Everything from here down is "boilerplate" to these 1xx.cf tests + "lookfor" slist => { + "promise_kept", "promise_repaired", "repair_failed", "repair_denied", + "repair_timeout", "cancel_kept", "cancel_repaired", "cancel_notkept", + }; + + classes: + "p1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "$(lookfor)", + }; + "p2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "!$(lookfor)", + }; + "pass_$(lookfor)" xor => { "p1_$(lookfor)", "p2_$(lookfor)" }; + + "f1_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", "ON"), + "!$(lookfor)", + }; + "f2_$(lookfor)" and => { + strcmp("$(expect[$(lookfor)])", ""), + "$(lookfor)", + }; + "f3_$(lookfor)" not => isvariable("expect[$(lookfor)]"); + "fail_$(lookfor)" or => {"f1_$(lookfor)", "f2_$(lookfor)", "f3_$(lookfor)"}; + + "oops" expression => classmatch("fail.*"); + "ok" and => { classmatch("pass.*"), "!oops" }; + + reports: + DEBUG:: + "ok: class '$(lookfor)' was set (should be)" + if => "p1_$(lookfor)"; + "ok: class '$(lookfor)' was not set (should not be)" + if => "p2_$(lookfor)"; + "ERROR: class '$(lookfor)' WAS NOT set (should be)" + if => "f1_$(lookfor)"; + "ERROR: class '$(lookfor)' was set (should NOT be)" + if => "f2_$(lookfor)"; + "ERROR: missing variable expect['$(lookfor)']" + if => "f3_$(lookfor)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/001.cf b/tests/acceptance/10_files/07_delete_lines/001.cf new file mode 100644 index 0000000000..06c754e263 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/001.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Delete a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/002.cf b/tests/acceptance/10_files/07_delete_lines/002.cf new file mode 100644 index 0000000000..72d526cbab --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/002.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Don't delete a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/003.cf b/tests/acceptance/10_files/07_delete_lines/003.cf new file mode 100644 index 0000000000..15808bf27c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/003.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Delete a contiguous group of three lines +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => + "header +header +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + "BEGIN + Three potatoe +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/004.cf b/tests/acceptance/10_files/07_delete_lines/004.cf new file mode 100644 index 0000000000..19b9f5058c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/004.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Delete a contiguous group of three lines, different way +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => + "header +header +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "BEGIN$(const.n)$(str)$(const.n)END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/005.cf b/tests/acceptance/10_files/07_delete_lines/005.cf new file mode 100644 index 0000000000..77b34d87ae --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/005.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Delete a number of lines via an slist +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "BEGIN", " Three potatoe", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/006.cf b/tests/acceptance/10_files/07_delete_lines/006.cf new file mode 100644 index 0000000000..bc09ada4c8 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/006.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Delete a number of lines as separate lines +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN"; + " Three potatoe"; + "END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/007.cf b/tests/acceptance/10_files/07_delete_lines/007.cf new file mode 100644 index 0000000000..16a3c72495 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/007.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN"; + "\s+Three.*"; + "END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/008.cf b/tests/acceptance/10_files/07_delete_lines/008.cf new file mode 100644 index 0000000000..53688e97db --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/008.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Delete a number of lines via an slist, different way +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { "BEGIN", " Three potatoe", "END" }; + + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/009.cf b/tests/acceptance/10_files/07_delete_lines/009.cf new file mode 100644 index 0000000000..53688e97db --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/009.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Delete a number of lines via an slist, different way +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { "BEGIN", " Three potatoe", "END" }; + + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/010.cf b/tests/acceptance/10_files/07_delete_lines/010.cf new file mode 100644 index 0000000000..43593ebab5 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/010.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/011.cf b/tests/acceptance/10_files/07_delete_lines/011.cf new file mode 100644 index 0000000000..ac3ddfe743 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/011.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/012.cf b/tests/acceptance/10_files/07_delete_lines/012.cf new file mode 100644 index 0000000000..a5b11d2afb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/012.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + "BEGIN + Three potatoe +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/013.cf b/tests/acceptance/10_files/07_delete_lines/013.cf new file mode 100644 index 0000000000..d1d98ff1e8 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/013.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "BEGIN$(const.n)$(str)$(const.n)END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/014.cf b/tests/acceptance/10_files/07_delete_lines/014.cf new file mode 100644 index 0000000000..e8b1d3d2b4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/014.cf @@ -0,0 +1,87 @@ +####################################################### +# +# Delete a number of lines via an slist (different order than in original) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/015.cf b/tests/acceptance/10_files/07_delete_lines/015.cf new file mode 100644 index 0000000000..fc4f32a1a2 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/015.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Delete a number of lines as separate lines (different order than in original) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + " Three potatoe"; + "BEGIN"; + "END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/016.cf b/tests/acceptance/10_files/07_delete_lines/016.cf new file mode 100644 index 0000000000..440ca7304a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/016.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes (different order than in original) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "\s+Three.*"; + "BEGIN"; + "END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/017.cf b/tests/acceptance/10_files/07_delete_lines/017.cf new file mode 100644 index 0000000000..13b997eaf3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/017.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Delete a number of lines, using regexes different way (different order than in original) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "^\s+Three.*$"; # Anchors should make no difference + "BEGIN"; + "END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/018.cf b/tests/acceptance/10_files/07_delete_lines/018.cf new file mode 100644 index 0000000000..8061800e34 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/018.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Delete a number of lines via an slist, different way (different order than original) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { " Three potatoe", "BEGIN", "END" }; + + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/019.cf b/tests/acceptance/10_files/07_delete_lines/019.cf new file mode 100644 index 0000000000..1b2e5ae887 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/019.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns (different order than original) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/020.cf b/tests/acceptance/10_files/07_delete_lines/020.cf new file mode 100644 index 0000000000..807d513a93 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/020.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns (different order than original) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/022.cf b/tests/acceptance/10_files/07_delete_lines/022.cf new file mode 100644 index 0000000000..f0b6744134 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/022.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a line, set a class +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"); +} + +body classes test_class(a) +{ + promise_repaired => { "$(a)" }; +} + + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/023.cf b/tests/acceptance/10_files/07_delete_lines/023.cf new file mode 100644 index 0000000000..48b1a0e2c9 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/023.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Don't delete a line, set a class +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"); +} + +body classes test_class(a) +{ + promise_kept => { "$(a)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/024.cf b/tests/acceptance/10_files/07_delete_lines/024.cf new file mode 100644 index 0000000000..49ae6ca6a5 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/024.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/025.cf b/tests/acceptance/10_files/07_delete_lines/025.cf new file mode 100644 index 0000000000..43d2082401 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/025.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + # Note - regex doesn't qualify for "starts_with"! + "tstr" slist => { " Three potatoe", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/026.cf b/tests/acceptance/10_files/07_delete_lines/026.cf new file mode 100644 index 0000000000..3fffcd48a3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/026.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "header", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/027.cf b/tests/acceptance/10_files/07_delete_lines/027.cf new file mode 100644 index 0000000000..b8b2dc0169 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/027.cf @@ -0,0 +1,96 @@ +####################################################### +# +# Delete a line using a list, all of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/028.cf b/tests/acceptance/10_files/07_delete_lines/028.cf new file mode 100644 index 0000000000..dfb2e291fd --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/028.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/029.cf b/tests/acceptance/10_files/07_delete_lines/029.cf new file mode 100644 index 0000000000..ab42cd2e87 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/029.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list, some of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/030.cf b/tests/acceptance/10_files/07_delete_lines/030.cf new file mode 100644 index 0000000000..dcc7c65869 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/030.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list, all of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/031.cf b/tests/acceptance/10_files/07_delete_lines/031.cf new file mode 100644 index 0000000000..9a11228841 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/031.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/032.cf b/tests/acceptance/10_files/07_delete_lines/032.cf new file mode 100644 index 0000000000..5fbc1718a4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/032.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*GI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/033.cf b/tests/acceptance/10_files/07_delete_lines/033.cf new file mode 100644 index 0000000000..e29477bfb1 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/033.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*eade.*", ".*EGI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/034.cf b/tests/acceptance/10_files/07_delete_lines/034.cf new file mode 100644 index 0000000000..8e2fa30207 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/034.cf @@ -0,0 +1,96 @@ +####################################################### +# +# Delete a line using a list, all of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/035.cf b/tests/acceptance/10_files/07_delete_lines/035.cf new file mode 100644 index 0000000000..b85b36d6bb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/035.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/036.cf b/tests/acceptance/10_files/07_delete_lines/036.cf new file mode 100644 index 0000000000..4f8500ec23 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/036.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list, some of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*C.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/037.cf b/tests/acceptance/10_files/07_delete_lines/037.cf new file mode 100644 index 0000000000..d00a0e0edb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/037.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list, all of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/038.cf b/tests/acceptance/10_files/07_delete_lines/038.cf new file mode 100644 index 0000000000..0149690580 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/038.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/039.cf b/tests/acceptance/10_files/07_delete_lines/039.cf new file mode 100644 index 0000000000..b59776d31c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/039.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three", "GI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/040.cf b/tests/acceptance/10_files/07_delete_lines/040.cf new file mode 100644 index 0000000000..308629e217 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/040.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "eade", "EGI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/041.cf b/tests/acceptance/10_files/07_delete_lines/041.cf new file mode 100644 index 0000000000..2c9b9e7f4e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/041.cf @@ -0,0 +1,96 @@ +####################################################### +# +# Delete a line using a list, all of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/042.cf b/tests/acceptance/10_files/07_delete_lines/042.cf new file mode 100644 index 0000000000..1600f7be4e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/042.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/043.cf b/tests/acceptance/10_files/07_delete_lines/043.cf new file mode 100644 index 0000000000..7951c8360a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/043.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list, some of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "C" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/044.cf b/tests/acceptance/10_files/07_delete_lines/044.cf new file mode 100644 index 0000000000..e37c5edaed --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/044.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list, all of which match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}); +} + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/045.cf b/tests/acceptance/10_files/07_delete_lines/045.cf new file mode 100644 index 0000000000..e66a2ce432 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/045.cf @@ -0,0 +1,84 @@ +####################################################### +# +# Delete a line using a list +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + " One potato + Two potato + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "true"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/046.cf b/tests/acceptance/10_files/07_delete_lines/046.cf new file mode 100644 index 0000000000..7b8b46f0ca --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/046.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Delete a line using a list +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "false"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/047.cf b/tests/acceptance/10_files/07_delete_lines/047.cf new file mode 100644 index 0000000000..1743194307 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/047.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@{test.tstr}"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + not_matching => "false"; +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/101.cf b/tests/acceptance/10_files/07_delete_lines/101.cf new file mode 100644 index 0000000000..f232ff8bb8 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/101.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Delete a line +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/102.cf b/tests/acceptance/10_files/07_delete_lines/102.cf new file mode 100644 index 0000000000..d5c330da5f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/102.cf @@ -0,0 +1,104 @@ +####################################################### +# +# Don't delete a line +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/103.cf b/tests/acceptance/10_files/07_delete_lines/103.cf new file mode 100644 index 0000000000..4f98c6017e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/103.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a contiguous group of three lines +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => + "header +header +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + "BEGIN + Three potatoe +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/104.cf b/tests/acceptance/10_files/07_delete_lines/104.cf new file mode 100644 index 0000000000..955c0a79d9 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/104.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Delete a group of lines at once, different way +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => + "header +header +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "BEGIN$(const.n)$(str)$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/105.cf b/tests/acceptance/10_files/07_delete_lines/105.cf new file mode 100644 index 0000000000..a11272dd21 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/105.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines via an slist +# Region editing, include_start_delimiter and include_end_delimiter +# Notice that this is a misinterpretation of our language, once the +# start_delimiter is deleted, the region will not be matched again. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "BEGIN", " Three potatoe", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/106.cf b/tests/acceptance/10_files/07_delete_lines/106.cf new file mode 100644 index 0000000000..1fc898f3cf --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/106.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a number of lines as separate lines +# Region editing, include_start_delimiter and include_end_delimiter +# Region is destroyed by first edit +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + # Region is destroyed by first edit + "BEGIN" + select_region => test_select; + " Three potatoe" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/107.cf b/tests/acceptance/10_files/07_delete_lines/107.cf new file mode 100644 index 0000000000..15ceab8b1e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/107.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes +# Region editing, include_start_delimiter and include_end_delimiter +# Region is destroyed by first edit +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + # Region is destroyed by first edit + "BEGIN" + select_region => test_select; + "\s+Three.*" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/108.cf b/tests/acceptance/10_files/07_delete_lines/108.cf new file mode 100644 index 0000000000..a5d3589ba7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/108.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a number of lines, using regexes different way +# Region editing, include_start_delimiter and include_end_delimiter +# Region is destroyed by first edit +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + # Region is destroyed by first edit + "BEGIN" + select_region => test_select; + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/109.cf b/tests/acceptance/10_files/07_delete_lines/109.cf new file mode 100644 index 0000000000..fb16c0b866 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/109.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a number of lines via an slist, different way +# Region editing, include_start_delimiter and include_end_delimiter +# Region is destroyed by first edit +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { "BEGIN", " Three potatoe", "END" }; + + delete_lines: + # Region is destroyed by first edit + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/110.cf b/tests/acceptance/10_files/07_delete_lines/110.cf new file mode 100644 index 0000000000..c34849f14a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/110.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns +# Region editing, include_start_delimiter and include_end_delimiter +# Region is destroyed by first edit +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + delete_lines: + # Region is destroyed by first edit + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/111.cf b/tests/acceptance/10_files/07_delete_lines/111.cf new file mode 100644 index 0000000000..453c028280 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/111.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns +# Region editing, include_start_delimiter and include_end_delimiter +# Region is destroyed by first edit +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + # Region is destroyed by first edit + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/112.cf b/tests/acceptance/10_files/07_delete_lines/112.cf new file mode 100644 index 0000000000..2ec27c12a0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/112.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe +BEGIN +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/113.cf b/tests/acceptance/10_files/07_delete_lines/113.cf new file mode 100644 index 0000000000..182f92ed29 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/113.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)$(const.n)BEGIN$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/114.cf b/tests/acceptance/10_files/07_delete_lines/114.cf new file mode 100644 index 0000000000..abdad872ca --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/114.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist (different order than in original) +# Region editing, include_start_delimiter and include_end_delimiter +# Second edit destroys the region +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + # Second edit destroys the region + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/115.cf b/tests/acceptance/10_files/07_delete_lines/115.cf new file mode 100644 index 0000000000..bbcfd537f8 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/115.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a number of lines as separate lines (different order than in original) +# Region editing, include_start_delimiter and include_end_delimiter +# Second edit destroys the region +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + # Second edit destroys the region + " Three potatoe" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/116.cf b/tests/acceptance/10_files/07_delete_lines/116.cf new file mode 100644 index 0000000000..5383d14a32 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/116.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes (different order than in original) +# Region editing, include_start_delimiter and include_end_delimiter +# Second edit destroys the region +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + # Second edit destroys the region + "\s+Three.*" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/117.cf b/tests/acceptance/10_files/07_delete_lines/117.cf new file mode 100644 index 0000000000..7fb9cbab3e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/117.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a number of lines, using regexes different way (different order than in original) +# Region editing, include_start_delimiter and include_end_delimiter +# Second edit destroys the region +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + # Second edit destroys the region + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/118.cf b/tests/acceptance/10_files/07_delete_lines/118.cf new file mode 100644 index 0000000000..897c42a551 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/118.cf @@ -0,0 +1,104 @@ +####################################################### +# +# Delete a number of lines via an slist, different way (different order than original) +# Region editing, include_start_delimiter and include_end_delimiter +# Second edit destroys the region +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { " Three potatoe", "BEGIN", "END" }; + + delete_lines: + # Second edit destroys the region + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/119.cf b/tests/acceptance/10_files/07_delete_lines/119.cf new file mode 100644 index 0000000000..6fba640034 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/119.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns (different order than original) +# Region editing, include_start_delimiter and include_end_delimiter +# Second edit destroys the region and therefore the third edit will not be possible. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + delete_lines: + # Second edit destroys the region + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/120.cf b/tests/acceptance/10_files/07_delete_lines/120.cf new file mode 100644 index 0000000000..7b6b8e4836 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/120.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns (different order than original) +# Region editing, include_start_delimiter and include_end_delimiter +# Second edit destroys the region +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + # Second edit destroys the region + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/122.cf b/tests/acceptance/10_files/07_delete_lines/122.cf new file mode 100644 index 0000000000..892856babc --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/122.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line, set a class +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body classes test_class(a) +{ + promise_repaired => { "$(a)" }; +} + + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/123.cf b/tests/acceptance/10_files/07_delete_lines/123.cf new file mode 100644 index 0000000000..d447101570 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/123.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Don't delete a line, set a class +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body classes test_class(a) +{ + promise_kept => { "$(a)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/124.cf b/tests/acceptance/10_files/07_delete_lines/124.cf new file mode 100644 index 0000000000..af3499959b --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/124.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/125.cf b/tests/acceptance/10_files/07_delete_lines/125.cf new file mode 100644 index 0000000000..0259ab5401 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/125.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + # Note - regex doesn't qualify for "starts_with"! + "tstr" slist => { " Three potatoe", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/126.cf b/tests/acceptance/10_files/07_delete_lines/126.cf new file mode 100644 index 0000000000..48ba30045f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/126.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "header", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/127.cf b/tests/acceptance/10_files/07_delete_lines/127.cf new file mode 100644 index 0000000000..5ba18a1db7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/127.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/128.cf b/tests/acceptance/10_files/07_delete_lines/128.cf new file mode 100644 index 0000000000..b273cad40f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/128.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/129.cf b/tests/acceptance/10_files/07_delete_lines/129.cf new file mode 100644 index 0000000000..edcb304581 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/129.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/130.cf b/tests/acceptance/10_files/07_delete_lines/130.cf new file mode 100644 index 0000000000..4b628d781a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/130.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/131.cf b/tests/acceptance/10_files/07_delete_lines/131.cf new file mode 100644 index 0000000000..e37ca7cc01 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/131.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/132.cf b/tests/acceptance/10_files/07_delete_lines/132.cf new file mode 100644 index 0000000000..7976219937 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/132.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*GI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/133.cf b/tests/acceptance/10_files/07_delete_lines/133.cf new file mode 100644 index 0000000000..01295d7856 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/133.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*eade.*", ".*EGI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/134.cf b/tests/acceptance/10_files/07_delete_lines/134.cf new file mode 100644 index 0000000000..c0d6c91bd5 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/134.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/135.cf b/tests/acceptance/10_files/07_delete_lines/135.cf new file mode 100644 index 0000000000..d18cd243d7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/135.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/136.cf b/tests/acceptance/10_files/07_delete_lines/136.cf new file mode 100644 index 0000000000..2b1b46ede4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/136.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*C.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/137.cf b/tests/acceptance/10_files/07_delete_lines/137.cf new file mode 100644 index 0000000000..631a262060 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/137.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/138.cf b/tests/acceptance/10_files/07_delete_lines/138.cf new file mode 100644 index 0000000000..a0d10ce699 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/138.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/139.cf b/tests/acceptance/10_files/07_delete_lines/139.cf new file mode 100644 index 0000000000..611a10a4db --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/139.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three", "GI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/140.cf b/tests/acceptance/10_files/07_delete_lines/140.cf new file mode 100644 index 0000000000..efb32de533 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/140.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "eade", "EGI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/141.cf b/tests/acceptance/10_files/07_delete_lines/141.cf new file mode 100644 index 0000000000..2d5c4fd3c1 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/141.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/142.cf b/tests/acceptance/10_files/07_delete_lines/142.cf new file mode 100644 index 0000000000..00cdc6a1e7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/142.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/143.cf b/tests/acceptance/10_files/07_delete_lines/143.cf new file mode 100644 index 0000000000..7ad2e7a830 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/143.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "C" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/144.cf b/tests/acceptance/10_files/07_delete_lines/144.cf new file mode 100644 index 0000000000..b5781d98c0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/144.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/145.cf b/tests/acceptance/10_files/07_delete_lines/145.cf new file mode 100644 index 0000000000..e91ffbada2 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/145.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "true", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/146.cf b/tests/acceptance/10_files/07_delete_lines/146.cf new file mode 100644 index 0000000000..de24dc4bbc --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/146.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/147.cf b/tests/acceptance/10_files/07_delete_lines/147.cf new file mode 100644 index 0000000000..bcf3ef5987 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/147.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list +# Region editing, include_start_delimiter and include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@{test.tstr}"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/201.cf b/tests/acceptance/10_files/07_delete_lines/201.cf new file mode 100644 index 0000000000..f3df98873e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/201.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a line +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/202.cf b/tests/acceptance/10_files/07_delete_lines/202.cf new file mode 100644 index 0000000000..137ae5af73 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/202.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Don't delete a line +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/203.cf b/tests/acceptance/10_files/07_delete_lines/203.cf new file mode 100644 index 0000000000..268bdb0d28 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/203.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Delete a group of three lines at once +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/204.cf b/tests/acceptance/10_files/07_delete_lines/204.cf new file mode 100644 index 0000000000..892029f129 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/204.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Delete a group of lines at once, different way +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/205.cf b/tests/acceptance/10_files/07_delete_lines/205.cf new file mode 100644 index 0000000000..fb24504fb9 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/205.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a number of lines via an slist +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/206.cf b/tests/acceptance/10_files/07_delete_lines/206.cf new file mode 100644 index 0000000000..b7e240fe0e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/206.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + " Three potatoe" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/207.cf b/tests/acceptance/10_files/07_delete_lines/207.cf new file mode 100644 index 0000000000..568a5840b9 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/207.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + "\s+Three.*" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/208.cf b/tests/acceptance/10_files/07_delete_lines/208.cf new file mode 100644 index 0000000000..89350fd40e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/208.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines, using regexes different way +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/209.cf b/tests/acceptance/10_files/07_delete_lines/209.cf new file mode 100644 index 0000000000..173ff8c02a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/209.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { "BEGIN", " Three potatoe", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/210.cf b/tests/acceptance/10_files/07_delete_lines/210.cf new file mode 100644 index 0000000000..bd5d0c950e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/210.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/211.cf b/tests/acceptance/10_files/07_delete_lines/211.cf new file mode 100644 index 0000000000..570f78c5b1 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/211.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/212.cf b/tests/acceptance/10_files/07_delete_lines/212.cf new file mode 100644 index 0000000000..a2128de706 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/212.cf @@ -0,0 +1,96 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe +BEGIN +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/213.cf b/tests/acceptance/10_files/07_delete_lines/213.cf new file mode 100644 index 0000000000..de0ac15804 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/213.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)$(const.n)BEGIN$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/214.cf b/tests/acceptance/10_files/07_delete_lines/214.cf new file mode 100644 index 0000000000..d1d625a2f3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/214.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a number of lines via an slist (different order than in original) +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/215.cf b/tests/acceptance/10_files/07_delete_lines/215.cf new file mode 100644 index 0000000000..81728372f8 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/215.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines (different order than in original) +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + " Three potatoe" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/216.cf b/tests/acceptance/10_files/07_delete_lines/216.cf new file mode 100644 index 0000000000..e782ff7ad7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/216.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes (different order than in original) +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "\s+Three.*" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/217.cf b/tests/acceptance/10_files/07_delete_lines/217.cf new file mode 100644 index 0000000000..53c6cc3679 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/217.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines, using regexes different way (different order than in original) +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/218.cf b/tests/acceptance/10_files/07_delete_lines/218.cf new file mode 100644 index 0000000000..cbb2909fb6 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/218.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way (different order than original) +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { " Three potatoe", "BEGIN", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/219.cf b/tests/acceptance/10_files/07_delete_lines/219.cf new file mode 100644 index 0000000000..e98a50d241 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/219.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns (different order than original) +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/220.cf b/tests/acceptance/10_files/07_delete_lines/220.cf new file mode 100644 index 0000000000..f68e5742b7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/220.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns (different order than original) +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/222.cf b/tests/acceptance/10_files/07_delete_lines/222.cf new file mode 100644 index 0000000000..8c78ca9c8a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/222.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line, set a class +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body classes test_class(a) +{ + promise_repaired => { "$(a)" }; +} + + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/223.cf b/tests/acceptance/10_files/07_delete_lines/223.cf new file mode 100644 index 0000000000..9668c3216c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/223.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Don't delete a line, set a class +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body classes test_class(a) +{ + promise_kept => { "$(a)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/224.cf b/tests/acceptance/10_files/07_delete_lines/224.cf new file mode 100644 index 0000000000..ab0f32ba51 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/224.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/225.cf b/tests/acceptance/10_files/07_delete_lines/225.cf new file mode 100644 index 0000000000..764ca436a1 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/225.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + # Note - regex doesn't qualify for "starts_with"! + "tstr" slist => { " Three potatoe", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/226.cf b/tests/acceptance/10_files/07_delete_lines/226.cf new file mode 100644 index 0000000000..96acdf9762 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/226.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "header", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/227.cf b/tests/acceptance/10_files/07_delete_lines/227.cf new file mode 100644 index 0000000000..a534939738 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/227.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/228.cf b/tests/acceptance/10_files/07_delete_lines/228.cf new file mode 100644 index 0000000000..3f0fc11b39 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/228.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/229.cf b/tests/acceptance/10_files/07_delete_lines/229.cf new file mode 100644 index 0000000000..eae1464210 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/229.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/230.cf b/tests/acceptance/10_files/07_delete_lines/230.cf new file mode 100644 index 0000000000..1520066391 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/230.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/231.cf b/tests/acceptance/10_files/07_delete_lines/231.cf new file mode 100644 index 0000000000..e48c7492ca --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/231.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/232.cf b/tests/acceptance/10_files/07_delete_lines/232.cf new file mode 100644 index 0000000000..6a9c71aa91 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/232.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*GI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/233.cf b/tests/acceptance/10_files/07_delete_lines/233.cf new file mode 100644 index 0000000000..daf01c0c88 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/233.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*eade.*", ".*EGI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/234.cf b/tests/acceptance/10_files/07_delete_lines/234.cf new file mode 100644 index 0000000000..3c8d8b42ee --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/234.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/235.cf b/tests/acceptance/10_files/07_delete_lines/235.cf new file mode 100644 index 0000000000..306ab9a40b --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/235.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/236.cf b/tests/acceptance/10_files/07_delete_lines/236.cf new file mode 100644 index 0000000000..10453ce3e4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/236.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*C.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/237.cf b/tests/acceptance/10_files/07_delete_lines/237.cf new file mode 100644 index 0000000000..c73e632695 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/237.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/238.cf b/tests/acceptance/10_files/07_delete_lines/238.cf new file mode 100644 index 0000000000..e7fa7b1fdf --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/238.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/239.cf b/tests/acceptance/10_files/07_delete_lines/239.cf new file mode 100644 index 0000000000..d0513b1587 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/239.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three", "GI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/240.cf b/tests/acceptance/10_files/07_delete_lines/240.cf new file mode 100644 index 0000000000..361d83abfc --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/240.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "eade", "EGI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/241.cf b/tests/acceptance/10_files/07_delete_lines/241.cf new file mode 100644 index 0000000000..1c12770e95 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/241.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/242.cf b/tests/acceptance/10_files/07_delete_lines/242.cf new file mode 100644 index 0000000000..52bc033e74 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/242.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/243.cf b/tests/acceptance/10_files/07_delete_lines/243.cf new file mode 100644 index 0000000000..cd0bb1d5fb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/243.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "C" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/244.cf b/tests/acceptance/10_files/07_delete_lines/244.cf new file mode 100644 index 0000000000..9baf78a311 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/244.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/245.cf b/tests/acceptance/10_files/07_delete_lines/245.cf new file mode 100644 index 0000000000..073fe3ca26 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/245.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "true", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/246.cf b/tests/acceptance/10_files/07_delete_lines/246.cf new file mode 100644 index 0000000000..62b315bec1 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/246.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/247.cf b/tests/acceptance/10_files/07_delete_lines/247.cf new file mode 100644 index 0000000000..1c480c7f6a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/247.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list +# select_region, include_start_delimiter, include_end_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@{test.tstr}"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_end_delimiter => "true"; +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/301.cf b/tests/acceptance/10_files/07_delete_lines/301.cf new file mode 100644 index 0000000000..32f2301ee0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/301.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a line +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/302.cf b/tests/acceptance/10_files/07_delete_lines/302.cf new file mode 100644 index 0000000000..4ba831a782 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/302.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Don't delete a line +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/303.cf b/tests/acceptance/10_files/07_delete_lines/303.cf new file mode 100644 index 0000000000..2c546e0a50 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/303.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Delete a group of three lines at once +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + "BEGIN + Three potatoe +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/304.cf b/tests/acceptance/10_files/07_delete_lines/304.cf new file mode 100644 index 0000000000..0ded7d3da6 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/304.cf @@ -0,0 +1,91 @@ +####################################################### +# +# Delete a group of lines at once, different way +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "BEGIN$(const.n)$(str)$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/305.cf b/tests/acceptance/10_files/07_delete_lines/305.cf new file mode 100644 index 0000000000..852e02526c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/305.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist +# select_region, only include_start_delimiter +# Notice that after the first iteration the region will never +# be matched again because the start marker does not exist anymore. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "BEGIN", " Three potatoe", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/306.cf b/tests/acceptance/10_files/07_delete_lines/306.cf new file mode 100644 index 0000000000..8112df0c05 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/306.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a number of lines as separate lines +# select_region, only include_start_delimiter +# This way of selecting lines does not work. Only the +# first line will be considered. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + " Three potatoe" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/307.cf b/tests/acceptance/10_files/07_delete_lines/307.cf new file mode 100644 index 0000000000..7723e53e84 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/307.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes +# select_region, only include_start_delimiter +# As with 306, this will not work since only the first line +# will be selected. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "\s+Three.*" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/308.cf b/tests/acceptance/10_files/07_delete_lines/308.cf new file mode 100644 index 0000000000..9e548215c7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/308.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a number of lines, using regexes different way +# select_region, only include_start_delimiter +# This will only work for the first match, the same reason +# as 306 and 307. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/309.cf b/tests/acceptance/10_files/07_delete_lines/309.cf new file mode 100644 index 0000000000..8da80db63b --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/309.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way +# select_region, only include_start_delimiter +# Notice that deleting "END" will fail because the end +# delimiter is not included in the region. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { " Three potatoe", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/310.cf b/tests/acceptance/10_files/07_delete_lines/310.cf new file mode 100644 index 0000000000..f8b9db590f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/310.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns +# select_region, only include_start_delimiter +# As with 309.cf "END" will not be deleted because is not part of the +# selected region. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*Three.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/311.cf b/tests/acceptance/10_files/07_delete_lines/311.cf new file mode 100644 index 0000000000..cb7185fd13 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/311.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns +# select_region, only include_start_delimiter +# Same as with 310 and 309, "END" is not deleted. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/312.cf b/tests/acceptance/10_files/07_delete_lines/312.cf new file mode 100644 index 0000000000..0438ef66f2 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/312.cf @@ -0,0 +1,96 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe +BEGIN +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/313.cf b/tests/acceptance/10_files/07_delete_lines/313.cf new file mode 100644 index 0000000000..6a455aebb9 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/313.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)$(const.n)BEGIN$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/314.cf b/tests/acceptance/10_files/07_delete_lines/314.cf new file mode 100644 index 0000000000..6b408642a4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/314.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a number of lines via an slist (different order than in original) +# select_region, only include_start_delimiter +# "END" is not going to be deleted, this time because "BEGIN" is deleted +# before, therefore the region will not be selected when "END" is used. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/315.cf b/tests/acceptance/10_files/07_delete_lines/315.cf new file mode 100644 index 0000000000..0fe9b96d95 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/315.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines (different order than in original) +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + " Three potatoe" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/316.cf b/tests/acceptance/10_files/07_delete_lines/316.cf new file mode 100644 index 0000000000..601f5e28f4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/316.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes (different order than in original) +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "\s+Three.*" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/317.cf b/tests/acceptance/10_files/07_delete_lines/317.cf new file mode 100644 index 0000000000..767ab39582 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/317.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines, using regexes different way (different order than in original) +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/318.cf b/tests/acceptance/10_files/07_delete_lines/318.cf new file mode 100644 index 0000000000..d7f88cf03a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/318.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way (different order than original) +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { " Three potatoe", "BEGIN", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/319.cf b/tests/acceptance/10_files/07_delete_lines/319.cf new file mode 100644 index 0000000000..a357c452af --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/319.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns (different order than original) +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/320.cf b/tests/acceptance/10_files/07_delete_lines/320.cf new file mode 100644 index 0000000000..81788088b0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/320.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns (different order than original) +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/322.cf b/tests/acceptance/10_files/07_delete_lines/322.cf new file mode 100644 index 0000000000..42a8e15624 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/322.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line, set a class +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body classes test_class(a) +{ + promise_repaired => { "$(a)" }; +} + + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/323.cf b/tests/acceptance/10_files/07_delete_lines/323.cf new file mode 100644 index 0000000000..b960741a20 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/323.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Don't delete a line, set a class +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body classes test_class(a) +{ + promise_kept => { "$(a)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/324.cf b/tests/acceptance/10_files/07_delete_lines/324.cf new file mode 100644 index 0000000000..f20bf14bb4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/324.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/325.cf b/tests/acceptance/10_files/07_delete_lines/325.cf new file mode 100644 index 0000000000..08e95ef6c9 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/325.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + # Note - regex doesn't qualify for "starts_with"! + "tstr" slist => { " Three potatoe", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/326.cf b/tests/acceptance/10_files/07_delete_lines/326.cf new file mode 100644 index 0000000000..a64eb1c8b2 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/326.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "header", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/327.cf b/tests/acceptance/10_files/07_delete_lines/327.cf new file mode 100644 index 0000000000..3cde3f13b3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/327.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/328.cf b/tests/acceptance/10_files/07_delete_lines/328.cf new file mode 100644 index 0000000000..ee91f3c6da --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/328.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/329.cf b/tests/acceptance/10_files/07_delete_lines/329.cf new file mode 100644 index 0000000000..52faf052c8 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/329.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/330.cf b/tests/acceptance/10_files/07_delete_lines/330.cf new file mode 100644 index 0000000000..3c482ef67d --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/330.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/331.cf b/tests/acceptance/10_files/07_delete_lines/331.cf new file mode 100644 index 0000000000..4d14ad8641 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/331.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/332.cf b/tests/acceptance/10_files/07_delete_lines/332.cf new file mode 100644 index 0000000000..d53c037d24 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/332.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*GI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/333.cf b/tests/acceptance/10_files/07_delete_lines/333.cf new file mode 100644 index 0000000000..ef4641a10f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/333.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*eade.*", ".*EGI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/334.cf b/tests/acceptance/10_files/07_delete_lines/334.cf new file mode 100644 index 0000000000..21b4ea8bb7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/334.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/335.cf b/tests/acceptance/10_files/07_delete_lines/335.cf new file mode 100644 index 0000000000..604a7c0aec --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/335.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/336.cf b/tests/acceptance/10_files/07_delete_lines/336.cf new file mode 100644 index 0000000000..09d488c6c0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/336.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*C.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/337.cf b/tests/acceptance/10_files/07_delete_lines/337.cf new file mode 100644 index 0000000000..edfa46e644 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/337.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/338.cf b/tests/acceptance/10_files/07_delete_lines/338.cf new file mode 100644 index 0000000000..840732cd16 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/338.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/339.cf b/tests/acceptance/10_files/07_delete_lines/339.cf new file mode 100644 index 0000000000..94e41987d0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/339.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three", "GI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/340.cf b/tests/acceptance/10_files/07_delete_lines/340.cf new file mode 100644 index 0000000000..fef9e0222e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/340.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "eade", "EGI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/341.cf b/tests/acceptance/10_files/07_delete_lines/341.cf new file mode 100644 index 0000000000..aac1bf4811 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/341.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/342.cf b/tests/acceptance/10_files/07_delete_lines/342.cf new file mode 100644 index 0000000000..d88f157063 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/342.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/343.cf b/tests/acceptance/10_files/07_delete_lines/343.cf new file mode 100644 index 0000000000..6d8833bc12 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/343.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "C" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/344.cf b/tests/acceptance/10_files/07_delete_lines/344.cf new file mode 100644 index 0000000000..f4b748baea --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/344.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/345.cf b/tests/acceptance/10_files/07_delete_lines/345.cf new file mode 100644 index 0000000000..9e4fa8012b --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/345.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Three potatoe +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "true", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/346.cf b/tests/acceptance/10_files/07_delete_lines/346.cf new file mode 100644 index 0000000000..da17fa64dc --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/346.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a line using a list +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/347.cf b/tests/acceptance/10_files/07_delete_lines/347.cf new file mode 100644 index 0000000000..b697c07e7a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/347.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list +# select_region, only include_start_delimiter +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@{test.tstr}"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; + include_start_delimiter => "true"; +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/401.cf b/tests/acceptance/10_files/07_delete_lines/401.cf new file mode 100644 index 0000000000..8c3a97acf3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/401.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a line +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/402.cf b/tests/acceptance/10_files/07_delete_lines/402.cf new file mode 100644 index 0000000000..730d413195 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/402.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Don't delete a line +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/403.cf b/tests/acceptance/10_files/07_delete_lines/403.cf new file mode 100644 index 0000000000..a154c32e63 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/403.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Delete a group of three lines at once, should do nothing because +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + "BEGIN + Three potatoe +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/404.cf b/tests/acceptance/10_files/07_delete_lines/404.cf new file mode 100644 index 0000000000..86b4779e03 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/404.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Delete a group of lines at once, different way, should do nothing because +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + Three potatoe +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "BEGIN$(const.n)$(str)$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/405.cf b/tests/acceptance/10_files/07_delete_lines/405.cf new file mode 100644 index 0000000000..45de8b5154 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/405.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a number of lines via an slist +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "BEGIN", " Three potatoe", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/406.cf b/tests/acceptance/10_files/07_delete_lines/406.cf new file mode 100644 index 0000000000..21743cf1e8 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/406.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + " Three potatoe" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/407.cf b/tests/acceptance/10_files/07_delete_lines/407.cf new file mode 100644 index 0000000000..3f0742309c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/407.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + "\s+Three.*" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/408.cf b/tests/acceptance/10_files/07_delete_lines/408.cf new file mode 100644 index 0000000000..2791993c9e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/408.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines, using regexes different way +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/409.cf b/tests/acceptance/10_files/07_delete_lines/409.cf new file mode 100644 index 0000000000..46760bbced --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/409.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { "BEGIN", " Three potatoe", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/410.cf b/tests/acceptance/10_files/07_delete_lines/410.cf new file mode 100644 index 0000000000..7d631a770f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/410.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/411.cf b/tests/acceptance/10_files/07_delete_lines/411.cf new file mode 100644 index 0000000000..e7079c2192 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/411.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/412.cf b/tests/acceptance/10_files/07_delete_lines/412.cf new file mode 100644 index 0000000000..53943b3479 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/412.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe +BEGIN +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/413.cf b/tests/acceptance/10_files/07_delete_lines/413.cf new file mode 100644 index 0000000000..9b21c65f98 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/413.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)$(const.n)BEGIN$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/414.cf b/tests/acceptance/10_files/07_delete_lines/414.cf new file mode 100644 index 0000000000..4c528972b1 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/414.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Delete a number of lines via an slist (different order than in original) +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/415.cf b/tests/acceptance/10_files/07_delete_lines/415.cf new file mode 100644 index 0000000000..8d21706460 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/415.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines (different order than in original) +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + " Three potatoe" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/416.cf b/tests/acceptance/10_files/07_delete_lines/416.cf new file mode 100644 index 0000000000..ce7ad1eead --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/416.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes (different order than in original) +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "\s+Three.*" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/417.cf b/tests/acceptance/10_files/07_delete_lines/417.cf new file mode 100644 index 0000000000..a184e72584 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/417.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines, using regexes different way (different order than in original) +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/418.cf b/tests/acceptance/10_files/07_delete_lines/418.cf new file mode 100644 index 0000000000..27d37a0ed4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/418.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way (different order than original) +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { " Three potatoe", "BEGIN", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/419.cf b/tests/acceptance/10_files/07_delete_lines/419.cf new file mode 100644 index 0000000000..b0f7bafff2 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/419.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns (different order than original) +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/420.cf b/tests/acceptance/10_files/07_delete_lines/420.cf new file mode 100644 index 0000000000..329b5cab3a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/420.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns (different order than original) +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/422.cf b/tests/acceptance/10_files/07_delete_lines/422.cf new file mode 100644 index 0000000000..8661cb12f7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/422.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line, set a class +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body classes test_class(a) +{ + promise_repaired => { "$(a)" }; +} + + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/423.cf b/tests/acceptance/10_files/07_delete_lines/423.cf new file mode 100644 index 0000000000..efb0fe6d6e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/423.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Don't delete a line, set a class +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body classes test_class(a) +{ + promise_kept => { "$(a)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/424.cf b/tests/acceptance/10_files/07_delete_lines/424.cf new file mode 100644 index 0000000000..8f4c73316e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/424.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/425.cf b/tests/acceptance/10_files/07_delete_lines/425.cf new file mode 100644 index 0000000000..220ff6c815 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/425.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/426.cf b/tests/acceptance/10_files/07_delete_lines/426.cf new file mode 100644 index 0000000000..ffe7eff1bf --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/426.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "header", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/427.cf b/tests/acceptance/10_files/07_delete_lines/427.cf new file mode 100644 index 0000000000..482bd2c011 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/427.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/428.cf b/tests/acceptance/10_files/07_delete_lines/428.cf new file mode 100644 index 0000000000..8189d07e12 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/428.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/429.cf b/tests/acceptance/10_files/07_delete_lines/429.cf new file mode 100644 index 0000000000..d5315ce8de --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/429.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/430.cf b/tests/acceptance/10_files/07_delete_lines/430.cf new file mode 100644 index 0000000000..b6cc7f85fe --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/430.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/431.cf b/tests/acceptance/10_files/07_delete_lines/431.cf new file mode 100644 index 0000000000..2922ac3a03 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/431.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/432.cf b/tests/acceptance/10_files/07_delete_lines/432.cf new file mode 100644 index 0000000000..5f7bb01f3f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/432.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*GI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/433.cf b/tests/acceptance/10_files/07_delete_lines/433.cf new file mode 100644 index 0000000000..6599712adc --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/433.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*eade.*", ".*EGI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/434.cf b/tests/acceptance/10_files/07_delete_lines/434.cf new file mode 100644 index 0000000000..aabbdb55c7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/434.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/435.cf b/tests/acceptance/10_files/07_delete_lines/435.cf new file mode 100644 index 0000000000..a0229276a0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/435.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/436.cf b/tests/acceptance/10_files/07_delete_lines/436.cf new file mode 100644 index 0000000000..1f24f3454d --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/436.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*C.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/437.cf b/tests/acceptance/10_files/07_delete_lines/437.cf new file mode 100644 index 0000000000..2e362cadbe --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/437.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/438.cf b/tests/acceptance/10_files/07_delete_lines/438.cf new file mode 100644 index 0000000000..23e4eca281 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/438.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/439.cf b/tests/acceptance/10_files/07_delete_lines/439.cf new file mode 100644 index 0000000000..9008dce3b4 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/439.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three", "GI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/440.cf b/tests/acceptance/10_files/07_delete_lines/440.cf new file mode 100644 index 0000000000..6889e23034 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/440.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "eade", "EGI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/441.cf b/tests/acceptance/10_files/07_delete_lines/441.cf new file mode 100644 index 0000000000..a362a36df0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/441.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/442.cf b/tests/acceptance/10_files/07_delete_lines/442.cf new file mode 100644 index 0000000000..34b5671917 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/442.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/443.cf b/tests/acceptance/10_files/07_delete_lines/443.cf new file mode 100644 index 0000000000..a03058434e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/443.cf @@ -0,0 +1,107 @@ +####################################################### +# +# Delete a line using a list, some of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "C" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/444.cf b/tests/acceptance/10_files/07_delete_lines/444.cf new file mode 100644 index 0000000000..4d33757b3b --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/444.cf @@ -0,0 +1,108 @@ +####################################################### +# +# Delete a line using a list, all of which match +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/445.cf b/tests/acceptance/10_files/07_delete_lines/445.cf new file mode 100644 index 0000000000..4054697ac3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/445.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "true", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/446.cf b/tests/acceptance/10_files/07_delete_lines/446.cf new file mode 100644 index 0000000000..91c39244b6 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/446.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Delete a line using a list +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/447.cf b/tests/acceptance/10_files/07_delete_lines/447.cf new file mode 100644 index 0000000000..74da46d73d --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/447.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Delete a line using a list +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@{test.tstr}"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/501.cf b/tests/acceptance/10_files/07_delete_lines/501.cf new file mode 100644 index 0000000000..6b6d7f0638 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/501.cf @@ -0,0 +1,105 @@ +####################################################### +# +# Delete a line +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/502.cf b/tests/acceptance/10_files/07_delete_lines/502.cf new file mode 100644 index 0000000000..1d6adf026e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/502.cf @@ -0,0 +1,106 @@ +####################################################### +# +# Don't delete a line +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/503.cf b/tests/acceptance/10_files/07_delete_lines/503.cf new file mode 100644 index 0000000000..edf94ba2af --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/503.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Delete a group of three lines at once +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + Three potatoe +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + "BEGIN + Three potatoe +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/504.cf b/tests/acceptance/10_files/07_delete_lines/504.cf new file mode 100644 index 0000000000..007cd30b54 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/504.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Delete a group of lines at once, different way +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + Three potatoe +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "BEGIN$(const.n)$(str)$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/505.cf b/tests/acceptance/10_files/07_delete_lines/505.cf new file mode 100644 index 0000000000..72663cc94c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/505.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a number of lines via an slist +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "BEGIN", " Three potatoe", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/506.cf b/tests/acceptance/10_files/07_delete_lines/506.cf new file mode 100644 index 0000000000..08d87dd9c3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/506.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Delete a number of lines as separate lines +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + " Three potatoe" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/507.cf b/tests/acceptance/10_files/07_delete_lines/507.cf new file mode 100644 index 0000000000..f02ed5d548 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/507.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + "\s+Three.*" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/508.cf b/tests/acceptance/10_files/07_delete_lines/508.cf new file mode 100644 index 0000000000..fc21653937 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/508.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Delete a number of lines, using regexes different way +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/509.cf b/tests/acceptance/10_files/07_delete_lines/509.cf new file mode 100644 index 0000000000..7c5287085f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/509.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines via an slist, different way +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { "BEGIN", " Three potatoe", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/510.cf b/tests/acceptance/10_files/07_delete_lines/510.cf new file mode 100644 index 0000000000..7d28adb1df --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/510.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/511.cf b/tests/acceptance/10_files/07_delete_lines/511.cf new file mode 100644 index 0000000000..d5bc06f8b3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/511.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/512.cf b/tests/acceptance/10_files/07_delete_lines/512.cf new file mode 100644 index 0000000000..0e46579228 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/512.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe +BEGIN +END"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/513.cf b/tests/acceptance/10_files/07_delete_lines/513.cf new file mode 100644 index 0000000000..31df3eb58c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/513.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => "$(actual)"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)$(const.n)BEGIN$(const.n)END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/514.cf b/tests/acceptance/10_files/07_delete_lines/514.cf new file mode 100644 index 0000000000..fbabb017fb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/514.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a number of lines via an slist (different order than in original) +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/515.cf b/tests/acceptance/10_files/07_delete_lines/515.cf new file mode 100644 index 0000000000..7e9c3a4c29 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/515.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Delete a number of lines as separate lines (different order than in original) +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + " Three potatoe" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/516.cf b/tests/acceptance/10_files/07_delete_lines/516.cf new file mode 100644 index 0000000000..bbdcd70d36 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/516.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes (different order than in original) +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "\s+Three.*" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/517.cf b/tests/acceptance/10_files/07_delete_lines/517.cf new file mode 100644 index 0000000000..68614dc034 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/517.cf @@ -0,0 +1,103 @@ +####################################################### +# +# Delete a number of lines, using regexes different way (different order than in original) +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/518.cf b/tests/acceptance/10_files/07_delete_lines/518.cf new file mode 100644 index 0000000000..546709838f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/518.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines via an slist, different way (different order than original) +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { " Three potatoe", "BEGIN", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/519.cf b/tests/acceptance/10_files/07_delete_lines/519.cf new file mode 100644 index 0000000000..8098ea5105 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/519.cf @@ -0,0 +1,102 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns (different order than original) +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/520.cf b/tests/acceptance/10_files/07_delete_lines/520.cf new file mode 100644 index 0000000000..f9d7313a75 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/520.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns (different order than original) +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +header + One potato + Two potato + Four +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/522.cf b/tests/acceptance/10_files/07_delete_lines/522.cf new file mode 100644 index 0000000000..78869c2b9f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/522.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Delete a line, set a class +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body classes test_class(a) +{ + promise_repaired => { "$(a)" }; +} + + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/523.cf b/tests/acceptance/10_files/07_delete_lines/523.cf new file mode 100644 index 0000000000..32b73e5ad3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/523.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Don't delete a line, set a class +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body classes test_class(a) +{ + promise_kept => { "$(a)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/524.cf b/tests/acceptance/10_files/07_delete_lines/524.cf new file mode 100644 index 0000000000..9baabd73b3 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/524.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/525.cf b/tests/acceptance/10_files/07_delete_lines/525.cf new file mode 100644 index 0000000000..ead662a40e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/525.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + # Note - regex doesn't qualify for "starts_with"! + "tstr" slist => { " Three potatoe", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/526.cf b/tests/acceptance/10_files/07_delete_lines/526.cf new file mode 100644 index 0000000000..de6c5e2b5a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/526.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Double (nested) header/trailer +# No line will be removed. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "header", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/527.cf b/tests/acceptance/10_files/07_delete_lines/527.cf new file mode 100644 index 0000000000..85afcab0b7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/527.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/528.cf b/tests/acceptance/10_files/07_delete_lines/528.cf new file mode 100644 index 0000000000..e94b7f5feb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/528.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/529.cf b/tests/acceptance/10_files/07_delete_lines/529.cf new file mode 100644 index 0000000000..bf3f6e929a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/529.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/530.cf b/tests/acceptance/10_files/07_delete_lines/530.cf new file mode 100644 index 0000000000..1239a23992 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/530.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/531.cf b/tests/acceptance/10_files/07_delete_lines/531.cf new file mode 100644 index 0000000000..f18f0f3133 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/531.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/532.cf b/tests/acceptance/10_files/07_delete_lines/532.cf new file mode 100644 index 0000000000..1cc328cb7b --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/532.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*GI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/533.cf b/tests/acceptance/10_files/07_delete_lines/533.cf new file mode 100644 index 0000000000..a57f16af70 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/533.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*eade.*", ".*EGI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/534.cf b/tests/acceptance/10_files/07_delete_lines/534.cf new file mode 100644 index 0000000000..4ef55de433 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/534.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/535.cf b/tests/acceptance/10_files/07_delete_lines/535.cf new file mode 100644 index 0000000000..b2b3e460e2 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/535.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/536.cf b/tests/acceptance/10_files/07_delete_lines/536.cf new file mode 100644 index 0000000000..ee8cb5d8f8 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/536.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*C.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/537.cf b/tests/acceptance/10_files/07_delete_lines/537.cf new file mode 100644 index 0000000000..01ba9f033b --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/537.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/538.cf b/tests/acceptance/10_files/07_delete_lines/538.cf new file mode 100644 index 0000000000..cd559adb56 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/538.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/539.cf b/tests/acceptance/10_files/07_delete_lines/539.cf new file mode 100644 index 0000000000..5a07fbc90c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/539.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three", "GI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/540.cf b/tests/acceptance/10_files/07_delete_lines/540.cf new file mode 100644 index 0000000000..6ed1c3cfd7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/540.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "eade", "EGI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/541.cf b/tests/acceptance/10_files/07_delete_lines/541.cf new file mode 100644 index 0000000000..797b19152e --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/541.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/542.cf b/tests/acceptance/10_files/07_delete_lines/542.cf new file mode 100644 index 0000000000..b78e2ea1f6 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/542.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/543.cf b/tests/acceptance/10_files/07_delete_lines/543.cf new file mode 100644 index 0000000000..8603c1630f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/543.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "C" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/544.cf b/tests/acceptance/10_files/07_delete_lines/544.cf new file mode 100644 index 0000000000..ace72c50b2 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/544.cf @@ -0,0 +1,112 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/545.cf b/tests/acceptance/10_files/07_delete_lines/545.cf new file mode 100644 index 0000000000..ecc8b67ad7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/545.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Delete a line using a list +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header + One potato + Two potato + Three potatoe +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "true", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/546.cf b/tests/acceptance/10_files/07_delete_lines/546.cf new file mode 100644 index 0000000000..cdaf320e42 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/546.cf @@ -0,0 +1,101 @@ +####################################################### +# +# Delete a line using a list +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/547.cf b/tests/acceptance/10_files/07_delete_lines/547.cf new file mode 100644 index 0000000000..453f857d6f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/547.cf @@ -0,0 +1,110 @@ +####################################################### +# +# Delete a line using a list +# Double (nested) header/trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +BEGIN +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +END +trailer"; + + "expected" string => + "header +BEGIN +header +BEGIN + One potato + Four +END +trailer +END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@{test.tstr}"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "header"; + select_end => "trailer"; +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/601.cf b/tests/acceptance/10_files/07_delete_lines/601.cf new file mode 100644 index 0000000000..722beacf37 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/601.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Delete a line +# Two regions - Only the first region will be selected. +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/602.cf b/tests/acceptance/10_files/07_delete_lines/602.cf new file mode 100644 index 0000000000..f8825a4c66 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/602.cf @@ -0,0 +1,119 @@ +####################################################### +# +# Don't delete a line +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/603.cf b/tests/acceptance/10_files/07_delete_lines/603.cf new file mode 100644 index 0000000000..975696f8f2 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/603.cf @@ -0,0 +1,116 @@ +####################################################### +# +# Delete a group of three lines at once +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe + Four"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/604.cf b/tests/acceptance/10_files/07_delete_lines/604.cf new file mode 100644 index 0000000000..e4a7f2ac52 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/604.cf @@ -0,0 +1,114 @@ +####################################################### +# +# Delete a group of lines at once, different way +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)$(const.n) Four" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/605.cf b/tests/acceptance/10_files/07_delete_lines/605.cf new file mode 100644 index 0000000000..79b0955d06 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/605.cf @@ -0,0 +1,118 @@ +####################################################### +# +# Delete a number of lines via an slist +# Two regions - Only first region is selected +# select_region, doesn't include header or trailer +# Notice that neither "BEGIN" nor "END" are included in +# the selected region, therefore only "Three potatoe" is +# deleted. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "BEGIN", " Three potatoe", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/606.cf b/tests/acceptance/10_files/07_delete_lines/606.cf new file mode 100644 index 0000000000..01c8c0354b --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/606.cf @@ -0,0 +1,116 @@ +####################################################### +# +# Delete a number of lines as separate lines +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + " Three potatoe" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/607.cf b/tests/acceptance/10_files/07_delete_lines/607.cf new file mode 100644 index 0000000000..f0f638d366 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/607.cf @@ -0,0 +1,116 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + "\s+Three.*" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/608.cf b/tests/acceptance/10_files/07_delete_lines/608.cf new file mode 100644 index 0000000000..e2d66988c7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/608.cf @@ -0,0 +1,116 @@ +####################################################### +# +# Delete a number of lines, using regexes different way +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "BEGIN" + select_region => test_select; + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/609.cf b/tests/acceptance/10_files/07_delete_lines/609.cf new file mode 100644 index 0000000000..ed3dbfc824 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/609.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Delete a number of lines via an slist, different way +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { "BEGIN", " Three potatoe", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/610.cf b/tests/acceptance/10_files/07_delete_lines/610.cf new file mode 100644 index 0000000000..4157f459d7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/610.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/611.cf b/tests/acceptance/10_files/07_delete_lines/611.cf new file mode 100644 index 0000000000..a0de6043cb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/611.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*BEGIN.*", ".*Three.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/612.cf b/tests/acceptance/10_files/07_delete_lines/612.cf new file mode 100644 index 0000000000..cf5153d301 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/612.cf @@ -0,0 +1,120 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# Two matching regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + break + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + break + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe + Four"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/613.cf b/tests/acceptance/10_files/07_delete_lines/613.cf new file mode 100644 index 0000000000..ed528ad7db --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/613.cf @@ -0,0 +1,119 @@ +####################################################### +# +# Don't delete multiple lines if they are not in the exact order +# Two matching regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + break + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + break + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)$(const.n) Four" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/614.cf b/tests/acceptance/10_files/07_delete_lines/614.cf new file mode 100644 index 0000000000..d9380c4371 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/614.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Delete a number of lines via an slist (different order than in original) +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/615.cf b/tests/acceptance/10_files/07_delete_lines/615.cf new file mode 100644 index 0000000000..4547db163f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/615.cf @@ -0,0 +1,116 @@ +####################################################### +# +# Delete a number of lines as separate lines (different order than in original) +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + " Three potatoe" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/616.cf b/tests/acceptance/10_files/07_delete_lines/616.cf new file mode 100644 index 0000000000..d194f56108 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/616.cf @@ -0,0 +1,116 @@ +####################################################### +# +# Delete a number of lines as separate lines, using regexes (different order than in original) +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "\s+Three.*" + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/617.cf b/tests/acceptance/10_files/07_delete_lines/617.cf new file mode 100644 index 0000000000..a3a9242596 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/617.cf @@ -0,0 +1,116 @@ +####################################################### +# +# Delete a number of lines, using regexes different way (different order than in original) +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + delete_lines: + "^\s+Three.*$" # Anchors should make no difference + select_region => test_select; + "BEGIN" + select_region => test_select; + "END" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/618.cf b/tests/acceptance/10_files/07_delete_lines/618.cf new file mode 100644 index 0000000000..336b5b8d9a --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/618.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Delete a number of lines via an slist, different way (different order than original) +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { " Three potatoe", "BEGIN", "END" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/619.cf b/tests/acceptance/10_files/07_delete_lines/619.cf new file mode 100644 index 0000000000..33644cbbbb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/619.cf @@ -0,0 +1,115 @@ +####################################################### +# +# Delete a number of lines via an slist, different way, different patterns (different order than original) +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete; + +} + +bundle edit_line test_delete +{ + vars: + "str" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/620.cf b/tests/acceptance/10_files/07_delete_lines/620.cf new file mode 100644 index 0000000000..6ab974f617 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/620.cf @@ -0,0 +1,114 @@ +####################################################### +# +# Delete a number of lines via an slist, original way, different patterns (different order than original) +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*BEGIN.*", ".*END.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(tstr)"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/622.cf b/tests/acceptance/10_files/07_delete_lines/622.cf new file mode 100644 index 0000000000..3572ffe108 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/622.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Delete a line, set a class +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe"; + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body classes test_class(a) +{ + promise_repaired => { "$(a)" }; +} + + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/623.cf b/tests/acceptance/10_files/07_delete_lines/623.cf new file mode 100644 index 0000000000..921f260adc --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/623.cf @@ -0,0 +1,126 @@ +####################################################### +# +# Don't delete a line, set a class +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => " Three potatoe "; # NOTE, DOES NOT MATCH! + + files: + "$(G.testfile).actual" + edit_line => test_delete("$(test.tstr)"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + classes => test_class("ok"), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body classes test_class(a) +{ + promise_kept => { "$(a)" }; +} + +####################################################### + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/624.cf b/tests/acceptance/10_files/07_delete_lines/624.cf new file mode 100644 index 0000000000..8562f7ce23 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/624.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Delete a line using a list +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/625.cf b/tests/acceptance/10_files/07_delete_lines/625.cf new file mode 100644 index 0000000000..ce73914d07 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/625.cf @@ -0,0 +1,120 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " Three potatoe", " Five", " Two", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/626.cf b/tests/acceptance/10_files/07_delete_lines/626.cf new file mode 100644 index 0000000000..bd29b9aff1 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/626.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "header", " Two.*", "BEGIN", "END" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/627.cf b/tests/acceptance/10_files/07_delete_lines/627.cf new file mode 100644 index 0000000000..1567df77e7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/627.cf @@ -0,0 +1,119 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/628.cf b/tests/acceptance/10_files/07_delete_lines/628.cf new file mode 100644 index 0000000000..d8cd73ee89 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/628.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/629.cf b/tests/acceptance/10_files/07_delete_lines/629.cf new file mode 100644 index 0000000000..fb0cb54fbd --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/629.cf @@ -0,0 +1,120 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four potatoe +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four potatoe +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four potatoe +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/630.cf b/tests/acceptance/10_files/07_delete_lines/630.cf new file mode 100644 index 0000000000..09a1bd38fd --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/630.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { " T", " O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_startwith_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/631.cf b/tests/acceptance/10_files/07_delete_lines/631.cf new file mode 100644 index 0000000000..e72a700d87 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/631.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Delete a line using a list +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/632.cf b/tests/acceptance/10_files/07_delete_lines/632.cf new file mode 100644 index 0000000000..5ddae1687f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/632.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*Three.*", ".*GI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/633.cf b/tests/acceptance/10_files/07_delete_lines/633.cf new file mode 100644 index 0000000000..88e2aa3643 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/633.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*eade.*", ".*EGI.*", ".*ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/634.cf b/tests/acceptance/10_files/07_delete_lines/634.cf new file mode 100644 index 0000000000..d0c5a8dfaf --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/634.cf @@ -0,0 +1,119 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Two regions - Only the first one is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/635.cf b/tests/acceptance/10_files/07_delete_lines/635.cf new file mode 100644 index 0000000000..f0dc92afdc --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/635.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/636.cf b/tests/acceptance/10_files/07_delete_lines/636.cf new file mode 100644 index 0000000000..41b6aa04df --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/636.cf @@ -0,0 +1,123 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Two regions - Only the first region is going to be selected +# select_region, doesn't include header or trailer +# Notice that the list is expanded in one step, not iterated +# over. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*C.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/637.cf b/tests/acceptance/10_files/07_delete_lines/637.cf new file mode 100644 index 0000000000..6182690f44 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/637.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { ".*T.*", ".*O.*" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_match_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/638.cf b/tests/acceptance/10_files/07_delete_lines/638.cf new file mode 100644 index 0000000000..458fded6d0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/638.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Delete a line using a list +# Two regions - Only the first region is selected +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "Three" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/639.cf b/tests/acceptance/10_files/07_delete_lines/639.cf new file mode 100644 index 0000000000..05e08077eb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/639.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Delete a line using a list, some of which don't match +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "hre", "GI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/640.cf b/tests/acceptance/10_files/07_delete_lines/640.cf new file mode 100644 index 0000000000..432a1b5cc5 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/640.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Delete a line using a list, all of which don't match +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "eade", "EGI", "ND" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/641.cf b/tests/acceptance/10_files/07_delete_lines/641.cf new file mode 100644 index 0000000000..a6f97691f9 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/641.cf @@ -0,0 +1,119 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/642.cf b/tests/acceptance/10_files/07_delete_lines/642.cf new file mode 100644 index 0000000000..98f98528d7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/642.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Delete a line using a singleton list which matches +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/643.cf b/tests/acceptance/10_files/07_delete_lines/643.cf new file mode 100644 index 0000000000..37a9bc9688 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/643.cf @@ -0,0 +1,123 @@ +####################################################### +# +# Delete a line using a list, some of which match +# Two regions - Only one region is selected +# select_region, doesn't include header or trailer +# Notice that the list is evaluated in one step, not in +# several iterations. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "C" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/644.cf b/tests/acceptance/10_files/07_delete_lines/644.cf new file mode 100644 index 0000000000..5660df3e5f --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/644.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Delete a line using a list, all of which match +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T", "O" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@(test.tstr)"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +body delete_select test_match(m) +{ + delete_if_not_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/645.cf b/tests/acceptance/10_files/07_delete_lines/645.cf new file mode 100644 index 0000000000..40830061eb --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/645.cf @@ -0,0 +1,113 @@ +####################################################### +# +# Delete a line using a list +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "true", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/646.cf b/tests/acceptance/10_files/07_delete_lines/646.cf new file mode 100644 index 0000000000..15fba10fd7 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/646.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Delete a line using a list +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*"); + +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/647.cf b/tests/acceptance/10_files/07_delete_lines/647.cf new file mode 100644 index 0000000000..adbae58ecd --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/647.cf @@ -0,0 +1,120 @@ +####################################################### +# +# Delete a line using a list +# Two regions - expect changes to occur in both +# select_region, doesn't include header or trailer +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "header +header +BEGIN + One potato + Two potato + Three potatoe + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + "expected" string => + "header +header +BEGIN + One potato + Four +END +middle +middle +BEGIN + One potato + Two potato + Three potatoe + Four +END +trailer +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" slist => { "T" }; + + files: + "$(G.testfile).actual" + edit_line => test_delete(".*potat.*", "@{test.tstr}"); + +} + +bundle edit_line test_delete(str, match) +{ + delete_lines: + "$(str)" + delete_select => test_match(@{match}), + not_matching => "false", + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; +} + +body delete_select test_match(m) +{ + delete_if_contains_from_list => { @{m} }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/700.cf b/tests/acceptance/10_files/07_delete_lines/700.cf new file mode 100644 index 0000000000..807d54fa7c --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/700.cf @@ -0,0 +1,38 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + file_type => "fifo", + edit_line => init_delete("foo"); +} + +bundle edit_line init_delete(str) +{ + delete_lines: + "$(str)"; +} + +bundle agent check +{ + classes: + "exists" expression => fileexists("$(G.testfile)"); + "ok" not => "exists"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/07_delete_lines/no_matching_end.cf b/tests/acceptance/10_files/07_delete_lines/no_matching_end.cf new file mode 100644 index 0000000000..b4e60705a0 --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/no_matching_end.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Try to delete a region where select_end never matches +# Redmine #6589 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => +"header +BEGIN + One potato + Two potato + Three potatoe + Four + END +trailer"; + "expected" string => +"header +BEGIN + One potato + Two potato + Three potatoe + Four + END +trailer"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; # anchored regex doesn't match due to whitespace +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/07_delete_lines/select_end_match_eof_true.cf b/tests/acceptance/10_files/07_delete_lines/select_end_match_eof_true.cf new file mode 100644 index 0000000000..d3416f0afa --- /dev/null +++ b/tests/acceptance/10_files/07_delete_lines/select_end_match_eof_true.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Try to delete a region where select_end does not match +# end region and 'select_end_match_eof' is set to false. +# CFE-2263 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => +"header +BEGIN + One potato + Two potato + Three potatoe + Four + END +trailer"; + "expected" string => +"header +BEGIN"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_delete(".*"); +} + +bundle edit_line test_delete(str) +{ + delete_lines: + "$(str)" + select_region => test_select; +} + +body select_region test_select +{ + select_start => "BEGIN"; + select_end => "END"; # anchored regex doesn't match due to whitespace, but + # 'select_end_match_eof' is true. + select_end_match_eof => "true"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/001.cf b/tests/acceptance/10_files/08_field_edits/001.cf new file mode 100644 index 0000000000..44a00922b1 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/001.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Replace fields, simple case +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/002.cf b/tests/acceptance/10_files/08_field_edits/002.cf new file mode 100644 index 0000000000..d7a45bf096 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/002.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Replace fields, simple case, index 0 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "1"; + start_fields_from_zero => "true"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/003.cf b/tests/acceptance/10_files/08_field_edits/003.cf new file mode 100644 index 0000000000..91ef91cf6c --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/003.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Replace fields, simple case index 1 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/004.x.cf b/tests/acceptance/10_files/08_field_edits/004.x.cf new file mode 100644 index 0000000000..6a7a544dd7 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/004.x.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Replace fields, broken start_fields_from_zero +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false "; # Illegal value + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/005.cf b/tests/acceptance/10_files/08_field_edits/005.cf new file mode 100644 index 0000000000..3cae2f25e2 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/005.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Replace fields, illegal select_field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "0"; # Must be greater than 1 to be legal + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/006.x.cf b/tests/acceptance/10_files/08_field_edits/006.x.cf new file mode 100644 index 0000000000..49debce6cc --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/006.x.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Replace fields, illegal select_field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "-1"; # Must be greater than 1 + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/007.x.cf b/tests/acceptance/10_files/08_field_edits/007.x.cf new file mode 100644 index 0000000000..f01f45ded4 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/007.x.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Replace fields, illegal select_field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "xyz"; # Must be numeric + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/008.x.cf b/tests/acceptance/10_files/08_field_edits/008.x.cf new file mode 100644 index 0000000000..49debce6cc --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/008.x.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Replace fields, illegal select_field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "-1"; # Must be greater than 1 + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/009.cf b/tests/acceptance/10_files/08_field_edits/009.cf new file mode 100644 index 0000000000..134fcff8dd --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/009.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Append field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker,XXX:charlie:delta:echo +apple:banana,XXX:carrot:dogfood +aardvark:baboon,XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "append"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/010.cf b/tests/acceptance/10_files/08_field_edits/010.cf new file mode 100644 index 0000000000..385ab83be5 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/010.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Append field, different value_separator +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker+XXX:charlie:delta:echo +apple:banana+XXX:carrot:dogfood +aardvark:baboon+XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "append"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/015.cf b/tests/acceptance/10_files/08_field_edits/015.cf new file mode 100644 index 0000000000..0d50cbbc39 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/015.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Prepend field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX,baker,booker,banker:charlie:delta:echo +apple:XXX,banana:carrot:dogfood +aardvark:XXX,baboon:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "prepend"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/016.cf b/tests/acceptance/10_files/08_field_edits/016.cf new file mode 100644 index 0000000000..3829fdd4c9 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/016.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Prepend field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX+baker,booker,banker:charlie:delta:echo +apple:XXX+banana:carrot:dogfood +aardvark:XXX+baboon:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "prepend"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/017.cf b/tests/acceptance/10_files/08_field_edits/017.cf new file mode 100644 index 0000000000..8351970f84 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/017.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Append field, testing blank values +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo+XXX +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:,+XXX::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "append"; + field_separator => ":"; + field_value => "XXX"; + select_field => "5"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/018.cf b/tests/acceptance/10_files/08_field_edits/018.cf new file mode 100644 index 0000000000..3160e69956 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/018.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Prepend field, testing blank values +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:XXX+echo +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:XXX+,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "prepend"; + field_separator => ":"; + field_value => "XXX"; + select_field => "5"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/019.cf b/tests/acceptance/10_files/08_field_edits/019.cf new file mode 100644 index 0000000000..238023bbe9 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/019.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Append field, testing blank values (spaces, not completely blank) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe: , ::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo,XXX +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe: , ,XXX::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "append"; + field_separator => ":"; + field_value => "XXX"; + select_field => "5"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/020.cf b/tests/acceptance/10_files/08_field_edits/020.cf new file mode 100644 index 0000000000..5f9a58f2b9 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/020.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Prepend field, testing blank values (spaces, not completely blank) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe: , ::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:XXX,echo +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:XXX, , ::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "prepend"; + field_separator => ":"; + field_value => "XXX"; + select_field => "5"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/023.cf b/tests/acceptance/10_files/08_field_edits/023.cf new file mode 100644 index 0000000000..17ac175b7e --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/023.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Alphanum field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX,baker,banker,booker:charlie:delta:echo +apple:XXX,banana:carrot:dogfood +aardvark:XXX,baboon:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/024.cf b/tests/acceptance/10_files/08_field_edits/024.cf new file mode 100644 index 0000000000..eb11cca2ae --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/024.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Alphanum field +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,bo0ker,bonker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX+baker,bo0ker,bonker:charlie:delta:echo +apple:XXX+banana:carrot:dogfood +aardvark:XXX+baboon:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/027.cf b/tests/acceptance/10_files/08_field_edits/027.cf new file mode 100644 index 0000000000..a967e66193 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/027.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Set field, after end of some lists +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo:XXX +apple:banana:carrot:dogfood::XXX +aardvark:baboon:colugo:dingo::XXX +ampallang:: :dydoe:,:XXX:::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "6"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/028.cf b/tests/acceptance/10_files/08_field_edits/028.cf new file mode 100644 index 0000000000..01a00b7fc2 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/028.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Alphanum field, lowercase +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker+xxx:charlie:delta:echo +apple:banana+xxx:carrot:dogfood +aardvark:baboon+xxx:colugo:dingo::fox +ampallang:xxx: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + field_value => "xxx"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/029.cf b/tests/acceptance/10_files/08_field_edits/029.cf new file mode 100644 index 0000000000..faa665046e --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/029.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Alphanum field, lowercase +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,banker,booker,xxx:charlie:delta:echo +apple:banana,xxx:carrot:dogfood +aardvark:baboon,xxx:colugo:dingo::fox +ampallang:xxx: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + field_value => "xxx"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/030.cf b/tests/acceptance/10_files/08_field_edits/030.cf new file mode 100644 index 0000000000..f55d66f881 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/030.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Set field, after end of some lists without extend_fields +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::XXX +ampallang:: :dydoe:,:XXX:::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "false"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "6"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/031.cf b/tests/acceptance/10_files/08_field_edits/031.cf new file mode 100644 index 0000000000..622d66ee03 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/031.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Set field, not allowing blanks (test needs to be double-checked for accuracy) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo:XXX +apple:banana:carrot:dogfood::XXX +aardvark:baboon:colugo:dingo:fox:XXX +ampallang: :dydoe:,::XXX +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "false"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "6"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/032.cf b/tests/acceptance/10_files/08_field_edits/032.cf new file mode 100644 index 0000000000..d3d33c25b0 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/032.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Delete fields, simple case +# Bug, tracked on https://cfengine.com/dev/issues/3506 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,XXX,banker:charlie:delta:echo +apple:XXX,banana:carrot:dogfood +aardvark:baboon,XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "delete"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/033.cf b/tests/acceptance/10_files/08_field_edits/033.cf new file mode 100644 index 0000000000..ed8bb37536 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/033.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Append field (default field_operation) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker,XXX:charlie:delta:echo +apple:banana,XXX:carrot:dogfood +aardvark:baboon,XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + # field_operation => "append"; # Default value + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/036.cf b/tests/acceptance/10_files/08_field_edits/036.cf new file mode 100644 index 0000000000..a6838b3451 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/036.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Append field, testing blank values (default field_operation) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo+XXX +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:,+XXX::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + # field_operation => "append"; # Default value + field_separator => ":"; + field_value => "XXX"; + select_field => "5"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/037.cf b/tests/acceptance/10_files/08_field_edits/037.cf new file mode 100644 index 0000000000..26d018be54 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/037.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Append field, testing blank values (spaces, not completely blank) (default field_operation) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe: , ::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:echo+XXX +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe: , +XXX::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + # field_operation => "append"; # Default value + field_separator => ":"; + field_value => "XXX"; + select_field => "5"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/038.x.cf b/tests/acceptance/10_files/08_field_edits/038.x.cf new file mode 100644 index 0000000000..c211071e17 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/038.x.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Replace fields, illegal value_separator +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + value_separator => ",+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/file_content.cf b/tests/acceptance/10_files/08_field_edits/file_content.cf new file mode 100644 index 0000000000..8f2e9f5978 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/file_content.cf @@ -0,0 +1,80 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "expected.singleline" string => "content in file"; + "expected.multiline" string => "multiline +content +in +file"; + + # Create the expected files using insert_lines + files: + "$(G.testfile).expected.singleline" + create => "true", + edit_line => init_insert("$(expected.singleline)"), + edit_defaults => init_empty; + + "$(G.testfile).expected.multiline" + create => "true", + edit_line => init_insert("$(expected.multiline)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + files: + "$(G.testfile).actual.singleline" + create => "true", + content => "content in file +"; + + "$(G.testfile).actual.multiline" + create => "true", + content => "multiline +content +in +file +"; + +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_check_diff("$(G.testfile).actual.singleline", + "$(G.testfile).expected.singleline", + "$(this.promise_filename)"); + + "" usebundle => dcs_check_diff("$(G.testfile).actual.multiline", + "$(G.testfile).expected.multiline", + "$(this.promise_filename)"); + +} diff --git a/tests/acceptance/10_files/08_field_edits/file_content.error.cf b/tests/acceptance/10_files/08_field_edits/file_content.error.cf new file mode 100644 index 0000000000..cc1fa1392f --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/file_content.error.cf @@ -0,0 +1,89 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + # Create a blank file to compare + files: + "$(G.testfile).expected.blank" + create => "true", + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).error.edit_line" + create => "true", + content => "whatever", + edit_line => whatever_line; + + "$(G.testfile).error.edit_xml" + create => "true", + content => "whatever", + edit_xml => whatever_xml; + + "$(G.testfile).error.edit_template" + create => "true", + content => "whatever", + edit_template => "/path/to/whatever"; + + "$(G.testfile).error.edit_template_string" + create => "true", + content => "whatever", + edit_template_string => "whatever"; + + +} + +bundle edit_line whatever_line +{ + +} + +bundle edit_xml whatever_xml +{ + +} + +####################################################### + +bundle agent check +{ + methods: + + "" usebundle => dcs_check_diff("$(G.testfile).error.edit_line", + "$(G.testfile).expected.blank", + "$(this.promise_filename)"); + + "" usebundle => dcs_check_diff("$(G.testfile).error.edit_xml", + "$(G.testfile).expected.blank", + "$(this.promise_filename)"); + + "" usebundle => dcs_check_diff("$(G.testfile).error.edit_template", + "$(G.testfile).expected.blank", + "$(this.promise_filename)"); + + "" usebundle => dcs_check_diff("$(G.testfile).error.edit_template_string", + "$(G.testfile).expected.blank", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/10_files/08_field_edits/set_classes_in_warn_only.cf b/tests/acceptance/10_files/08_field_edits/set_classes_in_warn_only.cf new file mode 100644 index 0000000000..f77f084b4c --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/set_classes_in_warn_only.cf @@ -0,0 +1,121 @@ +####################################################### +# +#Tries to edit a file in warn_only, and must have a kept class defined +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "filename_kept" string => "shadow.kept"; + "filename_failed" string => "shadow.failed"; + + "content" string => + "nicolas:$1$NoSH4e.u$U4uEzaOuC0tmQThtI3hj00:16988:0:99999:7:::"; + + "name" string => + "nicolas"; + + "field_kept" string => + "$1$NoSH4e.u$U4uEzaOuC0tmQThtI3hj00"; + + "field_failed" string => + "bad"; + + files: + "$(G.testfile).$(init.filename_kept)" + create => "true", + edit_line => init_insert("$(init.content)"), + edit_defaults => init_empty; + + "$(G.testfile).$(init.filename_failed)" + create => "true", + edit_line => init_insert("$(init.content)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).$(init.filename_kept)" + create => "false", + edit_line => test_set_user_field("${init.name}", 2, "${init.field_kept}"), + action => test_warn_only, + classes => test_classes_generic("test_result_kept"); + + "$(G.testfile).$(init.filename_failed)" + create => "false", + edit_line => test_set_user_field("${init.name}", 2, "${init.field_failed}"), + action => test_warn_only, + classes => test_classes_generic("test_result_failed"); +} + +bundle edit_line test_set_user_field(user,field,val) +{ + field_edits: + "$(user):.*" + comment => "Edit a user attribute in the password file", + edit_field => test_col(":","$(field)","$(val)","set"); +} + +body edit_field test_col(split,col,newval,method) +{ + field_separator => "$(split)"; + select_field => "$(col)"; + value_separator => ","; + field_value => "$(newval)"; + field_operation => "$(method)"; + extend_fields => "true"; + allow_blank_fields => "true"; +} + +body action test_warn_only +{ + action_policy => "warn"; + ifelapsed => "60"; +} + +body classes test_classes_generic(x) +{ + promise_repaired => { "$(x)_repaired" }; + repair_failed => { "$(x)_failed" }; + repair_denied => { "$(x)_denied" }; + repair_timeout => { "$(x)_timeout" }; + promise_kept => { "$(x)_ok" }; +} + + +####################################################### + +bundle agent check +{ + reports: + test_result_kept_ok.test_result_failed_failed:: + "$(this.promise_filename) Pass"; + + !(test_result_kept_ok.test_result_failed_failed):: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/10_files/08_field_edits/set_empty_field_erroneous_message.cf b/tests/acceptance/10_files/08_field_edits/set_empty_field_erroneous_message.cf new file mode 100644 index 0000000000..2927709f55 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/set_empty_field_erroneous_message.cf @@ -0,0 +1,27 @@ +####################################################### +# +# catch spurious editing messages in field editing +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => file_make($(G.testfile), "root:sikrt:16104::::::"); + "" usebundle => dcs_passif_output("", + ".*(Edited|Setting).*", + "$(sys.cf_agent) -KI -f $(this.promise_filename).sub | $(G.grep) field", + $(this.promise_filename)); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/set_empty_field_erroneous_message.cf.sub b/tests/acceptance/10_files/08_field_edits/set_empty_field_erroneous_message.cf.sub new file mode 100644 index 0000000000..e2814166ed --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/set_empty_field_erroneous_message.cf.sub @@ -0,0 +1,17 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { editor }; +} + +bundle agent editor +{ + vars: + "null_shadow_entries" ilist => {4, 5, 6, 7, 8}; + + files: + "$(G.testfile)" + handle => "null_root_user_account_expiration_shadow_entries", + edit_line => set_user_field("root", $(null_shadow_entries), ""), + classes => if_repaired("root_password_modified"); +} diff --git a/tests/acceptance/10_files/08_field_edits/set_value_line_long.cf b/tests/acceptance/10_files/08_field_edits/set_value_line_long.cf new file mode 100644 index 0000000000..088f06343d --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/set_value_line_long.cf @@ -0,0 +1,80 @@ +####################################################### +# +# Test setting a field in a line longer than 4k +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "a:a:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaend"; + + "expected" string => + "a:b:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaend"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => ":"; + field_value => "b"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => "+"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/10_files/08_field_edits/staging/011.x.cf b/tests/acceptance/10_files/08_field_edits/staging/011.x.cf new file mode 100644 index 0000000000..cffad95e10 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/011.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Append field, field_separator the same as value_separator +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker+XXX:charlie:delta:echo +apple:banana+XXX:carrot:dogfood +aardvark:baboon+XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "append"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ":"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/012.x.cf b/tests/acceptance/10_files/08_field_edits/staging/012.x.cf new file mode 100644 index 0000000000..094624008a --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/012.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Append field, field_separator missing +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker?XXX:charlie:delta:echo +apple:banana?XXX:carrot:dogfood +aardvark:baboon?XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "append"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + # value_separator => ","; # How can you append if you don't have sep? +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/013.x.cf b/tests/acceptance/10_files/08_field_edits/staging/013.x.cf new file mode 100644 index 0000000000..13e05dabef --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/013.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Prepend field, field_separator missing +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "prepend"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + # value_separator => ","; # How can you prepend if you don't have sep? +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/014.x.cf b/tests/acceptance/10_files/08_field_edits/staging/014.x.cf new file mode 100644 index 0000000000..4c69bc6891 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/014.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Prepend field, field_separator same as value_separator +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "prepend"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ":"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/021.x.cf b/tests/acceptance/10_files/08_field_edits/staging/021.x.cf new file mode 100644 index 0000000000..4700c76a07 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/021.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Alphanum field, field_separator missing +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + # value_separator => ","; # How can you alphanum if you don't have sep? +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/022.x.cf b/tests/acceptance/10_files/08_field_edits/staging/022.x.cf new file mode 100644 index 0000000000..bb5c572a48 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/022.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Alphanum field, field_separator same as value_separator +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ":"; # Shouldn't be the same as field_separator +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/025.cf b/tests/acceptance/10_files/08_field_edits/staging/025.cf new file mode 100644 index 0000000000..794b178da1 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/025.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Alphanum field, testing blank values +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:XXX,echo +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:XXX,,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + field_value => "XXX"; + select_field => "5"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/026.cf b/tests/acceptance/10_files/08_field_edits/staging/026.cf new file mode 100644 index 0000000000..56a54fa478 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/026.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Alphanum field, testing blank values (spaces, not completely blank) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe: , ::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:charlie:delta:XXX,echo +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:XXX, , ::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + field_value => "XXX"; + select_field => "5"; + start_fields_from_zero => "false"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/034.x.cf b/tests/acceptance/10_files/08_field_edits/staging/034.x.cf new file mode 100644 index 0000000000..65edcb3aab --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/034.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Append field, field_separator the same as value_separator (default field_operation) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker+XXX:charlie:delta:echo +apple:banana+XXX:carrot:dogfood +aardvark:baboon+XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + # field_operation => "append"; # Default value + field_separator => ":"; + field_value => "XXX"; + select_field => "2"; + start_fields_from_zero => "false"; + value_separator => ":"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/035.x.cf b/tests/acceptance/10_files/08_field_edits/staging/035.x.cf new file mode 100644 index 0000000000..205525e57c --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/035.x.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Append field, field_separator missing (default field_operation) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker?XXX:charlie:delta:echo +apple:banana?XXX:carrot:dogfood +aardvark:baboon?XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "append"; + field_separator => ":"; + # field_value => "XXX"; # Default value + select_field => "2"; + start_fields_from_zero => "false"; + # value_separator => ","; # How can you append if you don't have sep? +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/039.cf b/tests/acceptance/10_files/08_field_edits/staging/039.cf new file mode 100644 index 0000000000..b60e18b4de --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/039.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Replace fields, regex separator +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:XXX:delta:echo +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:XXX,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => "[:,]"; + field_value => "XXX"; + select_field => "5"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/040.cf b/tests/acceptance/10_files/08_field_edits/staging/040.cf new file mode 100644 index 0000000000..b69ccc8a66 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/040.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Replace fields, regex separator value_separator not included in it +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:XXX:delta:echo +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:XXX,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => "[:,]"; + field_value => "XXX"; + select_field => "5"; + value_separator => "/"; # This is not included in the field_separator +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/041.x.cf b/tests/acceptance/10_files/08_field_edits/staging/041.x.cf new file mode 100644 index 0000000000..cf17444e5e --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/041.x.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Replace fields, regex separator with value_separator included in it +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:baker,booker,banker:XXX:delta:echo +apple:banana:carrot:dogfood:XXX +aardvark:baboon:colugo:dingo:XXX:fox +ampallang:: :dydoe:XXX,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "set"; + field_separator => "[:,]"; + field_value => "XXX"; + select_field => "5"; + value_separator => ","; # This is included in the field_separator +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/042.x.cf b/tests/acceptance/10_files/08_field_edits/staging/042.x.cf new file mode 100644 index 0000000000..6e84185d67 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/042.x.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Replace fields, missing field_value +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + # field_value => "XXX"; # useless without this! + select_field => "2"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/043.x.cf b/tests/acceptance/10_files/08_field_edits/staging/043.x.cf new file mode 100644 index 0000000000..42e7870fe1 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/043.x.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Replace fields, missing field_value +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "append"; + field_separator => ":"; + # field_value => "XXX"; # useless without this! + select_field => "2"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/044.x.cf b/tests/acceptance/10_files/08_field_edits/staging/044.x.cf new file mode 100644 index 0000000000..739502098d --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/044.x.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Replace fields, missing field_value +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "prepend"; + field_separator => ":"; + # field_value => "XXX"; # useless without this! + select_field => "2"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/045.x.cf b/tests/acceptance/10_files/08_field_edits/staging/045.x.cf new file mode 100644 index 0000000000..48fb7cad45 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/045.x.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Replace fields, missing field_value +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "delete"; + field_separator => ":"; + # field_value => "XXX"; # useless without this! + select_field => "2"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/08_field_edits/staging/046.x.cf b/tests/acceptance/10_files/08_field_edits/staging/046.x.cf new file mode 100644 index 0000000000..6e84185d67 --- /dev/null +++ b/tests/acceptance/10_files/08_field_edits/staging/046.x.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Replace fields, missing field_value +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "able:baker,booker,banker:charlie:delta:echo +apple:banana:carrot:dogfood +aardvark:baboon:colugo:dingo::fox +ampallang:: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + "expected" string => + "able:XXX:charlie:delta:echo +apple:XXX:carrot:dogfood +aardvark:XXX:colugo:dingo::fox +ampallang:XXX: :dydoe:,::::: +:blowfish:conger:dogfish:eel:flounder"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_edit; + +} + +bundle edit_line test_edit +{ + field_edits: + "a.*" + edit_field => test_col; +} + +body edit_field test_col +{ + allow_blank_fields => "true"; + extend_fields => "true"; + field_operation => "alphanum"; + field_separator => ":"; + # field_value => "XXX"; # useless without this! + select_field => "2"; + value_separator => ","; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/001.cf b/tests/acceptance/10_files/09_insert_lines/001.cf new file mode 100644 index 0000000000..5c9861c020 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/001.cf @@ -0,0 +1,89 @@ +####################################################### +# +# Insert a number of lines +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Four +END"; + + "expected" string => + "BEGIN + One potato + Two potato + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "BEGIN$(const.n)$(str)$(const.n)END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/002.cf b/tests/acceptance/10_files/09_insert_lines/002.cf new file mode 100644 index 0000000000..658e3aa31b --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/002.cf @@ -0,0 +1,96 @@ +####################################################### +# +# Insert a number of lines at the end of a fully bounded region +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Four +END"; + + "expected" string => + "BEGIN + One potato + Two potato + Four + Three potatoe +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + select_region => test_region; +} + +body select_region test_region +{ + select_start => "BEGIN"; + select_end => "END"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/003.cf b/tests/acceptance/10_files/09_insert_lines/003.cf new file mode 100644 index 0000000000..bd96b1a2d7 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/003.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert a number of lines at the end of a partially bounded region +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Four +END"; + + "expected" string => + "BEGIN + One potato + Two potato + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + select_region => test_region; +} + +body select_region test_region +{ + select_start => "BEGIN"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/004.cf b/tests/acceptance/10_files/09_insert_lines/004.cf new file mode 100644 index 0000000000..dc8756b99d --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/004.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Insert a number of lines at the end of a fully bounded +# region, before the first instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " big potatoes +BEGIN + One potato + Two potato + Two potatoes + Four +END + small potatoes"; + + "expected" string => + " big potatoes +BEGIN + Three potatoe + One potato + Two potato + Two potatoes + Four +END + small potatoes"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + select_region => test_region, + location => test_before_first(".*potato.*"); +} + +body select_region test_region +{ + select_start => "BEGIN"; + select_end => "END"; +} + +body location test_before_first(line) +{ + before_after => "before"; + first_last => "first"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/005.cf b/tests/acceptance/10_files/09_insert_lines/005.cf new file mode 100644 index 0000000000..a9b0be7a40 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/005.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Insert a number of lines at the end of a fully bounded +# region, after the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " big potatoes +BEGIN + One potato + Two potato + Two potatoes + Four +END + small potatoes"; + + "expected" string => + " big potatoes +BEGIN + One potato + Two potato + Two potatoes + Three potatoe + Four +END + small potatoes"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + select_region => test_region, + location => test_after_last(".*potato.*"); +} + +body select_region test_region +{ + select_start => "BEGIN"; + select_end => "END"; +} + +body location test_after_last(line) +{ + before_after => "after"; + first_last => "last"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/006.cf b/tests/acceptance/10_files/09_insert_lines/006.cf new file mode 100644 index 0000000000..59b127c617 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/006.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Insert a number of lines at the end of a fully bounded +# region, after the first instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " big potatoes +BEGIN + One potato + Two potato + Two potatoes + Four +END + small potatoes"; + + "expected" string => + " big potatoes +BEGIN + One potato + Three potatoe + Two potato + Two potatoes + Four +END + small potatoes"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + select_region => test_region, + location => test_after_first(".*potato.*"); +} + +body select_region test_region +{ + select_start => "BEGIN"; + select_end => "END"; +} + +body location test_after_first(line) +{ + before_after => "after"; + first_last => "first"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/007.cf b/tests/acceptance/10_files/09_insert_lines/007.cf new file mode 100644 index 0000000000..019d6e8535 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/007.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Insert a number of lines at the end of a fully bounded +# region, before the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " big potatoes +BEGIN + One potato + Two potato + Two potatoes + Four +END + small potatoes"; + + "expected" string => + " big potatoes +BEGIN + One potato + Two potato + Three potatoe + Two potatoes + Four +END + small potatoes"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + select_region => test_region, + location => test_before_last(".*potato.*"); +} + +body select_region test_region +{ + select_start => "BEGIN"; + select_end => "END"; +} + +body location test_before_last(line) +{ + before_after => "before"; + first_last => "last"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/008.cf b/tests/acceptance/10_files/09_insert_lines/008.cf new file mode 100644 index 0000000000..d88208645a --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/008.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Insert a number of lines before the first instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "expected" string => + "BEGIN + Three potatoe + One potato + Two potato + Two potatoes + Four +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + location => test_before_first(".*potato.*"); +} + +body location test_before_first(line) +{ + before_after => "before"; + first_last => "first"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/009.cf b/tests/acceptance/10_files/09_insert_lines/009.cf new file mode 100644 index 0000000000..7f323e02ff --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/009.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Insert a number of lines after the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Three potatoe + Four +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + location => test_after_last(".*potato.*"); +} + +body location test_after_last(line) +{ + before_after => "after"; + first_last => "last"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/010.cf b/tests/acceptance/10_files/09_insert_lines/010.cf new file mode 100644 index 0000000000..863ab7c406 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/010.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Insert a number of lines after the first instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "expected" string => + "BEGIN + One potato + Three potatoe + Two potato + Two potatoes + Four +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + location => test_after_first(".*potato.*"); +} + +body location test_after_first(line) +{ + before_after => "after"; + first_last => "first"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/011.cf b/tests/acceptance/10_files/09_insert_lines/011.cf new file mode 100644 index 0000000000..f9c3198053 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/011.cf @@ -0,0 +1,99 @@ +####################################################### +# +# Insert a number of lines before the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "expected" string => + "BEGIN + One potato + Two potato + Three potatoe + Two potatoes + Four +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "tstr" string => + " One potato + Two potato + Three potatoe + Four"; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + location => test_before_last(".*potato.*"); +} + +body location test_before_last(line) +{ + before_after => "before"; + first_last => "last"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/012.cf b/tests/acceptance/10_files/09_insert_lines/012.cf new file mode 100644 index 0000000000..69a680b20e --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/012.cf @@ -0,0 +1,100 @@ +####################################################### +# +# Insert a number of lines before the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Spuds spuds spuds + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Three potatoe + Spuds spuds spuds + Two potatoes + Four +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + insert_type => "file", + location => test_before_last(".*potato.*"); +} + +body location test_before_last(line) +{ + before_after => "before"; + first_last => "last"; + select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/013.cf b/tests/acceptance/10_files/09_insert_lines/013.cf new file mode 100644 index 0000000000..9ef8afe2a2 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/013.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Insert a number of lines before the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Spuds spuds spuds + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END + Three potatoe + Spuds spuds spuds"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + insert_type => "file"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/014.cf b/tests/acceptance/10_files/09_insert_lines/014.cf new file mode 100644 index 0000000000..33203fbdd4 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/014.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Insert a number of lines before the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Spuds spuds spuds + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END + Three potatoe + Spuds spuds spuds"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); + +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(str)" + insert_type => "file", + insert_select => test_insert_exclude("Xhree"); +} + +body insert_select test_insert_exclude(r) +{ + insert_if_not_match_from_list => { "$(r)" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/015.cf b/tests/acceptance/10_files/09_insert_lines/015.cf new file mode 100644 index 0000000000..5439e8711c --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/015.cf @@ -0,0 +1,98 @@ +####################################################### +# +# Insert a number of lines before the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Spuds spuds spuds + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END + Three potatoe + Spuds spuds spuds"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); + +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + insert_select => test_insert_exclude("Three"); +} + +body insert_select test_insert_exclude(r) +{ + insert_if_not_match_from_list => { "$(r)" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/016.cf b/tests/acceptance/10_files/09_insert_lines/016.cf new file mode 100644 index 0000000000..443d6e17c6 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/016.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Insert a number of lines before the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Spuds spuds spuds + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Four +END + Spuds spuds spuds"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); + +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + insert_select => test_insert_exclude(".*Three.*"); +} + +body insert_select test_insert_exclude(r) +{ + insert_if_not_match_from_list => { "$(r)" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/017.cf b/tests/acceptance/10_files/09_insert_lines/017.cf new file mode 100644 index 0000000000..66882d5a44 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/017.cf @@ -0,0 +1,44 @@ +####################################################### +# +# Try to insert lines from a file that is really a directory. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + + +bundle agent test { + files: + "$(G.testfile)" + edit_line => insert_dir, + create => "true"; +} + +bundle edit_line insert_dir { + insert_lines: + "$(G.testroot)" insert_type => "file", + classes => if_notkept("ok"); +} + +####################################################### + +bundle agent check +{ + reports: + DEBUG:: + "This should only pass if inserting lines from a directory fails"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/019.cf b/tests/acceptance/10_files/09_insert_lines/019.cf new file mode 100644 index 0000000000..e4ca9e318f --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/019.cf @@ -0,0 +1,109 @@ +####################################################### +# +# Files read in as templates, check class handling +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_expected, + edit_defaults => init_empty; + + files: + "$(G.testfile).template" + create => "true", + edit_line => init_template, + edit_defaults => init_empty; + +} + +####### + +bundle edit_line init_template +{ +insert_lines: + +"[%CFEngine BEGIN %] +WARNING +No unauthorized logins. All logins are recorded. +Property of Example.com +[%CFEngine END %] + +[%CFEngine nosuchclass:: %] +manager: Mr. White +phone: 555-555-5555 + +[%CFEngine fishy:: %] +manager: Mr. Black +phone: 555-555-4444 + +[%CFEngine any:: %] +AAAAAAAA +" + +insert_type => "preserve_all_lines"; +} + +####### + +bundle edit_line init_expected +{ +insert_lines: + +"WARNING +No unauthorized logins. All logins are recorded. +Property of Example.com + +manager: Mr. Black +phone: 555-555-4444 + +AAAAAAAA +" + +insert_type => "preserve_block"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ +classes: + + "fishy" expression => "any"; + +files: + + "$(G.testfile).actual" + create => "true", + edit_template => "$(G.testfile).template"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).expected", + "$(G.testfile).actual", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/020.cf b/tests/acceptance/10_files/09_insert_lines/020.cf new file mode 100644 index 0000000000..2397538784 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/020.cf @@ -0,0 +1,111 @@ +####################################################### +# +# Files read in as templates, check variable expansion +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_expected, + edit_defaults => init_empty; + + files: + "$(G.testfile).template" + create => "true", + edit_line => init_template, + edit_defaults => init_empty; + +} + +####### + +bundle edit_line init_template +{ +insert_lines: + +"BEGIN +$(const.dollar)(test.scalar) + +CCCCCCCC +CCCCCCCC +CCCCCCCC + +DDDDDDDD +[%CFEngine BEGIN %] +$(const.dollar)(test.list) +[%CFEngine END %] +" + +insert_type => "preserve_all_lines"; +} + +####### + +bundle edit_line init_expected +{ +insert_lines: + +"BEGIN +One upon a time... + +CCCCCCCC +CCCCCCCC +CCCCCCCC + +DDDDDDDD +Many times +Upon a star +" + +insert_type => "preserve_all_lines"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ +vars: + + "scalar" string => "One upon a time..."; + "list" slist => { "Many times", "Upon a star" }; + +classes: + + "fishy" expression => "any"; + +files: + + "$(G.testfile).actual" + create => "true", + edit_template => "$(G.testfile).template"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).expected", + "$(G.testfile).actual", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/021.cf b/tests/acceptance/10_files/09_insert_lines/021.cf new file mode 100644 index 0000000000..a5bfd3e5bf --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/021.cf @@ -0,0 +1,86 @@ +####################################################### +# +# Test idempotency of preserve_block +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_expected, + edit_defaults => init_empty; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => init_file, + edit_defaults => init_empty; + +} + +####### + +bundle edit_line init_file +{ +insert_lines: + +"auto eth0.1190 +iface eth0.1190 inet manual + up ifconfig eth0.1190 up" + +insert_type => "preserve_block"; +} + +####### + +bundle edit_line init_expected +{ +insert_lines: + +"auto eth0.1190 +iface eth0.1190 inet manual + up ifconfig eth0.1190 up" + +insert_type => "preserve_block"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ +files: + + # Do the same again to test idempotency of preserve_block + + "$(G.testfile).actual" + edit_line => init_expected; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).expected", + "$(G.testfile).actual", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/101.cf b/tests/acceptance/10_files/09_insert_lines/101.cf new file mode 100644 index 0000000000..3e0023269c --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/101.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a variable, verify that exact match works when given an +# exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "exact_match" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/102.cf b/tests/acceptance/10_files/09_insert_lines/102.cf new file mode 100644 index 0000000000..3534363373 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/102.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that exact match works when given a +# not-exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "exact_match" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/103.cf b/tests/acceptance/10_files/09_insert_lines/103.cf new file mode 100644 index 0000000000..9f6433dd90 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/103.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_trailing works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/104.cf b/tests/acceptance/10_files/09_insert_lines/104.cf new file mode 100644 index 0000000000..7d4bed7678 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/104.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_trailing works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +###################################################### +# +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_leading", "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/105.cf b/tests/acceptance/10_files/09_insert_lines/105.cf new file mode 100644 index 0000000000..68630cb9c8 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/105.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a variable, verify that pairs of whitespace_policy work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_leading", "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/106.cf b/tests/acceptance/10_files/09_insert_lines/106.cf new file mode 100644 index 0000000000..57e3dad4c0 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/106.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a variable, verify that pairs of whitespace_policy work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_leading", "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/107.cf b/tests/acceptance/10_files/09_insert_lines/107.cf new file mode 100644 index 0000000000..27580172ac --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/107.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a variable, verify that pairs of whitespace_policy work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_trailing", "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/108.cf b/tests/acceptance/10_files/09_insert_lines/108.cf new file mode 100644 index 0000000000..54157e6a63 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/108.cf @@ -0,0 +1,92 @@ +####################################################### +# +# Insert lines from a variable, verify that pairs of whitespace_policy work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_leading", "ignore_trailing", "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/109.cf b/tests/acceptance/10_files/09_insert_lines/109.cf new file mode 100644 index 0000000000..f1c6d5f97b --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/109.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Insert lines with preserve_block "after" and check convergence +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => "START"; + + "expected" string => +"START + 1 + x2x + xx3xx + x4x + 5 + x6x + xx7xx + xxx8xxx + xxxx9xxxx + xxxxx10xxxx + xxxxxx11xxxxx + xxxxxxx12xxxxxx + xxxxxxxx13xxxxxxx +xxxxxxxxx14xxxxxxxx +xxxxxxxxx15xxxxxxxxx + xxx16xx + xxx17xx + xxx18xx + xxx19xx + xxx20xx +xxxxxxxxx21xxxxxxx"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ +insert_lines: + +" 1 + x2x + xx3xx + x4x + 5 + x6x + xx7xx + xxx8xxx + xxxx9xxxx + xxxxx10xxxx + xxxxxx11xxxxx + xxxxxxx12xxxxxx + xxxxxxxx13xxxxxxx +xxxxxxxxx14xxxxxxxx +xxxxxxxxx15xxxxxxxxx + xxx16xx + xxx17xx + xxx18xx + xxx19xx + xxx20xx +xxxxxxxxx21xxxxxxx" + + location => prepend, + insert_type => "preserve_block"; +} + +body location prepend +{ +before_after => "after"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/112.cf b/tests/acceptance/10_files/09_insert_lines/112.cf new file mode 100644 index 0000000000..9cf6119ad5 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/112.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_trailing works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_leading" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/113.cf b/tests/acceptance/10_files/09_insert_lines/113.cf new file mode 100644 index 0000000000..883d9e00f0 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/113.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_embedded works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/114.cf b/tests/acceptance/10_files/09_insert_lines/114.cf new file mode 100644 index 0000000000..280790130c --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/114.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_embedded works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_leading", "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/115.cf b/tests/acceptance/10_files/09_insert_lines/115.cf new file mode 100644 index 0000000000..937a1f12b8 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/115.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy ignore_trailing +# works when given a close-enough exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/116.cf b/tests/acceptance/10_files/09_insert_lines/116.cf new file mode 100644 index 0000000000..7e135e391a --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/116.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy ignore_leading +# works when given a close-enough exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_leading" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/117.cf b/tests/acceptance/10_files/09_insert_lines/117.cf new file mode 100644 index 0000000000..db52fe3e1c --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/117.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_leading works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/118.cf b/tests/acceptance/10_files/09_insert_lines/118.cf new file mode 100644 index 0000000000..5e8a0c5fc6 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/118.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_leading works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/119.cf b/tests/acceptance/10_files/09_insert_lines/119.cf new file mode 100644 index 0000000000..7f99df3f4c --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/119.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_leading works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_embedded", "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/121.cf b/tests/acceptance/10_files/09_insert_lines/121.cf new file mode 100644 index 0000000000..75f223d6d0 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/121.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy ignore_embedded +# works when given a close-enough exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/122.cf b/tests/acceptance/10_files/09_insert_lines/122.cf new file mode 100644 index 0000000000..5bd406048a --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/122.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_embedded works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "ignore_leading" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/201.cf b/tests/acceptance/10_files/09_insert_lines/201.cf new file mode 100644 index 0000000000..24e646e1ea --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/201.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a file, verify that exact match works when given an +# exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "exact_match" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/202.cf b/tests/acceptance/10_files/09_insert_lines/202.cf new file mode 100644 index 0000000000..2758ab0b3a --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/202.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that exact match works when given a +# not-exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "exact_match" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/203.cf b/tests/acceptance/10_files/09_insert_lines/203.cf new file mode 100644 index 0000000000..e8bf97494d --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/203.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_trailing works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/204.cf b/tests/acceptance/10_files/09_insert_lines/204.cf new file mode 100644 index 0000000000..b92a540e31 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/204.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_trailing works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_leading", "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/205.cf b/tests/acceptance/10_files/09_insert_lines/205.cf new file mode 100644 index 0000000000..b6b962803f --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/205.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a file, verify that pairs of whitespace_policy work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_leading", "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/206.cf b/tests/acceptance/10_files/09_insert_lines/206.cf new file mode 100644 index 0000000000..9df27e54f5 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/206.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a file, verify that pairs of whitespace_policy work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_leading", "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/207.cf b/tests/acceptance/10_files/09_insert_lines/207.cf new file mode 100644 index 0000000000..b8386c80d6 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/207.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a file, verify that pairs of whitespace_policy work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_trailing", "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/208.cf b/tests/acceptance/10_files/09_insert_lines/208.cf new file mode 100644 index 0000000000..08a779f172 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/208.cf @@ -0,0 +1,93 @@ +####################################################### +# +# Insert lines from a file, verify that pairs of whitespace_policy work +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_leading", "ignore_trailing", "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/212.cf b/tests/acceptance/10_files/09_insert_lines/212.cf new file mode 100644 index 0000000000..25a19edd1b --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/212.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_trailing works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_leading" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/213.cf b/tests/acceptance/10_files/09_insert_lines/213.cf new file mode 100644 index 0000000000..968197b0c1 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/213.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_embedded works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/214.cf b/tests/acceptance/10_files/09_insert_lines/214.cf new file mode 100644 index 0000000000..2b539760d1 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/214.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_embedded works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_leading", "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/215.cf b/tests/acceptance/10_files/09_insert_lines/215.cf new file mode 100644 index 0000000000..bd710de6ae --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/215.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy ignore_trailing +# works when given a close-enough exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/216.cf b/tests/acceptance/10_files/09_insert_lines/216.cf new file mode 100644 index 0000000000..54e1d9418e --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/216.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy ignore_leading +# works when given a close-enough exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_leading" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/217.cf b/tests/acceptance/10_files/09_insert_lines/217.cf new file mode 100644 index 0000000000..c04075d0bf --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/217.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_leading works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe +Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe +Leading embedded and trailing spaces"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/218.cf b/tests/acceptance/10_files/09_insert_lines/218.cf new file mode 100644 index 0000000000..307534fe82 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/218.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_leading works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/219.cf b/tests/acceptance/10_files/09_insert_lines/219.cf new file mode 100644 index 0000000000..ed9ca61279 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/219.cf @@ -0,0 +1,96 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_leading works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_embedded", "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff_expected("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", + "yes"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/221.cf b/tests/acceptance/10_files/09_insert_lines/221.cf new file mode 100644 index 0000000000..21c786b17c --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/221.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy ignore_embedded +# works when given a close-enough exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/222.cf b/tests/acceptance/10_files/09_insert_lines/222.cf new file mode 100644 index 0000000000..9de53b3b09 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/222.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_embedded works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "ignore_leading" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/300.cf b/tests/acceptance/10_files/09_insert_lines/300.cf new file mode 100644 index 0000000000..820c7569e6 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/300.cf @@ -0,0 +1,157 @@ +####################################################### +# +# Test insertion of newlines (Issue 555) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "template", "expected" }; + + "template" string => + "## +# Sendmail Alias File +# @(#)B11.23_LRaliases $Revision: 1.1.212.1 $ $Date: 99/09/13 15:13:16 $ +# +# Mail to an alias in this file will be sent to the users, programs, or +# files designated following the colon. +# Aliases defined in this file will NOT be expanded in headers from +# mailx(1), but WILL be visible over networks and in headers from +# rmail(1). +# +# >>>>>>>>>> The program \"/usr/sbin/newaliases\" must be run after +# >> NOTE >> this file is updated, or else any changes will not be +# >>>>>>>>>> visible to sendmail. +## + +# Alias for mailer daemon +MAILER-DAEMON : root + +# RFC 822 requires that every host have a mail address \"postmaster\" +postmaster : root + +# Aliases to handle mail to msgs and news +nobody : /dev/null + +# System Admistration aliases +operator : root +uucp : root +daemon : root + +# Ftp maintainer. +ftp-bugs : root + +# Local aliases +root : dcpds.sysadmin@dcpds.cpms.osd.mil,:include:/etc/mail/aliases.root +oracle : dcpds.dba@dcpds.cpms.osd.mil +exinfac : dcpds.exits@dcpds.cpms.osd.mil +applmgr : dcpds.dba@dcpds.cpms.osd.mil"; + + "expected" string => + "## +# Sendmail Alias File +# @(#)B11.23_LRaliases $Revision: 1.1.212.1 $ $Date: 99/09/13 15:13:16 $ +# +# Mail to an alias in this file will be sent to the users, programs, or +# files designated following the colon. +# Aliases defined in this file will NOT be expanded in headers from +# mailx(1), but WILL be visible over networks and in headers from +# rmail(1). +# +# >>>>>>>>>> The program \"/usr/sbin/newaliases\" must be run after +# >> NOTE >> this file is updated, or else any changes will not be +# >>>>>>>>>> visible to sendmail. +## + +# Alias for mailer daemon +MAILER-DAEMON : root + +# RFC 822 requires that every host have a mail address \"postmaster\" +postmaster : root + +# Aliases to handle mail to msgs and news +nobody : /dev/null + +# System Admistration aliases +operator : root +uucp : root +daemon : root + +# Ftp maintainer. +ftp-bugs : root + +# Local aliases +root : dcpds.sysadmin@dcpds.cpms.osd.mil,:include:/etc/mail/aliases.root +oracle : dcpds.dba@dcpds.cpms.osd.mil +exinfac : dcpds.exits@dcpds.cpms.osd.mil +applmgr : dcpds.dba@dcpds.cpms.osd.mil +stgmgmt : :include:/etc/mail/aliases.stgmgmt"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "MailAliases" slist => { "stgmgmt : :include:/etc/mail/aliases.stgmgmt" }; + + files: + "$(G.testfile).actual" + edit_line => copy_and_append_lines("$(G.testfile).template", + "@(this.MailAliases)"), + edit_defaults => empty, + create => "true"; +} + + +bundle edit_line copy_and_append_lines(file,lines) +{ + insert_lines: + + "$(file)" + insert_type => "file"; + + "$(lines)" + insert_type => "string"; + +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/301.cf b/tests/acceptance/10_files/09_insert_lines/301.cf new file mode 100644 index 0000000000..c618a7e382 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/301.cf @@ -0,0 +1,68 @@ +####################################################### +# +# Test that specifying whitespace_policy escapes meta-characters (Issue 644) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "expected" string => "*foobar*"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert("$(init.expected)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + + "$(G.testfile).actual" + edit_line => set_value_rev, + create => "true"; +} + +bundle edit_line set_value_rev +{ + insert_lines: + "*foobar*" + whitespace_policy => { "ignore_embedded" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/302.cf b/tests/acceptance/10_files/09_insert_lines/302.cf new file mode 100644 index 0000000000..006fea558d --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/302.cf @@ -0,0 +1,88 @@ +####################################################### +# +# Test non matching end selector in region. +# This will cause content won't be added to file. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "actual" string => "[quux] +bar +"; + + "expected" string => "[quux] +bar +"; + + "files" slist => { "actual", "expected" }; + + files: + "$(G.testfile).$(files)" + create => "true", + edit_line => init_insert("$(init.$(files))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => example_edit_line; +} + +bundle edit_line example_edit_line +{ + insert_lines: + "foo" + select_region => example_select, + location => example_location; +} + +body select_region example_select +{ + select_start => "\[quux\]"; + select_end => "\[.*\]"; +} + +body location example_location +{ + select_line_matching => "bar"; + before_after => "after"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/303.cf b/tests/acceptance/10_files/09_insert_lines/303.cf new file mode 100644 index 0000000000..dcaebe6649 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/303.cf @@ -0,0 +1,90 @@ +######################################################## +# +# ISSUE 1023 +# +######################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +######################################################## + +bundle agent init +{ + vars: + "actual1qaz" string => ""; + "actual2qaz" string => ""; + "actual3qaz" string => ""; + + "expected1qaz" string => "AAAA"; + "expected2qaz" string => "AAAA"; + "expected3qaz" string => "AAAA"; + + "files" slist => { "actual1qaz", "expected1qaz", "actual2qaz", "expected2qaz", + "actual3qaz", "expected3qaz" }; + + + files: + "$(G.testfile).actual1qaz" touch => "true"; + "$(G.testfile).actual2qaz" touch => "true"; + "$(G.testfile).actual3qaz" touch => "true"; + + "$(G.testfile).expected1qaz" + touch => "true", + edit_line => init_insert("$(expected1qaz)"); + "$(G.testfile).expected2qaz" + touch => "true", + edit_line => init_insert("$(expected1qaz)"); + "$(G.testfile).expected3qaz" + touch => "true", + edit_line => init_insert("$(expected1qaz)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle agent test +{ + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine5120" }; + + files: + "$(G.testfile).(actual)?\dq.*" + pathtype => "regex", + edit_line => example_edit_line; +} + +bundle edit_line example_edit_line +{ + insert_lines: + "AAAA"; +} + +bundle agent check +{ + methods: + "first" usebundle => dcs_check_diff("$(G.testfile).actual1qaz", + "$(G.testfile).expected1qaz", + "$(this.promise_filename)"); + "second" usebundle => dcs_check_diff("$(G.testfile).actual2qaz", + "$(G.testfile).expected2qaz", + "$(this.promise_filename)"); + "third" usebundle => dcs_check_diff("$(G.testfile).actual3qaz", + "$(G.testfile).expected3qaz", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/400.cf b/tests/acceptance/10_files/09_insert_lines/400.cf new file mode 100644 index 0000000000..6d63fbe02b --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/400.cf @@ -0,0 +1,43 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + "$(G.testfile)" + file_type => "fifo", + edit_line => init_insert("foo"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle agent check +{ + classes: + "exists" expression => fileexists("$(G.testfile)"); + "ok" not => "exists"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/09_insert_lines/block_insert_duplicate.cf b/tests/acceptance/10_files/09_insert_lines/block_insert_duplicate.cf new file mode 100644 index 0000000000..7187a58b34 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/block_insert_duplicate.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Insert lines at the top of file a multi-line header, +# verify that insertion is convergent - Redmine #1525 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "header" string => "############### +### This file is managed by CFEngine +### Do not update it manually, oh no +### do not +###############"; + + files: + "$(G.testfile).expected" + create => "true", + edit_line => insert_header; + + commands: + !windows:: + "$(G.echo)" + args => "\"${init.header}\" > \"$(G.testfile).actual\"", + contain => in_shell; + windows:: # newlines in shell are not handled properly on Windows... + "$(G.printf)" + args => '"###############\n### This file is managed by CFEngine\n### Do not update it manually, oh no\n### do not\n###############\n" > "$(G.testfile).actual"', + contain => in_shell; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + files: + "$(G.testfile).actual" + create => "true", + edit_line => insert_header; +} + +bundle edit_line insert_header +{ + insert_lines: + "${init.header}" + location => start, + insert_type => "preserve_block"; +} + + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 + diff --git a/tests/acceptance/10_files/09_insert_lines/block_insert_order.cf b/tests/acceptance/10_files/09_insert_lines/block_insert_order.cf new file mode 100644 index 0000000000..f168755d1b --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/block_insert_order.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy ignore_trailing +# works when given a close-enough exact match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert, + edit_defaults => init_empty; +} + +bundle edit_line init_insert +{ + insert_lines: + "line1 +line2"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert; +} + +bundle edit_line test_insert +{ + delete_lines: + ".*"; + + insert_lines: + "line1 +line2" + insert_type => "preserve_block", + location => start; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/crlf-in-cftemplate.cf b/tests/acceptance/10_files/09_insert_lines/crlf-in-cftemplate.cf new file mode 100644 index 0000000000..2df2c6c3a3 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf-in-cftemplate.cf @@ -0,0 +1,82 @@ +############################################################################## +# Test that CRLF is handled correctly on Windows. +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "!windows"; + + vars: + "datadir" string => "$(this.promise_dirname)/crlf_data"; + "filelist" slist => { "file-with-lf.txt", + "file-with-crlf.txt", + "file-with-mixed-eol.txt" + }; + + files: + "$(G.testdir)/." + create => "yes"; + "$(G.testdir)/$(filelist)" + copy_from => copy_file("$(filelist)"); +} + +body copy_from copy_file(file) +{ + source => "$(datadir)/$(file)"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + files: + "$(G.testdir)/$(init.filelist)" + edit_template => "$(init.datadir)/$(init.filelist).cftemplate", + template_method => "cfengine"; +} + +####################################################### + +bundle agent check +{ + vars: + "ok_classes" slist => { "diff_ok_file_with_lf_txt", + "diff_ok_file_with_crlf_txt", + "diff_ok_file_with_mixed_eol_txt", + "size_ok_file_with_lf_txt", + "size_ok_file_with_crlf_txt", + "size_ok_file_with_mixed_eol_txt" + }; + "classes_set" slist => classesmatching("(diff|size)_ok.*"); + classes: + "diff_ok_$(init.filelist)" expression => returnszero( + "$(G.diff) $(G.testdir)/$(init.filelist) $(init.datadir)/$(init.filelist).expected >$(G.dev_null) 2>&1", + "useshell"); + "size_ok_$(init.filelist)" expression => strcmp(filestat("$(G.testdir)/$(init.filelist)", "size"), + filestat("$(init.datadir)/$(init.filelist).expected", "size")); + + "ok" and => { @(ok_classes) }; + + reports: + DEBUG.!ok:: + "Classes expected: $(ok_classes)"; + "Classes actually set: $(classes_set)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/09_insert_lines/crlf-in-edit_line-insert-file.cf b/tests/acceptance/10_files/09_insert_lines/crlf-in-edit_line-insert-file.cf new file mode 100644 index 0000000000..33b35afad4 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf-in-edit_line-insert-file.cf @@ -0,0 +1,103 @@ +############################################################################## +# Test that CRLF is handled correctly on Windows. +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "!windows"; + + vars: + "datadir" string => "$(this.promise_dirname)/crlf_data"; + "filelist" slist => { "file-with-lf.txt", + "file-with-crlf.txt", + "file-with-mixed-eol.txt" + }; + + files: + "$(G.testdir)/." + create => "yes"; + "$(G.testdir)/$(filelist)" + copy_from => copy_file("$(filelist)"); +} + +body copy_from copy_file(file) +{ + source => "$(datadir)/$(file)"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + files: + "$(G.testdir)/$(init.filelist)" + edit_line => insert_line; +} + +bundle edit_line insert_line +{ + insert_lines: + "Inserted line" + select_region => second_line; + "$(init.datadir)/insert_file.txt" + insert_type => "file", + select_region => third_line; +} + +body select_region second_line +{ + select_start => "alpha"; + select_end => "beta"; +} + +body select_region third_line +{ + select_start => "beta"; + select_end => "charlie"; +} + +####################################################### + +bundle agent check +{ + vars: + "ok_classes" slist => { "diff_ok_file_with_lf_txt", + "diff_ok_file_with_crlf_txt", + "diff_ok_file_with_mixed_eol_txt", + "size_ok_file_with_lf_txt", + "size_ok_file_with_crlf_txt", + "size_ok_file_with_mixed_eol_txt" + }; + "classes_set" slist => classesmatching("(diff|size)_ok.*"); + classes: + "diff_ok_$(init.filelist)" expression => returnszero( + "$(G.diff) $(G.testdir)/$(init.filelist) $(init.datadir)/$(init.filelist).expected >$(G.dev_null) 2>&1", + "useshell"); + "size_ok_$(init.filelist)" expression => strcmp(filestat("$(G.testdir)/$(init.filelist)", "size"), + filestat("$(init.datadir)/$(init.filelist).expected", "size")); + + "ok" and => { @(ok_classes) }; + + reports: + DEBUG.!ok:: + "Classes expected: $(ok_classes)"; + "Classes actually set: $(classes_set)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/09_insert_lines/crlf-in-edit_line.cf b/tests/acceptance/10_files/09_insert_lines/crlf-in-edit_line.cf new file mode 100644 index 0000000000..4981106520 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf-in-edit_line.cf @@ -0,0 +1,104 @@ +############################################################################## +# Test that CRLF is handled correctly on Windows. +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "!windows"; + + vars: + "datadir" string => "$(this.promise_dirname)/crlf_data"; + "filelist" slist => { "file-with-lf.txt", + "file-with-crlf.txt", + "file-with-mixed-eol.txt" + }; + + files: + "$(G.testdir)/." + create => "yes"; + "$(G.testdir)/$(filelist)" + copy_from => copy_file("$(filelist)"); +} + +body copy_from copy_file(file) +{ + source => "$(datadir)/$(file)"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + files: + "$(G.testdir)/$(init.filelist)" + edit_line => insert_line; +} + +bundle edit_line insert_line +{ + insert_lines: + "Inserted line" + select_region => second_line; + "Inserted block +Block line 1 +Block line 2" + select_region => third_line; +} + +body select_region second_line +{ + select_start => "alpha"; + select_end => "beta"; +} + +body select_region third_line +{ + select_start => "beta"; + select_end => "charlie"; +} + +####################################################### + +bundle agent check +{ + vars: + "ok_classes" slist => { "diff_ok_file_with_lf_txt", + "diff_ok_file_with_crlf_txt", + "diff_ok_file_with_mixed_eol_txt", + "size_ok_file_with_lf_txt", + "size_ok_file_with_crlf_txt", + "size_ok_file_with_mixed_eol_txt" + }; + "classes_set" slist => classesmatching("(diff|size)_ok.*"); + classes: + "diff_ok_$(init.filelist)" expression => returnszero( + "$(G.diff) $(G.testdir)/$(init.filelist) $(init.datadir)/$(init.filelist).expected >$(G.dev_null) 2>&1", + "useshell"); + "size_ok_$(init.filelist)" expression => strcmp(filestat("$(G.testdir)/$(init.filelist)", "size"), + filestat("$(init.datadir)/$(init.filelist).expected", "size")); + + "ok" and => { @(ok_classes) }; + + reports: + DEBUG.!ok:: + "Classes expected: $(ok_classes)"; + "Classes actually set: $(classes_set)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/09_insert_lines/crlf-in-mustache.cf b/tests/acceptance/10_files/09_insert_lines/crlf-in-mustache.cf new file mode 100644 index 0000000000..cd00b69c5e --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf-in-mustache.cf @@ -0,0 +1,82 @@ +############################################################################## +# Test that CRLF is handled correctly on Windows. +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "!windows"; + + vars: + "datadir" string => "$(this.promise_dirname)/crlf_data"; + "filelist" slist => { "file-with-lf.txt", + "file-with-crlf.txt", + "file-with-mixed-eol.txt" + }; + + files: + "$(G.testdir)/." + create => "yes"; + "$(G.testdir)/$(filelist)" + copy_from => copy_file("$(filelist)"); +} + +body copy_from copy_file(file) +{ + source => "$(datadir)/$(file)"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + files: + "$(G.testdir)/$(init.filelist)" + edit_template => "$(init.datadir)/$(init.filelist).mustache", + template_method => "mustache"; +} + +####################################################### + +bundle agent check +{ + vars: + "ok_classes" slist => { "diff_ok_file_with_lf_txt", + "diff_ok_file_with_crlf_txt", + "diff_ok_file_with_mixed_eol_txt", + "size_ok_file_with_lf_txt", + "size_ok_file_with_crlf_txt", + "size_ok_file_with_mixed_eol_txt" + }; + "classes_set" slist => classesmatching("(diff|size)_ok.*"); + classes: + "diff_ok_$(init.filelist)" expression => returnszero( + "$(G.diff) $(G.testdir)/$(init.filelist) $(init.datadir)/$(init.filelist).expected >$(G.dev_null) 2>&1", + "useshell"); + "size_ok_$(init.filelist)" expression => strcmp(filestat("$(G.testdir)/$(init.filelist)", "size"), + filestat("$(init.datadir)/$(init.filelist).expected", "size")); + + "ok" and => { @(ok_classes) }; + + reports: + DEBUG.!ok:: + "Classes expected: $(ok_classes)"; + "Classes actually set: $(classes_set)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/.gitattributes b/tests/acceptance/10_files/09_insert_lines/crlf_data/.gitattributes new file mode 100644 index 0000000000..fa1385d99a --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/.gitattributes @@ -0,0 +1 @@ +* -text diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt new file mode 100644 index 0000000000..b5c09785bf --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt @@ -0,0 +1,3 @@ +alpha +beta +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.cftemplate b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.cftemplate new file mode 100644 index 0000000000..aff5ab03ee --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.cftemplate @@ -0,0 +1,9 @@ +alpha +Inserted line +beta +[%CFEngine BEGIN %] +Inserted block +Block line 1 +Block line 2 +[%CFEngine END %] +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.expected b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.expected new file mode 100644 index 0000000000..6d189ac994 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.expected @@ -0,0 +1,7 @@ +alpha +Inserted line +beta +Inserted block +Block line 1 +Block line 2 +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.mustache b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.mustache new file mode 100644 index 0000000000..8a5ad95534 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-crlf.txt.mustache @@ -0,0 +1,9 @@ +alpha +Inserted line +beta +{{#classes.any}} +Inserted block +Block line 1 +Block line 2 +{{/classes.any}} +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt new file mode 100644 index 0000000000..7e896ab7b9 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt @@ -0,0 +1,3 @@ +alpha +beta +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.cftemplate b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.cftemplate new file mode 100644 index 0000000000..43d97532a1 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.cftemplate @@ -0,0 +1,9 @@ +alpha +Inserted line +beta +[%CFEngine BEGIN %] +Inserted block +Block line 1 +Block line 2 +[%CFEngine END %] +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.expected b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.expected new file mode 100644 index 0000000000..634f696cb5 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.expected @@ -0,0 +1,7 @@ +alpha +Inserted line +beta +Inserted block +Block line 1 +Block line 2 +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.mustache b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.mustache new file mode 100644 index 0000000000..9fb3ab7bc1 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-lf.txt.mustache @@ -0,0 +1,9 @@ +alpha +Inserted line +beta +{{#classes.any}} +Inserted block +Block line 1 +Block line 2 +{{/classes.any}} +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt new file mode 100644 index 0000000000..16d67d26f0 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt @@ -0,0 +1,3 @@ +alpha +beta +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.cftemplate b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.cftemplate new file mode 100644 index 0000000000..43d97532a1 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.cftemplate @@ -0,0 +1,9 @@ +alpha +Inserted line +beta +[%CFEngine BEGIN %] +Inserted block +Block line 1 +Block line 2 +[%CFEngine END %] +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.expected b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.expected new file mode 100644 index 0000000000..6d189ac994 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.expected @@ -0,0 +1,7 @@ +alpha +Inserted line +beta +Inserted block +Block line 1 +Block line 2 +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.mustache b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.mustache new file mode 100644 index 0000000000..9fb3ab7bc1 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/file-with-mixed-eol.txt.mustache @@ -0,0 +1,9 @@ +alpha +Inserted line +beta +{{#classes.any}} +Inserted block +Block line 1 +Block line 2 +{{/classes.any}} +charlie diff --git a/tests/acceptance/10_files/09_insert_lines/crlf_data/insert_file.txt b/tests/acceptance/10_files/09_insert_lines/crlf_data/insert_file.txt new file mode 100644 index 0000000000..b3c6288ac5 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/crlf_data/insert_file.txt @@ -0,0 +1,3 @@ +Inserted block +Block line 1 +Block line 2 diff --git a/tests/acceptance/10_files/09_insert_lines/edit_line_bundle_called_from_namespace.cf b/tests/acceptance/10_files/09_insert_lines/edit_line_bundle_called_from_namespace.cf new file mode 100644 index 0000000000..6ef2a4cef7 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/edit_line_bundle_called_from_namespace.cf @@ -0,0 +1,61 @@ +####################################################### +# +# Call an edit_line bundle from another namespace +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "edit_line_bundle_called_from_namespace.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert, + edit_defaults => init_empty; +} + +bundle edit_line init_insert +{ + insert_lines: + "Hello"; +} + +bundle edit_line simple_insert(line) +{ + insert_lines: + "$(line)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + methods: + "jump" usebundle => testing_namespace:namespaced_test("$(G.testfile).actual"); +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/edit_line_bundle_called_from_namespace.cf.sub b/tests/acceptance/10_files/09_insert_lines/edit_line_bundle_called_from_namespace.cf.sub new file mode 100644 index 0000000000..4eb6d44c35 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/edit_line_bundle_called_from_namespace.cf.sub @@ -0,0 +1,17 @@ +body file control +{ + namespace => "testing_namespace"; +} + +bundle agent namespaced_test(file) +{ + files: + "$(file)" + create => "true", + edit_defaults => default:init_empty, + classes => default:scoped_classes_generic("bundle", "edit"), + edit_line => default:simple_insert("Hello"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/insert-large-lines.cf b/tests/acceptance/10_files/09_insert_lines/insert-large-lines.cf new file mode 100644 index 0000000000..4f0a600e8d --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/insert-large-lines.cf @@ -0,0 +1,37 @@ + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "data" string => readfile("$(this.promise_filename).txt", "16431"); + files: + "$(G.testfile).actual" + create => "true", + edit_defaults => init_empty, + edit_line => test_insert("$(data)"); +} + +bundle edit_line test_insert(data) +{ + insert_lines: + "$(data)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(this.promise_filename).txt", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/10_files/09_insert_lines/insert-large-lines.cf.txt b/tests/acceptance/10_files/09_insert_lines/insert-large-lines.cf.txt new file mode 100644 index 0000000000..3a4205a6d4 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/insert-large-lines.cf.txt @@ -0,0 +1,4 @@ +........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... +........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... +........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... +........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... diff --git a/tests/acceptance/10_files/09_insert_lines/insert_line_end_do_not_match_eof_control_body.cf b/tests/acceptance/10_files/09_insert_lines/insert_line_end_do_not_match_eof_control_body.cf new file mode 100644 index 0000000000..10726cff67 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/insert_line_end_do_not_match_eof_control_body.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Test matching EOF in file where end selector in region don't matches +# anything and local edit_line 'select_end_match_eof' option is +# overriding global body common control. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + select_end_match_eof => "true"; +} + +####################################################### + +bundle agent init +{ + vars: + "actual" string => "[quux] +bar +foo +"; + + "expected" string => "[quux] +bar +foo +"; + + "files" slist => { "actual", "expected" }; + + files: + "$(G.testfile).$(files)" + create => "true", + edit_line => init_insert("$(init.$(files))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => example_edit_line; +} + +bundle edit_line example_edit_line +{ + insert_lines: + "foo" + select_region => example_select, + location => example_location; +} + +body select_region example_select +{ + select_start => "\[quux\]"; + select_end => "\[.*\]"; + select_end_match_eof => "false"; +} + +body location example_location +{ + select_line_matching => "bar"; + before_after => "after"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof.cf b/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof.cf new file mode 100644 index 0000000000..7baba7e4d6 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Test matching EOF in file where end selector in region don't matches +# anything but select_end_match_eof is true. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "actual" string => "[quux] +bar +"; + + "expected" string => "[quux] +bar +foo +"; + + "files" slist => { "actual", "expected" }; + + files: + "$(G.testfile).$(files)" + create => "true", + edit_line => init_insert("$(init.$(files))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => example_edit_line; +} + +bundle edit_line example_edit_line +{ + insert_lines: + "foo" + select_region => example_select, + location => example_location; +} + +body select_region example_select +{ + select_start => "\[quux\]"; + select_end => "\[.*\]"; + select_end_match_eof => "true"; +} + +body location example_location +{ + select_line_matching => "bar"; + before_after => "after"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof_control_body.cf b/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof_control_body.cf new file mode 100644 index 0000000000..9db635ffa3 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof_control_body.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Test matching EOF in file where end selector in region don't matches +# anything and control body 'select_end_match_eof' is true. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + select_end_match_eof => "true"; +} + +####################################################### + +bundle agent init +{ + vars: + "actual" string => "[quux] +bar +"; + + "expected" string => "[quux] +bar +foo +"; + + "files" slist => { "actual", "expected" }; + + files: + "$(G.testfile).$(files)" + create => "true", + edit_line => init_insert("$(init.$(files))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => example_edit_line; +} + +bundle edit_line example_edit_line +{ + insert_lines: + "foo" + select_region => example_select, + location => example_location; +} + +body select_region example_select +{ + select_start => "\[quux\]"; + select_end => "\[.*\]"; +} + +body location example_location +{ + select_line_matching => "bar"; + before_after => "after"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof_default.cf b/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof_default.cf new file mode 100644 index 0000000000..63aabc5c32 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/insert_line_end_match_eof_default.cf @@ -0,0 +1,90 @@ +####################################################### +# +# Test matching EOF default value for select region. +# +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "actual" string => "[quux] +bar +foo +"; + + "expected" string => "[quux] +bar +foo +"; + + "files" slist => { "actual", "expected" }; + + files: + "$(G.testfile).$(files)" + create => "true", + edit_line => init_insert("$(init.$(files))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => example_edit_line; +} + +bundle edit_line example_edit_line +{ + insert_lines: + "foo" + select_region => example_select, + location => example_location; +} + +body select_region example_select +{ + select_start => "\[quux\]"; + select_end => "\[.*\]"; +} + +body location example_location +{ + select_line_matching => "bar"; + before_after => "after"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/insertion-order-reversal.cf b/tests/acceptance/10_files/09_insert_lines/insertion-order-reversal.cf new file mode 100644 index 0000000000..bf7dc5c062 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/insertion-order-reversal.cf @@ -0,0 +1,95 @@ +####################################################### +# +# Insert a number of lines, insuring they are not printed in reverse order (Issue 809) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + + "insert" string => + "BEGIN + Five potato + Six potato + Seven potatoe + More! +END"; + + "expected" string => + "BEGIN + Five potato + Six potato + Seven potatoe + More! +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(str) +{ + delete_lines: + ".*"; + + insert_lines: + "$(str)" + insert_type => "preserve_block", + location => start; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/multi-files-dirs.cf b/tests/acceptance/10_files/09_insert_lines/multi-files-dirs.cf new file mode 100644 index 0000000000..72f8b97503 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/multi-files-dirs.cf @@ -0,0 +1,91 @@ +####################################################### +# +# Insert a line into variously named files, in various directories (Issue 888) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "expected", "$(results)" }; + "testdirs" slist => { "dirA/", "dirB/", "dirC/" }; + "testfiles" slist => { "dirA/testfa", "dirB/testfb", "dirC/testfc" }; + + files: + "$(G.testdir)/$(testdirs)" + comment => "Create directory: $(testdirs)."; + + "$(G.testdir)/$(testfiles)" + comment => "Create target file: $(testfiles).", + create => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine4853" }; + + files: + any:: + "$(G.testdir)/.*/testf.*" + create => "true", + edit_line => myedit; +} + +bundle edit_line myedit +{ + insert_lines: + any:: + "one two three test"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => "one two three test"; + "results" slist => { "resultA", "resultB", "resultC" }; + + "resultA" string => readfile( + "$(G.testdir)/dirA/testfa", "33"); + "resultB" string => readfile( + "$(G.testdir)/dirB/testfb", "33"); + "resultC" string => readfile( + "$(G.testdir)/dirC/testfc", "33"); + + classes: + "ok_A" expression => strcmp("$(expect)", "$(resultA)"); + "ok_B" expression => strcmp("$(expect)", "$(resultB)"); + "ok_C" expression => strcmp("$(expect)", "$(resultC)"); + + "ok" and => { "ok_A", "ok_B", "ok_C" }; + + reports: + DEBUG:: + "expected: '$(expect)'"; + "results:"; + "resultA: '$(resultA)'"; + "resultB: '$(resultB)'"; + "resultC: '$(resultC)'"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/namespaced_edit_line_bundle.cf b/tests/acceptance/10_files/09_insert_lines/namespaced_edit_line_bundle.cf new file mode 100644 index 0000000000..3cf0c14b91 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/namespaced_edit_line_bundle.cf @@ -0,0 +1,58 @@ +####################################################### +# +# Call an edit_line bundle from another namespace +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "namespaced_edit_line_bundle.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile).expected" + create => "true", + edit_line => init_insert, + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "Hello"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + create => "true", + edit_defaults => init_empty, + edit_line => testing_namespace:simple_insert; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/namespaced_edit_line_bundle.cf.sub b/tests/acceptance/10_files/09_insert_lines/namespaced_edit_line_bundle.cf.sub new file mode 100644 index 0000000000..3d7b54bfef --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/namespaced_edit_line_bundle.cf.sub @@ -0,0 +1,14 @@ +body file control +{ + namespace => "testing_namespace"; +} + +bundle edit_line simple_insert +{ + insert_lines: + "Hello"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/nothing_to_repeat_error.cf b/tests/acceptance/10_files/09_insert_lines/nothing_to_repeat_error.cf new file mode 100644 index 0000000000..63a2116a14 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/nothing_to_repeat_error.cf @@ -0,0 +1,26 @@ +####################################################### +# +# catch "nothing to repeat" error in file editing +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_output(".*info: Inserted the promised line.*", + ".*nothing to repeat.*", + "$(sys.cf_agent) -KI -f $(this.promise_filename).sub | $(G.grep) nofile", + $(this.promise_filename)); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/nothing_to_repeat_error.cf.sub b/tests/acceptance/10_files/09_insert_lines/nothing_to_repeat_error.cf.sub new file mode 100644 index 0000000000..5d950a517b --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/nothing_to_repeat_error.cf.sub @@ -0,0 +1,25 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { build_linux_limits_conf }; +} + +bundle agent build_linux_limits_conf +{ + vars: + "limits[glob-dash-nofile]" string => "* - nofile 30000", policy => "free"; + "limits[app-soft-nproc]" string => "app soft nproc 16357", policy => "free"; + "values" slist => getvalues("limits"); + + files: + "$(G.testfile)" + create => "true", + edit_line => insert("@(build_linux_limits_conf.values)"), + edit_defaults => empty; +} + +bundle edit_line insert(str) +{ + insert_lines: + "$(str)"; +} diff --git a/tests/acceptance/10_files/09_insert_lines/preserve_block_blank_lines.cf b/tests/acceptance/10_files/09_insert_lines/preserve_block_blank_lines.cf new file mode 100644 index 0000000000..7e8153d553 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/preserve_block_blank_lines.cf @@ -0,0 +1,83 @@ +# https://dev.cfengine.com/issues/7094 +# + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body edit_defaults test_empty +{ + empty_file_before_editing => "true"; + edit_backup => "false"; +} + +bundle agent init +{ + vars: + "test_file" string => "$(G.testfile).test"; + + files: + "$(test_file)" + create => "true", + edit_defaults => test_empty; +} + +bundle agent test +{ + files: + "$(init.test_file)" + edit_line => add_bad(); + + "$(init.test_file)" + edit_line => add_bad2(); +} + +bundle agent check +{ + vars: + "expected" string => "Bad 1 + Bad 2 + + Bad 3 +"; + + "actual" string => readfile("$(init.test_file)", "inf"); + + classes: + "ok" expression => strcmp("$(expected)", "$(actual)"); + + reports: + DEBUG:: + "OK: Expected '$(expected)' == '$(actual)'" + if => "ok"; + + "FAIL: Expected '$(expected)' <> '$(actual)'" + if => "!ok"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + +} + +bundle edit_line add_bad() { + insert_lines: + "Bad 1 + Bad 2 + + Bad 3" + insert_type => "preserve_block"; +} + +bundle edit_line add_bad2() { + insert_lines: + " Bad 2 + + Bad 3" + insert_type => "preserve_block"; + +} diff --git a/tests/acceptance/10_files/09_insert_lines/same_text_in_two_regions.cf b/tests/acceptance/10_files/09_insert_lines/same_text_in_two_regions.cf new file mode 100644 index 0000000000..af7e446a6b --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/same_text_in_two_regions.cf @@ -0,0 +1,59 @@ +# Tests whether you can insert the same text into two regions. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => file_make("$(G.testfile)", "block1 +block2 +block3 +block4"); + + "any" usebundle => file_make("$(G.testfile).expected", "block1 +test1 +block2 +block3 +test1 +block4"); +} + +bundle agent test +{ + files: + "$(G.testfile)" + edit_line => insert_in_two_regions; +} + +bundle edit_line insert_in_two_regions +{ + insert_lines: + "test1" + select_region => sel1; + "test1" + select_region => sel2; +} + +body select_region sel1 +{ + select_start => "block1"; + select_end => "block2"; +} + +body select_region sel2 +{ + select_start => "block3"; + select_end => "block4"; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile)", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf b/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf new file mode 100644 index 0000000000..2f14d1e7c1 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf @@ -0,0 +1,96 @@ +############################################################################## +# +# Redmine #2778 +# 3.5.x: incorrect output of: +# "insert_lines promise uses the same select_line_matching anchor [...]" +# +# This test is loosely modelled on "execresult_multiples.cf" (post-3.5.x). +# +# The original Redmine 2778 report concerned editing a "sendmail.mc" file. +# But in developing this acceptance test, I discovered that some existing +# tests in this area also exhibit this behaviour, such as "009.cf". +# +# This test has two subtests: +# o a copy of "009.cf"; +# o the "sendmail.mc" edit from the original report. +# +# The subtests functionally pass but exhibit the incorrect output. +# +# Our intention is to fail if any of: +# o incorrect output is seen (our prime concern) +# o subtest itself functionally fails (its problem, not ours) +# +# David Lee +# December 2013 +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + +} + +####################################################### + +bundle agent test +{ + vars: + any:: + # Run subtests. Need to be in verbose mode to see the output. + # The full verbose output is too big for variable assignment here. + # So extract (grep) only potentially interesting lines. + "subout_1" string => execresult("$(sys.cf_agent) -Kv -f $(this.promise_filename).sub_1 2>&1 | $(G.egrep) -i 'select_line_matching'", "useshell"); + "subout_2" string => execresult("$(sys.cf_agent) -Kv -f $(this.promise_filename).sub_2 2>&1 | $(G.egrep) -i 'select_line_matching'", "useshell"); + + reports: + DEBUG:: + "bundle test: this.promise_filename: $(this.promise_filename)"; + "bundle test: subtest_1: $(this.promise_filename).sub_1"; + "bundle test: subout_1: $(subout_1)"; + "bundle test: subtest_2: $(this.promise_filename).sub_2"; + "bundle test: subout_2: $(subout_2)"; +} + + +####################################################### + +bundle agent check +{ + vars: + any:: + # These patterns spell trouble. + "pattern_fail" string => ".*select_line_matching anchor.*"; + + # Examine output from 'test' to decide whether good or bad. + classes: + any:: + "ok_pattern_1" not => regcmp("$(pattern_fail)", "$(test.subout_1)"); + "ok_pattern_2" not => regcmp("$(pattern_fail)", "$(test.subout_2)"); + + reports: + DEBUG:: + "Attempted subtest '$(this.promise_filename).sub_1' in verbose mode."; + "Significant output of sub_1 was '$(test.subout_1)'."; + "Attempted subtest '$(this.promise_filename).sub_2' in verbose mode."; + "Significant output of sub_2 was '$(test.subout_2)'."; + + DEBUG.!ok_pattern_1:: + "failing: pattern '$(pattern_fail)' in subtest_1"; + + DEBUG.!ok_pattern_2:: + "failing: pattern '$(pattern_fail)' in subtest_2"; + + ok_pattern_1.ok_pattern_2:: + "$(this.promise_filename) Pass"; + !(ok_pattern_1.ok_pattern_2):: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf.sub_1 b/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf.sub_1 new file mode 100644 index 0000000000..50a092b91c --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf.sub_1 @@ -0,0 +1,103 @@ +####################################################### +# +# Redmine #2778 +# This subtest should be identical to "009.cf" with the exception of +# these comment lines. +# +####################################################### +# +# Insert a number of lines after the last instance of a line +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "states" slist => { "actual", "expected" }; + + "actual" string => +"BEGIN + One potato + Two potato + Two potatoes + Four +END"; + + "expected" string => +"BEGIN + One potato + Two potato + Two potatoes + Three potatoe + Four +END"; + +files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ +insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ +vars: + "tstr" string => +" One potato + Two potato + Three potatoe + Four"; + +files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(test.tstr)"); + +} + +bundle edit_line test_insert(str) +{ +insert_lines: + "$(str)" + location => test_after_last(".*potato.*"); +} + +body location test_after_last(line) +{ +before_after => "after"; +first_last => "last"; +select_line_matching => "$(line)"; +} + +####################################################### + +bundle agent check +{ +methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf.sub_2 b/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf.sub_2 new file mode 100644 index 0000000000..1b5afc024e --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/selectlinematching.cf.sub_2 @@ -0,0 +1,91 @@ +####################################################### +# +# Redmine 2778: +# +# A reduced form of the generating case. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +vars: + "states" slist => { "actual", "expected" }; + + "actual" string => +"dnl # +dnl define(`SMART_HOST', `smtp.your.provider')dnl +dnl #"; + + "expected" string => +"dnl # +dnl define(`SMART_HOST', `smtp.your.provider')dnl +define(`SMART_HOST', `mail1.$m')dnl +define(`MAIL_HUB', `mail1.$m')dnl +dnl #"; + +files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ +insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ +vars: + "hostname" string => "mail1.$m"; + +files: + "$(G.testfile).actual" + create => "true", + edit_line => test_insert("$(hostname)"); + +} + +bundle edit_line test_insert(h) +{ + insert_lines: +# These sendmail "define" lines comprise a cfengine multi-line string. +"define(`SMART_HOST', `$(h)')dnl +define(`MAIL_HUB', `$(h)')dnl" + location => location_sendmail_mc; +} + +body location location_sendmail_mc +{ + select_line_matching => "^dnl.*SMART_HOST.*"; + before_after => "after"; +} + +####################################################### + +bundle agent check +{ +methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/10_files/09_insert_lines/staging/120.x.cf b/tests/acceptance/10_files/09_insert_lines/staging/120.x.cf new file mode 100644 index 0000000000..bce956b51f --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/staging/120.x.cf @@ -0,0 +1,91 @@ +####################################################### +# +# Insert lines from a variable, verify that whitespace_policy other than +# ignore_leading works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Three potatoe + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(init.insert)"); +} + +bundle edit_line test_insert(lines) +{ + insert_lines: + "$(lines)" + whitespace_policy => { "exact_match", "ignore_embedded", "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/staging/220.x.cf b/tests/acceptance/10_files/09_insert_lines/staging/220.x.cf new file mode 100644 index 0000000000..47704b3108 --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/staging/220.x.cf @@ -0,0 +1,91 @@ +####################################################### +# +# Insert lines from a file, verify that whitespace_policy other than +# ignore_leading works when given a not-close-enough match +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected", "insert" }; + + "actual" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END"; + + "insert" string => + " One potato + Two potato + Three potatoe + Leading embedded and trailing spaces + Four"; + + "expected" string => + "BEGIN + One potato + Two potato + Two potatoes + Leading embedded and trailing spaces + Four +END + Leading embedded and trailing spaces "; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_insert("$(G.testfile).insert"); +} + +bundle edit_line test_insert(filename) +{ + insert_lines: + "$(filename)" + insert_type => "file", + whitespace_policy => { "exact_match", "ignore_embedded", "ignore_trailing" }; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/staging/304.cf b/tests/acceptance/10_files/09_insert_lines/staging/304.cf new file mode 100644 index 0000000000..574c32329f --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/staging/304.cf @@ -0,0 +1,52 @@ +body common control +{ + inputs => { "../../default.cf.sub", "304.cf.namespaced.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +######################################################## + +bundle agent init +{ + vars: + "expected" slist => { "var1=value1" }; + + "files" slist => { "actual", "expected" }; + + files: + "$(G.testfile).actual" touch => "true"; + + "$(G.testfile).expected" + touch => "true", + edit_defaults => init_empty, + edit_line => init_insert("$(expected)"); +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle agent test +{ + methods: + "call" usebundle => cfdc_yumclient:config("$(G.testfile).actual", "2"); + "call" usebundle => cfdc_yumclient:config("$(G.testfile).actual", "1"); +} + +bundle agent check +{ + methods: + "first" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/09_insert_lines/staging/304.cf.namespaced.sub b/tests/acceptance/10_files/09_insert_lines/staging/304.cf.namespaced.sub new file mode 100644 index 0000000000..42a3ed6a8b --- /dev/null +++ b/tests/acceptance/10_files/09_insert_lines/staging/304.cf.namespaced.sub @@ -0,0 +1,16 @@ +body file control +{ + namespace => "cfdc_yumclient"; +} + +bundle agent config(file, i) +{ + vars: + "data[var$(i)]" string => "value$(i)"; + + files: + "$(file)" + handle => "ensure_present", + edit_defaults => default:init_empty, + edit_line => default:set_variable_values("cfdc_yumclient:config.data"); +} diff --git a/tests/acceptance/10_files/10_links/001.cf b/tests/acceptance/10_files/10_links/001.cf new file mode 100644 index 0000000000..8959a66836 --- /dev/null +++ b/tests/acceptance/10_files/10_links/001.cf @@ -0,0 +1,60 @@ +####################################################### +# +# Subdirectories shall not be created when action is +# warn. Related to bug #926. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/subdir/mylink" + link_from => ln_s("$(G.testdir)/sourcefile"), + action => test_warn_only; +} + +body action test_warn_only +{ + action_policy => "warn"; +} + +body link_from ln_s(x) +{ + link_type => "symlink"; + source => "$(x)"; + when_no_source => "force"; +} + +####################################################### + +bundle agent check +{ + classes: + "subdir_created" expression => fileexists("$(G.testdir)/subdir"); + + reports: + !subdir_created:: + "$(this.promise_filename) Pass"; + + subdir_created:: + "$(this.promise_filename) Fail"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/10_links/002-namespaced_links.cf.sub b/tests/acceptance/10_files/10_links/002-namespaced_links.cf.sub new file mode 100644 index 0000000000..345eb4528b --- /dev/null +++ b/tests/acceptance/10_files/10_links/002-namespaced_links.cf.sub @@ -0,0 +1,12 @@ +body file control +{ + namespace => "testing_links"; +} + +bundle agent namespaced_test +{ +files: + "$(G.testdir)/subdir/mylink" + link_from => default:ln_s("$(G.testdir)/sourcefile"), + action => default:warn_only; +} diff --git a/tests/acceptance/10_files/10_links/002.cf b/tests/acceptance/10_files/10_links/002.cf new file mode 100644 index 0000000000..70b5fb63d8 --- /dev/null +++ b/tests/acceptance/10_files/10_links/002.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Check that action and link_from bodies are accessible from outside +# the current namespace. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "002-namespaced_links.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: +} + +####################################################### + +bundle agent test +{ + methods: + "run" usebundle => testing_links:namespaced_test(); +} + +body link_from ln_s(x) +{ + link_type => "symlink"; + source => "$(x)"; + when_no_source => "force"; +} + +####################################################### + +bundle agent check +{ + classes: + "subdir_created" expression => fileexists("$(G.testdir)/subdir"); + + reports: + !subdir_created:: + "$(this.promise_filename) Pass"; + + subdir_created:: + "$(this.promise_filename) Fail"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/10_links/delete_dangling_symlink.cf b/tests/acceptance/10_files/10_links/delete_dangling_symlink.cf new file mode 100644 index 0000000000..3264843d64 --- /dev/null +++ b/tests/acceptance/10_files/10_links/delete_dangling_symlink.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Delete dangling symlinks +# https://dev.cfengine.com/issues/6582 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/mylink" + link_from => ln_s("$(G.testdir)/sourcefile"); + + "$(G.testdir)/mylink" + delete => tidy; +} + +body link_from ln_s(x) +{ + link_type => "symlink"; + source => "$(x)"; + when_no_source => "force"; +} + +####################################################### + +bundle agent check +{ + classes: + "link_absent" not => fileexists("$(G.testdir)/mylink"); + + reports: + link_absent:: + "$(this.promise_filename) Pass"; + + !link_absent:: + "$(this.promise_filename) Fail"; +} diff --git a/tests/acceptance/10_files/10_links/edit_linked_file.cf b/tests/acceptance/10_files/10_links/edit_linked_file.cf new file mode 100644 index 0000000000..2a832c1b03 --- /dev/null +++ b/tests/acceptance/10_files/10_links/edit_linked_file.cf @@ -0,0 +1,69 @@ +# Test that editing a symlink does not mangle it + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testdir)/file_dir/file" + create => "true", + edit_line => edit_init; + "$(G.testdir)/link_dir/." + create => "true"; + "$(G.testdir)/link_dir/link" + link_from => link_init; +} + +bundle edit_line edit_init +{ + insert_lines: + "Initial line"; +} + +body link_from link_init +{ + link_type => "symlink"; + source => "../file_dir/file"; +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "windows"; + + files: + "$(G.testdir)/link_dir/link" + edit_line => edit_test; +} + +bundle edit_line edit_test +{ + insert_lines: + "Added line"; +} + +bundle agent check +{ + classes: + "link_ok" expression => islink("$(G.testdir)/link_dir/link"); + "file_ok" not => islink("$(G.testdir)/file_dir/file"); + "link_line_one_ok" expression => regline("Initial line", "$(G.testdir)/link_dir/link"); + "file_line_one_ok" expression => regline("Initial line", "$(G.testdir)/file_dir/file"); + "link_line_two_ok" expression => regline("Added line", "$(G.testdir)/link_dir/link"); + "file_line_two_ok" expression => regline("Added line", "$(G.testdir)/file_dir/file"); + "ok" and => { "link_ok", "file_ok", "link_line_one_ok", "file_line_one_ok", "link_line_two_ok", "file_line_two_ok" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/10_links/owner-mismatch-link-and-target.cf b/tests/acceptance/10_files/10_links/owner-mismatch-link-and-target.cf new file mode 100755 index 0000000000..1202fab036 --- /dev/null +++ b/tests/acceptance/10_files/10_links/owner-mismatch-link-and-target.cf @@ -0,0 +1,94 @@ +#!/var/cfengine/bin/cf-agent -f- +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + + # The symlink is initially created with uid 0, the uid we expect in the end. + # This tests that promising the uid of a symlink is not confused by the symlink target having a different uid + # - We do not expect to see any REPAIR related to the UID of the symlink + "/tmp/symlink-target" + create => "true", + perms => uid(2); + + "/tmp/symlink" + link_from => ln_s( "/tmp/symlink-target" ), + perms => uid(0); +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3116" } + string => "Test that promising ownership of symlinks is not confused by target"; + + "test_skip_unsupported" string => "windows"; + + files: + "/tmp/symlink" + handle => "link_1", + classes => results( "namespace", "link_1_uid" ), + perms => uid(0); + + "/tmp/symlink" + handle => "link_2", + classes => results( "namespace", "link_2_uid" ), + perms => uid(0); +} + +bundle agent check +{ + classes: + "symlink_owner_ok" expression => strcmp( "0", filestat( "/tmp/symlink", "uid" ) ); + "pass" and => { "symlink_owner_ok" }; + "fail" or => { "link_1_uid_repaired", "link_2_uid_repaired" }; + reports: + + link_1_uid_repaired:: + "We think we repaired the UID of the symlink for promise with handle link_1"; + + link_2_uid_repaired:: + "We think we repaired the UID of the symlink for promise with handle link_2"; + + link_1_uid_repaired.link_2_uid_repaired:: + "We think we repaired the UID of the symlink multiple times"; + + symlink_owner_ok:: + "The symlink is owned by uid 0 as expected"; + + !symlink_owner_ok:: + "The symlink is NOT owned by uid 0 unexpectedly"; + + pass.!fail:: + "$(this.promise_filename) Pass"; + !pass|fail:: + "$(this.promise_filename) FAIL"; +} +body perms uid( uid ) +{ + owners => { "$(uid)" }; +} + +body link_from ln_s(x) +# @brief Create a symbolink link to `x` +# The link is created even if the source of the link does not exist. +# @param x The source of the link +{ + link_type => "symlink"; + source => "$(x)"; + when_no_source => "force"; +} + +bundle agent __main__ +{ + methods: + "init"; + "test"; + "check"; +} diff --git a/tests/acceptance/10_files/10_links/select_file_by_symlink_target.cf b/tests/acceptance/10_files/10_links/select_file_by_symlink_target.cf new file mode 100644 index 0000000000..047f17c56e --- /dev/null +++ b/tests/acceptance/10_files/10_links/select_file_by_symlink_target.cf @@ -0,0 +1,76 @@ +####################################################### +# +# Select files based on symlink target +# Ref: https://dev.cfengine.com/issues/6878 +# +######################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "symlink" string => "$(G.testdir)/symlink_to_tmp_target"; + "target" string => "$(G.testdir)/target"; + + files: + "$(target)/." + create => "true"; + + "$(symlink)" + link_from => linkfrom("$(target)", "symlink"), + classes => scoped_classes_generic("namespace", "init_symlink"); +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_unsupported" + string => "windows"; + + # Delete the file only if it is a symlink pointing to the expected target + files: + "$(init.symlink)" + file_select => symlinked_to("$(init.target)"), + delete => nodir, + pathtype => "literal", + classes => scoped_classes_generic("namespace", "test_symlink"); +} +####################################################### + +bundle agent check +{ + classes: + "OK" not => fileexists("$(init.symlink)"); + + reports: + OK:: + "$(this.promise_filename) Pass"; + + !OK:: + "$(this.promise_filename) Fail"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 + +body file_select symlinked_to(name) +{ + file_types => { "symlink" }; + issymlinkto => { "$(name)" }; + file_result => "issymlinkto"; +} + +body delete nodir +{ + rmdirs => "false"; +} diff --git a/tests/acceptance/10_files/10_links/select_file_by_type.cf b/tests/acceptance/10_files/10_links/select_file_by_type.cf new file mode 100644 index 0000000000..985c182bcf --- /dev/null +++ b/tests/acceptance/10_files/10_links/select_file_by_type.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Select files based on type +# +# Ref: https://dev.cfengine.com/issues/6878 +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "symlink" string => "$(G.testdir)/symlink_to_tmp_target"; + "target" string => "$(G.testdir)/target"; + + files: + "$(target)/." + create => "true"; + + "$(symlink)" + delete => tidy; + + "$(symlink)" + link_from => linkfrom("$(target)", "symlink"), + classes => scoped_classes_generic("namespace", "init_symlink"); +} + +####################################################### +bundle agent test +{ + files: + "$(init.symlink)" + file_select => select_type("symlink"), + delete => nodir, + pathtype => "literal", + classes => scoped_classes_generic("namespace", "test_symlink"); +} +####################################################### + +bundle agent check +{ + classes: + "OK" not => fileexists("$(init.symlink)"); + + reports: + OK:: + "$(this.promise_filename) Pass"; + + !OK:: + "$(this.promise_filename) Fail"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 +body delete nodir +{ + rmdirs => "false"; +} + +body file_select select_type(type) +{ + file_types => { "$(type)" }; + file_result => "file_types"; +} diff --git a/tests/acceptance/10_files/11_xml_edits/CFE-3806.cf b/tests/acceptance/10_files/11_xml_edits/CFE-3806.cf new file mode 100644 index 0000000000..4e5946f94a --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/CFE-3806.cf @@ -0,0 +1,79 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "starting_xml" string => ' + Public + For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted. + + + + '; + + files: + "$(G.testfile)-1.xml" + content => "$(starting_xml)"; + + "$(G.testfile)-2.xml" + content => "$(starting_xml)"; +} +bundle agent test +{ + meta: + "description" -> { "CFE-3806" } + string => "Test that trailing newline on insert_tree promisers do not remove ending tag for select_xpath"; + + "test_soft_fail" + string => "any", + meta => { "CFE-3860" }; + + files: + + "$(G.testfile)-1.xml" + edit_xml => insert_tree_with_trailing_newline("1.1.1.1", "tcp", "9443"); + + "$(G.testfile)-2.xml" + edit_xml => insert_tree_without_trailing_newline("1.1.1.1", "tcp", "9443"); +} + +bundle agent check +{ + methods: + "Pass/Fail" + usebundle => dcs_check_strcmp( readfile( "$(G.testfile)-1.xml"), + readfile( "$(G.testfile)-2.xml"), + $(this.promise_filename), "no" ); +} + +bundle edit_xml insert_tree_with_trailing_newline(source, protocol, port) +{ + build_xpath: + "/zone"; + + insert_tree: + ' + + + +' + select_xpath => "/zone"; + +} +bundle edit_xml insert_tree_without_trailing_newline(source, protocol, port) +{ + build_xpath: + "/zone"; + + insert_tree: + ' + + + ' + select_xpath => "/zone"; +} diff --git a/tests/acceptance/10_files/11_xml_edits/build_xpath_001.cf b/tests/acceptance/10_files/11_xml_edits/build_xpath_001.cf new file mode 100644 index 0000000000..f66a8468c8 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/build_xpath_001.cf @@ -0,0 +1,81 @@ +###################################################################### +# +# File editing edit_xml - example for building an XPath in an empty file +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "expected" string => + " +cfe_alias"; + + files: + "$(G.testfile).actual" + create => "true", + edit_defaults => init_empty; + + "$(G.testfile).expected" + create => "true", + edit_line => init_insert("$(init.expected)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "xpath" string => + "/Server/Service/Engine/Host[ @name=\"cfe_host\" | Alias = cfe_alias ]"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_build("$(test.xpath)"); +} + +bundle edit_xml test_build(str) +{ + build_xpath: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/build_xpath_002.cf b/tests/acceptance/10_files/11_xml_edits/build_xpath_002.cf new file mode 100644 index 0000000000..c16317aaa1 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/build_xpath_002.cf @@ -0,0 +1,80 @@ +###################################################################### +# +# File editing edit_xml - example for building an XPath in a node in a non-empty file +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " +"; + + "expected" string => + " +cfe_alias"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "xpath" string => + "/Server/Service/Engine/Host[ Alias = cfe_alias | @name=\"cfe_host\" ]"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_build("$(test.xpath)"); +} + +bundle edit_xml test_build(str) +{ + build_xpath: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/build_xpath_003.cf b/tests/acceptance/10_files/11_xml_edits/build_xpath_003.cf new file mode 100644 index 0000000000..9f4e653f15 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/build_xpath_003.cf @@ -0,0 +1,80 @@ +###################################################################### +# +# File editing edit_xml - example for building an XPath in a node in a non-empty file +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " +"; + + "expected" string => + " +cfe_alias"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "xpath" string => + "/Server/Service/Engine/OneFish/TwoFish/RedFish/BlueFish/Host[ Alias = cfe_alias | @name=\"cfe_host\" ]"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_build("$(test.xpath)"); +} + +bundle edit_xml test_build(str) +{ + build_xpath: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/delete_attribute_001.cf b/tests/acceptance/10_files/11_xml_edits/delete_attribute_001.cf new file mode 100644 index 0000000000..ee4d2742e3 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/delete_attribute_001.cf @@ -0,0 +1,95 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + + + + +"; + + "expected" string => + " + + + + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "attribute_name" string => "topping"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_delete_attribute("$(test.attribute_name)"); +} + +bundle edit_xml test_delete_attribute(str) +{ + delete_attribute: + "$(str)" + select_xpath => "/Potatoes/Potato[@topping=\'brocolli\']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/delete_attribute_002.cf b/tests/acceptance/10_files/11_xml_edits/delete_attribute_002.cf new file mode 100644 index 0000000000..e40fec30ff --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/delete_attribute_002.cf @@ -0,0 +1,82 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " +Nemo"; + + "expected" string => + " +Nemo"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "attribute_name" string => "type"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_delete("$(test.attribute_name)"); +} + +bundle edit_xml test_delete(str) +{ + delete_attribute: + "$(str)" + build_xpath => "/Server/Service/Engine/OneFish/TwoFish/RedFish/BlueFish", + select_xpath => "/Server/Service/Engine/OneFish"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/delete_text_001.cf b/tests/acceptance/10_files/11_xml_edits/delete_text_001.cf new file mode 100644 index 0000000000..3c57480ff4 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/delete_text_001.cf @@ -0,0 +1,93 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + Hot Potatoes!!! + + + + +"; + + "expected" string => + " + + + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "text" string => "Hot Potatoes!!!"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_delete("$(test.text)"); +} + +bundle edit_xml test_delete(str) +{ + delete_text: + "$(str)" + select_xpath => "/Potatoes/Potato[@name=\'OnePotato\']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/delete_text_002.cf b/tests/acceptance/10_files/11_xml_edits/delete_text_002.cf new file mode 100644 index 0000000000..e6bb71b1d8 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/delete_text_002.cf @@ -0,0 +1,82 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " +Nemo"; + + "expected" string => + " +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "text" string => "Nemo"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_delete("$(test.text)"); +} + +bundle edit_xml test_delete(str) +{ + delete_text: + "$(str)" + build_xpath => "/Server/Service/Engine/OneFish[@type=\'clownfish\']/TwoFish/RedFish/BlueFish", + select_xpath => "/Server/Service/Engine/OneFish[@type=\'clownfish\']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/delete_tree_001.cf b/tests/acceptance/10_files/11_xml_edits/delete_tree_001.cf new file mode 100644 index 0000000000..7d219b608f --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/delete_tree_001.cf @@ -0,0 +1,113 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + + + + + + + + + + + + + +"; + + "expected" string => + " + + + + + + + + + + + + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "tree" string => + " +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_delete("$(test.tree)"); +} + +bundle edit_xml test_delete(str) +{ + delete_tree: + "$(str)" + select_xpath => "/Potatoes/Potato[@name='OnePotato']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/delete_tree_002.cf b/tests/acceptance/10_files/11_xml_edits/delete_tree_002.cf new file mode 100644 index 0000000000..e677aba6c7 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/delete_tree_002.cf @@ -0,0 +1,95 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + +cfe_alias +"; + + "expected" string => + " +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "tree" string => + " + + + +cfe_alias +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_delete("$(test.tree)"); +} + +bundle edit_xml test_delete(str) +{ + delete_tree: + "$(str)" + build_xpath => "/Server/Service/Engine/OneFish/TwoFish/RedFish/BlueFish", + select_xpath => "/Server/Service/Engine"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/insert_host_example.cf b/tests/acceptance/10_files/11_xml_edits/insert_host_example.cf new file mode 100644 index 0000000000..c22fcbfd82 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/insert_host_example.cf @@ -0,0 +1,153 @@ +###################################################################### +# +# File editing edit_xml - example for inserting a host into a Tomcat configuration file +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + + + + + + + + + + + + + + + + + + +"; + + "expected" string => + " + + + + + + + + + + + + + + + + + + + + + + +cfe_alias + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "host" string => + " + + + +cfe_alias +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.host)"); +} + +bundle edit_xml test_insert(str) +{ + insert_tree: + "$(str)" + select_xpath => "/Server/Service/Engine"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/insert_text_001.cf b/tests/acceptance/10_files/11_xml_edits/insert_text_001.cf new file mode 100644 index 0000000000..f7b276f3bc --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/insert_text_001.cf @@ -0,0 +1,95 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + + + + +"; + + "expected" string => + " + + + Hot Potatoes!!! + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "text" string => "Hot Potatoes!!!"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.text)"); +} + +bundle edit_xml test_insert(str) +{ + insert_text: + "$(str)" + select_xpath => "/Potatoes/Potato[@name=\'OnePotato\']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/insert_text_002.cf b/tests/acceptance/10_files/11_xml_edits/insert_text_002.cf new file mode 100644 index 0000000000..655bab0658 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/insert_text_002.cf @@ -0,0 +1,82 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " +Marlin"; + + "expected" string => + " +MarlinNemo"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "text" string => "Nemo"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.text)"); +} + +bundle edit_xml test_insert(str) +{ + insert_text: + "$(str)" + build_xpath => "/Server/Service/Engine/OneFish/TwoFish[@type=\'clownfish\']/RedFish/BlueFish", + select_xpath => "/Server/Service/Engine/OneFish/TwoFish[@type=\'clownfish\']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/insert_tree_001.cf b/tests/acceptance/10_files/11_xml_edits/insert_tree_001.cf new file mode 100644 index 0000000000..8c71f3b706 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/insert_tree_001.cf @@ -0,0 +1,114 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + + + + + + + + + + + + +"; + + "expected" string => + " + + + + + + + + + + + + + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "tree" string => + " +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.tree)"); +} + +bundle edit_xml test_insert(str) +{ + insert_tree: + "$(str)" + select_xpath => "/Potatoes/Potato[@name=\'OnePotato\']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/insert_tree_003.cf b/tests/acceptance/10_files/11_xml_edits/insert_tree_003.cf new file mode 100644 index 0000000000..f80f8cb340 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/insert_tree_003.cf @@ -0,0 +1,95 @@ +###################################################################### +# +# File editing edit_xml - Inserting a tree into a non-empty file, using build_xpath body attribute +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " +"; + + "expected" string => + " + + + + +cfe_alias +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "content" string => + " + + + +cfe_alias +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.content)"); +} + +bundle edit_xml test_insert(str) +{ + insert_tree: + "$(str)" + build_xpath => "/Server/Service/Engine/OneFish/TwoFish/RedFish/BlueFish", + select_xpath => "/Server/Service/Engine"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/set_attribute_001.cf b/tests/acceptance/10_files/11_xml_edits/set_attribute_001.cf new file mode 100644 index 0000000000..6706169d8a --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/set_attribute_001.cf @@ -0,0 +1,96 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + + + + +"; + + "expected" string => + " + + + + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "attribute_name" string => "topping"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.attribute_name)"); +} + +bundle edit_xml test_insert(str) +{ + set_attribute: + "$(str)" + select_xpath => "/Potatoes/Potato[@name=\'OnePotato\']", + attribute_value => "brocolli"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/set_attribute_002.cf b/tests/acceptance/10_files/11_xml_edits/set_attribute_002.cf new file mode 100644 index 0000000000..2c0347fcec --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/set_attribute_002.cf @@ -0,0 +1,83 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " +"; + + "expected" string => + " +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "attribute_name" string => "type"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.attribute_name)"); +} + +bundle edit_xml test_insert(str) +{ + set_attribute: + "$(str)" + build_xpath => "/Server/Service/Engine/OneFish/TwoFish/RedFish/BlueFish", + select_xpath => "/Server/Service/Engine/OneFish", + attribute_value => "clownfish"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/set_attribute_003.cf b/tests/acceptance/10_files/11_xml_edits/set_attribute_003.cf new file mode 100644 index 0000000000..14b342a244 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/set_attribute_003.cf @@ -0,0 +1,96 @@ +###################################################################### +# +# File editing edit_xml - test changing an existing attribute of a node +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + + + + +"; + + "expected" string => + " + + + + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "attribute_name" string => "topping"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_set("$(test.attribute_name)"); +} + +bundle edit_xml test_set(str) +{ + set_attribute: + "$(str)" + select_xpath => "/Potatoes/Potato[@name=\'OnePotato\']", + attribute_value => "sourcream"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/set_text_001.cf b/tests/acceptance/10_files/11_xml_edits/set_text_001.cf new file mode 100644 index 0000000000..6f9ac85802 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/set_text_001.cf @@ -0,0 +1,94 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " + + + + + + + +"; + + "expected" string => + " + + Hot Potatoes!!! + + + + +"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "text" string => "Hot Potatoes!!!"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.text)"); +} + +bundle edit_xml test_insert(str) +{ + set_text: + "$(str)" + select_xpath => "/Potatoes/Potato[@name=\'OnePotato\']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/set_text_002.cf b/tests/acceptance/10_files/11_xml_edits/set_text_002.cf new file mode 100644 index 0000000000..291ecb9c1f --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/set_text_002.cf @@ -0,0 +1,82 @@ +###################################################################### +# +# File editing edit_xml +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + " +Marlin"; + + "expected" string => + " +Nemo"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + vars: + "text" string => "Nemo"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_insert("$(test.text)"); +} + +bundle edit_xml test_insert(str) +{ + set_text: + "$(str)" + build_xpath => "/Server/Service/Engine/OneFish[@type=\'clownfish\']/TwoFish/RedFish/BlueFish", + select_xpath => "/Server/Service/Engine/OneFish[@type=\'clownfish\']"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/staging/insert_tree_002.cf b/tests/acceptance/10_files/11_xml_edits/staging/insert_tree_002.cf new file mode 100644 index 0000000000..a9e459074c --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/staging/insert_tree_002.cf @@ -0,0 +1,89 @@ +###################################################################### +# +# File editing edit_xml - Inserting a tree into an empty file +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "expected" string => + " + + + + +cfe_alias +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_defaults => init_empty; + + "$(G.testfile).expected" + create => "true", + edit_line => init_insert("$(init.expected)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + vars: + "content" string => + " + + + +cfe_alias +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_create("$(test.content)"); +} + +bundle edit_xml test_create(str) +{ + insert_tree: + "$(str)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => xml_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/11_xml_edits/staging/insert_tree_004.cf b/tests/acceptance/10_files/11_xml_edits/staging/insert_tree_004.cf new file mode 100644 index 0000000000..171c3fb7a5 --- /dev/null +++ b/tests/acceptance/10_files/11_xml_edits/staging/insert_tree_004.cf @@ -0,0 +1,92 @@ +###################################################################### +# +# File editing edit_xml - Inserting a tree into an initially empty file, using build_xpath body attribute +# Note that selext_xpath must still be specified, as file will no longer be empty after build_xpath is verified. +# +###################################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "expected" string => + " + + + + +cfe_alias +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_defaults => init_empty; + + "$(G.testfile).expected" + create => "true", + edit_line => init_insert("$(init.expected)"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### +bundle agent test +{ + vars: + "content" string => + " + + + +cfe_alias +"; + + files: + "$(G.testfile).actual" + create => "true", + edit_xml => test_create("$(test.content)"); +} + +bundle edit_xml test_create(str) +{ + insert_tree: + "$(str)" + build_xpath => "/Server/Service/Engine/OneFish/TwoFish/RedFish/BlueFish", + select_xpath => "/Server/Service/Engine"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)", "no"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/12_acl/acl.cf b/tests/acceptance/10_files/12_acl/acl.cf new file mode 100644 index 0000000000..cc3b37437a --- /dev/null +++ b/tests/acceptance/10_files/12_acl/acl.cf @@ -0,0 +1,91 @@ +###################################################### +# +# ACL tests +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + classes: + "cfengine_internal_getfacl" expression => fileexists("/var/cfengine/bin/getfacl"); + "system_getfacl" expression => fileexists("/usr/bin/getfacl"); + + vars: + linux.cfengine_internal_getfacl:: + "cmd" string => "/var/cfengine/bin/getfacl"; + linux.!cfengine_internal_getfacl.system_getfacl:: + "cmd" string => "/usr/bin/getfacl"; + + files: + "$(G.testdir)$(const.dirsep)file" + create => "true", + acl => acl_sanity_check; +} + +body acl acl_sanity_check +{ + acl_method => "append"; + aces => { "all:=rwx:allow" }; +} + +####################################################### + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; + + vars: + linux:: + "sanity_perms" string => execresult("$(init.cmd) $(G.testdir)$(const.dirsep)file 2>&1 | $(G.grep) -v 'Removing leading'", "noshell"); + classes: + linux:: + # Some filesystems don't have ACL capability, so we do this sanity check. + "linux_go_ahead" expression => regcmp(".*other::rwx.*", "$(sanity_perms)"); + vars: + linux_go_ahead|windows:: + "result" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "noshell"); + !linux_go_ahead.!windows:: + "result" string => ""; + + reports: + DEBUG.!linux_go_ahead.!windows:: + "ACL support not detected. Try remounting the filesystem with -o acl."; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => regcmp(".*(error|failed).*", "$(test.result)"); + reports: + DEBUG:: + "Tests various ACL features"; + DEBUG.!ok:: + "Subtest(s) failing: $(test.result)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + files: + "$(G.testdir)$(const.dirsep)file" + acl => acl_sanity_check_reset; +} + +body acl acl_sanity_check_reset +{ + acl_method => "append"; + aces => { "all:-rwx:allow" }; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/12_acl/acl.cf.sub b/tests/acceptance/10_files/12_acl/acl.cf.sub new file mode 100644 index 0000000000..b24e8abe78 --- /dev/null +++ b/tests/acceptance/10_files/12_acl/acl.cf.sub @@ -0,0 +1,290 @@ +###################################################### +# +# ACL tests +# This test is not convergent, since it tests the +# same file over and over. But it saves us a lot +# of test code. +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "init", "test" }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + classes: + "cfengine_internal_getfacl" expression => fileexists("/var/cfengine/bin/getfacl"); + "system_getfacl" expression => fileexists("/usr/bin/getfacl"); + + vars: + linux.cfengine_internal_getfacl:: + "cmd" string => "/var/cfengine/bin/getfacl"; + linux.!cfengine_internal_getfacl.system_getfacl:: + "cmd" string => "/usr/bin/getfacl"; + windows:: + "cmd" string => "C:\windows\system32\cmd.exe /C cacls"; + + vars: + windows:: + "rootuser" string => "Administrator"; + !windows:: + "rootuser" string => "root"; + + vars: + # ACLs may work on many Unixes, but only Linux has the tool we use to check the permissions. + linux:: + # Ordering is important here, in the case of append. + "acl_method[regular_posix]" string => "overwrite"; + "aces[regular_posix]" slist => { "user:*:=rw:allow", "group:*:=rw:allow", "all:=r:allow" }; + "regcmp[regular_posix]" string => ".*user::rw-.*group::rw-.*other::r--.*"; + + "acl_method[regular_posix2]" string => "overwrite"; + "aces[regular_posix2]" slist => { "user:*:=x:allow", "group:*:=x:allow", "all:=w:allow" }; + "regcmp[regular_posix2]" string => ".*user::--x.*group::--x.*other::-w-.*"; + + "acl_method[specific_user]" string => "overwrite"; + "aces[specific_user]" slist => { "user:*:=r:allow", "group:*:=r:allow", "all:=r:allow", "user:root:=rwx:allow" }; + "regcmp[specific_user]" string => ".*user::r--.*user:root:rwx.*group::r--.*other::r--.*"; + "notregcmp[specific_user]" string => ".*group:root:.*"; + + "acl_method[specific_group_append]" string => "append"; + "aces[specific_group_append]" slist => { "group:root:=rw:allow" }; + "regcmp[specific_group_append]" string => ".*user::r--.*user:root:rwx.*group::r--.*group:root:rw-.*other::r--.*"; + + "acl_method[no_mask]" string => "overwrite"; + "aces[no_mask]" slist => { "user:*:=r:allow", "group:*:=r:allow", "all:=r:allow", "user:root:=rx:allow" }; + "regcmp[no_mask]" string => ".*user::r--.*user:root:r-x.*group::r--.*mask::r-x.*other::r--.*"; + + "acl_type[specific_mask]" string => "posix"; + "acl_method[specific_mask]" string => "overwrite"; + "aces[specific_mask]" slist => { "user:*:=r:allow", "group:*:=r:allow", "mask:=r:allow", "all:=r:allow", "user:root:=rw:allow" }; + "regcmp[specific_mask]" string => ".*user::r--.*user:root:rw-.*group::r--.*mask::r--.*other::r--.*"; + "notregcmp[specific_mask]" string => ".*group:root:.*"; + + "acl_method[mask_and_append_group]" string => "append"; + "aces[mask_and_append_group]" slist => { "group:root:=rx:allow" }; + "regcmp[mask_and_append_group]" string => ".*user::r--.*user:root:rw-.*group::r--.*group:root:r-x.*mask::rwx.*other::r--.*"; + + "acl_method[no_perm_group]" string => "append"; + "aces[no_perm_group]" slist => { "group:root:=:allow" }; + "regcmp[no_perm_group]" string => ".*user::r--.*user:root:rw-.*group::r--.*group:root:---.*mask::rw-.*other::r--.*"; + + "acl_method[add_perm]" string => "append"; + "aces[add_perm]" slist => { "user:*:+x:allow" }; + "regcmp[add_perm]" string => ".*user::r-x.*user:root:rw-.*group::r--.*group:root:---.*mask::rw-.*other::r--.*"; + + "acl_method[add_perm_user]" string => "append"; + "aces[add_perm_user]" slist => { "user:root:+x:allow" }; + "regcmp[add_perm_user]" string => ".*user::r-x.*user:root:rwx.*group::r--.*group:root:---.*mask::rwx.*other::r--.*"; + + "acl_method[rm_perm]" string => "append"; + "aces[rm_perm]" slist => { "all:-r:allow", "user:root:-rw:allow" }; + "regcmp[rm_perm]" string => ".*user::r-x.*user:root:--x.*group::r--.*group:root:---.*mask::r-x.*other::---.*"; + + "acl_method[overwrite_add_remove]" string => "overwrite"; # Overwrite starts with blank permissions. + "aces[overwrite_add_remove]" slist => { "all:+r:allow", "user:*:-x:allow", "group:*:+x:allow" }; + "regcmp[overwrite_add_remove]" string => ".*user::---.*group::--x.*other::r--.*"; + "notregcmp[overwrite_add_remove]" string => ".*user:root:.*|.*group:root:.*"; + + # This test oscillates between pass and fail. Check out "other::r--" vs "other::r-x" + "acl_method[default]" string => "overwrite"; + "aces[default]" slist => { "all:r:allow", "user:*:rwx:allow", "group:*:rw:allow" }; + "acl_default[default]" string => "access"; + "use_dir[default]" string => "true"; + "regcmp[default]" string => ".*user::rwx.*group::rw-.*other::r--.*default:user::rwx.*default:group::rw-.*default:other::r--.*"; + + # Fails but should pass. + "acl_method[default_specify]" string => "overwrite"; + "aces[default_specify]" slist => { "user:*:r:allow", "group:*:r:allow", "all:r:allow" }; + "acl_default[default_specify]" string => "specify"; + "specify_default_aces[default_specify]" slist => { "user:*:rwx:allow", "group:*:rw:allow", "all:rw:allow", "user:root:x:allow" }; + "use_dir[default_specify]" string => "true"; + "regcmp[default_specify]" string => ".*user::r--.*group::r--.*other::r--.*default:user::rwx.*default:user:root:--x.*default:group::rw-.*default:other::rw-.*"; + + "tests" slist => { "regular_posix", + "regular_posix2", + "specific_user", + "specific_group_append", + "no_mask", + "specific_mask", + "mask_and_append_group", + "no_perm_group", + "add_perm", + "add_perm_user", + "rm_perm", + "overwrite_add_remove", + "default", + "default_specify", + } ; + + windows:: + "acl_method[read]" string => "overwrite"; + "aces[read]" slist => { "user:Administrator:r:allow" }; + "acl_inherit[read]" string => "false"; + "regcmp[read]" string => ".*Administrator:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_READ_DATA.*FILE_READ_EA.*FILE_READ_ATTRIBUTES.*"; + "notregcmp[read]" string => ".*\(ID\).*"; + + "acl_method[write]" string => "overwrite"; + "aces[write]" slist => { "user:Administrator:w:allow" }; + "regcmp[write]" string => ".*Administrator:.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_WRITE_EA.*FILE_WRITE_ATTRIBUTES.*"; + "notregcmp[write]" string => ".*\(ID\).*"; + + "acl_method[append_read]" string => "append"; + "aces[append_read]" slist => { "user:Administrator:r:allow" }; + "regcmp[append_read]" string => ".*Administrator:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_READ_DATA.*FILE_READ_EA.*FILE_READ_ATTRIBUTES.*"; + "notregcmp[append_read]" string => ".*(Administrator:.*(FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_EA|FILE_WRITE_ATTRIBUTES)|\(ID\)).*"; + + "acl_method[add_write]" string => "append"; + "aces[add_write]" slist => { "user:Administrator:+w:allow" }; + "regcmp[add_write]" string => ".*Administrator:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_GENERIC_WRITE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_WRITE_EA.*FILE_READ_ATTRIBUTES.*FILE_WRITE_ATTRIBUTES.*"; + "notregcmp[add_write]" string => ".*\(ID\).*"; + + "acl_method[rm_write]" string => "append"; + "aces[rm_write]" slist => { "user:Administrator:-w:allow" }; + "regcmp[rm_write]" string => ".*Administrator:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_READ_DATA.*FILE_READ_EA.*FILE_READ_ATTRIBUTES.*"; + "notregcmp[rm_write]" string => ".*\(ID\).*"; + + "acl_method[no_default]" string => "overwrite"; + "aces[no_default]" slist => { "user:Administrator:rwx:allow" }; + "acl_default[no_default]" string => "clear"; + "use_dir[no_default]" string => "true"; + "regcmp[no_default]" string => ".*Administrator:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_GENERIC_WRITE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_WRITE_EA.*FILE_EXECUTE.*FILE_READ_ATTRIBUTES.*FILE_WRITE_ATTRIBUTES.*"; + "notregcmp[no_default]" string => ".*(\(OI\)|\(CI\)).*"; + + "acl_method[default]" string => "overwrite"; + "aces[default]" slist => { "user:Administrator:rwx:allow" }; + "acl_default[default]" string => "access"; + "use_dir[default]" string => "true"; + "regcmp[default]" string => ".*Administrator:\(OI\)\(CI\).*READ_CONTROL.*FILE_GENERIC_READ.*FILE_GENERIC_WRITE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_WRITE_EA.*FILE_EXECUTE.*FILE_READ_ATTRIBUTES.*FILE_WRITE_ATTRIBUTES.*"; + + "acl_method[inherit]" string => "overwrite"; + "aces[inherit]" slist => { "group:Everyone:r:allow" }; + "acl_inherit[inherit]" string => "true"; + "regcmp[inherit]" string => ".*Everyone:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_READ_DATA.*FILE_READ_EA.*FILE_READ_ATTRIBUTES.*Administrator:\(ID\).*READ_CONTROL.*FILE_GENERIC_READ.*FILE_GENERIC_WRITE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_WRITE_EA.*FILE_READ_ATTRIBUTES.*FILE_WRITE_ATTRIBUTES.*"; + + "acl_method[inherit_nochange]" string => "append"; + "aces[inherit_nochange]" slist => { "group:Everyone:+w:allow" }; + "acl_inherit[inherit_nochange]" string => "nochange"; + "regcmp[inherit_nochange]" string => ".*Everyone:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_GENERIC_WRITE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_WRITE_EA.*FILE_READ_ATTRIBUTES.*FILE_WRITE_ATTRIBUTES.*Administrator:\(ID\).*READ_CONTROL.*FILE_GENERIC_READ.*FILE_GENERIC_WRITE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_WRITE_EA.*FILE_READ_ATTRIBUTES.*FILE_WRITE_ATTRIBUTES.*"; + + "acl_method[empty]" string => "append"; + "aces[empty]" slist => { "group:Everyone:-rw:allow" }; + "acl_inherit[empty]" string => "nochange"; + "regcmp[empty]" string => ".*Administrator:\(ID\).*READ_CONTROL.*FILE_GENERIC_READ.*FILE_GENERIC_WRITE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_WRITE_EA.*FILE_READ_ATTRIBUTES.*FILE_WRITE_ATTRIBUTES.*"; + "notregcmp[empty]" string => ".*Everyone.*"; + + "acl_method[no_inherit]" string => "append"; + "aces[no_inherit]" slist => { "group:Everyone:+rw:allow" }; + "acl_inherit[no_inherit]" string => "false"; + "regcmp[no_inherit]" string => ".*Everyone:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_GENERIC_WRITE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_WRITE_EA.*FILE_READ_ATTRIBUTES.*FILE_WRITE_ATTRIBUTES.*"; + "notregcmp[no_inherit]" string => ".*\(ID\).*"; + + "acl_type[ext_attr_delete]" string => "ntfs"; + "acl_method[ext_attr_delete]" string => "overwrite"; + "aces[ext_attr_delete]" slist => { "user:Administrator:(d):allow" }; + "regcmp[ext_attr_delete]" string => ".*Administrator:.*DELETE.*"; + "notregcmp[ext_attr_delete]" string => ".*(\(ID\)|READ|WRITE).*"; + + "acl_type[ext_attr_half1]" string => "ntfs"; + "acl_method[ext_attr_half1]" string => "append"; + "aces[ext_attr_half1]" slist => { "user:Administrator:+(rtxTwa):allow" }; + "regcmp[ext_attr_half1]" string => ".*Administrator:.*DELETE.*FILE_READ_DATA.*FILE_WRITE_DATA.*FILE_APPEND_DATA.*FILE_READ_EA.*FILE_EXECUTE.*FILE_READ_ATTRIBUTES.*"; + "notregcmp[ext_attr_half1]" string => ".*\(ID\).*"; + + "acl_type[ext_attr_half2]" string => "ntfs"; + "acl_method[ext_attr_half2]" string => "append"; + "aces[ext_attr_half2]" slist => { "user:Administrator:-(drtxTwa),+(bBpcoD):allow" }; + "regcmp[ext_attr_half2]" string => ".*Administrator:.*READ_CONTROL.*WRITE_DAC.*WRITE_OWNER.*FILE_WRITE_EA.*FILE_DELETE_CHILD.*FILE_WRITE_ATTRIBUTES.*"; + "notregcmp[ext_attr_half2]" string => ".*(\(ID\)|[^_]DELETE|FILE_READ_DATA|FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_READ_EA|FILE_EXECUTE|FILE_READ_ATTRIBUTES).*"; + + "tests" slist => { "read", + "write", + "append_read", + "add_write", + "rm_write", + "no_default", + "default", + "inherit", + "inherit_nochange", + "empty", + "no_inherit", + "ext_attr_delete", + "ext_attr_half1", + "ext_attr_half2", + }; + + files: + "$(G.testdir)$(const.dirsep)file" + create => "true"; +} + +####################################################### + +bundle agent test +{ + methods: + linux|windows:: + "any" usebundle => test_and_check("$(init.tests)"); +} + +bundle agent test_and_check(testname) +{ + classes: + "use_dir" expression => isvariable("init.use_dir[$(testname)]"); + methods: + use_dir:: + "any" usebundle => test_exec("$(testname)", "$(G.testdir)"); + "any" usebundle => test_check("$(testname)", "$(G.testdir)"); + !use_dir:: + "any" usebundle => test_exec("$(testname)", "$(G.testdir)$(const.dirsep)file"); + "any" usebundle => test_check("$(testname)", "$(G.testdir)$(const.dirsep)file"); +} + +bundle agent test_exec(testname, file) +{ + classes: + "acl_type" expression => isvariable("init.acl_type[$(testname)]"); + "acl_default" expression => isvariable("init.acl_default[$(testname)]"); + acl_default:: + "specify_default_aces" expression => strcmp("init.acl_default[$(testname)]", "specify"); + files: + "$(file)" acl => acldef("$(testname)", "$(file)"); +} + +body acl acldef(testname, file) +{ + acl_method => "$(init.acl_method[$(testname)])"; + acl_inherit => "$(init.acl_inherit[$(testname)])"; + aces => { "@(init.aces[$(testname)])" }; + acl_type:: + acl_type => "$(init.acl_type[$(testname)])"; + !acl_type:: + acl_type => "generic"; + acl_default:: + acl_default => "$(init.acl_default[$(testname)])"; + specify_default_aces:: + specify_default_aces => { "@(init.specify_default_aces[$(testname)])" }; +} + +bundle agent test_check(testname, file) +{ + vars: + "output" string => execresult("$(init.cmd) $(file)", "noshell"); + + classes: + "regcmp_matched" expression => regcmp("$(init.regcmp[$(testname)])", "$(output)"); + "notregcmp_exists" expression => isvariable("init.notregcmp[$(testname)]"); + notregcmp_exists:: + "notregcmp_matched" expression => regcmp("$(init.notregcmp[$(testname)])", "$(output)"); + reports: + !regcmp_matched|notregcmp_matched:: + "Test \"$(testname)\" failed: Got \"$(output)\""; + "Should contain \"$(init.regcmp[$(testname)])\""; + (!regcmp_matched|notregcmp_matched)¬regcmp_exists:: + "Should NOT contain \"$(init.notregcmp[$(testname)])\""; +} diff --git a/tests/acceptance/10_files/12_acl/file_copy_acl.cf b/tests/acceptance/10_files/12_acl/file_copy_acl.cf new file mode 100644 index 0000000000..47df84c132 --- /dev/null +++ b/tests/acceptance/10_files/12_acl/file_copy_acl.cf @@ -0,0 +1,119 @@ +###################################################### +# +# ACL tests for file copying. +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + classes: + "cfengine_internal_getfacl" expression => fileexists("/var/cfengine/bin/getfacl"); + "system_getfacl" expression => fileexists("/usr/bin/getfacl"); + + vars: + linux.cfengine_internal_getfacl:: + "cmd" string => "/var/cfengine/bin/getfacl"; + linux.!cfengine_internal_getfacl.system_getfacl:: + "cmd" string => "/usr/bin/getfacl"; + windows:: + "cmd" string => "C:\windows\system32\cmd.exe /C cacls"; + + files: + "$(G.testdir)/source_plain" + create => "true"; + "$(G.testdir)/source_ext" + create => "true", + acl => testacl; +} + +body acl testacl +{ + !windows:: + acl_method => "append"; + aces => { "user:root:wx" }; + windows:: + acl_method => "overwrite"; + aces => { "user:Administrator:r" }; + acl_inherit => "false"; +} + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; + + files: + "$(G.testdir)/destination_plain" + copy_from => source_plain; + "$(G.testdir)/destination_ext_preserve" + copy_from => source_ext_preserve; + "$(G.testdir)/destination_ext_no_preserve" + copy_from => source_ext_no_preserve; +} + +body copy_from source_plain +{ + source => "$(G.testdir)/source_plain"; +} + +body copy_from source_ext_preserve +{ + source => "$(G.testdir)/source_ext"; + preserve => "yes"; +} + +body copy_from source_ext_no_preserve +{ + source => "$(G.testdir)/source_ext"; + preserve => "no"; +} + +bundle agent check +{ + vars: + "destination_ext_preserve_output" string => execresult("$(init.cmd) $(G.testdir)$(const.dirsep)destination_ext_preserve", "noshell"); + "destination_ext_no_preserve_output" string => execresult("$(init.cmd) $(G.testdir)$(const.dirsep)destination_ext_no_preserve", "noshell"); + + !windows:: + "destination_plain_output" string => execresult("/bin/ls -lZ $(G.testdir)$(const.dirsep)destination_plain", "noshell"); + "source_ext_output" string => execresult("$(init.cmd) $(G.testdir)$(const.dirsep)source_ext", "noshell"); + + classes: + !windows:: + "destination_plain_ok" not => regcmp(".*[-rwx]{10}\+.*", "$(destination_plain_output)"); + "source_ext_ok" expression => regcmp(".*user:root:-wx.*", "$(source_ext_output)"); + "destination_ext_preserve_ok" expression => regcmp(".*user:root:-wx.*", "$(destination_ext_preserve_output)"); + "destination_ext_no_preserve_ok" not => regcmp(".*user:root:-wx.*", "$(destination_ext_no_preserve_output)"); + "targets_ok" and => { "destination_plain_ok", "destination_ext_preserve_ok", "destination_ext_no_preserve_ok" }; + # The !source_ext_ok test is in case the underlying file system does not support ACLs. + "ok" or => { "!source_ext_ok", "targets_ok" }; + + windows:: + "destination_ext_preserve_ok" expression => regcmp(".*Administrator:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_READ_DATA.*FILE_READ_EA.*FILE_READ_ATTRIBUTES.*", "$(destination_ext_preserve_output)"); + "destination_ext_no_preserve_ok" not => regcmp(".*Administrator:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_READ_DATA.*FILE_READ_EA.*FILE_READ_ATTRIBUTES.*", "$(destination_ext_no_preserve_output)"); + "ok" and => { "destination_ext_preserve_ok", "destination_ext_no_preserve_ok" }; + + reports: + DEBUG.!windows:: + "Destination file permissions (should not contain a '+'): $(destination_plain_output)"; + "Destination file permissions (should contain \"user:root:-wx\"): $(destination_ext_preserve_output)"; + "Destination file permissions (should not contain \"user:root:-wx\"): $(destination_ext_no_preserve_output)"; + DEBUG.windows:: + "Destination file permissions (should contain read attributes): $(destination_ext_preserve_output)"; + "Destination file permissions (should not contain read attributes): $(destination_ext_no_preserve_output)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/12_acl/file_edit_acl.cf b/tests/acceptance/10_files/12_acl/file_edit_acl.cf new file mode 100644 index 0000000000..bf8d1d6a80 --- /dev/null +++ b/tests/acceptance/10_files/12_acl/file_edit_acl.cf @@ -0,0 +1,104 @@ +###################################################### +# +# ACL tests for file editing. +# +##################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + classes: + "cfengine_internal_getfacl" expression => fileexists("/var/cfengine/bin/getfacl"); + "system_getfacl" expression => fileexists("/usr/bin/getfacl"); + + vars: + linux.cfengine_internal_getfacl:: + "cmd" string => "/var/cfengine/bin/getfacl"; + linux.!cfengine_internal_getfacl.system_getfacl:: + "cmd" string => "/usr/bin/getfacl"; + windows:: + "cmd" string => "C:\windows\system32\cmd.exe /C cacls"; + + files: + "$(G.testdir)/file_plain" + create => "true"; + "$(G.testdir)/basetest_ext" + create => "true", + acl => testacl; + "$(G.testdir)/file_ext" + create => "true", + acl => testacl; +} + +body acl testacl +{ + !windows:: + acl_method => "append"; + aces => { "user:root:wx" }; + windows:: + acl_method => "overwrite"; + aces => { "user:Administrator:r" }; + acl_inherit => "false"; +} + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "windows"; + + files: + "$(G.testdir)/file_plain" + edit_line => file_edit; + files: + "$(G.testdir)/file_ext" + edit_line => file_edit; +} + +bundle edit_line file_edit +{ + insert_lines: + "New line!"; +} + +bundle agent check +{ + vars: + "file_ext_output" string => execresult("$(init.cmd) $(G.testdir)$(const.dirsep)file_ext", "noshell"); + + !windows:: + "file_plain_output" string => execresult("/bin/ls -lZ $(G.testdir)$(const.dirsep)file_plain", "noshell"); + "basetest_ext_output" string => execresult("$(init.cmd) $(G.testdir)$(const.dirsep)basetest_ext", "noshell"); + + classes: + !windows:: + "file_plain_ok" not => regcmp(".*[-rwx]{10}\+.*", "$(file_plain_output)"); + "basetest_ext_ok" expression => regcmp(".*user:root:-wx.*", "$(basetest_ext_output)"); + "file_ext_ok" expression => regcmp(".*user:root:-wx.*", "$(file_ext_output)"); + # The !basetest_ext_ok test is in case the underlying file system does not support ACLs. + "ext_ok" or => { "!basetest_ext_ok", "file_ext_ok" }; + "ok" and => { "file_plain_ok", "ext_ok" }; + + windows:: + "ok" expression => regcmp(".*Administrator:.*READ_CONTROL.*FILE_GENERIC_READ.*FILE_READ_DATA.*FILE_READ_EA.*FILE_READ_ATTRIBUTES.*", "$(file_ext_output)"); + + reports: + DEBUG.!windows:: + "Destination file permissions (should not contain a '+'): $(file_plain_output)"; + "Destination file permissions (should contain \"user:root:-wx\"): $(file_ext_output)"; + DEBUG.windows:: + "Destination file permissions (should contain read attributes): $(file_ext_output)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/13_file_dir/001.cf b/tests/acceptance/10_files/13_file_dir/001.cf new file mode 100644 index 0000000000..7a006a1c11 --- /dev/null +++ b/tests/acceptance/10_files/13_file_dir/001.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Acceptance test for issue #4070 +# Ensure that cfengine creates a +# file that has the same name as a pre-existing +# directory without consuming 100% cpu and crashing +# This test does what it says in 4070 - it creates +# a directory e.g. /tmp/backup/my_dir +# It then causes cfengine to create a file with an +# identical name i.e. /tmp/backup/my_dir +# The names above are simplified so as not to cause +# confusion. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent init +{ + + ####################################### + #Clean up test directory before testing + ####################################### + + files: + "$(G.testdir)/." + depth_search => recurse("inf"), + delete => tidy, + file_select => all, + classes => if_ok("dir_purged"); + + commands: + dir_purged:: + "$(G.mkdir) -p" + args => "$(G.testdir)/backup/_tmp_TEST_cfengine_test"; +} + +bundle edit_line test_insert(str) +{ + insert_lines: + "$(sys.date)"; +} + +####################################################### + +bundle agent test +{ + files: + agent_ran:: + "$(G.testdir)/test" + edit_line => test_insert("inserted text"), + classes => if_ok("file_edited"); + + commands: + "$(sys.cf_agent)" + args => "-f $(this.promise_filename).sub -K", + classes => if_ok("agent_ran"); + + file_edited:: + "$(sys.cf_agent)" + args => "-f $(this.promise_filename).sub -K -D SECOND_RUN", + classes => classes_generic("agent_ran_test"), + contain => test_timeout; + +} + +body contain test_timeout +{ + exec_timeout => "30"; +} + +####################################################### + +bundle agent check +{ + + classes: + "ok" expression => fileexists("$(G.testdir)/SECOND_RUN"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + +} diff --git a/tests/acceptance/10_files/13_file_dir/001.cf.sub b/tests/acceptance/10_files/13_file_dir/001.cf.sub new file mode 100644 index 0000000000..fb84e230e4 --- /dev/null +++ b/tests/acceptance/10_files/13_file_dir/001.cf.sub @@ -0,0 +1,32 @@ +body common control +{ + bundlesequence => { "create_test_file" }; + inputs => { "../../default.cf.sub" }; +} + +body agent control +{ + default_repository => "$(G.testdir)/backup"; +} + +bundle agent create_test_file +{ + files: + "$(G.testdir)/test" + create => "true", + edit_line => append_if_no_line("username:x:1:3:gcos:/home/dir:/bin/false"); + "$(G.testdir)/test" + changes => diff; + SECOND_RUN:: + "$(G.testdir)/SECOND_RUN" + create => "true"; +} + +body changes diff +{ + hash => "sha256"; + report_changes => "content"; + report_diffs => "true"; + update_hashes => "yes"; +} + diff --git a/tests/acceptance/10_files/13_file_dir/never_delete_nonempty_dir.cf b/tests/acceptance/10_files/13_file_dir/never_delete_nonempty_dir.cf new file mode 100644 index 0000000000..a626d698b4 --- /dev/null +++ b/tests/acceptance/10_files/13_file_dir/never_delete_nonempty_dir.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Test rmdirs => true never deletes non empty directories +# but returns promise kept/repaired, even if dir not empty. +# Ref: https://dev.cfengine.com/issues/6331 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dir" string => "$(G.testdir)/dir/."; + "subdir1" string => "$(dir)/subdir1/."; + "subdir2" string => "$(dir)/subdir2/."; + + files: + "$(dir)" create => "true"; + "$(subdir1)" create => "true"; + "$(subdir2)" create => "true"; + "$(subdir2)/blah" create => "true"; +} + +####################################################### + +bundle agent test +{ + # When trying to recursively delete "dir", it will be unable to delete + # "dir/subdir2" because it's not empty, but it should be skipped + # silently, and the whole promise will be success. + + files: + "$(init.dir)" + depth_search => recurse_include_basedir("inf"), + file_select => not_blah, + delete => tidy, + # success is being set despite skipping non-empty dir + classes => if_ok("success"); +} + +####################################################### + +bundle agent check +{ + classes: + "dir_exists" expression => isdir($(init.dir)); + "subdir1_deleted" not => isdir($(init.subdir1)); + "subdir2_exists" expression => isdir($(init.subdir2)); + + "ok" expression => "success.dir_exists.subdir1_deleted.subdir2_exists"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + + + +body depth_search recurse_include_basedir(d) +{ + depth => "$(d)"; + include_basedir => "true"; +} + +body file_select not_blah +{ + leaf_name => { "blah" }; + file_result => "!leaf_name"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/13_file_dir/never_delete_parent_dir.cf b/tests/acceptance/10_files/13_file_dir/never_delete_parent_dir.cf new file mode 100644 index 0000000000..3043af80ba --- /dev/null +++ b/tests/acceptance/10_files/13_file_dir/never_delete_parent_dir.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Test rmdirs => true never deletes parent directory, i.e. the promiser. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + # Create a bunch of subdirectories and files. All will be deleted later. + + vars: + "dir" string => "$(G.testdir)/dir"; + "subdir1" string => "$(dir)/subdir1/."; + "subdir2" string => "$(dir)/subdir2/."; + + files: + "$(dir)/." create => "true"; + "$(subdir1)" create => "true"; + "$(subdir2)" create => "true"; + "$(subdir2)/blah" create => "true"; +} + +####################################################### + +bundle agent test +{ + # Delete everything under "dir", but the promiser itself should never + # be removed. + + files: + "$(init.dir)" + depth_search => recurse_include_basedir("inf"), + file_select => all, + delete => tidy, + # success should be set after removing all subdirs + classes => if_ok("success"); +} + +####################################################### + +bundle agent check +{ + classes: + "dir_exists" expression => isdir($(init.dir)); + + "ok" expression => "success.dir_exists"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + + + +body depth_search recurse_include_basedir(d) +{ + depth => "$(d)"; + include_basedir => "true"; +} + + +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/FIM/monitoring_symlinks_does_not_constantly_report_change.cf b/tests/acceptance/10_files/FIM/monitoring_symlinks_does_not_constantly_report_change.cf new file mode 100644 index 0000000000..fe1b5d5cd0 --- /dev/null +++ b/tests/acceptance/10_files/FIM/monitoring_symlinks_does_not_constantly_report_change.cf @@ -0,0 +1,61 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + + files: + "$(G.testfile)" + link_from => ln_s( $(this.promise_filename) ); +} + +bundle agent test +{ + meta: + "description" + string => "Test that monitoring a symlink does not constantly report changes"; + + "test_skip_unsupported" + string => "windows", + comment => "Windows does not support symlinks, so it makes no sense to + test there"; + + "test_soft_fail" + string => "!windows", + meta => { "redmine7692" }; +} + + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub"; + + commands: + "$(command)" + handle => "first_agent_run", + comment => "We must run the agent at least once so that the file can be + added to the FIM database. The second run we scan for output.", + classes => scoped_classes_generic("bundle", "first_agent_run"); + + methods: + first_agent_run_reached:: + # We fail if we see Permissions, inode or mtime changes + "Check second run" + usebundle => dcs_passif_output(".*fim_kept.*", ".*(Permissions for|inode for|Last modified time for).*$(G.testfile).*changed.*", $(command), $(this.promise_filename)); +} + +body link_from ln_s(x) +# @brief Create a symbolink link to `x` +# The link is created even if the source of the link does not exist. +# @param x The source of the link +{ + link_type => "symlink"; + source => "$(x)"; + when_no_source => "force"; +} + diff --git a/tests/acceptance/10_files/FIM/monitoring_symlinks_does_not_constantly_report_change.cf.sub b/tests/acceptance/10_files/FIM/monitoring_symlinks_does_not_constantly_report_change.cf.sub new file mode 100644 index 0000000000..9ca0b5f791 --- /dev/null +++ b/tests/acceptance/10_files/FIM/monitoring_symlinks_does_not_constantly_report_change.cf.sub @@ -0,0 +1,31 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + version => "1.0"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + changes => detect_all_change; + + vars: + "classes" + slist => classesmatching("fim_.*"); + + reports: + "$(classes)"; +} + +body changes detect_all_change +# @brief Detect all file changes using the best hash method +# +# This is fierce, and will cost disk cycles +# +{ + hash => "best"; + report_changes => "all"; + update_hashes => "yes"; +} + diff --git a/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files.cf b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files.cf new file mode 100644 index 0000000000..6600f3c9db --- /dev/null +++ b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files.cf @@ -0,0 +1,54 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body agent control +{ + files_single_copy => { @(def.control_agent_files_single_copy) }; +} +# NOTE that def.control_agent_files_single_copy DOES NOT EXIST +bundle agent init +{ + vars: + "copy_files" slist => { "one", "two" }; + + files: + "$(G.testdir)/$(copy_files)" + create => "true", + edit_line => insert_lines("$(copy_files)"); +} + +bundle agent test +{ + meta: + "description" string => "Test that files_single_copy does not prevent file patterns that do not match from being copied multiple times."; + + "test_soft_fail" + string => "any", + meta => { "CFE-2459" }; + + files: + + # Here we iterate over each copy_file to promise the content + # of the testfile. We expect that with files_single_copy in + # effect only the first file should be copied. + + "$(G.testfile)" + copy_from => local_dcp( "$(G.testdir)/$(init.copy_files)" ); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(G.testfile), "$(G.testdir)/two", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_1.cf b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_1.cf new file mode 100644 index 0000000000..638da9481a --- /dev/null +++ b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_1.cf @@ -0,0 +1,59 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body agent control +{ + # (?!) should always fail to match. It is the zero-width negative + # look-ahead. If what is in the parentheses matches then the whole + # match fails. Given that it has nothing in it, it will fail the + # match for anything (including nothing). + + files_single_copy => { "(?!)" }; +} + +bundle agent init +{ + vars: + "copy_files" slist => { "one", "two" }; + + files: + "$(G.testdir)/$(copy_files)" + create => "true", + edit_line => insert_lines("$(copy_files)"); +} + +bundle agent test +{ + meta: + "description" string => "Test that files_single_copy does not prevent file patterns that do not match from being copied multiple times."; + + "test_soft_fail" + string => "any", + meta => { "CFE-2459" }; + + files: + + # Here we iterate over each copy_file to promise the content + # of the testfile. We expect that with files_single_copy in + # effect only the first file should be copied. + + "$(G.testfile)" + copy_from => local_dcp( "$(G.testdir)/$(init.copy_files)" ); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(G.testfile), "$(G.testdir)/two", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_2.cf b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_2.cf new file mode 100644 index 0000000000..a23ce078db --- /dev/null +++ b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_2.cf @@ -0,0 +1,56 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body agent control +{ + # $^ should match an empty string which should never be a way to + # reference a file so this should never match any file. + files_single_copy => { "$^" }; +} + +bundle agent init +{ + vars: + "copy_files" slist => { "one", "two" }; + + files: + "$(G.testdir)/$(copy_files)" + create => "true", + edit_line => insert_lines("$(copy_files)"); +} + +bundle agent test +{ + meta: + "description" string => "Test that files_single_copy does not prevent file patterns that do not match from being copied multiple times."; + + "test_soft_fail" + string => "any", + meta => { "CFE-2459" }; + + files: + + # Here we iterate over each copy_file to promise the content + # of the testfile. We expect that with files_single_copy in + # effect only the first file should be copied. + + "$(G.testfile)" + copy_from => local_dcp( "$(G.testdir)/$(init.copy_files)" ); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(G.testfile), "$(G.testdir)/two", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_3.cf b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_3.cf new file mode 100644 index 0000000000..e49510713e --- /dev/null +++ b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_3.cf @@ -0,0 +1,56 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body agent control +{ + # Here we test a sane filename pattern against a file we definitely + # aren't copying. + files_single_copy => { "/var/cfengine/state/WORKAROUND-CFE-2459" }; +} + +bundle agent init +{ + vars: + "copy_files" slist => { "one", "two" }; + + files: + "$(G.testdir)/$(copy_files)" + create => "true", + edit_line => insert_lines("$(copy_files)"); +} + +bundle agent test +{ + meta: + "description" string => "Test that files_single_copy does not prevent file patterns that do not match from being copied multiple times."; + + "test_soft_fail" + string => "any", + meta => { "CFE-2459" }; + + files: + + # Here we iterate over each copy_file to promise the content + # of the testfile. We expect that with files_single_copy in + # effect only the first file should be copied. + + "$(G.testfile)" + copy_from => local_dcp( "$(G.testdir)/$(init.copy_files)" ); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(G.testfile), "$(G.testdir)/two", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_4.cf b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_4.cf new file mode 100644 index 0000000000..6f64d7c6a4 --- /dev/null +++ b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_4.cf @@ -0,0 +1,57 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body agent control +{ + # Here we test a sane filename pattern against a file we definitely + # aren't copying. But this time we are using a variable witin the + # pattern. + files_single_copy => { "$(sys.workdir)/state/WORKAROUND-CFE-2459" }; +} + +bundle agent init +{ + vars: + "copy_files" slist => { "one", "two" }; + + files: + "$(G.testdir)/$(copy_files)" + create => "true", + edit_line => insert_lines("$(copy_files)"); +} + +bundle agent test +{ + meta: + "description" string => "Test that files_single_copy does not prevent file patterns that do not match from being copied multiple times."; + + "test_soft_fail" + string => "any", + meta => { "CFE-2459" }; + + files: + + # Here we iterate over each copy_file to promise the content + # of the testfile. We expect that with files_single_copy in + # effect only the first file should be copied. + + "$(G.testfile)" + copy_from => local_dcp( "$(G.testdir)/$(init.copy_files)" ); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(G.testfile), "$(G.testdir)/two", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_5.cf b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_5.cf new file mode 100644 index 0000000000..607032403a --- /dev/null +++ b/tests/acceptance/10_files/agent_control_files_single_copy_does_not_apply_to_nonmatching_files_5.cf @@ -0,0 +1,55 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +# Body agent control is not defined, so files_single_copy should not match any files. +#body agent control +#{ +# files_single_copy => { @(def.control_agent_files_single_copy) }; +#} + +bundle agent init +{ + vars: + "copy_files" slist => { "one", "two" }; + + files: + "$(G.testdir)/$(copy_files)" + create => "true", + edit_line => insert_lines("$(copy_files)"); +} + +bundle agent test +{ + meta: + "description" string => "Test that files_single_copy does not prevent file patterns that do not match from being copied multiple times."; + + "test_skip_needs_work" + string => "windows", + meta => { "CFE-2459" }; + + files: + + # Here we iterate over each copy_file to promise the content + # of the testfile. We expect that with files_single_copy in + # effect only the first file should be copied. + + "$(G.testfile)" + copy_from => local_dcp( "$(G.testdir)/$(init.copy_files)" ); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(G.testfile), "$(G.testdir)/two", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/agent_control_files_single_copy_prevents_subsequent_copy_on_exact_match.cf b/tests/acceptance/10_files/agent_control_files_single_copy_prevents_subsequent_copy_on_exact_match.cf new file mode 100644 index 0000000000..ec3cb1cd45 --- /dev/null +++ b/tests/acceptance/10_files/agent_control_files_single_copy_prevents_subsequent_copy_on_exact_match.cf @@ -0,0 +1,55 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body agent control +{ + # Here we explicitly target the testfile. + files_single_copy => { $(G.testfile) }; +} + +bundle agent init +{ + vars: + "copy_files" slist => { "one", "two" }; + + files: + "$(G.testdir)/$(copy_files)" + create => "true", + edit_line => insert_lines("$(copy_files)"); +} + +bundle agent test +{ + meta: + "description" string => "Test that files_single_copy prevents subsequnt copy of a file when there is an exact match."; + + "test_skip_needs_work" + string => "windows", + meta => { "CFE-2459" }; + + files: + + # Here we iterate over each copy_file to promise the content + # of the testfile. We expect that with files_single_copy in + # effect only the first file should be copied. + + "$(G.testfile)" + copy_from => local_dcp( "$(G.testdir)/$(init.copy_files)" ); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(G.testfile), "$(G.testdir)/one", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/agent_control_files_single_copy_prevents_subsequent_copy_on_regular_expression_match.cf b/tests/acceptance/10_files/agent_control_files_single_copy_prevents_subsequent_copy_on_regular_expression_match.cf new file mode 100644 index 0000000000..41b4fa12ad --- /dev/null +++ b/tests/acceptance/10_files/agent_control_files_single_copy_prevents_subsequent_copy_on_regular_expression_match.cf @@ -0,0 +1,57 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body agent control +{ + # Here we cast a wide net (any file) to make sure regular expression + # matching works. + + files_single_copy => { ".*" }; +} + +bundle agent init +{ + vars: + "copy_files" slist => { "one", "two" }; + + files: + "$(G.testdir)/$(copy_files)" + create => "true", + edit_line => insert_lines("$(copy_files)"); +} + +bundle agent test +{ + meta: + "description" string => "Test that files_single_copy prevents subsequnt copy of a file when there is a matching regular expression."; + + "test_skip_needs_work" + string => "windows", + meta => { "CFE-2459" }; + + files: + + # Here we iterate over each copy_file to promise the content + # of the testfile. We expect that with files_single_copy in + # effect only the first file should be copied. + + "$(G.testfile)" + copy_from => local_dcp( "$(G.testdir)/$(init.copy_files)" ); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(G.testfile), "$(G.testdir)/one", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/can_set_sticky_bits_without_root_owning_directory.cf b/tests/acceptance/10_files/can_set_sticky_bits_without_root_owning_directory.cf new file mode 100644 index 0000000000..7ca867896b --- /dev/null +++ b/tests/acceptance/10_files/can_set_sticky_bits_without_root_owning_directory.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Acceptance test for Zendesk issue #2745 +# Ensure that cfengine creates a +# directory not owned by root and the setgid +# is set on the directory +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent test +{ + meta: + + "description" + string => "Test that the agent can set the sticky + bit on a directory that root does not + own."; + + "test_skip_unsupported" + string => "windows"; + "test_skip_needs_work" + string => "!has_stat"; + + vars: + !windows:: + #"test_dir" string => "/tmp/test_setgid"; + "mode" int => "2700"; + "owner" string => "daemon"; + "group" string => "daemon"; + + files: + !windows:: + "$(G.testdir)/test_sgid/." + create => "true", + perms => mog( $(mode), $(owner), $(group) ), + classes => scoped_classes_generic("namespace", "test_sgid_dir"); +} + +####################################################### + +bundle agent check +{ + + vars: + test_sgid_dir_reached:: + "expect_permoct" string => "$(test.mode)"; + "result_permoct" string => execresult('$(G.stat) -c %a $(G.testdir)/test_sgid', "useshell"); + + methods: + "Compare Results" + usebundle => dcs_check_strcmp( "$(expect_permoct)", "$(result_permoct)", $(this.promise_filename), "false"); + + reports: + DEBUG:: + "expected: permoct = '$(check.expect_permoct)'"; + "got: permoct = '$(check.result_permoct)'"; +} diff --git a/tests/acceptance/10_files/copy_from_preserve_false.cf b/tests/acceptance/10_files/copy_from_preserve_false.cf new file mode 100644 index 0000000000..70a7375919 --- /dev/null +++ b/tests/acceptance/10_files/copy_from_preserve_false.cf @@ -0,0 +1,84 @@ +body file control +{ + inputs => { "../default.cf.sub" }; +} + +bundle agent main +{ + methods: + "Test" usebundle => default("$(this.promise_filename)") ; +} + +bundle agent init +{ + files: + + # We set the permissions of our source file so that we can be sure they + # are not the permissions of the target file at the end. + + "$(this.promise_filename).src" + perms => m( "$(check.expect[src])" ); + + # We make sure the target file exists and has permissions different from + # the source file. + + "$(this.promise_filename).target" + delete => tidy; + + "$(this.promise_filename).target" + create => "true", + perms => m( "$(check.expect[target])" ); +} + +bundle agent test +{ + meta: + + "description" + string => "Test that preserve false in body copy_from doesn't modify + permissions of the promised file"; + + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + files: + + # We promise that the target is a copy of the source file Since preserve + # is set to false, we expect the original permissions on the target file + # to remain unchanged. + + "$(this.promise_filename).target" + copy_from => local_cp_digest_preserve_false( "$(this.promise_filename).src" ); + +} +bundle agent check +{ + vars: + "expect[src]" string => "500"; + "expect[target]" string => "600"; + + "observe[src]" string => filestat( "$(this.promise_filename).src", permoct); + "observe[target]" string => filestat( "$(this.promise_filename).target", permoct); + + methods: + + "Test Result" + usebundle => dcs_check_strcmp( + "$(expect[target])", + "$(observe[target])", + $(this.promise_filename), + "false"); + + reports: + DEBUG|EXTRA:: + ".src Expect $(expect[src]), observe $(observe[target])"; + ".target Expect $(expect[target]), observe $(observe[target])"; + +} +body copy_from local_cp_digest_preserve_false (from) +{ + source => "$(from)"; + preserve => "false"; + compare => "digest"; + copy_backup => "false"; +} diff --git a/tests/acceptance/10_files/copy_from_preserve_false.cf.src b/tests/acceptance/10_files/copy_from_preserve_false.cf.src new file mode 100755 index 0000000000..91e04af74e --- /dev/null +++ b/tests/acceptance/10_files/copy_from_preserve_false.cf.src @@ -0,0 +1,2 @@ +This file is used as part of a test. Its hashed and compared against another +file. diff --git a/tests/acceptance/10_files/copy_from_this_promiser.cf b/tests/acceptance/10_files/copy_from_this_promiser.cf new file mode 100755 index 0000000000..877c147ae1 --- /dev/null +++ b/tests/acceptance/10_files/copy_from_this_promiser.cf @@ -0,0 +1,92 @@ +#!/var/cfengine/bin/cf-agent -f- +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle agent init { + vars: + "ROOT" string => "/tmp/CFE-2489"; + "SOURCE" string => "$(ROOT)/SOURCE"; + "use_bundles" + slist => { "bundle_uses_copy_from_without_param", + "bundle_uses_copy_from_with_param" }; + + files: + "$(init.ROOT)/$(use_bundles)/." create => "true"; + + # Setup source files to copy_from + "$(init.ROOT)/SOURCE/$(init.ROOT)/$(use_bundles)/file.txt" + create => "true"; +} +bundle agent test { + meta: + "description" -> { "CFE-2489" } + string => "Test that this.promiser holds the correct value in a copy_from body that uses parameters"; + + "test_soft_fail" + string => "any", + meta => { "CFE-2489" }; + + methods: + "XXX" usebundle => $(init.use_bundles); +} + +bundle agent bundle_uses_copy_from_without_param { + + files: + + # As expected, when a copy_from promise uses a non-parameterized body the + # this.promiser context is as expected (points to the promised file). + + "$(init.ROOT)/$(this.bundle)/file.txt" + copy_from => my_copy_without_param(); + +} +body copy_from my_copy_without_param() { + source => "$(init.ROOT)/SOURCE/$(this.promiser)"; + compare => "digest"; +} + +bundle agent bundle_uses_copy_from_with_param { + + files: + + # Oddly, when a copy_from promise uses a parameterized body, the + # this.promiser context does not contain the promised file, instead, it + # seems to contain the parents promiser. So, in this case, it wrongly gets + # XXX from the 'test' bundle. + + "$(init.ROOT)/$(this.bundle)/file.txt" + copy_from => my_copy_with_param( "use_of_paramater_should_not_change_this_promiser" ); + +} +body copy_from my_copy_with_param(root) { + source => "$(init.ROOT)/SOURCE/$(this.promiser)"; + compare => "digest"; +} + +bundle agent check +{ + + classes: + + "pass" + expression => filesexist( '[ "$(init.ROOT)/bundle_uses_copy_from_with_param/file.txt", + "$(init.ROOT)/bundle_uses_copy_from_without_param/file.txt" ]' ); + reports: + + "$(this.promise_filename) $(with)" + with => ifelse( "pass", "Pass", "FAIL"); +} + +bundle agent __main__ +{ + methods: + "init"; + "test"; + "check"; +} diff --git a/tests/acceptance/10_files/copy_local_file.cf b/tests/acceptance/10_files/copy_local_file.cf new file mode 100644 index 0000000000..b24def6767 --- /dev/null +++ b/tests/acceptance/10_files/copy_local_file.cf @@ -0,0 +1,48 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that copying a file locally works"; + "story_id" string => "5576"; + "covers" string => "operational_repaired"; +} + +# Ref: https://dev.cfengine.com/issues/5576 + +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "target" string => "$(G.testdir)/target"; + + files: + "$(target)" + create => "true", + edit_line => insert_lines("Initial Content"); +} + +bundle agent test +{ + files: + # Ensure the target file is exactly the same as this file + "$(init.target)" + copy_from => local_dcp($(this.promise_filename)); +} + +####################################################### + +bundle agent check +{ + + methods: + "" usebundle => dcs_check_diff($(init.target), $(this.promise_filename), $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/copy_local_file3.cf b/tests/acceptance/10_files/copy_local_file3.cf new file mode 100644 index 0000000000..bff436667c --- /dev/null +++ b/tests/acceptance/10_files/copy_local_file3.cf @@ -0,0 +1,29 @@ +####################################################### +# +# Test local_cp +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + files: + "$(G.testfile).expected" + copy_from => local_cp("$(this.promise_filename).expected"); +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(this.promise_filename).expected", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/10_files/copy_local_file3.cf.expected b/tests/acceptance/10_files/copy_local_file3.cf.expected new file mode 100644 index 0000000000..4cb0ba849d --- /dev/null +++ b/tests/acceptance/10_files/copy_local_file3.cf.expected @@ -0,0 +1 @@ +EXPECT \ No newline at end of file diff --git a/tests/acceptance/10_files/file_select_attrs_consistent_behavior/main.cf b/tests/acceptance/10_files/file_select_attrs_consistent_behavior/main.cf new file mode 100644 index 0000000000..9b511b638a --- /dev/null +++ b/tests/acceptance/10_files/file_select_attrs_consistent_behavior/main.cf @@ -0,0 +1,215 @@ +####################################################### +# +# Redmine#6584: file_select mtime consistency +# +####################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + "datafile" string => "$(this.promise_dirname)/rules.json"; +} +bundle agent init +{ + vars: + "files" + slist => { "file1.log", "file2.log", "file3.log" }, + comment => "These are the files we will try to delete"; + + "tests" + slist => { + "test_delete_with_spec_from_data", + "test_delete_with_spec_from_policy", + }, + comment => "We have two tests combined here because they should be kept in sync."; + + "data" data => readjson($(g.datafile), 1M); + "idx" slist => getindices("data"); + + files: + "$(G.testdir)/$(tests)/$(data[$(idx)][location])/." + create => "true", + comment => "Make sure our directories exist so that files can be created"; + + "$(G.testdir)/$(tests)/$(data[$(idx)][location])/$(files)" + create => "true", + comment => "Make sure the files exist so they can be deleted"; + + commands: + "$(G.touch)" + args => "-a -m -t 200901181205.09 $(G.testdir)/$(tests)/$(data[$(idx)][location])/$(files)", + comment => "Make sure the files are 'old' so they will be selected for deletion"; +} + +bundle agent test +{ + meta: + "test_soft_fail" + string => "any", + meta => { "redmine#6584", "zendesk#1466" }; + + methods: + "Test delete with spec from policy" + usebundle => test_delete_with_spec_from_policy; + + "Test delete with spec from data" + usebundle => test_delete_with_spec_from_data($(g.datafile)); +} + +bundle agent test_delete_with_spec_from_policy { + files: + # This promise should be an effictive mirror of the promise with handle 'test_delete_with_spec_from_data' + "$(G.testdir)/$(this.bundle)/tmp/logs/tidy" + delete => tidy, + file_select => + select_file_with_extension_retention_mtime(".*.log", # == 'filter' key in JSON + "plain", # == 'type' key in JSON + "2", # == 'retention' key in JSON + "$(G.testdir)/$(this.bundle)/tmp/logs/tidy"), # == 'location' key in JSON + depth_search => recurse("2"), # == 'depth' key in JSON + action => if_elapsed_inform("10"), # == 'frequency' key in JSON + if => "any"; # == 'context' key in JSON + + reports: + DEBUG:: + "'$(G.testdir)/$(this.bundle)/tmp/logs/tidy'"; +} + +bundle agent test_delete_with_spec_from_data(ref) +{ + vars: + "input_cleanup" + comment => "Read json file into container", + data => readjson("${ref}", 1024); + + "i" + comment => "Get index of json for iteration", + slist => getindices("input_cleanup"); + + methods: + "do_cleanup" usebundle => do_cleanup("${input_cleanup[${i}][location]}", + "${input_cleanup[${i}][context]}", + "${input_cleanup[${i}][action]}", + "${input_cleanup[${i}][frequency]}", + "${input_cleanup[${i}][type]}", + "${input_cleanup[${i}][filter]}", + "${input_cleanup[${i}][retention]}", + "${input_cleanup[${i}][depth]}", + "${input_cleanup[${i}][size]}", + "${input_cleanup[${i}][reason]}"); + + reports: + DEBUG:: + " # Spec from json data file '$(ref)'# +location => ${input_cleanup[${i}][location]} +context => ${input_cleanup[${i}][context]} +action => ${input_cleanup[${i}][action]} +frequency => ${input_cleanup[${i}][frequency]} +type => ${input_cleanup[${i}][type]} +filter => ${input_cleanup[${i}][filter]} +retention => ${input_cleanup[${i}][retention]} +depth => ${input_cleanup[${i}][depth]} +size => ${input_cleanup[${i}][size]} +reason => ${input_cleanup[${i}][reason]} +"; +} + + + +bundle agent do_cleanup(location, + context, + action, + frequency, + type, + filter, + retention, + depth, + size, + reason) { + classes: + #"action_tidy" expression => strcmp("$(action)", "TIDY"); + #"action_rotate" expression => strcmp("$(action)", "ROTATE"); + #"requested_size" expression => regcmp("[0-9]*", "$(size)"); + + files: + #action_tidy:: + "$(G.testdir)/test_delete_with_spec_from_data/$(location)" + handle => "test_delete_with_spec_from_data", + delete => tidy, + file_select => select_file_with_extension_retention_mtime("$(filter)", + "$(type)", + "$(retention)", + "$(G.testdir)/test_delete_with_spec_from_data/$(location)"), + depth_search => recurse("$(depth)"), + action => if_elapsed_inform("$(frequency)"), + if => "$(context)"; + + reports: + DEBUG:: + "Filter=$(filter) Type=$(type) Retension=$(retention) Location=$(G.testdir)/test_delete_with_spec_from_data/$(location).* Depth=$(depth) Freq=$(frequency) Context=$(context)"; +} + +bundle agent check +{ + vars: + "files[test_delete_with_spec_from_data]" slist => lsdir("$(G.testdir)/test_delete_with_spec_from_data/tmp/logs/tidy", ".*.log", "false"); + "files[test_delete_with_spec_from_policy]" slist => lsdir("$(G.testdir)/test_delete_with_spec_from_policy/tmp/logs/tidy", ".*.log", "false"); + + "count_files[test_delete_with_spec_from_data]" + int => length("files[test_delete_with_spec_from_data]") + ; + "count_files[test_delete_with_spec_from_policy]" + int => length("files[test_delete_with_spec_from_policy]"); + + "files_in_tidy_dir_count_$(init.tests)" int => length("files_in_tidy_dir[$(init.tests)]"); + "OK_tests" slist => maplist("OK_$(this)", @(init.tests)); + + classes: + "OK_$(init.tests)" + not => isgreaterthan("$(count_files[$(init.tests)])", 0), + comment => "Define a class for each test that has an empty directory as desired"; + + "OK" + and => { @(OK_tests) }, + comment => "Pass if all tests are OK"; + + reports: + DEBUG:: + "files[test_delete_with_spec_from_data] = $(files[test_delete_with_spec_from_data])"; + "files[test_delete_with_spec_from_policy] = $(files[test_delete_with_spec_from_policy])"; + "count_files[test_delete_with_spec_from_data] = $(count_files[test_delete_with_spec_from_data])"; + "count_files[test_delete_with_spec_from_policy] = $(count_files[test_delete_with_spec_from_policy])"; + + "$(init.tests) pass" + if => "OK_$(init.tests)"; + + "$(init.tests) fail" + if => "!OK_$(init.tests)"; + + OK:: + "$(this.promise_filename) Pass"; + + !OK:: + "$(this.promise_filename) FAIL"; +} +body file_select select_file_with_extension_retention_mtime(filter, type, days_old, location) { + mtime => irange(0, ago(0, 0, $(days_old), 0, 0, 0)); + path_name => { "$(location)/.*" }; + leaf_name => { "$(filter)" }; + file_types => { "$(type)" }; +# The intention is to select files older than x days (mtime between now and x days ago should be retained) I believe the second file_result is correct. +# file_result => "mtime.path_name.leaf_name.file_types"; # Oddly this works for test_delete_with_spec_from_policy, but not with test_delete_with_spec_from_data + file_result => "!mtime.path_name.leaf_name.file_types"; # Oddly this works for test_delete_with_spec_from_data, but not with test_delete_with_spec_from_policy +} + +body action if_elapsed_inform(x) { + report_level => "inform"; + ifelapsed => "$(x)"; + expireafter => "$(x)"; +} diff --git a/tests/acceptance/10_files/file_select_attrs_consistent_behavior/rules.json b/tests/acceptance/10_files/file_select_attrs_consistent_behavior/rules.json new file mode 100644 index 0000000000..352beedf05 --- /dev/null +++ b/tests/acceptance/10_files/file_select_attrs_consistent_behavior/rules.json @@ -0,0 +1,14 @@ +[ +{ +"location": "/tmp/logs/tidy" +"context": "any", +"action": "TIDY", +"frequency": "10", +"type": "plain", +"filter": ".*.log", +"retention": "2", +"depth": "2", +"size": "ANY", +"reason": "Tidy all logs" +} +] diff --git a/tests/acceptance/10_files/files_with_spaces.cf b/tests/acceptance/10_files/files_with_spaces.cf new file mode 100644 index 0000000000..01124151da --- /dev/null +++ b/tests/acceptance/10_files/files_with_spaces.cf @@ -0,0 +1,67 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + meta: + "description" string => "ensure clean starting point before test"; + + files: + "$(G.testdir)/$(test.file[$(test.idx)])" + delete => tidy; +} + +bundle agent test +{ + meta: + "description" + string => "Test that file paths with spaces can be created without special treatment"; + + vars: + "file[1]" string => "file with space.txt"; + "file[2]" string => "path with/a space.txt"; + + "idx" slist => getindices( file ); + + files: + "$(G.testdir)/$(file[$(idx)])" + classes => scoped_classes_generic("namespace", "file_$(idx)"), + create => "true"; +} + +bundle agent check +{ + vars: + "expected_classes" slist => maplist( "file_$(this)_repaired", "test.idx" ); +# NOT WORKING -- variable not expanded "/tmp/TESTDIR.cfengine/$(test#file[2])" + "expected_files" slist => maplist( "$(G.testdir)/$(test.file[$(this)])", "test.idx"); +# NOT WORKING -- Only the first file is actually tested +# "expected_files" slist => { "$(G.testdir)/$(test.file[@(test.idx)])" }; +# NOT WORKING -- only the second file is created +# "expected_files" slist => { "$(G.testdir)/$(test.file[$(test.idx)])" }; + + "classes" slist => classesmatching("file_.*"); + classes: + "have_expected_classes" and => { @(expected_classes) }; + "have_expected_files" expression => filesexist( @(expected_files) ); + + methods: + "Report Test Result" + usebundle => dcs_passif_expected("have_expected_classes,have_expected_files", "FAIL", $(this.promise_filename) ), + inherit => "true"; + + reports: + DEBUG:: + "Expecting class: '$(expected_classes)'"; + "Expecting file: '$(expected_files)'"; + + "Have expected classes" + if => "have_expected_classes"; + + "Have expected files" + if => "have_expected_files"; +} diff --git a/tests/acceptance/10_files/locally_copied_sparse_file_preserves_sparseness.cf b/tests/acceptance/10_files/locally_copied_sparse_file_preserves_sparseness.cf new file mode 100644 index 0000000000..2999a77986 --- /dev/null +++ b/tests/acceptance/10_files/locally_copied_sparse_file_preserves_sparseness.cf @@ -0,0 +1,88 @@ +# Test that locally copied files keep their sparsness +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(test.file[sparse])" + delete => tidy, + handle => "init_tidy_sparse"; + + "$(test.file[copy])" + delete => tidy, + handle => "init_tidy_copy"; + + + commands: + "/bin/dd" + args => "if=/dev/zero of=$(test.file[sparse]) seek=1024 bs=1024 count=0", + if => not( fileexists( "$(test.file[sparse])" )), + depends_on => { "init_tidy_sparse", "init_tidy_copy" }; +} + +bundle agent test +{ + meta: + "description" string => "ENT-2769: Test that when a sparse file is copied locally the sparseness is preserved"; + + # Various exotic filesystems don't support sparseness, see the unit + # test "files_copy_test.c" for details. + "test_skip_unsupported" string => "!linux"; + + vars: + "file[sparse]" string => "$(G.testdir)/sparse"; + "file[copy]" string => "$(G.testdir)/copy"; + + files: + "$(file[copy])" + copy_from => local_dcp( "$(file[sparse])" ); +} + +bundle agent check +{ + vars: + "size[sparse]" + string => filestat( "$(test.file[sparse])", size ), + if => fileexists( "$(test.file[sparse])" ); + + "size[copy]" + string => filestat( "$(test.file[copy])", size ), + if => fileexists( "$(test.file[copy])" ); + + "du[sparse]" + string => execresult( "expr `du -k $(test.file[sparse]) | cut -f1` '*' 1024", useshell), + if => fileexists( "$(test.file[sparse])" ); + + "du[copy]" + string => execresult( "expr `du -k $(test.file[copy]) | cut -f1` '*' 1024", useshell), + if => fileexists( "$(test.file[copy])" ); + + classes: + "size_match" expression => strcmp( "$(size[sparse])", "$(size[copy])" ); + "du_less" expression => islessthan( "$(du[copy])", "$(size[copy])" ); + + "OK" and => { "size_match", "du_less" }; + + reports: + OK:: + "$(this.promise_filename) Pass"; + + !OK:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "Sparse filestat.size: $(size[sparse])"; + "Sparse du -k: '$(du[sparse])'"; + "Copy filestat.size: $(size[copy])"; + "Copy du -k: '$(du[copy])'"; + DEBUG.size_match:: + "Sparse file and copy match in apparent size."; + DEBUG.du_less:: + "Copy of sparse file is using more bytes on disk than the real size of the file, in bytes, so it's probably non-sparse!"; + +} diff --git a/tests/acceptance/10_files/move_obstruction_respects_warn_only.cf b/tests/acceptance/10_files/move_obstruction_respects_warn_only.cf new file mode 100644 index 0000000000..6c44b1a413 --- /dev/null +++ b/tests/acceptance/10_files/move_obstruction_respects_warn_only.cf @@ -0,0 +1,51 @@ +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "target" string => "$(G.testfile)"; + + files: + "$(target)/." + create => "true"; +} + +body link_from ln_s(x) +{ + link_type => "symlink"; + source => "$(x)"; + when_no_source => "force"; +} + + +bundle agent test +{ + files: + "$(init.target)" + move_obstructions => "true", + link_from => ln_s("/tmp"), + action => warn_only; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => isdir("$(init.target)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf b/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf new file mode 100644 index 0000000000..37e7e2e183 --- /dev/null +++ b/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf @@ -0,0 +1,56 @@ +bundle common test_meta +{ + vars: + "description" -> { "Mustache Specification" } + string => "Missed variables should be empty strings by + default in rendered mustache templates as described in the + Mustache Manual.", + comment => "https://mustache.github.io/mustache.5.html"; +} + +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + "template_data" data => '{ "key1": "value1", "key3": "value3" }'; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent test +{ + files: + "$(init.template_target)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + template_data => @(init.template_data); +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -KIf $(this.promise_filename).sub -DAUTO"; + "actual" string => "$(init.template_target)"; + "expected" string => "$(this.promise_filename).mustache.expected"; + "test" string => "$(this.promise_filename)"; + + methods: + "check" usebundle => dcs_check_diff( $(actual), $(expected), $(test) ); + + reports: + DEBUG:: + "Comparing render of '$(actual)' with expected output '$(expected)'"; +} diff --git a/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf.mustache b/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf.mustache new file mode 100644 index 0000000000..44f5218722 --- /dev/null +++ b/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf.mustache @@ -0,0 +1,3 @@ +key1 = {{{key1}}} +key2 = {{{key2}}} +key3 = {{{key3}}} diff --git a/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf.mustache.expected b/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf.mustache.expected new file mode 100644 index 0000000000..10ae785cae --- /dev/null +++ b/tests/acceptance/10_files/mustache_missing_variable_is_empty_string_by_default.cf.mustache.expected @@ -0,0 +1,3 @@ +key1 = value1 +key2 = +key3 = value3 diff --git a/tests/acceptance/10_files/mustache_propagates_failure.cf b/tests/acceptance/10_files/mustache_propagates_failure.cf new file mode 100644 index 0000000000..c892da6677 --- /dev/null +++ b/tests/acceptance/10_files/mustache_propagates_failure.cf @@ -0,0 +1,52 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that Mustache templates propagate classes to calling bundle"; +} + +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +bundle agent render_mustache +{ + files: + "$(init.template_target)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache"; +} + +bundle agent test +{ + methods: + "any" usebundle => render_mustache, + classes => if_repaired("mustache_propagated"); +} + +####################################################### + +bundle agent check +{ + reports: + mustache_propagated:: + "$(this.promise_filename) Pass"; + !mustache_propagated:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/mustache_propagates_failure.cf.mustache b/tests/acceptance/10_files/mustache_propagates_failure.cf.mustache new file mode 100644 index 0000000000..323fae03f4 --- /dev/null +++ b/tests/acceptance/10_files/mustache_propagates_failure.cf.mustache @@ -0,0 +1 @@ +foobar diff --git a/tests/acceptance/10_files/mustache_render_multiline_template_data.cf b/tests/acceptance/10_files/mustache_render_multiline_template_data.cf new file mode 100644 index 0000000000..eff73e17c2 --- /dev/null +++ b/tests/acceptance/10_files/mustache_render_multiline_template_data.cf @@ -0,0 +1,39 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that data passed to edit_template can be rendered as multiline JSON."; +} + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "template_target" string => "$(G.testfile)"; + + "data" data => parsejson('{ "key": "value", "key2": [ "list", "elements" ], "key3": { "complex": { "data": "structure", "with": [ "deep", "data" ] } } }'); + + files: + "$(template_target)" + create => "true"; + + + "$(template_target)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + template_data => mergedata(data); +} + +####################################################### + +bundle agent check +{ + methods: + "check" + usebundle => dcs_check_diff( $(test.template_target), "$(this.promise_filename).expected", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/mustache_render_multiline_template_data.cf.expected b/tests/acceptance/10_files/mustache_render_multiline_template_data.cf.expected new file mode 100644 index 0000000000..9da80faf65 --- /dev/null +++ b/tests/acceptance/10_files/mustache_render_multiline_template_data.cf.expected @@ -0,0 +1,16 @@ +{ + "key": "value", + "key2": [ + "list", + "elements" + ], + "key3": { + "complex": { + "data": "structure", + "with": [ + "deep", + "data" + ] + } + } +} diff --git a/tests/acceptance/10_files/mustache_render_multiline_template_data.cf.mustache b/tests/acceptance/10_files/mustache_render_multiline_template_data.cf.mustache new file mode 100644 index 0000000000..52afca3e9e --- /dev/null +++ b/tests/acceptance/10_files/mustache_render_multiline_template_data.cf.mustache @@ -0,0 +1 @@ +{{%-top-}} diff --git a/tests/acceptance/10_files/mustache_respects_dryryn.cf b/tests/acceptance/10_files/mustache_respects_dryryn.cf new file mode 100644 index 0000000000..827ee76a7e --- /dev/null +++ b/tests/acceptance/10_files/mustache_respects_dryryn.cf @@ -0,0 +1,40 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that body agent control dryrun restricts mustache template rendering"; + "story_id" string => "5535"; + "covers" string => "dryrun_repaired"; +} + +# Ref: https://dev.cfengine.com/issues/6739 + +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -KIf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*Pass.*", ".*FAIL.*", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/mustache_respects_dryryn.cf.sub b/tests/acceptance/10_files/mustache_respects_dryryn.cf.sub new file mode 100644 index 0000000000..c5c63c3565 --- /dev/null +++ b/tests/acceptance/10_files/mustache_respects_dryryn.cf.sub @@ -0,0 +1,41 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control +{ + dryrun => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + # The template should not be rendered because dryrun is set + "$(template_target)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache"; +} + +####################################################### + +bundle agent check +{ + classes: + "fail" expression => regline("SHOULD NOT RENDER", $(test.template_target) ); + + reports: + !fail:: + "$(this.promise_filename) Pass"; + fail:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/mustache_respects_dryryn.cf.sub.mustache b/tests/acceptance/10_files/mustache_respects_dryryn.cf.sub.mustache new file mode 100644 index 0000000000..4f49108c47 --- /dev/null +++ b/tests/acceptance/10_files/mustache_respects_dryryn.cf.sub.mustache @@ -0,0 +1 @@ +SHOULD NOT RENDER diff --git a/tests/acceptance/10_files/mustache_respects_dryryn_option.cf b/tests/acceptance/10_files/mustache_respects_dryryn_option.cf new file mode 100644 index 0000000000..cfd23834c1 --- /dev/null +++ b/tests/acceptance/10_files/mustache_respects_dryryn_option.cf @@ -0,0 +1,40 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that --dry-run restricts mustache template rendering"; + "story_id" string => "5535"; + "covers" string => "dryrun_repaired"; +} + +# Ref: https://dev.cfengine.com/issues/6739 + +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) --dry-run -KIf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*Pass.*", ".*FAIL.*", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/mustache_respects_dryryn_option.cf.sub b/tests/acceptance/10_files/mustache_respects_dryryn_option.cf.sub new file mode 100644 index 0000000000..2069f341ac --- /dev/null +++ b/tests/acceptance/10_files/mustache_respects_dryryn_option.cf.sub @@ -0,0 +1,36 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + # The template should not be rendered because dryrun is set + "$(template_target)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache"; +} + +####################################################### + +bundle agent check +{ + classes: + "fail" expression => regline("SHOULD NOT RENDER", $(test.template_target) ); + + reports: + !fail:: + "$(this.promise_filename) Pass"; + fail:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/mustache_respects_dryryn_option.cf.sub.mustache b/tests/acceptance/10_files/mustache_respects_dryryn_option.cf.sub.mustache new file mode 100644 index 0000000000..4f49108c47 --- /dev/null +++ b/tests/acceptance/10_files/mustache_respects_dryryn_option.cf.sub.mustache @@ -0,0 +1 @@ +SHOULD NOT RENDER diff --git a/tests/acceptance/10_files/mustache_respects_warn_only.cf b/tests/acceptance/10_files/mustache_respects_warn_only.cf new file mode 100644 index 0000000000..3fe37dc238 --- /dev/null +++ b/tests/acceptance/10_files/mustache_respects_warn_only.cf @@ -0,0 +1,59 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that body action_policy warn restricts mustache template rendering"; + "story_id" string => "5535"; + "covers" string => "dryrun_repaired"; +} + +# Ref: https://dev.cfengine.com/issues/6739 + +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "template_target" string => "$(G.testfile)"; + + files: + "$(template_target)" + create => "true"; +} + +bundle agent test +{ + vars: + "template_target" string => "$(init.template_target)"; + + files: + # The template should not be rendered because dryrun is set + "$(template_target)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + action => warn_only, + classes => classes_generic("mustache_warn"); +} + +####################################################### + +bundle agent check +{ + classes: + "fail" expression => regline("SHOULD NOT RENDER", $(test.template_target) ); + "defined_class" expression => "mustache_warn_failed"; + + reports: + !fail.defined_class:: + "$(this.promise_filename) Pass"; + fail|!defined_class:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/mustache_respects_warn_only.cf.mustache b/tests/acceptance/10_files/mustache_respects_warn_only.cf.mustache new file mode 100644 index 0000000000..4f49108c47 --- /dev/null +++ b/tests/acceptance/10_files/mustache_respects_warn_only.cf.mustache @@ -0,0 +1 @@ +SHOULD NOT RENDER diff --git a/tests/acceptance/10_files/no_error_archiving_previous_backup_using_copy_from_without_create_true.cf b/tests/acceptance/10_files/no_error_archiving_previous_backup_using_copy_from_without_create_true.cf new file mode 100644 index 0000000000..20f251a24f --- /dev/null +++ b/tests/acceptance/10_files/no_error_archiving_previous_backup_using_copy_from_without_create_true.cf @@ -0,0 +1,36 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3640" } + string => "Test that automatically created backup copies of files promised via copy_from do not produce errors."; + + vars: + "agent_log_path" string => "$(sys.workdir)$(const.dirsep)state$(const.dirsep)agent.log"; + "cf_agent" string => ifelse(isvariable("sys.cf_agent"), "$(sys.cf_agent)", "/var/cfengine/bin/cf-agent"); + + commands: + "$(cf_agent) -Kf $(this.promise_filename).sub >$(test.agent_log_path) 2>&1" + contain => in_shell; +} + +bundle agent check +{ + vars: + "result" + string => readfile($(test.agent_log_path)), + if => fileexists( $(test.agent_log_path) ); + + methods: + "Pass/FAIL" + usebundle => dcs_check_regcmp(".*(error|Failed to clean backup).*", + $(result), + $(this.promise_filename), + "true"); # We do not expect to find an error in the agent output +} diff --git a/tests/acceptance/10_files/no_error_archiving_previous_backup_using_copy_from_without_create_true.cf.sub b/tests/acceptance/10_files/no_error_archiving_previous_backup_using_copy_from_without_create_true.cf.sub new file mode 100644 index 0000000000..304fd40013 --- /dev/null +++ b/tests/acceptance/10_files/no_error_archiving_previous_backup_using_copy_from_without_create_true.cf.sub @@ -0,0 +1,105 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + version => "1.0"; +} + +bundle agent init +{ + vars: + + # body agent control default_repository uses this + 'bck' + string => '$(G.testdir)/backups'; + + files: + '$(init.bck)/.' create => 'true'; +} + + +bundle agent __main__ +{ + methods: + "init"; + "test"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3640" } + string => "Test that automatically created backup copies of files promised via copy_from do not produce errors."; + + vars: + 'file' string => "$(G.testfile)"; + + files: + # Let's have a file + '$(file)' + create => "true", + classes => outcomes('create'); + + # Let's have the file be a copy of this policy, this should create the first backup file + '$(file)' + copy_from => path('$(this.promise_filename)'), + handle => 'copy_1', + classes => outcomes('copy_1'); + + # Let's make sure the file differs from this policy file, so that we have reason to copy the file again + '$(file)' + edit_line => lines_present( 'edit after first copy' ), + handle => 'edit_1', + classes => outcomes('edit_1'); + + # Let's have the file be a copy of this policy, this should create the second backup file + # This promise triggered an error on AIX, see CFE-3640. + '$(file)' + copy_from => path('$(this.promise_filename)'), + handle => 'copy_2', + classes => outcomes('copy_2'); + + # Let's make sure the file differs from this policy file, so that we have reason to copy the file again + '$(file)' + edit_line => lines_present( 'edit after second copy' ), + handle => 'edit_2', + classes => outcomes('edit_2'); + + # Let's have the file be a copy of this policy, this should create the second backup file + '$(file)' + copy_from => path('$(this.promise_filename)'), + handle => 'copy_3', + classes => outcomes('copy_3'); + + reports: + DEBUG|EXTRA:: + 'CFEngine $(sys.cf_version) (running on $(sys.flavour))'; + 'Results: $(with)' with => join(' ', classesmatching('_.*')); +} + +body copy_from path(path) +{ + source => translatepath($(path)); +} + +body agent control +{ + default_repository => '$(init.bck)'; +} + +body classes outcomes(p) +{ + promise_kept => { '$(p)_kept', '$(p)_reached' }; + promise_repaired => { '$(p)_repaired', '$(p)_reached' }; + repair_failed => { '$(p)_failed', '$(p)_reached', '$(p)_error' }; + repair_denied => { '$(p)_denied', '$(p)_reached', '$(p)_error' }; + repair_timeout => { '$(p)_timeout', '$(p)_reached', '$(p)_error' }; + scope => 'bundle'; +} +bundle edit_line lines_present(lines) +{ + insert_lines: + + "$(lines)" + comment => "Append lines if they don't exist"; +} + diff --git a/tests/acceptance/10_files/purge_reports_as_repaired.cf b/tests/acceptance/10_files/purge_reports_as_repaired.cf new file mode 100644 index 0000000000..8483caae6b --- /dev/null +++ b/tests/acceptance/10_files/purge_reports_as_repaired.cf @@ -0,0 +1,48 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that purging a directory in a target propagates promise as repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dirs" slist => { + "$(G.testdir)/source/.", + "$(G.testdir)/target/.", + "$(G.testdir)/target/subdir/." + }; + + files: + "$(dirs)" create => "true"; +} + +bundle agent test +{ + files: + "$(G.testdir)/target/" copy_from => copyfrom_sync("$(G.testdir)/source/."), + depth_search => recurse("inf"), + classes => if_repaired("purge_propagated"); +} + +####################################################### + +bundle agent check +{ + reports: + purge_propagated:: + "$(this.promise_filename) Pass"; + !purge_propagated:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/rename/main.cf b/tests/acceptance/10_files/rename/main.cf new file mode 100644 index 0000000000..45bf929575 --- /dev/null +++ b/tests/acceptance/10_files/rename/main.cf @@ -0,0 +1,92 @@ +body file control +{ + inputs => { "../../default.cf.sub" }; +} + +bundle agent main +{ + methods: + "init,test,check,cleanup" + usebundle => default("$(this.promise_filename)") ; +} + +bundle agent init +{ + files: + + # First we initialize some directories to iterate over and some files that + # will be renamed during the test + + "$(G.testdir)/test/$(check.dirs)/incoming/$(check.files)" + create => "true"; +} + +bundle agent test +{ + meta: + + "description" + string => "Test that files can be renamed using regular expression + matches from the promiser"; + + "test_soft_fail" + string => "any", + meta => { "CFE-2879" }; + + files: + + # We promise that the target is a copy of the source file Since preserve + # is set to false, we expect the original permissions on the target file + # to remain unchanged. + + "$(G.testdir)/test/$(check.dirs)/incoming/((?!.*renamed)[a-z]*)" + rename => to( "$(G.testdir)/test/$(check.dirs)/incoming/$(match.1)_renamed" ); + +} + +body rename to(file) +{ + newname => "$(file)"; +} + +bundle agent check +{ + vars: + "dirs" + slist => { "knuth", "lamport", "turing" }; + + "files" + slist => { "one", "two", "three" }; + + # Append _renamed to each of the files + "expected_files" slist => maplist( "$(this)_renamed", files ); + + "expected_paths" slist => { + "$(G.testdir)/test/knuth/incoming/one_renamed", + "$(G.testdir)/test/knuth/incoming/two_renamed", + "$(G.testdir)/test/knuth/incoming/three_renamed", + "$(G.testdir)/test/lamport/incoming/one_renamed", + "$(G.testdir)/test/lamport/incoming/two_renamed", + "$(G.testdir)/test/lamport/incoming/three_renamed", + "$(G.testdir)/test/turing/incoming/one_renamed", + "$(G.testdir)/test/turing/incoming/two_renamed", + "$(G.testdir)/test/turing/incoming/three_renamed", + }; + + classes: + "all_expected_files_exist" + expression => filesexist( @(expected_paths) ); + + methods: + + "Pass or Fail" + usebundle => dcs_passif( "all_expected_files_exist", + $(this.promise_filename)); + + reports: + DEBUG|EXTRA:: + "Expected path exists '$(expected_paths)'" + if => fileexists( $(expected_paths) ); + "Expected path MISSING '$(expected_paths)'" + unless => fileexists( $(expected_paths) ); +} diff --git a/tests/acceptance/10_files/replace_patterns/basic_replace.cf b/tests/acceptance/10_files/replace_patterns/basic_replace.cf new file mode 100644 index 0000000000..03e43d8ee8 --- /dev/null +++ b/tests/acceptance/10_files/replace_patterns/basic_replace.cf @@ -0,0 +1,72 @@ +####################################################### +# +# Replace a pattern and use match.0 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN +END"; + + "expected" string => + "BEGIN_REPLACED +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +###################################################### +# +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_replace("$(init.parameters)"); +} + +bundle edit_line test_replace(parameters) +{ + replace_patterns: + "^BEGIN$" replace_with => value("BEGIN_REPLACED"); +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/replace_patterns/noop_replace.cf b/tests/acceptance/10_files/replace_patterns/noop_replace.cf new file mode 100644 index 0000000000..84ad65ce0a --- /dev/null +++ b/tests/acceptance/10_files/replace_patterns/noop_replace.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Replace a pattern and use match.0 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN +kernel /vmlinuz-2.6.18-348.el5 ro noapic nolapic apci=off time process_timing=everything root=LABEL=/foo +kernel /vmlinuz-2.6.18-348.el5 ro root=LABEL=/1 +END"; + + "expected" string => + "BEGIN +kernel /vmlinuz-2.6.18-348.el5 ro noapic nolapic apci=off time process_timing=everything root=LABEL=/foo +kernel /vmlinuz-2.6.18-348.el5 ro root=LABEL=/1 +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +###################################################### +# +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_replace("$(init.parameters)"); +} + +bundle edit_line test_replace(parameters) +{ + replace_patterns: + + # should not change anything + "^.+$" + replace_with => value("$(match.0)"); +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/replace_patterns/staging/advanced_replace.cf b/tests/acceptance/10_files/replace_patterns/staging/advanced_replace.cf new file mode 100644 index 0000000000..00f7d35d2f --- /dev/null +++ b/tests/acceptance/10_files/replace_patterns/staging/advanced_replace.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Replace a pattern and use match.0 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "states" slist => { "actual", "expected" }; + + "actual" string => + "BEGIN +kernel /vmlinuz-2.6.18-348.el5 ro noapic nolapic apci=off time process_timing=everything root=LABEL=/foo +kernel /vmlinuz-2.6.18-348.el5 ro root=LABEL=/1 +END"; + + "parameters" string => "noapic nolapic apci=off time process_timing=everything"; + + "expected" string => + "BEGIN +kernel /vmlinuz-2.6.18-348.el5 ro root=LABEL=/1 noapic nolapic apci=off time process_timing=everything +kernel /vmlinuz-2.6.18-348.el5 ro root=LABEL=/foo noapic nolapic apci=off time process_timing=everything +END"; + + files: + "$(G.testfile).$(states)" + create => "true", + edit_line => init_insert("$(init.$(states))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +###################################################### +# +bundle agent test +{ + files: + "$(G.testfile).actual" + edit_line => test_replace("$(init.parameters)"); +} + +bundle edit_line test_replace(parameters) +{ + replace_patterns: + + # replace the parameters with an empty string + "$(parameters)" + handle => "test_replace_parameters", + replace_with => value(""); + + # should append $(parameters) to any line that begins with "kernel" + "^\s*(kernel.+)$" + depends_on => { "test_replace_parameters" }, + replace_with => value("$(match.1) $(parameters)"); + + # should not change anything + "^\s*(kernel.+)$" + replace_with => value("$(match.0)"); +} + +body replace_with value(x) +{ + replace_value => "$(x)"; + occurrences => "all"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/basic_classes.cf b/tests/acceptance/10_files/templating/basic_classes.cf new file mode 100644 index 0000000000..5ea91c8a1e --- /dev/null +++ b/tests/acceptance/10_files/templating/basic_classes.cf @@ -0,0 +1,97 @@ +####################################################### +# +# Test basic classes for CFEngine templates. +# Redmine #2928 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ +files: + "$(G.testfile).expected" + create => "true", +edit_defaults => init_empty, + edit_line => init_expect; + + "$(G.testfile).template" + create => "true", + edit_line => template, + edit_defaults => init_empty; +} + +####################################################### + +bundle agent test +{ + classes: + "two" expression => "any"; + "three" expression => "any"; + + files: + "$(G.testfile).actual" + create => "true", + edit_defaults => init_empty, + edit_template => "$(G.testfile).template"; +} + +####################################################### + +bundle edit_line template +{ +insert_lines: + +"[%CFEngine BEGIN %] +one +[%CFEngine END %] + +[%CFEngine two:: %] +two + +[%CFEngine default:three:: %] +three +" + + insert_type => "preserve_block"; +} + +####################################################### + +bundle edit_line init_expect +{ +insert_lines: + +"one + +two + +three +" + insert_type => "preserve_block"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/basic_classes.template b/tests/acceptance/10_files/templating/basic_classes.template new file mode 100644 index 0000000000..cc2a0a18bf --- /dev/null +++ b/tests/acceptance/10_files/templating/basic_classes.template @@ -0,0 +1,9 @@ +[%CFEngine BEGIN %] +one +[%CFEngine END %] + +[%CFEngine two:: %] +two + +[%CFEngine default:three:: %] +three \ No newline at end of file diff --git a/tests/acceptance/10_files/templating/cftemplate.cf b/tests/acceptance/10_files/templating/cftemplate.cf new file mode 100644 index 0000000000..78225555fb --- /dev/null +++ b/tests/acceptance/10_files/templating/cftemplate.cf @@ -0,0 +1,63 @@ +####################################################### +# +# Test a complete CFEngine template, including trailing newlines. +# Redmine #1900 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### +####################################################### + +bundle agent test +{ + meta: + + "description" -> { "CFE-270" } + string => "Test that classic templating does not insert extra newlines"; + + "test_soft_fail" -> { "CFE-270" } + string => "any", + meta => { "CFE-270" }; + + + files: + "$(G.testfile).expected" + copy_from => my_local_cp("$(this.promise_filename).expected"); + + "$(G.testfile).actual" + create => "true", + edit_defaults => my_init_empty, + edit_template => "$(this.promise_filename).template"; +} + +####################################################### + +body copy_from my_local_cp(from) +{ + source => "$(from)"; +} + +body edit_defaults my_init_empty +{ + empty_file_before_editing => "true"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/cftemplate.cf.expected b/tests/acceptance/10_files/templating/cftemplate.cf.expected new file mode 100644 index 0000000000..85954eabcf --- /dev/null +++ b/tests/acceptance/10_files/templating/cftemplate.cf.expected @@ -0,0 +1,5 @@ +1 +2 +3 +4 +5 \ No newline at end of file diff --git a/tests/acceptance/10_files/templating/cftemplate.cf.template b/tests/acceptance/10_files/templating/cftemplate.cf.template new file mode 100644 index 0000000000..e199926899 --- /dev/null +++ b/tests/acceptance/10_files/templating/cftemplate.cf.template @@ -0,0 +1,7 @@ +[%CFEngine any:: %] +1 +2 +[%CFEngine cfengine:: %] +3 +4 +5 \ No newline at end of file diff --git a/tests/acceptance/10_files/templating/classic_template_wont_render_cf_null_with_empty_datacontainer.cf b/tests/acceptance/10_files/templating/classic_template_wont_render_cf_null_with_empty_datacontainer.cf new file mode 100644 index 0000000000..5722852acb --- /dev/null +++ b/tests/acceptance/10_files/templating/classic_template_wont_render_cf_null_with_empty_datacontainer.cf @@ -0,0 +1,91 @@ +body common control +{ +# ignore_missing_bundles => "true"; + + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + +} + +bundle agent test +{ + meta: + "description" string => "Test that classic templates will not render + cf_null when dereferencing an absent data + container."; + + "test_soft_fail" + string => "cfengine_3_7|cfengine_3_8|cfengine_3_9", + meta => { "CFE-2422" }; + + vars: + # No vars are expected to be found here + "matching_vars" slist => variablesmatching(".*", "nomatch"); + + methods: + "any" usebundle => cmerge( "absent_container", @(matching_vars) ); + "any" usebundle => template_file( $(G.testfile), + "$(this.promise_filename).tpl", + @(cmerge.absent_container)); +} + +bundle agent check +{ + classes: + "some_content" expression => regline( "This is a test file", $(G.testfile) ); + "cf_null_in_rendered_file" expression => regline( ".*cf_null.*", $(G.testfile) ); + + # The test should pass if the file is rendered without cf_null in the file + + "ok" expression => "some_content.!cf_null_in_rendered_file"; + + # The test should fail if the expected contnet is not rendered or if + # cf_null is found in the rendered file. + + "fail" expression => "!some_content|cf_null_in_rendered_file"; + + methods: + ok:: + "" usebundle => dcs_pass( $(this.promise_filename) ); + + fail:: + "" usebundle => dcs_fail( $(this.promise_filename) ); + + reports: + DEBUG:: + "Found some correct content in rendered file" + if => "some_content"; + + "Found cf_null in rendered file but should not have" + if => "cf_null_in_rendered_file"; +} + +bundle agent template_file(file, template, data_container) +{ + +# Note: the classic template looks inside this bundle to dereference various +# values from data_container. + + vars: + "index" slist => getindices("data_container"); + + files: + "$(file)" + create => "true", + edit_template => "$(template)"; + + reports: + "$(this.bundle)"; +} + +### Picked from the STDLIB ### + +bundle agent cmerge(name, varlist) +{ + vars: + "$(name)" data => parsejson('[]'), policy => "free"; + "$(name)" data => mergedata($(name), $(varlist)), policy => "free"; # iterates! + "$(name)_str" string => format("%S", $(name)), policy => "free"; +} + + diff --git a/tests/acceptance/10_files/templating/classic_template_wont_render_cf_null_with_empty_datacontainer.cf.tpl b/tests/acceptance/10_files/templating/classic_template_wont_render_cf_null_with_empty_datacontainer.cf.tpl new file mode 100644 index 0000000000..f12c27a3da --- /dev/null +++ b/tests/acceptance/10_files/templating/classic_template_wont_render_cf_null_with_empty_datacontainer.cf.tpl @@ -0,0 +1,3 @@ +This is a test file +$(template_file.data_container[$(template_file.index)][bogus]) $(template_file.data_container[$(template_file.index)][foo]) $(template_file.data_container[$(template_file.index)][bar]) +more stuff diff --git a/tests/acceptance/10_files/templating/demo.datastate.mustache b/tests/acceptance/10_files/templating/demo.datastate.mustache new file mode 100644 index 0000000000..cefd1a4f2d --- /dev/null +++ b/tests/acceptance/10_files/templating/demo.datastate.mustache @@ -0,0 +1,35 @@ +

    {{vars.test.header}}

    + +{{#vars.test.items}} + {{.}} +{{/vars.test.items}} + +{{#classes.empty}} +

    The list is empty.

    +{{/classes.empty}} + +{{#vars.test.d}} + key "{{@}}" value "{{.}}" +{{/vars.test.d}} + +{{#vars.test.h}} + key "{{@}}" value "{{.}}" +{{/vars.test.h}} + +{{#vars.test.h_under_q}} + key "{{@}}" value "{{%.}}" +{{/vars.test.h_under_q}} + +{{#vars.test.h_in_array}} + key "{{@}}" value "{{%.}}" +{{/vars.test.h_in_array}} + +{{#vars.test.h2_in_array}} + key "{{@}}" value "{{%.}}" +{{/vars.test.h2_in_array}} + +{{#vars.test.h2_in_array}} +{{#vars.test.h_under_q}} + nested key should be "q": "{{@}}" value "{{%.}}" +{{/vars.test.h_under_q}} +{{/vars.test.h2_in_array}} diff --git a/tests/acceptance/10_files/templating/demo.expected b/tests/acceptance/10_files/templating/demo.expected new file mode 100644 index 0000000000..7a2e1087c5 --- /dev/null +++ b/tests/acceptance/10_files/templating/demo.expected @@ -0,0 +1,6 @@ +

    Colors

    + +
  • red
  • +
  • green
  • +
  • blue
  • + diff --git a/tests/acceptance/10_files/templating/demo.json b/tests/acceptance/10_files/templating/demo.json new file mode 100644 index 0000000000..8c668b3b1b --- /dev/null +++ b/tests/acceptance/10_files/templating/demo.json @@ -0,0 +1,9 @@ +{ + "header": "Colors", + "items": [ + {"name": "red", "first": true, "url": "#Red"}, + {"name": "green", "link": true, "url": "#Green"}, + {"name": "blue", "link": true, "url": "#Blue"} + ], + "empty": false +} diff --git a/tests/acceptance/10_files/templating/demo.mustache b/tests/acceptance/10_files/templating/demo.mustache new file mode 100644 index 0000000000..66fad7879b --- /dev/null +++ b/tests/acceptance/10_files/templating/demo.mustache @@ -0,0 +1,16 @@ +

    {{header}}

    +{{#bug}} +{{/bug}} + +{{#items}} + {{#first}} +
  • {{name}}
  • + {{/first}} + {{#link}} +
  • {{name}}
  • + {{/link}} +{{/items}} + +{{#empty}} +

    The list is empty.

    +{{/empty}} diff --git a/tests/acceptance/10_files/templating/edit_template_string/mustache_edit_template_string_vs_edit_template.cf b/tests/acceptance/10_files/templating/edit_template_string/mustache_edit_template_string_vs_edit_template.cf new file mode 100644 index 0000000000..460453b7f3 --- /dev/null +++ b/tests/acceptance/10_files/templating/edit_template_string/mustache_edit_template_string_vs_edit_template.cf @@ -0,0 +1,98 @@ +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + + "$(G.testdir)/pghba.conf-from-edit_template" + delete => tidy; + + "$(G.testdir)/pghba.conf-from-edit_template_string" + delete => tidy; +} +bundle agent test +{ + meta: + "description" -> { "CFE-2910" } + string => "Test that there is no difference when rendering the same + mustache template with edit_template and edit_template_string"; + + vars: + + "pghbadirectives" data => '[ + { + "type": "local", + "database": "all", + "user": "all", + "address": "", + "method": "trust" + }, + { + "type": "host", + "database": "replication", + "user": "all", + "address": "primary-replication", + "method": "trust" + }, + { + "type": "host", + "database": "replication", + "user": "all", + "address": "secondary-replication", + "method": "trust" + } + ]'; + + # Reparent the config so that we have a named key to iterate over. + "conf" data => mergedata( '{ "pghba": pghbadirectives }' ); + + files: + + "$(G.testdir)/pghba.conf-from-edit_template" + create => "true", + template_method => "mustache", + template_data => @(conf), + edit_template => '$(this.promise_dirname)/pghba.conf.mustache'; + + "$(G.testdir)/pghba.conf-from-edit_template_string" + create => "true", + template_method => "inline_mustache", + template_data => @(conf), + edit_template_string => readfile( '$(this.promise_dirname)/pghba.conf.mustache' ); + + +reports: + + DEBUG|EXTRA:: + "$(G.testdir)/pghba.conf-from-edit_template" + printfile => cat( "$(G.testdir)/pghba.conf-from-edit_template"); + + "$(G.testdir)/pghba.conf-from-edit_template_string" + printfile => cat( "$(G.testdir)/pghba.conf-from-edit_template_string"); + +} + +bundle agent check +{ + methods: + + "Pass/FAIL" + usebundle => dcs_check_diff_expected("$(G.testdir)/pghba.conf-from-edit_template", + "$(G.testdir)/pghba.conf-from-edit_template_string", + "$(this.promise_filename)", + "no"); +} + +body printfile cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/10_files/templating/edit_template_string/mustache_edit_template_string_vs_string_mustache.cf b/tests/acceptance/10_files/templating/edit_template_string/mustache_edit_template_string_vs_string_mustache.cf new file mode 100644 index 0000000000..510a706865 --- /dev/null +++ b/tests/acceptance/10_files/templating/edit_template_string/mustache_edit_template_string_vs_string_mustache.cf @@ -0,0 +1,127 @@ +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + + "$(G.testdir)/pghba.conf-from-edit_template_string" + delete => tidy; + + "$(G.testdir)/pghba.conf-from-string_mustache" + delete => tidy; +} +bundle agent test +{ + meta: + "description" -> { "CFE-2910" } + string => "Test that there is no difference when rendering the same + mustache template with edit_template and edit_template_string"; + + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + vars: + + "pghbadirectives" data => '[ + { + "type": "local", + "database": "all", + "user": "all", + "address": "", + "method": "trust" + }, + { + "type": "host", + "database": "replication", + "user": "all", + "address": "primary-replication", + "method": "trust" + }, + { + "type": "host", + "database": "replication", + "user": "all", + "address": "secondary-replication", + "method": "trust" + } + ]'; + + # Reparent the config so that we have a named key to iterate over. + "conf" data => mergedata( '{ "pghba": pghbadirectives }' ); + + files: + + "$(G.testdir)/pghba.conf-from-edit_template_string" + create => "true", + template_method => "inline_mustache", + template_data => @(conf), + edit_template_string => readfile( '$(this.promise_dirname)/pghba.conf.mustache' ); + + "$(G.testdir)/pghba.conf-from-edit_template_string" + comment => "We need to append a blank line in order to match up with + report_to_file from string_mustache", + edit_line => lines_present( "" ); + +reports: + + # This is how we get a file from string_mustache + "$(with)" + with => string_mustache( readfile('$(this.promise_dirname)/pghba.conf.mustache'), @(conf) ), + report_to_file => "$(G.testdir)/pghba.conf-from-string_mustache", + if => not( fileexists( "$(G.testdir)/pghba.conf-from-string_mustache" )); + + DEBUG|EXTRA:: + "$(G.testdir)/pghba.conf-from-edit_template_string" + printfile => cat( "$(G.testdir)/pghba.conf-from-edit_template_string"); + + "$(G.testdir)/pghba.conf-from-string_mustache" + printfile => cat( "$(G.testdir)/pghba.conf-from-string_mustache" ); +} + +bundle agent check +{ + methods: + + "Pass/FAIL" + usebundle => dcs_check_diff_expected("$(G.testdir)/pghba.conf-from-string_mustache", + "$(G.testdir)/pghba.conf-from-edit_template_string", + "$(this.promise_filename)", + "no"); +} + + +body printfile cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} +bundle edit_line lines_present(lines) +# @brief Ensure `lines` are present in the file. Lines that do not exist are appended to the file +# @param List or string that should be present in the file +# +# **Example:** +# +# ```cf3 +# bundle agent example +# { +# vars: +# "nameservers" slist => { "8.8.8.8", "8.8.4.4" }; +# +# files: +# "/etc/resolv.conf" edit_line => lines_present( @(nameservers) ); +# "/etc/ssh/sshd_config" edit_line => lines_present( "PermitRootLogin no" ); +# } +# ``` +{ + insert_lines: + + "$(lines)" + comment => "Append lines if they don't exist"; +} diff --git a/tests/acceptance/10_files/templating/edit_template_string/pghba.conf.mustache b/tests/acceptance/10_files/templating/edit_template_string/pghba.conf.mustache new file mode 100644 index 0000000000..4eec5494f4 --- /dev/null +++ b/tests/acceptance/10_files/templating/edit_template_string/pghba.conf.mustache @@ -0,0 +1,3 @@ +{{#pghba}} +{{{type}}} {{{database}}} {{{user}}} {{{address}}} {{{method}}} +{{/pghba}} diff --git a/tests/acceptance/10_files/templating/empty_template_empty_file.cf b/tests/acceptance/10_files/templating/empty_template_empty_file.cf new file mode 100644 index 0000000000..a1bcd10345 --- /dev/null +++ b/tests/acceptance/10_files/templating/empty_template_empty_file.cf @@ -0,0 +1,79 @@ +####################################################### +# +# Redmine#3870: Test that an empty CFEngine template will empty the file. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + edit_defaults => init_empty, + create => "true", + edit_line => init_insert_lines("TEXT NOT GOOD"); +} + +bundle edit_line init_insert_lines(lines) +{ + insert_lines: + + "$(lines)" + comment => "Append lines if they don't exist"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; + edit_backup => "false"; + #max_file_size => "300000"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_file" string => "$(this.promise_filename).template"; + + files: + "$(G.testfile)" + edit_template => "$(template_file)"; + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile)"; +} + + +####################################################### + +bundle agent check +{ + vars: + "expect" string => ""; + "actual" string => readfile("$(G.testfile)", 1000); + + classes: + "ok" expression => regcmp("$(expect)", "$(actual)"); + + reports: + DEBUG:: + "expect: '$(expect)'"; + "actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/empty_template_empty_file.cf.template b/tests/acceptance/10_files/templating/empty_template_empty_file.cf.template new file mode 100644 index 0000000000..3c59e8d61c --- /dev/null +++ b/tests/acceptance/10_files/templating/empty_template_empty_file.cf.template @@ -0,0 +1,3 @@ +[%CFEngine no_such_class:: %] +Redmine#3870: text will not be inserted + diff --git a/tests/acceptance/10_files/templating/inline_demo.expected b/tests/acceptance/10_files/templating/inline_demo.expected new file mode 100644 index 0000000000..c4243546c4 --- /dev/null +++ b/tests/acceptance/10_files/templating/inline_demo.expected @@ -0,0 +1 @@ +This is a test: 1 diff --git a/tests/acceptance/10_files/templating/large_template_blocks.cf b/tests/acceptance/10_files/templating/large_template_blocks.cf new file mode 100644 index 0000000000..692967a2e7 --- /dev/null +++ b/tests/acceptance/10_files/templating/large_template_blocks.cf @@ -0,0 +1,75 @@ +####################################################### +# +# Test instatiation of template blocks larger than 4096 characters. +# Redmine #3852 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "origtestdir" string => dirname("$(this.promise_filename)"); + + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_file" string => "$(this.promise_filename).template"; + + classes: + + + files: + "$(G.testfile)" + create => "true", + edit_template => "$(template_file)"; + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile)"; +} + + + +####################################################### + +bundle agent check +{ + vars: + "expected" int => "110"; + "actual" int => countlinesmatching(".*0123456789.*", "$(G.testfile)"); + + classes: + "ok" expression => strcmp("$(expected)", "$(actual)"); + + reports: + DEBUG:: + "expected: '$(expected)'"; + "actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/templating/large_template_blocks.cf.template b/tests/acceptance/10_files/templating/large_template_blocks.cf.template new file mode 100644 index 0000000000..2ee519d584 --- /dev/null +++ b/tests/acceptance/10_files/templating/large_template_blocks.cf.template @@ -0,0 +1,118 @@ +This first block, smaller than 4096 characters is rendered: +[%CFEngine BEGIN %] +00-1234567890123456789012345678901234567890123456789012345678901234567890 +01-1234567890123456789012345678901234567890123456789012345678901234567890 +02-1234567890123456789012345678901234567890123456789012345678901234567890 +03-1234567890123456789012345678901234567890123456789012345678901234567890 +04-1234567890123456789012345678901234567890123456789012345678901234567890 +05-1234567890123456789012345678901234567890123456789012345678901234567890 +06-1234567890123456789012345678901234567890123456789012345678901234567890 +07-1234567890123456789012345678901234567890123456789012345678901234567890 +08-1234567890123456789012345678901234567890123456789012345678901234567890 +09-1234567890123456789012345678901234567890123456789012345678901234567890 +[%CFEngine END %] + +The second block, which is larger than 4096 characters, +is not rendered in CFE3.5.x: +[%CFEngine BEGIN %] +00-1234567890123456789012345678901234567890123456789012345678901234567890 +01-1234567890123456789012345678901234567890123456789012345678901234567890 +02-1234567890123456789012345678901234567890123456789012345678901234567890 +03-1234567890123456789012345678901234567890123456789012345678901234567890 +04-1234567890123456789012345678901234567890123456789012345678901234567890 +05-1234567890123456789012345678901234567890123456789012345678901234567890 +06-1234567890123456789012345678901234567890123456789012345678901234567890 +07-1234567890123456789012345678901234567890123456789012345678901234567890 +08-1234567890123456789012345678901234567890123456789012345678901234567890 +09-1234567890123456789012345678901234567890123456789012345678901234567890 +10-1234567890123456789012345678901234567890123456789012345678901234567890 +11-1234567890123456789012345678901234567890123456789012345678901234567890 +12-1234567890123456789012345678901234567890123456789012345678901234567890 +13-1234567890123456789012345678901234567890123456789012345678901234567890 +14-1234567890123456789012345678901234567890123456789012345678901234567890 +15-1234567890123456789012345678901234567890123456789012345678901234567890 +16-1234567890123456789012345678901234567890123456789012345678901234567890 +17-1234567890123456789012345678901234567890123456789012345678901234567890 +18-1234567890123456789012345678901234567890123456789012345678901234567890 +19-1234567890123456789012345678901234567890123456789012345678901234567890 +20-1234567890123456789012345678901234567890123456789012345678901234567890 +21-1234567890123456789012345678901234567890123456789012345678901234567890 +22-1234567890123456789012345678901234567890123456789012345678901234567890 +23-1234567890123456789012345678901234567890123456789012345678901234567890 +24-1234567890123456789012345678901234567890123456789012345678901234567890 +25-1234567890123456789012345678901234567890123456789012345678901234567890 +26-1234567890123456789012345678901234567890123456789012345678901234567890 +27-1234567890123456789012345678901234567890123456789012345678901234567890 +28-1234567890123456789012345678901234567890123456789012345678901234567890 +29-1234567890123456789012345678901234567890123456789012345678901234567890 +30-1234567890123456789012345678901234567890123456789012345678901234567890 +31-1234567890123456789012345678901234567890123456789012345678901234567890 +32-1234567890123456789012345678901234567890123456789012345678901234567890 +33-1234567890123456789012345678901234567890123456789012345678901234567890 +34-1234567890123456789012345678901234567890123456789012345678901234567890 +35-1234567890123456789012345678901234567890123456789012345678901234567890 +36-1234567890123456789012345678901234567890123456789012345678901234567890 +37-1234567890123456789012345678901234567890123456789012345678901234567890 +38-1234567890123456789012345678901234567890123456789012345678901234567890 +39-1234567890123456789012345678901234567890123456789012345678901234567890 +40-1234567890123456789012345678901234567890123456789012345678901234567890 +41-1234567890123456789012345678901234567890123456789012345678901234567890 +42-1234567890123456789012345678901234567890123456789012345678901234567890 +43-1234567890123456789012345678901234567890123456789012345678901234567890 +44-1234567890123456789012345678901234567890123456789012345678901234567890 +45-1234567890123456789012345678901234567890123456789012345678901234567890 +46-1234567890123456789012345678901234567890123456789012345678901234567890 +47-1234567890123456789012345678901234567890123456789012345678901234567890 +48-1234567890123456789012345678901234567890123456789012345678901234567890 +49-1234567890123456789012345678901234567890123456789012345678901234567890 +50-1234567890123456789012345678901234567890123456789012345678901234567890 +51-1234567890123456789012345678901234567890123456789012345678901234567890 +52-1234567890123456789012345678901234567890123456789012345678901234567890 +53-1234567890123456789012345678901234567890123456789012345678901234567890 +54-1234567890123456789012345678901234567890123456789012345678901234567890 +55-1234567890123456789012345678901234567890123456789012345678901234567890 +56-1234567890123456789012345678901234567890123456789012345678901234567890 +57-1234567890123456789012345678901234567890123456789012345678901234567890 +58-1234567890123456789012345678901234567890123456789012345678901234567890 +59-1234567890123456789012345678901234567890123456789012345678901234567890 +60-1234567890123456789012345678901234567890123456789012345678901234567890 +61-1234567890123456789012345678901234567890123456789012345678901234567890 +62-1234567890123456789012345678901234567890123456789012345678901234567890 +63-1234567890123456789012345678901234567890123456789012345678901234567890 +64-1234567890123456789012345678901234567890123456789012345678901234567890 +65-1234567890123456789012345678901234567890123456789012345678901234567890 +66-1234567890123456789012345678901234567890123456789012345678901234567890 +67-1234567890123456789012345678901234567890123456789012345678901234567890 +68-1234567890123456789012345678901234567890123456789012345678901234567890 +69-1234567890123456789012345678901234567890123456789012345678901234567890 +70-1234567890123456789012345678901234567890123456789012345678901234567890 +71-1234567890123456789012345678901234567890123456789012345678901234567890 +72-1234567890123456789012345678901234567890123456789012345678901234567890 +73-1234567890123456789012345678901234567890123456789012345678901234567890 +74-1234567890123456789012345678901234567890123456789012345678901234567890 +75-1234567890123456789012345678901234567890123456789012345678901234567890 +76-1234567890123456789012345678901234567890123456789012345678901234567890 +77-1234567890123456789012345678901234567890123456789012345678901234567890 +78-1234567890123456789012345678901234567890123456789012345678901234567890 +79-1234567890123456789012345678901234567890123456789012345678901234567890 +80-1234567890123456789012345678901234567890123456789012345678901234567890 +81-1234567890123456789012345678901234567890123456789012345678901234567890 +82-1234567890123456789012345678901234567890123456789012345678901234567890 +83-1234567890123456789012345678901234567890123456789012345678901234567890 +84-1234567890123456789012345678901234567890123456789012345678901234567890 +85-1234567890123456789012345678901234567890123456789012345678901234567890 +86-1234567890123456789012345678901234567890123456789012345678901234567890 +87-1234567890123456789012345678901234567890123456789012345678901234567890 +88-1234567890123456789012345678901234567890123456789012345678901234567890 +89-1234567890123456789012345678901234567890123456789012345678901234567890 +90-1234567890123456789012345678901234567890123456789012345678901234567890 +91-1234567890123456789012345678901234567890123456789012345678901234567890 +92-1234567890123456789012345678901234567890123456789012345678901234567890 +93-1234567890123456789012345678901234567890123456789012345678901234567890 +94-1234567890123456789012345678901234567890123456789012345678901234567890 +95-1234567890123456789012345678901234567890123456789012345678901234567890 +96-1234567890123456789012345678901234567890123456789012345678901234567890 +97-1234567890123456789012345678901234567890123456789012345678901234567890 +98-1234567890123456789012345678901234567890123456789012345678901234567890 +99-1234567890123456789012345678901234567890123456789012345678901234567890 +[%CFEngine END %] diff --git a/tests/acceptance/10_files/templating/missing_file.cf b/tests/acceptance/10_files/templating/missing_file.cf new file mode 100644 index 0000000000..ad9f68bd28 --- /dev/null +++ b/tests/acceptance/10_files/templating/missing_file.cf @@ -0,0 +1,61 @@ +############################################################################## +# +# Redmine #3573: establish message for missing target file with a template +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + +} + +####################################################### + +bundle agent test +{ + vars: + any:: + # Run subtests. Need to be in verbose mode to see the output. + # The full verbose output is too big for variable assignment here. + # So extract (grep) only potentially interesting lines. + "subout" string => execresult("$(sys.cf_agent) -KIv -b run -f $(this.promise_filename).sub 2>&1 | $(G.grep) '/no/such/file'", "useshell"); +} + + +####################################################### + +bundle agent check +{ + vars: + "must_have" string => ".*Cannot render file '/no/such/file': file does not exist.*"; + "cant_have" string => ".*no longer access file.*"; + + classes: + "ok_1" not => regcmp($(cant_have), "$(test.subout)"); + "ok_2" expression => regcmp($(must_have), "$(test.subout)"); + + reports: + DEBUG:: + "Attempted subtest '$(this.promise_filename).sub'"; + "Significant output was '$(test.subout)'."; + + DEBUG.!ok_1:: + "failing: can't have pattern '$(cant_have)' in subtest"; + + DEBUG.!ok_2:: + "failing: must have pattern '$(must_have)' in subtest"; + + ok_1.ok_2:: + "$(this.promise_filename) Pass"; + !(ok_1.ok_2):: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/templating/missing_file.cf.sub b/tests/acceptance/10_files/templating/missing_file.cf.sub new file mode 100644 index 0000000000..e2f73fb7b4 --- /dev/null +++ b/tests/acceptance/10_files/templating/missing_file.cf.sub @@ -0,0 +1,19 @@ +############################################################################## +# +# Redmine #3573: establish message for missing target file with a template +# +############################################################################## + +bundle agent run +{ + files: + "/no/such/file" + create => "false", + edit_defaults => init_empty, + edit_template => "$(this.promise_filename)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} diff --git a/tests/acceptance/10_files/templating/missing_template_file.cf b/tests/acceptance/10_files/templating/missing_template_file.cf new file mode 100644 index 0000000000..8e4821ae0c --- /dev/null +++ b/tests/acceptance/10_files/templating/missing_template_file.cf @@ -0,0 +1,56 @@ +############################################################################## +# +# Redmine #4241: set correct classes if template is missing +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub", "../../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testfile)" + create => "true", + edit_template => "$(this.promise_filename).nonexistent", + classes => scoped_classes_generic("namespace", "test_file_template"); + + methods: + DEBUG:: + "report" usebundle => dcs_report_generic_classes("test_file_template"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" and => { "!test_file_template_kept", + "test_file_template_repaired", # create => "true" + "test_file_template_failed", # edit_template + "!test_file_template_denied", + "!test_file_template_timeout" }; + + reports: + DEBUG.created:: + "Correctly created the test file $(G.testfile)"; + DEBUG.!created:: + "Erroneously did not create the test file $(G.testfile)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/templating/mustache_abuse.cf b/tests/acceptance/10_files/templating/mustache_abuse.cf new file mode 100644 index 0000000000..57986e4723 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_abuse.cf @@ -0,0 +1,121 @@ +####################################################### +# +# Abusive Mustache relationship +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "tests" slist => { "0", "1", "2", "3", "4", "5", "6" }; + "templates" data => parsejson(' +[ + "{{x}}", + "{{x}} {{y}}", + "{{null}}", + "{{}}", + "{{#boolean}}IT IS TRUE{{/boolean}}", + "{{^boolean}}IT IS FALSE{{/boolean}}", + "{{#list}}{{k}}={{v}}, {{/list}}" +]'); + + files: + "$(G.testfile).$(tests).tmpl" + create => "true", + edit_defaults => test_empty, + edit_line => init_insert_lines("$(templates[$(tests)])"); +} + +bundle edit_line init_insert_lines(lines) +{ + insert_lines: + + "$(lines)" + comment => "Append lines if they don't exist"; +} + +####################################################### + +bundle agent test +{ + vars: + "tdata" data => parsejson(' +[ + "{ \\\"x\\\": 123 }", + "{ \\\"x\\\": 123, \\\"y\\\": 456 }", + "[ null ]", + "{ \\\"x\\\": 123, \\\"y\\\": 456 }", + "{\\\"boolean\\\": true}", + "{\\\"boolean\\\": false}", + "{ \\\"list\\\": [ { \\\"k\\\": 789, \\\"v\\\": 0 }, { \\\"k\\\": null, \\\"v\\\": true }, { \\\"k\\\": -1, \\\"v\\\": -2 } ] }", +]'); + + files: + "$(G.testfile).$(init.tests)" + create => "true", + edit_defaults => test_empty, + edit_template => "$(G.testfile).$(init.tests).tmpl", + template_method => "mustache", + template_data => parsejson("$(tdata[$(init.tests)])"); + + reports: + DEBUG:: + "Rendering template file $(G.testfile).$(init.tests).tmpl to $(G.testfile).$(init.tests)"; +} + +body edit_defaults test_empty +{ + empty_file_before_editing => "true"; + edit_backup => "false"; +} + + +####################################################### + +bundle agent check +{ + vars: + "expected" data => parsejson(' +[ + "123", + "123 456", + "", + "{{}}", + "IT IS TRUE" + "IT IS FALSE", + "789=0, =true, -1=-2, " +]'); + + "actual_$(init.tests)" string => readfile("$(G.testfile).$(init.tests)", 10000); + + classes: + "ok_$(init.tests)" expression => regcmp("$(expected[$(init.tests)])", + "$(actual_$(init.tests))"); + + "ok" and => { ok_0, ok_1, ok_2, ok_3, ok_4, ok_5 }; + + reports: + DEBUG:: + "OK $(init.tests): Expected '$(expected[$(init.tests)])' == '$(actual_$(init.tests))'" + if => "ok_$(init.tests)"; + + "FAIL $(init.tests): Expected '$(expected[$(init.tests)])' <> '$(actual_$(init.tests))'" + if => "!ok_$(init.tests)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/mustache_container_serialization.cf b/tests/acceptance/10_files/templating/mustache_container_serialization.cf new file mode 100644 index 0000000000..d6c2371aa7 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_container_serialization.cf @@ -0,0 +1,81 @@ +####################################################### +# +# Demo of Mustache templates with no external JSON data +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + vars: + "a" string => "[]"; + "b" string => "{}"; + "c" string => '{"a":100,"b":200}'; + "d" string => '[{"a":100,"b":200},null,true,false,"abcde"]'; + + "test" slist => { "a", "b", "c", "d" }; + + methods: + "" usebundle => file_mustache_jsonstring("$(this.promise_filename).mustache", + '{ "name": "$(test)", "data": $($(test)) }', + "$(G.testdir)/$(test)"); + + "" usebundle => test2; +} + +bundle agent test2 +{ + files: + "$(G.testfile).expected" create => "true", edit_line => insertthem; + "$(G.testfile).actual" create => "true", edit_line => readthem; +} + +bundle edit_line insertthem +{ + vars: + "test" slist => { @(test.test) }; + "parsed_$(test)" data => parsejson("$(test.$(test))"); + "fulldump_$(test)" string => storejson("parsed_$(test)"); + + insert_lines: + "$(test)$(test.$(test))$(test) +$(test)$(fulldump_$(test))$(test)" insert_type => "preserve_block"; + + reports: + EXTRA:: + "Full dump of $(test) is $(fulldump_$(test))"; +} + +bundle edit_line readthem +{ + vars: + "test" slist => { @(test.test) }; + "read_$(test)" string => readfile("$(G.testdir)/$(test)", 1k); + insert_lines: + "$(read_$(test))"; + + reports: + EXTRA:: + "For $(test) we got '$(read_$(test))'"; +} + +####################################################### + +bundle agent check +{ + vars: + "test" slist => { @(test.test) }; + + methods: + "" usebundle => dcs_check_diff("$(G.testfile).expected", + "$(G.testfile).actual", + $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/templating/mustache_container_serialization.cf.mustache b/tests/acceptance/10_files/templating/mustache_container_serialization.cf.mustache new file mode 100644 index 0000000000..9401fc0cb1 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_container_serialization.cf.mustache @@ -0,0 +1,2 @@ +{{name}}{{$data}}{{name}} +{{name}}{{%data}}{{name}} \ No newline at end of file diff --git a/tests/acceptance/10_files/templating/mustache_datastate_demo.cf b/tests/acceptance/10_files/templating/mustache_datastate_demo.cf new file mode 100644 index 0000000000..42f462e489 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_datastate_demo.cf @@ -0,0 +1,125 @@ +####################################################### +# +# Demo of Mustache templates with no external JSON data +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "origtestdir" string => dirname("$(this.promise_filename)"); + + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + classes: + "empty" expression => "any"; + + vars: + "template_file" string => "$(init.origtestdir)/demo.datastate.mustache"; + "header" string => "Colors"; + "items" slist => { "red", "green", "blue" }; + "d" data => parsejson('[4,5,6]'); + "h" data => parsejson('{ "a": "x", "b": "y"}'); + "h_under_q" data => mergedata('{ "q": h }'); # wrap h in a map + "h_in_array" data => mergedata('[ h ]'); # wrap h in an array + "h2_in_array" data => mergedata(h_in_array, h_in_array); # more arrays! + + files: + "$(G.testfile)" + create => "true", + edit_template => "$(template_file)", + template_method => "mustache"; + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile)"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => '

    Colors

    + + red + green + blue + +

    The list is empty.

    + + key "0" value "4" + key "1" value "5" + key "2" value "6" + + key "a" value "x" + key "b" value "y" + + key "q" value "{ + "a": "x", + "b": "y" +}" + + key "0" value "{ + "a": "x", + "b": "y" +}" + + key "0" value "{ + "a": "x", + "b": "y" +}" + key "1" value "{ + "a": "x", + "b": "y" +}" + + nested key should be "q": "q" value "{ + "a": "x", + "b": "y" +}" + nested key should be "q": "q" value "{ + "a": "x", + "b": "y" +}" +'; + + "actual" string => readfile("$(G.testfile)", inf); + + classes: + "ok" expression => strcmp($(expect), $(actual)); + + reports: + DEBUG:: + "'$(expect)' != '$(actual)'" if => "!ok"; + "'$(expect)' == '$(actual)'" if => "ok"; + "expect: '$(expect)'"; + "actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/templating/mustache_demo.cf b/tests/acceptance/10_files/templating/mustache_demo.cf new file mode 100644 index 0000000000..d5476bd847 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_demo.cf @@ -0,0 +1,73 @@ +####################################################### +# +# Demo from mustache.github.io +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "origtestdir" string => dirname("$(this.promise_filename)"); + + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_file" string => "$(init.origtestdir)/demo.mustache"; + + files: + "$(G.testfile)" + create => "true", + edit_template => "$(template_file)", + template_method => "mustache", + template_data => readjson("$(init.origtestdir)/demo.json", 10000); + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile)"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => readfile("$(init.origtestdir)/demo.expected", 10000); + "actual" string => readfile("$(G.testfile)", 10000); + + classes: + "ok" expression => regcmp("$(expect)", "$(actual)"); + + reports: + DEBUG:: + "expect: '$(expect)'"; + "actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/mustache_expect_list_find_string.cf b/tests/acceptance/10_files/templating/mustache_expect_list_find_string.cf new file mode 100644 index 0000000000..69e115abb8 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_expect_list_find_string.cf @@ -0,0 +1,150 @@ +####################################################### +# +# Test that mustache DTRT when encountering string when expects list. Also +# check that after mustache template is applied you have a different filestat +# (indicating you wrote to a different file and moved into place) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + methods: + "empty good" usebundle => file_empty("$(G.testfile).good"); + "empty bad" usebundle => file_empty("$(G.testfile).bad"); + + "first filestat good" usebundle => init_filestat("good1", "$(G.testfile).good"); + "first filestat bad" usebundle => init_filestat("bad1", "$(G.testfile).bad"); + + "Remove good" usebundle => dcs_fini("$(G.testfile).good"); + "Remove bad" usebundle => dcs_fini("$(G.testfile).bad"); + + "ready good" usebundle => file_make("$(G.testfile).good", "# Set good"); + "ready bad" usebundle => file_make("$(G.testfile).bad", "# Set bad"); + + "link good" usebundle => file_hardlink("$(G.testfile).good", "$(G.testfile).goodlink"); + "link bad" usebundle => file_hardlink("$(G.testfile).bad", "$(G.testfile).badlink"); + + "second filestat good" usebundle => init_filestat("good2", "$(G.testfile).good"); + "second filestat bad" usebundle => init_filestat("bad2", "$(G.testfile).bad"); +} + +bundle agent init_filestat(n, f) +{ + vars: + "filestat_$(n)" string => format("%s,%s", filestat($(f), "basename"), filestat($(f), "nlink")); +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + + methods: + "mustache good" usebundle => file_mustache_jsonstring($(template), + '{"mykeys": ["template expects list of strings"]}', + "$(G.testfile).good"); + + "mustache bad" usebundle => file_mustache_jsonstring($(template), + '{"mykeys": "string but template expects list of strings"}', + "$(G.testfile).bad"); + + "third filestat good" usebundle => init_filestat("good3", "$(G.testfile).good"); + "third filestat bad" usebundle => init_filestat("bad3", "$(G.testfile).bad"); + + vars: + "template" string => "$(this.promise_filename).mustache"; + + "actual_good" string => readfile("$(G.testfile).good", 4096); + "actual_bad" string => readfile("$(G.testfile).bad", 4096); +} + + +####################################################### + +bundle agent check +{ + vars: + "expected_good" string => "#DO NOT EDIT - MANAGED FILE +template expects list of strings +"; + + "expected_bad" string => "# Set bad"; + + "filestats_good" slist => { "$(init_filestat.filestat_good1)", "$(init_filestat.filestat_good2)", "$(init_filestat.filestat_good3)" }; + "filestats_bad" slist => { "$(init_filestat.filestat_bad1)", "$(init_filestat.filestat_bad2)", "$(init_filestat.filestat_bad3)" }; + + "filestats_good_str" string => format("%S", filestats_good); + "filestats_bad_str" string => format("%S", filestats_bad); + + classes: + "ok_filestats_good_1_2" not => strcmp("$(init_filestat.filestat_good1)", "$(init_filestat.filestat_good2)"); + "ok_filestats_good_2_3" not => strcmp("$(init_filestat.filestat_good2)", "$(init_filestat.filestat_good3)"); + + "ok_filestats_bad_1_2" not => strcmp("$(init_filestat.filestat_bad1)", "$(init_filestat.filestat_bad2)"); + "ok_filestats_bad_2_3" expression => strcmp("$(init_filestat.filestat_bad2)", "$(init_filestat.filestat_bad3)"); + + "ok_content_good" expression => strcmp($(expected_good), $(test.actual_good)); + "ok_content_bad" expression => strcmp($(expected_bad), $(test.actual_bad)); + + + methods: + "" usebundle => dcs_passif_expected("ok_filestats_good_1_2,ok_filestats_good_2_3,ok_filestats_bad_1_2,ok_filestats_bad_2_3,ok_content_good,ok_content_bad", + "", + $(this.promise_filename)), + inherit => "true"; + + reports: + EXTRA:: + "OK: As expected good '$(expected_good)'" + if => "ok_content_good"; + + "OK: As expected bad '$(expected_bad)'" + if => "ok_content_bad"; + + "OK: good filestats $(filestats_good_str) change from step 1 to step 2" + if => "ok_filestats_good_1_2"; + + "OK: good filestats $(filestats_good_str) change from step 2 to step 3" + if => "ok_filestats_good_2_3"; + + "OK: bad filestats $(filestats_bad_str) change from step 1 to step 2" + if => "ok_filestats_bad_1_2"; + + "OK: bad filestats $(filestats_bad_str) don't change from step 2 to step 3" + if => "ok_filestats_bad_2_3"; + + DEBUG:: + "FAIL: Expected '$(expected_good)' <> '$(test.actual_good)'" + if => "!ok_content_good"; + + "FAIL: Expected '$(expected_bad)' <> '$(test.actual_bad)'" + if => "!ok_content_bad"; + + "FAIL: good filestats $(filestats_good_str) don't change enough from step 1 to step 2" + if => "!ok_filestats_good_1_2"; + + "FAIL: good filestats $(filestats_good_str) don't change enough from step 2 to step 3" + if => "!ok_filestats_good_2_3"; + + "FAIL: bad filestats $(filestats_bad_str) don't change enough from step 1 to step 2" + if => "!ok_filestats_bad_1_2"; + + "FAIL: bad filestats $(filestats_bad_str) change from step 2 to step 3" + if => "!ok_filestats_bad_2_3"; + +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/mustache_expect_list_find_string.cf.mustache b/tests/acceptance/10_files/templating/mustache_expect_list_find_string.cf.mustache new file mode 100644 index 0000000000..8a9dbe5fd2 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_expect_list_find_string.cf.mustache @@ -0,0 +1,3 @@ +#DO NOT EDIT - MANAGED FILE +{{#mykeys}}{{.}} +{{/mykeys}} diff --git a/tests/acceptance/10_files/templating/mustache_from_json_or_classic_array.cf b/tests/acceptance/10_files/templating/mustache_from_json_or_classic_array.cf new file mode 100644 index 0000000000..4b601e4054 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_from_json_or_classic_array.cf @@ -0,0 +1,227 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +bundle agent init { + + methods: + any:: + "json_data"; + "array_data"; +} + +bundle agent json_data { + + vars: + any:: + "key_values" + data => ' + { + "one" : "1", + "two": "2", + "three": "3", + }'; + +} + +bundle agent array_data { + + vars: + any:: + + "key_values[one]" + string => "1"; + + "key_values[two]" + string => "2"; + + "key_values[three]" + string => "3"; + +} + +bundle agent test { + + meta: + "description" -> { "CFE-3124" } + string => "Test that rendering mustache from classic arrays works like rendering from JSON data"; + "test_soft_fail" + string => "any", + meta => { "CFE-3124" }; + + vars: + + "cases" + slist => { + "from_json_with_explicit_data", + "from_array_with_explicit_data", + "from_json_with_implicit_datastate", + "from_array_with_implicit_datastate", + "from_json_with_bundlestate", + "from_array_with_bundlestate", + "from_json_with_explicit_datastate", + "from_array_with_explicit_datastate", + }; + + files: + any:: + + "$(G.testdir)/$(this.handle)" + create => "true", + handle => "from_json_with_explicit_data", + template_method => "inline_mustache", + template_data => @(json_data.key_values), + edit_template_string => "TEMPLATE=RENDERED +{{#-top-}} +{{@}}={{.}} +{{/-top-}}"; + + "$(G.testdir)/$(this.handle)" + create => "true", + handle => "from_array_with_explicit_data", + template_method => "inline_mustache", + template_data => @(array_data.key_values), + edit_template_string => "TEMPLATE=RENDERED +{{#-top-}} +{{@}}={{.}} +{{/-top-}}"; + + "$(G.testdir)/$(this.handle)" + create => "true", + handle => "from_json_with_implicit_datastate", + template_method => "inline_mustache", + edit_template_string => "TEMPLATE=RENDERED +{{#vars.json_data.key_values}} +{{@}}={{.}} +{{/vars.json_data.key_values}}"; + + "$(G.testdir)/$(this.handle)" + create => "true", + handle => "from_array_with_implicit_datastate", + template_method => "inline_mustache", + edit_template_string => "TEMPLATE=RENDERED +{{#vars.array_data.key_values}} +{{@}}={{.}} +{{/vars.array_data.key_values}}"; + + "$(G.testdir)/$(this.handle)" + create => "true", + handle => "from_json_with_bundlestate", + template_method => "inline_mustache", + template_data => bundlestate( "json_data" ), + edit_template_string => "TEMPLATE=RENDERED +{{#key_values}} +{{@}}={{.}} +{{/key_values}}"; + + "$(G.testdir)/$(this.handle)" + create => "true", + handle => "from_array_with_bundlestate", + template_method => "inline_mustache", + template_data => bundlestate( "array_data" ), + edit_template_string => "TEMPLATE=RENDERED +{{#key_values}} +{{@}}={{.}} +{{/key_values}}"; + + "$(G.testdir)/$(this.handle)" + create => "true", + handle => "from_json_with_explicit_datastate", + template_method => "inline_mustache", + template_data => datastate(), + edit_template_string => "TEMPLATE=RENDERED +{{#vars.json_data.key_values}} +{{@}}={{.}} +{{/vars.json_data.key_values}}"; + + "$(G.testdir)/$(this.handle)" + create => "true", + handle => "from_array_with_explicit_datastate", + template_method => "inline_mustache", + template_data => datastate(), + edit_template_string => "TEMPLATE=RENDERED +{{#vars.array_data.key_values}} +{{@}}={{.}} +{{/vars.array_data.key_values}}"; + + # "datastate()$(const.n)$(with)" + # with => string_mustache("{{%-top-}}", datastate() ); +} +bundle agent check +{ + vars: + + # Here we read in the data file create by each test case so that we can + # check if it was rendered as expected. Reading in the data as an env file + # we can ignore ordering which classic arrays do not promise. + + "env_$(test.cases)" + data => readenvfile( "$(G.testdir)/$(test.cases)" ); + + "passed_tests" + slist => classesmatching( ".*", "class_for_passing_case" ); + + "implicitly_failed_tests" + slist => classesmatching( ".*", "class_for_failing_case" ); + + "expected_passing" + slist => maplist( "passed_$(this)", @(test.cases) ); + + classes: + + "passed_$(test.cases)" + and => { + strcmp( "1", "$(env_$(test.cases)[one])" ), + strcmp( "2", "$(env_$(test.cases)[two])" ), + strcmp( "3", "$(env_$(test.cases)[three])" ), + strcmp( "TEMPLATE", "RENDERED" ), + }, + meta => { "class_for_passing_case" }, + comment => "We pass a test case if each expected key value pair is found in the rendered data"; + + + "implicitly_failed_$(test.cases)" + not => "passed_$(test.cases)", + meta => { "class_for_failing_case" }, + comment => "With no passing class for the test case, we must have failed"; + + + "ok" and => { @(expected_passing) }; + "fail" expression => isgreaterthan( length( "$(this.bundle).implicitly_failed_tests" ), 0 ); + + reports: + ok:: + "$(this.promise_filename) Pass"; + + fail:: + "$(this.promise_filename) FAIL"; + + DEBUG|EXTRA:: + "test cases passed: $(with)" with => join(", ", passed_tests ); + "test cases that failed implicitly: $(with)" with => join(", ", implicitly_failed_tests ); + "Output from each test case:"; + "$(G.testdir)/$(test.cases)" + printfile => my_cat( $(this.promiser) ); + +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle agent __main__ { + + methods: + any:: + "init"; + "test"; + "check"; +} diff --git a/tests/acceptance/10_files/templating/mustache_html_escape.cf b/tests/acceptance/10_files/templating/mustache_html_escape.cf new file mode 100644 index 0000000000..5a85acc37d --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_html_escape.cf @@ -0,0 +1,77 @@ +####################################################### +# +# Test that characters are NOT escaped outside of variables +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "origtestdir" string => dirname("$(this.promise_filename)"); + + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_suppress_fail" string => "!any", + meta => { "redmine7620" }; + + vars: + "template_file" string => "$(this.promise_filename).mustache"; + + files: + "$(G.testfile)" + create => "true", + edit_template => "$(template_file)", + template_method => "mustache", + template_data => readjson("$(this.promise_filename).json", 10000); + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile) using $(this.promise_filename).json"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => readfile("$(this.promise_filename).expected", 10000); + "actual" string => readfile("$(G.testfile)", 10000); + + classes: + "ok" expression => strcmp("$(expect)", "$(actual)"); + + reports: + DEBUG:: + "expect: '$(expect)'"; + "actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/mustache_html_escape.cf.expected b/tests/acceptance/10_files/templating/mustache_html_escape.cf.expected new file mode 100644 index 0000000000..c3101676b6 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_html_escape.cf.expected @@ -0,0 +1,2 @@ +& == & +& ~= & diff --git a/tests/acceptance/10_files/templating/mustache_html_escape.cf.json b/tests/acceptance/10_files/templating/mustache_html_escape.cf.json new file mode 100644 index 0000000000..3b9e16b803 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_html_escape.cf.json @@ -0,0 +1,7 @@ +{ + "vars": { + "test": { + "amp": "&" + } + } +} diff --git a/tests/acceptance/10_files/templating/mustache_html_escape.cf.mustache b/tests/acceptance/10_files/templating/mustache_html_escape.cf.mustache new file mode 100644 index 0000000000..caf470b19f --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_html_escape.cf.mustache @@ -0,0 +1,2 @@ +{{{vars.test.amp}}} == & +{{vars.test.amp}} ~= & diff --git a/tests/acceptance/10_files/templating/mustache_inline_demo.cf b/tests/acceptance/10_files/templating/mustache_inline_demo.cf new file mode 100644 index 0000000000..275432052a --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_inline_demo.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Demo from mustache.github.io +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "origtestdir" string => dirname("$(this.promise_filename)"); + + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_str" string => "This is a test: {{a}}"; + "d" data => parsejson('{ "a": "1" }'); + + files: + "$(G.testfile)" + create => "true", + edit_template_string => "$(template_str)", + template_method => "inline_mustache", + template_data => "@(test.d)"; + + reports: + DEBUG:: + "Rendering template file $(template_str) to $(G.testfile)"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => readfile("$(init.origtestdir)/inline_demo.expected", 10000); + "actual" string => readfile("$(G.testfile)", 10000); + + classes: + "ok" expression => regcmp("$(expect)", "$(actual)"); + + reports: + DEBUG:: + "expect: '$(expect)'"; + "actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/mustache_invalid_template.cf b/tests/acceptance/10_files/templating/mustache_invalid_template.cf new file mode 100644 index 0000000000..e86994d28b --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_invalid_template.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test that invalid mustache template does not cause a segfault +# Redmine:4702 (https://cfengine.com/dev/issues/4702) +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "template_content" string => ' +{{#vars.sys.interfaces}}{{.}} {{#vars.sys.interface_flags[{{.}}]}} {{/vars.sys.interfaces}}'; + + files: + "$(G.testfile).template" + create => "true", + edit_line => insert_lines($(template_content)), + edit_defaults => empty; + + "$(G.testfile).output" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_file" string => "$(G.testfile).template"; + + files: + "$(G.testfile).output" + create => "true", + edit_template => "$(template_file)", + template_method => "mustache"; + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile).output"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "any", + comment => "If we made it to here we didn't segfault"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/templating/mustache_invalid_template_data.cf b/tests/acceptance/10_files/templating/mustache_invalid_template_data.cf new file mode 100644 index 0000000000..062290dfbe --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_invalid_template_data.cf @@ -0,0 +1,94 @@ +####################################################### +# +# Test that invalid mustache template data is not replaced by datastate(). +# CFE-2194 +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "template_content" string => ' +template_data: +{{%-top-}} +'; + + files: + "$(G.testfile).template" + create => "true", + edit_line => insert_lines($(template_content)), + edit_defaults => empty; + + "$(G.testfile).output" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_file" string => "$(G.testfile).template"; + + "invalid_data" + data => parsejson('{ + "port": 3508, + "protocol": 2, + "filepath": "$(no_such_variable)", + "encryption-level": 256, + "loglevel": 1, + "users": + [ + {"user": "thomas", "level": "admin"}, + {"user": "malin", "level": "guest"} + ] + }'); + + files: + "$(G.testfile).output" + create => "true", + edit_template => "$(template_file)", + template_method => "mustache", + template_data => @(invalid_data); + + classes: + "invalid_data_exists" expression => isvariable("invalid_data"); + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile).output"; + + "invalid_data defined!" + if => "invalid_data_exists"; +} + + +####################################################### + +bundle agent check +{ + classes: + "ok" -> {"CFE-2194"} + not => fileexists("$(G.testfile).output"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/templating/mustache_no_end.error.cf b/tests/acceptance/10_files/templating/mustache_no_end.error.cf new file mode 100644 index 0000000000..96f49c6234 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_end.error.cf @@ -0,0 +1,8 @@ +# This previously caused a crash (segfault), now we are testing that +# it prints error and exits with 0. +bundle agent main +{ + reports: + "With: $(with)" + with => string_mustache("{{"); +} diff --git a/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_direct_data.cf b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_direct_data.cf new file mode 100644 index 0000000000..1e3db50284 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_direct_data.cf @@ -0,0 +1,92 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that a mustache template does not fall back to datastate when its given an invalid container directly"; +} + +bundle agent init +{ + # First we make sure to start of with am emty target + files: + "$(G.testfile)" + handle => "init_testfile_absent", + delete => tidy; + + "$(G.testfile)" + handle => "init_testfile_present", + create => "true", + depends_on => { "init_testfile_absent" }, + comment => "We first remove the file, and then re-create it so that we + know that it is empty to start with."; +} + +bundle agent test +{ + # Second we promise to render template passing data that does not exist via template_data + vars: + "classes" + slist => classesmatching("template_render_with_non_existing_data_container.*"); + + files: + "$(G.testfile)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + template_data => @(missing_ns:missing_bundle.missing_data), + classes => scoped_classes_generic("bundle", "template_render_with_non_existing_data_container"); + + reports: + DEBUG:: + "$(this.bundle): Found class '$(classes)'"; +} + +bundle agent check +{ + meta: + "test_soft_fail" string => "any", meta => { "redmine7699" }; + + # Finally we look to see what classes were defined, and look at the target file to see if it has changed. + vars: + "expected" string => ""; + "actual" string => readfile($(G.testfile), inf); + + classes: + # I assume here that the promise should not even be actuated when invalid + # data is provided thus we should have found 0 classes + "fail_classes" + expression => isgreaterthan(length("test.classes"), 0); + + # Another failure condition is if the rendered file is not empty as + # expected + "fail_content" + not => strcmp($(expected), $(actual)); + + "fail" + or => { "fail_classes", "fail_content" }; + + "exception" + and => { "fail", "ok" }; + + reports: + DEBUG:: + "DEBUG $(this.bundle): Found '$(test.classes)' that should not have been found" + if => "fail_classes"; + + "DEBUG $(this.bundle): Found the content of $(G.testfile) was not as expected" + if => "fail_content"; + + fail:: + "$(this.promise_filename) FAIL"; + + !fail|exception:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_direct_data.cf.mustache b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_direct_data.cf.mustache new file mode 100644 index 0000000000..fec00edef1 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_direct_data.cf.mustache @@ -0,0 +1 @@ +Var from datastate: {{{vars.sys.cf_version}}} diff --git a/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_mergedata.cf b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_mergedata.cf new file mode 100644 index 0000000000..944bc788e6 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_mergedata.cf @@ -0,0 +1,92 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that a mustache template does not fallback to datastate when its given an invalid container via mergedata"; +} + +bundle agent init +{ + # First we make sure to start with an empty target file + files: + "$(G.testfile)" + handle => "init_testfile_absent", + delete => tidy; + + "$(G.testfile)" + handle => "init_testfile_present", + create => "true", + depends_on => { "init_testfile_absent" }, + comment => "We first remove the file, and then re-create it so that we + know that it is empty to start with."; +} + +bundle agent test +{ + # Second we try to render the template using the result of mergedata with a non existing container + vars: + "classes" + slist => classesmatching("template_render_with_non_existing_data_container.*"); + + files: + "$(G.testfile)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + template_data => mergedata("missing_ns:missing_bundle.missing_data"), + classes => scoped_classes_generic("bundle", "template_render_with_non_existing_data_container"); + + reports: + DEBUG:: + "$(this.bundle): Found class '$(classes)'"; +} + +bundle agent check +{ + meta: + "test_soft_fail" string => "any", meta => { "redmine7699" }; + + # Finally we check to see what classes were defined, and inspect the target file for changes. + vars: + "expected" string => ""; + "actual" string => readfile($(G.testfile), inf); + + classes: + # I assume here that the promise should not even be actuated when invalid + # data is provided thus we should have found 0 classes + "fail_classes" + expression => isgreaterthan(length("test.classes"), 0); + + # Another failure condition is if the rendered file is not empty as + # expected + "fail_content" + not => strcmp($(expected), $(actual)); + + "fail" + or => { "fail_classes", "fail_content" }; + + "exception" + and => { "fail", "ok" }; + + reports: + DEBUG:: + "DEBUG $(this.bundle): Found '$(test.classes)' that should not have been found" + if => "fail_classes"; + + "DEBUG $(this.bundle): Found the content of $(G.testfile) was not as expected" + if => "fail_content"; + + fail:: + "$(this.promise_filename) FAIL"; + + !fail|exception:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_mergedata.cf.mustache b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_mergedata.cf.mustache new file mode 100644 index 0000000000..fec00edef1 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_mergedata.cf.mustache @@ -0,0 +1 @@ +Var from datastate: {{{vars.sys.cf_version}}} diff --git a/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_parsejson_storejson.cf b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_parsejson_storejson.cf new file mode 100644 index 0000000000..165bad22f3 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_parsejson_storejson.cf @@ -0,0 +1,94 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that a mustache template does not try to render when its given an invalid container via parsejson(storejson())"; +} + +bundle agent init +{ + # First we make sure to start with an empty target file. + + files: + "$(G.testfile)" + handle => "init_testfile_absent", + delete => tidy; + + "$(G.testfile)" + handle => "init_testfile_present", + create => "true", + depends_on => { "init_testfile_absent" }, + comment => "We first remove the file, and then re-create it so that we + know that it is empty to start with."; + +} + +bundle agent test +{ + # Second we try to render the template while passing in an non existing data container to template_data via parsejson(storejson + vars: + "classes" + slist => classesmatching("template_render_with_non_existing_data_container.*"); + + files: + "$(G.testfile)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + template_data => parsejson(storejson('missing_ns:missing_bundle.missing_data')), + classes => scoped_classes_generic("bundle", "template_render_with_non_existing_data_container"); + + reports: + DEBUG:: + "$(this.bundle): Found class '$(classes)'"; +} + +bundle agent check +{ + meta: + "test_soft_fail" string => "any", meta => { "redmine7699" }; + + # Finally we check to see what classes were defined, and inspect the target file for change + vars: + "expected" string => ""; + "actual" string => readfile($(G.testfile), inf); + + classes: + # I assume here that the promise should not even be actuated when invalid + # data is provided thus we should have found 0 classes + "fail_classes" + expression => isgreaterthan(length("test.classes"), 0); + + # Another failure condition is if the rendered file is not empty as + # expected + "fail_content" + not => strcmp($(expected), $(actual)); + + "fail" + or => { "fail_classes", "fail_content" }; + + "exception" + and => { "fail", "ok" }; + + reports: + DEBUG:: + "DEBUG $(this.bundle): Found '$(test.classes)' that should not have been found" + if => "fail_classes"; + + "DEBUG $(this.bundle): Found the content of $(G.testfile) was not as expected" + if => "fail_content"; + + fail:: + "$(this.promise_filename) FAIL"; + + !fail|exception:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_parsejson_storejson.cf.mustache b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_parsejson_storejson.cf.mustache new file mode 100644 index 0000000000..fec00edef1 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_fallback_to_datastate_with_undefined_parsejson_storejson.cf.mustache @@ -0,0 +1 @@ +Var from datastate: {{{vars.sys.cf_version}}} diff --git a/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_direct_data.cf b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_direct_data.cf new file mode 100644 index 0000000000..59e796863a --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_direct_data.cf @@ -0,0 +1,92 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that a mustache template does not try to render when its given an invalid container directly"; +} + +bundle agent init +{ + # First we make sure to start of with am emty target + files: + "$(G.testfile)" + handle => "init_testfile_absent", + delete => tidy; + + "$(G.testfile)" + handle => "init_testfile_present", + create => "true", + depends_on => { "init_testfile_absent" }, + comment => "We first remove the file, and then re-create it so that we + know that it is empty to start with."; +} + +bundle agent test +{ + # Second we promise to render template passing data that does not exist via template_data + vars: + "classes" + slist => classesmatching("template_render_with_non_existing_data_container.*"); + + files: + "$(G.testfile)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + template_data => @(missing_ns:missing_bundle.missing_data), + classes => scoped_classes_generic("bundle", "template_render_with_non_existing_data_container"); + + reports: + DEBUG:: + "$(this.bundle): Found class '$(classes)'"; +} + +bundle agent check +{ + meta: + "test_soft_fail" string => "any", meta => { "redmine7699" }; + + # Finally we look to see what classes were defined, and look at the target file to see if it has changed. + vars: + "expected" string => ""; + "actual" string => readfile($(G.testfile), inf); + + classes: + # I assume here that the promise should not even be actuated when invalid + # data is provided thus we should have found 0 classes + "fail_classes" + expression => isgreaterthan(length("test.classes"), 0); + + # Another failure condition is if the rendered file is not empty as + # expected + "fail_content" + not => strcmp($(expected), $(actual)); + + "fail" + or => { "fail_classes", "fail_content" }; + + "exception" + and => { "fail", "ok" }; + + reports: + DEBUG:: + "DEBUG $(this.bundle): Found '$(test.classes)' that should not have been found" + if => "fail_classes"; + + "DEBUG $(this.bundle): Found the content of $(G.testfile) was not as expected" + if => "fail_content"; + + fail:: + "$(this.promise_filename) FAIL"; + + !fail|exception:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_direct_data.cf.mustache b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_direct_data.cf.mustache new file mode 100644 index 0000000000..6530b63ec9 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_direct_data.cf.mustache @@ -0,0 +1 @@ +Hi there diff --git a/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_mergedata.cf b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_mergedata.cf new file mode 100644 index 0000000000..1fa3fe9650 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_mergedata.cf @@ -0,0 +1,92 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that a mustache template does not try to render when its given an invalid container via mergedata"; +} + +bundle agent init +{ + # First we make sure to start with an empty target file + files: + "$(G.testfile)" + handle => "init_testfile_absent", + delete => tidy; + + "$(G.testfile)" + handle => "init_testfile_present", + create => "true", + depends_on => { "init_testfile_absent" }, + comment => "We first remove the file, and then re-create it so that we + know that it is empty to start with."; +} + +bundle agent test +{ + # Second we try to render the template using the result of mergedata with a non existing container + vars: + "classes" + slist => classesmatching("template_render_with_non_existing_data_container.*"); + + files: + "$(G.testfile)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + template_data => mergedata("missing_ns:missing_bundle.missing_data"), + classes => scoped_classes_generic("bundle", "template_render_with_non_existing_data_container"); + + reports: + DEBUG:: + "$(this.bundle): Found class '$(classes)'"; +} + +bundle agent check +{ + meta: + "test_soft_fail" string => "any", meta => { "redmine7699" }; + + # Finally we check to see what classes were defined, and inspect the target file for changes. + vars: + "expected" string => ""; + "actual" string => readfile($(G.testfile), inf); + + classes: + # I assume here that the promise should not even be actuated when invalid + # data is provided thus we should have found 0 classes + "fail_classes" + expression => isgreaterthan(length("test.classes"), 0); + + # Another failure condition is if the rendered file is not empty as + # expected + "fail_content" + not => strcmp($(expected), $(actual)); + + "fail" + or => { "fail_classes", "fail_content" }; + + "exception" + and => { "fail", "ok" }; + + reports: + DEBUG:: + "DEBUG $(this.bundle): Found '$(test.classes)' that should not have been found" + if => "fail_classes"; + + "DEBUG $(this.bundle): Found the content of $(G.testfile) was not as expected" + if => "fail_content"; + + fail:: + "$(this.promise_filename) FAIL"; + + !fail|exception:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_mergedata.cf.mustache b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_mergedata.cf.mustache new file mode 100644 index 0000000000..6530b63ec9 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_mergedata.cf.mustache @@ -0,0 +1 @@ +Hi there diff --git a/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_parsejson_storejson.cf b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_parsejson_storejson.cf new file mode 100644 index 0000000000..165bad22f3 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_parsejson_storejson.cf @@ -0,0 +1,94 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that a mustache template does not try to render when its given an invalid container via parsejson(storejson())"; +} + +bundle agent init +{ + # First we make sure to start with an empty target file. + + files: + "$(G.testfile)" + handle => "init_testfile_absent", + delete => tidy; + + "$(G.testfile)" + handle => "init_testfile_present", + create => "true", + depends_on => { "init_testfile_absent" }, + comment => "We first remove the file, and then re-create it so that we + know that it is empty to start with."; + +} + +bundle agent test +{ + # Second we try to render the template while passing in an non existing data container to template_data via parsejson(storejson + vars: + "classes" + slist => classesmatching("template_render_with_non_existing_data_container.*"); + + files: + "$(G.testfile)" + edit_template => "$(this.promise_filename).mustache", + template_method => "mustache", + template_data => parsejson(storejson('missing_ns:missing_bundle.missing_data')), + classes => scoped_classes_generic("bundle", "template_render_with_non_existing_data_container"); + + reports: + DEBUG:: + "$(this.bundle): Found class '$(classes)'"; +} + +bundle agent check +{ + meta: + "test_soft_fail" string => "any", meta => { "redmine7699" }; + + # Finally we check to see what classes were defined, and inspect the target file for change + vars: + "expected" string => ""; + "actual" string => readfile($(G.testfile), inf); + + classes: + # I assume here that the promise should not even be actuated when invalid + # data is provided thus we should have found 0 classes + "fail_classes" + expression => isgreaterthan(length("test.classes"), 0); + + # Another failure condition is if the rendered file is not empty as + # expected + "fail_content" + not => strcmp($(expected), $(actual)); + + "fail" + or => { "fail_classes", "fail_content" }; + + "exception" + and => { "fail", "ok" }; + + reports: + DEBUG:: + "DEBUG $(this.bundle): Found '$(test.classes)' that should not have been found" + if => "fail_classes"; + + "DEBUG $(this.bundle): Found the content of $(G.testfile) was not as expected" + if => "fail_content"; + + fail:: + "$(this.promise_filename) FAIL"; + + !fail|exception:: + "$(this.promise_filename) Pass"; + + exception:: + "$(this.promise_filename) EXCEPTION"; +} diff --git a/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_parsejson_storejson.cf.mustache b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_parsejson_storejson.cf.mustache new file mode 100644 index 0000000000..6530b63ec9 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_no_render_with_undefined_parsejson_storejson.cf.mustache @@ -0,0 +1 @@ +Hi there diff --git a/tests/acceptance/10_files/templating/mustache_render_quote.cf b/tests/acceptance/10_files/templating/mustache_render_quote.cf new file mode 100644 index 0000000000..f90bd2d213 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_render_quote.cf @@ -0,0 +1,75 @@ +####################################################### +# +# Acceptance test to ensure that mustache renders quotes in variables +# correctly. +# +# Redmine: https://dev.cfengine.com/issues/6516 +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "variable_containing_double_quote" string => 'Something "special" with quotes'; + "variable_containing_single_quote" string => "Something 'special' with quotes"; + + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_file" string => "$(this.promise_filename).mustache"; + + files: + "$(G.testfile)" + create => "true", + edit_template => "$(template_file)", + template_method => "mustache"; + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile)"; +} + +####################################################### + +bundle agent check +{ + vars: + "expect" string => readfile("$(this.promise_filename).expected", 4000); + "actual" string => readfile("$(G.testfile)", 4000); + + classes: + "ok" expression => regcmp("$(expect)", "$(actual)"); + + reports: + DEBUG:: + "expect: '$(expect)'"; + "actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/mustache_render_quote.cf.expected b/tests/acceptance/10_files/templating/mustache_render_quote.cf.expected new file mode 100644 index 0000000000..a39297bd8f --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_render_quote.cf.expected @@ -0,0 +1,8 @@ +# Escaped variable references by default: +Something "special" with quotes +Something 'special' with quotes +# Unescaped variable references: +Something "special" with quotes +Something 'special' with quotes +Something "special" with quotes +Something 'special' with quotes diff --git a/tests/acceptance/10_files/templating/mustache_render_quote.cf.mustache b/tests/acceptance/10_files/templating/mustache_render_quote.cf.mustache new file mode 100644 index 0000000000..f1a1fa85ea --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_render_quote.cf.mustache @@ -0,0 +1,8 @@ +# Escaped variable references by default: +{{vars.init.variable_containing_double_quote}} +{{vars.init.variable_containing_single_quote}} +# Unescaped variable references: +{{&vars.init.variable_containing_double_quote}} +{{&vars.init.variable_containing_single_quote}} +{{{vars.init.variable_containing_double_quote}}} +{{{vars.init.variable_containing_single_quote}}} diff --git a/tests/acceptance/10_files/templating/mustache_repair_existing.cf b/tests/acceptance/10_files/templating/mustache_repair_existing.cf new file mode 100644 index 0000000000..b9d7121e3e --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_repair_existing.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Test that _repaired classes are defined on template repair +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "origtestdir" string => dirname("$(this.promise_filename)"); + + files: + "$(G.testfile)" + create => "true", + comment => "Need to see if we define repair when file exists"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + vars: + "template_file" string => "$(init.origtestdir)/demo.mustache"; + + files: + "$(G.testfile)" + edit_template => "$(template_file)", + template_method => "mustache", + classes => classes_generic("templated_file"), + template_data => readjson("$(init.origtestdir)/demo.json", 4096); + + reports: + DEBUG:: + "Rendering template file $(template_file) to $(G.testfile)"; +} + +bundle agent check +{ + vars: + "expect" string => readfile("$(init.origtestdir)/demo.expected", 4096); + "actual" string => readfile("$(G.testfile)", 4096); + + classes: + "content_ok" expression => regcmp("$(expect)", "$(actual)"); + "repair_ok" expression => "templated_file_repaired"; + "ok" and => { "content_ok", "repair_ok" }; + + reports: + DEBUG:: + "expect: '$(expect)'"; + "actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/mustache_section.cf b/tests/acceptance/10_files/templating/mustache_section.cf new file mode 100644 index 0000000000..9210e50ec2 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_section.cf @@ -0,0 +1,32 @@ +####################################################### +# +# Check that we warn about Mustache sections over non-iterables/non-booleans +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + methods: + "" usebundle => dcs_fini($(G.testfile)); +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_output1(".*Mustache sections can only take a boolean or a container.+value, but section 'x' isn't getting one of those.*", + "$(sys.cf_agent) -KI -f $(this.promise_filename).sub | grep Mustache", + $(this.promise_filename)); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/mustache_section.cf.sub b/tests/acceptance/10_files/templating/mustache_section.cf.sub new file mode 100644 index 0000000000..579b1fd869 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_section.cf.sub @@ -0,0 +1,14 @@ +body common control +{ + inputs => { "../../plucked.cf.sub" }; + bundlesequence => { test }; + version => "1.0"; +} + +bundle agent test +{ + methods: + "m" usebundle => file_mustache_jsonstring("$(this.promise_filename).mustache", + '{ "x": "y" }', + "$(sys.workdir)/state/out.txt"); +} diff --git a/tests/acceptance/10_files/templating/mustache_section.cf.sub.mustache b/tests/acceptance/10_files/templating/mustache_section.cf.sub.mustache new file mode 100644 index 0000000000..de028cc219 --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_section.cf.sub.mustache @@ -0,0 +1 @@ +{{#x}}{{.}}{{/x}} \ No newline at end of file diff --git a/tests/acceptance/10_files/templating/mustache_top_level_iteration.cf b/tests/acceptance/10_files/templating/mustache_top_level_iteration.cf new file mode 100644 index 0000000000..414ccdec1e --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_top_level_iteration.cf @@ -0,0 +1,35 @@ +####################################################### +# +# Demo of Mustache templates with top-level iteration +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent test +{ + vars: + "mydata1" data => '{ "a": 100 }'; + "mydata2" data => '[ "p", "q" ]'; + + "actual1" string => string_mustache("{{#-top-}}key {{@}} value {{.}} {{/-top-}}", mydata1); + "actual2" string => string_mustache("{{#-top-}}value {{.}} {{/-top-}}", mydata2); +} + +####################################################### + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/templating/mustache_top_level_iteration.cf.expected.json b/tests/acceptance/10_files/templating/mustache_top_level_iteration.cf.expected.json new file mode 100644 index 0000000000..a22f781a4b --- /dev/null +++ b/tests/acceptance/10_files/templating/mustache_top_level_iteration.cf.expected.json @@ -0,0 +1,11 @@ +{ + "actual1": "key a value 100 ", + "actual2": "value p value q ", + "mydata1": { + "a": 100 + }, + "mydata2": [ + "p", + "q" + ] +} diff --git a/tests/acceptance/10_files/templating/staging/array_expansion.cf b/tests/acceptance/10_files/templating/staging/array_expansion.cf new file mode 100644 index 0000000000..5938168769 --- /dev/null +++ b/tests/acceptance/10_files/templating/staging/array_expansion.cf @@ -0,0 +1,64 @@ +####################################################### +# +# Test array expansion for CFEngine templates. +# Redmine #3442 +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + +} + +####################################################### + +bundle agent init +{ + vars: + "data[user-a][home]" string => "/tmp/test/user-a"; + + methods: + "" usebundle => file_make("$(G.testfile).expected", + "CASE1: passed var /tmp/test/user-a .foo .bar +CASE2: local var /tmp/test/user-a .foo .bar"); + + "" usebundle => file_make("$(G.testfile).template", + "CASE1: passed var $(const.dollar)($(const.dollar)(test2.info)[user-a][home]) .foo .bar +CASE2: local var $(const.dollar)($(const.dollar)(test2.local_info)[user-a][home]) .foo .bar"); +} + +####################################################### + +bundle agent test +{ + methods: + "" usebundle => test2("init.data"); + +} + +bundle agent test2(info) +{ + vars: + "local_info" string => "init.data"; + + files: + "$(G.testfile).actual" + create => "true", + edit_defaults => empty, + edit_template => "$(G.testfile).template"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/templating/timed/expired_edit_line_locks.cf b/tests/acceptance/10_files/templating/timed/expired_edit_line_locks.cf new file mode 100644 index 0000000000..dc603adba7 --- /dev/null +++ b/tests/acceptance/10_files/templating/timed/expired_edit_line_locks.cf @@ -0,0 +1,44 @@ +# Check whether file promises have a shorter expiry time than edit_line promises +# resulting from templates. This will create an empty file because the file is +# opened for editing, but all the edit_line promises have expired. + +body common control +{ + inputs => { "../../../dcs.cf.sub", "../../../plucked.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent test +{ + + commands: + # Note, no -K, we are testing locks. + "$(sys.cf_agent) -v -D AUTO,DEBUG -f $(this.promise_filename).sub" + contain => in_shell; +} + +bundle agent check +{ + methods: + test_pass_1:: + "any" usebundle => dcs_wait($(this.promise_filename), 70); + + vars: + test_pass_2:: + "content_edit_line" string => readfile("$(G.testfile).edit_line", 10000); + "content_cftemplate" string => readfile("$(G.testfile).cftemplate", 10000); + "content_mustache" string => readfile("$(G.testfile).mustache", 10000); + + classes: + test_pass_2:: + "ok_edit_line" expression => strcmp($(content_edit_line), "text"), + scope => "namespace"; + "ok_cftemplate" expression => strcmp($(content_cftemplate), "text"), + scope => "namespace"; + "ok_mustache" expression => strcmp($(content_mustache), "text"), + scope => "namespace"; + + methods: + test_pass_2:: + "any" usebundle => dcs_passif("ok_edit_line.ok_cftemplate.ok_mustache", $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/templating/timed/expired_edit_line_locks.cf.sub b/tests/acceptance/10_files/templating/timed/expired_edit_line_locks.cf.sub new file mode 100644 index 0000000000..030f8dc7af --- /dev/null +++ b/tests/acceptance/10_files/templating/timed/expired_edit_line_locks.cf.sub @@ -0,0 +1,32 @@ +body common control +{ + inputs => { "../../../dcs.cf.sub", "../../../plucked.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent init +{ + methods: + "any" usebundle => file_make("$(G.testfile).template", "text"); +} + +bundle agent test +{ + files: + "$(G.testfile).edit_line" + create => "true", + edit_line => insert_lines("text"), + edit_defaults => empty; + + "$(G.testfile).cftemplate" + create => "true", + edit_template => "$(G.testfile).template", + template_method => "cfengine"; + + "$(G.testfile).mustache" + create => "true", + edit_template => "$(G.testfile).template", + template_method => "mustache"; +} + + diff --git a/tests/acceptance/10_files/this_promiser.cf b/tests/acceptance/10_files/this_promiser.cf new file mode 100644 index 0000000000..c56ebc84ae --- /dev/null +++ b/tests/acceptance/10_files/this_promiser.cf @@ -0,0 +1,213 @@ +####################################################### +# +# Test this.promiser on files promises +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body classes promiser0_generic +{ + promise_repaired => { "$(this.promiser)_repaired", "$(this.promiser)_0_ok" }; + promise_kept => { "$(this.promiser)_kept", "$(this.promiser)_0_ok" }; +} + +body classes promiser1_generic +{ + promise_repaired => { "$(this.promiser)_repaired", "$(this.promiser)_1_ok" }; + promise_kept => { "$(this.promiser)_kept", "$(this.promiser)_1_ok" }; +} + +body classes promiser2_generic +{ + promise_repaired => { "$(this.promiser)_repaired", "$(this.promiser)_2_ok" }; + promise_kept => { "$(this.promiser)_kept", "$(this.promiser)_2_ok" }; +} + +body classes promiser3_generic +{ + promise_repaired => { "$(this.promiser)_repaired", "$(this.promiser)_3_ok" }; + promise_kept => { "$(this.promiser)_kept", "$(this.promiser)_3_ok" }; +} + +body classes promiser4_generic +{ + promise_repaired => { "$(this.promiser)_repaired", "$(this.promiser)_4_ok" }; + promise_kept => { "$(this.promiser)_kept", "$(this.promiser)_4_ok" }; +} + +bundle agent init +{ + vars: + "files" slist => { "aa", "ab", "ac", "ba", "bb", "bc" }; + + files: + "$(G.testdir)/copy_me_source" + create => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10257" }; + commands: + "$(G.true)" + classes => promiser0_generic; + files: + # 6 promisers + "$(G.testdir)/$(init.files)" + create => "true", + classes => promiser1_generic; + + # 3 promisers + "$(G.testdir)/a.*" + perms => m("666"), + classes => promiser2_generic; + + # 2 promisers - 1 file inside the directory and the directory itself (as the + # real promiser) + "$(G.testdir)" + file_select => by_exec_cmd, + perms => m("666"), + classes => promiser2_generic, + depth_search => test_recurse; + + # 6 promisers - 6 files inside the directory and the directory itself (as the + # real promiser) + "$(G.testdir)" + file_select => test_plain, + depth_search => test_recurse, + delete => tidyfiles, + classes => promiser3_generic; + + "$(G.testdir)/copy_me" + copy_from => local_cp("$(this.promiser)_source"); + + methods: + "template_test" usebundle => template_test; +} + +bundle agent template_test +{ + vars: + "testdir" string => "$(G.testdir)/template_test"; + "cfe_files" slist => { "cfe_file1", "cfe_file2" }; + "mustache_files" slist => { "mustache_file1", "mustache_file2" }; + + files: + # create empty files so that we can test pattern and file_select cases + "$(testdir)/." + create => "true"; + + "$(testdir)/$(cfe_files)" + create => "true"; + "$(testdir)/$(mustache_files)" + create => "true"; + + "$(testdir)/$(cfe_files).cf-template" + create => "true", + edit_line => insert_lines("Just a cfengine line for $(this.promiser)."); + + "$(testdir)/$(mustache_files).mustache" + create => "true", + edit_line => insert_lines("Just a mustache line for $(this.promiser)."); + + "$(testdir)/cfe_file[1,2]" + perms => m("666"), + template_method => "cfengine", + edit_template => "$(this.promiser).cf-template", + classes => promiser4_generic; + + "$(testdir)/mustache_.*" + file_select => mustache_files, + perms => m("666"), + template_method => "mustache", + edit_template => "$(this.promiser).mustache", + classes => promiser4_generic; +} + +body file_select mustache_files +{ + leaf_name => { "@(template_test.mustache_files)" }; + file_result => "leaf_name"; +} + +body delete tidyfiles +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +body file_select test_plain +{ + leaf_name => { "[a,b][a,b,c]" }; + file_types => { "plain" }; + file_result => "file_types.leaf_name"; +} + +body file_select by_exec_cmd +# Redmine #3530 +{ + leaf_name => {"ba"}; + exec_program => "$(G.ls) $(this.promiser)"; + file_result => "leaf_name.exec_program"; +} + +body depth_search test_recurse +{ + depth => "inf"; +} + + +####################################################### + +bundle agent check +{ + vars: + "ok_promisers" slist => classesmatching(".*_ok"); + "ok_0_count" int => countclassesmatching(".*_0_ok"); + "ok_1_count" int => countclassesmatching(".*_1_ok"); + "ok_2_count" int => countclassesmatching(".*_2_ok"); + "ok_3_count" int => countclassesmatching(".*_3_ok"); + "ok_4_count" int => countclassesmatching(".*_4_ok"); + "ok_pattern_promiser_count" int => countclassesmatching(".*__[01234]_ok"), + comment => "the patterns should be cannonified and classes for them should be defined"; + + classes: + "ok_expand" expression => none("__this_promiser_[0,1,2,3]_ok", ok_promisers); + "ok0" expression => strcmp("$(ok_0_count)", 1); + "ok1" expression => strcmp("$(ok_1_count)", 6); + "ok2" expression => strcmp("$(ok_2_count)", 6); + "ok3" expression => strcmp("$(ok_3_count)", 7); + "ok4" expression => strcmp("$(ok_4_count)", 6); + "ok5" expression => fileexists("$(G.testdir)/copy_me"); + "ok_pattern" expression => strcmp("$(ok_pattern_promiser_count)", 3); + + "ok" and => { "ok_expand", "ok0", "ok1", "ok2", "ok3", "ok4", "ok5", "ok_pattern" }; + reports: + DEBUG:: + "$(ok_promisers)"; + "0: $(ok_0_count) (expected 1)"; + "1: $(ok_1_count) (expected 6)"; + "2: $(ok_2_count) (expected 6)"; + "3: $(ok_3_count) (expected 7)"; + "4: $(ok_4_count) (expected 6)"; + "pattern promiser classes: $(ok_pattern_promiser_count) (expected 3)"; + DEBUG.!ok5:: + "5: file not copied"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 27 diff --git a/tests/acceptance/10_files/unsafe/can_set_sticky_bits_with_nonroot_owner_and_group.cf b/tests/acceptance/10_files/unsafe/can_set_sticky_bits_with_nonroot_owner_and_group.cf new file mode 100644 index 0000000000..afc5c9bd2d --- /dev/null +++ b/tests/acceptance/10_files/unsafe/can_set_sticky_bits_with_nonroot_owner_and_group.cf @@ -0,0 +1,106 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +####################################################### + +bundle agent init +{ + vars: + "users" slist => { "user01", "user02" }; + "modes" slist => { + "2700", + "2750", + "2755", + "2770", + "2771", + "2775", + "2751", + }; + + users: + "$(users)" + policy => "present"; + +} + +bundle agent cleanup +{ + users: + "$(init.users)" + policy => "absent", + comment => "Purge the users we created to test with"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" string => "Test that sgid can be set when ownership is non root"; + + "test_skip_unsupported" + string => "windows"; + "test_skip_needs_work" + string => "!has_stat", + meta => { "redmine7989", "zendesk2797" }; + + files: + "$(G.testdir)/test_sgid/$(init.modes)/." + create => "true", + perms => test_perms( $(init.modes), "user01", "user02"), + classes => scoped_classes_generic("namespace", "file_$(init.modes)"), + comment => "Make sure a directory named for each desired mode we want to test exists."; +} + +body perms test_perms(m, o, g) +{ + mode => "$(m)"; + owners => { "$(o)" }; + groups => { "$(g)" }; +} + +####################################################### + +bundle agent check +{ + + vars: + # We expect to find an ok_ prefixed class for each of the modes defined in + # init. + "expected_classes" slist => maplist("ok_$(this)", @(init.modes)); + + # Get the octal mode for each of the files + "result_permoct_$(init.modes)" + string => execresult('$(G.stat) -c %a $(G.testdir)/test_sgid/$(init.modes)', "useshell"), + if => "file_$(init.modes)_reached"; + + DEBUG|DEBUG_check:: + # We collect up the classes that match our expected pattern for debug + # reports + "classes" slist => classesmatching("ok_.*"); + + classes: + # Define an ok_ prefixed class for each of the modes defined in init if it + # matches what we observed with stat. + "ok_$(init.modes)" expression => strcmp("$(init.modes)", "$(result_permoct_$(init.modes))"); + + + # And here we define the class to pass or fail the test. + "ok" and => { @(expected_classes) }; + "not_ok" not => "ok"; + + reports: + DEBUG|DEBUG_check:: + "Found '$(G.testdir)/test_sgid/$(init.modes)' = '$(result_permoct_$(init.modes))' expected '$(init.modes)'"; + "Found class: '$(classes)')"; + + ok:: + "$(this.promise_filename) Pass"; + not_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/10_files/unsafe/multiple-group-candidates-first-found-group-perms-if-no-listed-candidate-matches.cf b/tests/acceptance/10_files/unsafe/multiple-group-candidates-first-found-group-perms-if-no-listed-candidate-matches.cf new file mode 100644 index 0000000000..ce8ef7a11b --- /dev/null +++ b/tests/acceptance/10_files/unsafe/multiple-group-candidates-first-found-group-perms-if-no-listed-candidate-matches.cf @@ -0,0 +1,100 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent _group_state(group, state) +{ + meta: + # no groupadd on windows + "test_skip_unsupported" string => "windows"; + + vars: + !aix:: + "groupadd" string => "/usr/sbin/groupadd"; + "groupdel" string => "/usr/sbin/groupdel"; + aix:: + "groupadd" string => "/usr/bin/mkgroup"; + "groupdel" string => "/usr/sbin/rmgroup"; + + classes: + "_have_group" expression => groupexists( $(group) ); + "_want_$(state)" expression => "any"; + + commands: + !_have_group._want_present:: + "$(groupadd)" + args => "$(group)"; + + _have_group._want_absent:: + "$(groupdel)" + args => "$(group)"; +} +bundle agent init +{ + vars: + # Group lengths up to 8 chars because of AIX and HP-UX limitations + "absent_groups" slist => { "abs_gr_1", "abs_gr_2" }; + "present_groups" slist => { "pr_gr_1", "pr_gr_2" }; + + "groups" slist => { + @(absent_groups), + @(present_groups), + }; + + methods: + "Ensure Groups Absent" + usebundle => _group_state( $(absent_groups), "absent" ); + + "Ensure Groups Present" + usebundle => _group_state( $(present_groups), "present" ); + + files: + "$(G.testfile)" + create => "true", + perms => g("root"); +} + +bundle agent test +{ + meta: + "description" + string => "When a list of perms owners is provided if the perms do not match + at least one of the owners then the perms are set to the first + present owner."; + + files: + "$(G.testfile)" + perms => g( @(init.groups) ); +} + +bundle agent check +{ + vars: + "observed_gid" string => filestat($(G.testfile), gid); + "desired_gid" int => getgid("pr_gr_1"); + + reports: + "$(this.promise_filename) Pass" + if => strcmp( $(observed_gid), $(desired_gid) ); + + "$(this.promise_filename) FAIL" + if => not( strcmp( $(observed_gid), $(desired_gid) ) ); + +} +bundle agent cleanup +{ + methods: + + "Ensure Groups Absent" + usebundle => _group_state( $(init.groups), "absent" ); +} +body perms g(g) +# @brief Set the file's group to the first presnet group +# @param g A list of candidate groups for the file +{ + groups => { "$(g)" }; +} + diff --git a/tests/acceptance/10_files/unsafe/multiple-owner-candidates-first-found-user-perms-if-no-listed-candidate-matches.cf b/tests/acceptance/10_files/unsafe/multiple-owner-candidates-first-found-user-perms-if-no-listed-candidate-matches.cf new file mode 100644 index 0000000000..1312bb36d2 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/multiple-owner-candidates-first-found-user-perms-if-no-listed-candidate-matches.cf @@ -0,0 +1,72 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + # Username lengths up to 8 chars because of AIX and HP-UX limitations + "absent_users" slist => { "abs_us_1", "abs_us_2" }; + "present_users" slist => { "pr_us_1", "pr_us_2" }; + + "users" slist => { + @(absent_users), + @(present_users), + }; + + users: + "$(absent_users)" + policy => "absent"; + + "$(present_users)" + policy => "present"; + + files: + "$(G.testfile)" + create => "true", + perms => o("root"); +} + +bundle agent test +{ + meta: + "description" + string => "When a list of perms owners is provided if the perms do not match + at least one of the owners then the perms are set to the first + present owner."; + + files: + "$(G.testfile)" + perms => o( @(init.users) ); +} + +bundle agent check +{ + vars: + "observed_uid" string => filestat($(G.testfile), uid); + "desired_uid" int => getuid("pr_us_1"); + + reports: + "$(this.promise_filename) Pass" + if => strcmp( $(observed_uid), $(desired_uid) ); + + "$(this.promise_filename) FAIL" + if => not( strcmp( $(observed_uid), $(desired_uid) ) ); + +} +bundle agent cleanup +{ + users: + "$(init.users)" + policy => "absent"; +} +body perms o(u) +# @brief Set the file's owner +# @param u The username of the new owner +{ + owners => { "$(u)" }; +} + diff --git a/tests/acceptance/10_files/zendesk_1776/zendesk_1776.cf b/tests/acceptance/10_files/zendesk_1776/zendesk_1776.cf new file mode 100644 index 0000000000..179ff10a67 --- /dev/null +++ b/tests/acceptance/10_files/zendesk_1776/zendesk_1776.cf @@ -0,0 +1,36 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that seleting files based on symlink do not generate undesired errors"; + "story_id" string => ""; + "covers" string => ""; +} + +# Ref: https://dev.cfengine.com/issues/6996 + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + meta: + "test_skip_unsupported" + string => "windows", + comment => "The test deals with symlinks which are not supported on + windows"; + + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*Pass.*", ".*readlink: Invalid argument.*", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/zendesk_1776/zendesk_1776.cf.sub b/tests/acceptance/10_files/zendesk_1776/zendesk_1776.cf.sub new file mode 100644 index 0000000000..84d54a9f2f --- /dev/null +++ b/tests/acceptance/10_files/zendesk_1776/zendesk_1776.cf.sub @@ -0,0 +1,89 @@ +######################################################################## +# +# Test that selecting symlinks does not generate unnecessary errors +# Ref: https://dev.cfengine.com/issues/6996 +######################################################################## +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +# Ensure there is a directory structure created with both plain files and +# symlinks to a plain file. +{ + files: + "$(G.testdir)/." + create => "true"; + + "$(G.testdir)/plainfile_0" + create => "true", + edit_line => insert_lines("Just some content"); + + "$(G.testdir)/file_select/." + create => "true"; + + "$(G.testdir)/file_select/plainfile_1" + copy_from => local_cp("/$(G.testdir)/plainfile_0"); + + "$(G.testdir)/file_select/plainfile_2" + copy_from => local_cp("/$(G.testdir)/plainfile_0"); + + "$(G.testdir)/file_select/symlink_1" + link_from => ln_s("/$(G.testdir)/plainfile_0"); + + "$(G.testdir)/file_select/symlink_2" + link_from => ln_s("/$(G.testdir)/plainfile_0"); +} + +bundle agent test +{ + files: + "$(G.testdir)/file_select/." + delete => tidy, + depth_search => recurse("inf"), + file_select => select_symlink_pointing_to_usr, + comment => "This does not select and delete non symlink files, but did + result in undesirable errors for Unable to read link in + filter. (readlink: Invalid argument)"; +} + +bundle agent check +{ + vars: + "files" slist => lsdir("$(G.testdir)/file_select/", ".*", "false"); + "dir_count" int => length(files); + + classes: + # Since we can't test ourself for stdout, we just make sure that the number + # of files is correct. A separate policy runs this one and inspects its + # output. It needs something to check for passing condition. And failing + # condition is the errors its looking for + "have_expected_number_of_files" expression => strcmp("$(dir_count)", "6"); + + "ok" expression => "have_expected_number_of_files"; + + reports: + ok:: + "Pass $(this.promise_filename)"; +} + +body file_select select_symlink_pointing_to_usr +{ + file_types => { "symlink" }; + issymlinkto => { "/usr/.*" }; + file_result => "file_types.issymlinkto"; +} + +body link_from ln_s(x) +# @brief Create a symbolink link to `x` +# The link is created even if the source of the link does not exist. +# @param x The source of the link +{ + link_type => "symlink"; + source => "$(x)"; + when_no_source => "force"; +} + diff --git a/tests/acceptance/10_files/zendesk_1779/zendesk_1779.cf b/tests/acceptance/10_files/zendesk_1779/zendesk_1779.cf new file mode 100644 index 0000000000..949757c7b9 --- /dev/null +++ b/tests/acceptance/10_files/zendesk_1779/zendesk_1779.cf @@ -0,0 +1,40 @@ +bundle common test_meta +{ + vars: + "description" string => "Test file_selection attributes are only checked as necessary"; + "story_id" string => ""; + "covers" string => ""; +} + +# Ref: https://dev.cfengine.com/issues/6997 + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + meta: + "test_soft_fail" + string => "!windows", + meta => { "redmine6997" }; + + "test_skip_unsupported" + string => "windows", + comment => "The test deals with symlinks which are not supported on + windows"; + + vars: + "command" string => "$(sys.cf_agent) -KIf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*Pass.*", ".*executed command for.*", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/10_files/zendesk_1779/zendesk_1779.cf.sub b/tests/acceptance/10_files/zendesk_1779/zendesk_1779.cf.sub new file mode 100644 index 0000000000..9ff9c29ac1 --- /dev/null +++ b/tests/acceptance/10_files/zendesk_1779/zendesk_1779.cf.sub @@ -0,0 +1,84 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +# Ensure there is a directory structure created with both plain files and +# symlinks to a plain file. +{ + files: + "$(G.testdir)/." + create => "true"; + + "$(G.testdir)/plainfile_0" + create => "true", + edit_line => insert_lines("Just some content"); + + "$(G.testdir)/file_select/." + create => "true"; + + "$(G.testdir)/file_select/plainfile_1" + copy_from => local_cp("/$(G.testdir)/plainfile_0"); + + "$(G.testdir)/file_select/plainfile_2" + copy_from => local_cp("/$(G.testdir)/plainfile_0"); + + "$(G.testdir)/file_select/symlink_1" + link_from => ln_s("/$(G.testdir)/plainfile_0"); + + "$(G.testdir)/file_select/symlink_2" + link_from => ln_s("/$(G.testdir)/plainfile_0"); +} + +bundle agent test +{ + files: + "$(G.testdir)/file_select/." + delete => tidy, + depth_search => recurse("inf"), + file_select => no_file_should_be_executed; +} + +bundle agent check +{ + vars: + "files" slist => lsdir("$(G.testdir)/file_select/", ".*", "false"); + "dir_count" int => length(files); + + classes: + # Since we can't test ourself for stdout, we just make sure that the number + # of files is correct. A separate policy runs this one and inspects its + # output. It needs something to check for passing condition. And failing + # condition is the errors its looking for + "have_expected_number_of_files" expression => strcmp("$(dir_count)", "6"); + + "ok" expression => "have_expected_number_of_files"; + + reports: + ok:: + "Pass $(this.promise_filename)"; +} + +body file_select no_file_should_be_executed +# Since no files match both leaf_name and file_types, there is no chance that +# file_result will succeed and no executions should be performed. +{ + leaf_name => { "plainfile.*" }; + file_types => { "symlink" }; + exec_program => "/bin/echo executed command for $(this.promiser)"; + file_result => "leaf_name.file_types.exec_program"; +} + +body link_from ln_s(x) +# @brief Create a symbolink link to `x` +# The link is created even if the source of the link does not exist. +# @param x The source of the link +{ + link_type => "symlink"; + source => "$(x)"; + when_no_source => "force"; +} + diff --git a/tests/acceptance/11_databases/00_syntax/missing_database_operation.cf b/tests/acceptance/11_databases/00_syntax/missing_database_operation.cf new file mode 100644 index 0000000000..c2ff5f06a5 --- /dev/null +++ b/tests/acceptance/11_databases/00_syntax/missing_database_operation.cf @@ -0,0 +1,40 @@ +# Test that missing database_operation does not segfault (Mantis #1046) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +bundle agent test +{ + databases: + "foobar" + database_type => "sql", + database_server => myserver; +} + +body database_server myserver +{ + db_server_owner => "owner"; + db_server_password => "password"; + db_server_host => "localhost"; + db_server_type => "postgres"; + db_server_connection_db => "postgres"; +} + +bundle agent check +{ + reports: + cfengine_3:: + "$(this.promise_filename) Pass"; +} +### PROJECT_ID: core +### CATEGORY_ID: 33 diff --git a/tests/acceptance/14_reports/00_output/001.cf b/tests/acceptance/14_reports/00_output/001.cf new file mode 100644 index 0000000000..21722d8e79 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/001.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test that printfile honors number_of_lines constraint (Issue 686) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "noshell"); +} + +####################################################### + +bundle agent check +{ + classes: + "firstline" expression => regcmp(".*firstline.*", "$(test.subout)"); + "nosecondline" not => regcmp(".*secondline.*", "$(test.subout)"); + "ok" and => { "firstline", "nosecondline" }; + + reports: + DEBUG:: + "$(test.subout)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +### PROJECT_ID: core +### CATEGORY_ID: 2 diff --git a/tests/acceptance/14_reports/00_output/001.cf.sub b/tests/acceptance/14_reports/00_output/001.cf.sub new file mode 100644 index 0000000000..6417b67c4d --- /dev/null +++ b/tests/acceptance/14_reports/00_output/001.cf.sub @@ -0,0 +1,20 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "test" }; + version => "1.0"; +} + +bundle agent test +{ +reports: + cfengine_3:: + "doesnotmatter" + printfile => p; +} + +body printfile p +{ +file_to_print => "$(this.promise_filename).in"; +number_of_lines => "1"; +} diff --git a/tests/acceptance/14_reports/00_output/001.cf.sub.in b/tests/acceptance/14_reports/00_output/001.cf.sub.in new file mode 100644 index 0000000000..ef5073bcc9 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/001.cf.sub.in @@ -0,0 +1,2 @@ +firstline +secondline diff --git a/tests/acceptance/14_reports/00_output/002.cf b/tests/acceptance/14_reports/00_output/002.cf new file mode 100644 index 0000000000..ae5b861e5d --- /dev/null +++ b/tests/acceptance/14_reports/00_output/002.cf @@ -0,0 +1,43 @@ +############################################################################## +# +# Redmine #2936: Check that list variables under reserved scope expand +# as they should in reports promises. +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + +} + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "noshell"); +} + +bundle agent check +{ + # If the output contains the string "$(sys.hardware_addresses)" then we + # failed to expand the variable! + classes: + "ok" not => regcmp(".*hardware_addresses.*", "$(test.subout)"); + +reports: + DEBUG:: + "$(test.subout)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 2 diff --git a/tests/acceptance/14_reports/00_output/002.cf.sub b/tests/acceptance/14_reports/00_output/002.cf.sub new file mode 100644 index 0000000000..2f2ac0ed9e --- /dev/null +++ b/tests/acceptance/14_reports/00_output/002.cf.sub @@ -0,0 +1,16 @@ +############################################################################## +# +# Redmine #2936: Check that list variables under reserved scope expand +# as they should in reports promises. +# +############################################################################## + + +body common control { + bundlesequence => {"example"}; +} + +bundle agent example { +reports: + "$(sys.hardware_addresses)"; +} diff --git a/tests/acceptance/14_reports/00_output/report_array.cf b/tests/acceptance/14_reports/00_output/report_array.cf new file mode 100644 index 0000000000..abb5aaa180 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_array.cf @@ -0,0 +1,42 @@ +# Check that reports are printed just once (Redmine#3446 https://cfengine.com/dev/issues/3446) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + +} + + +bundle agent test +{ +vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "noshell"); +} + + +bundle agent check +{ +classes: + "ok1" expression => regcmp(".*9b70d955f7e126bc082e4a99eb2b3d08e4ebc29a.*", "$(test.subout)"); + "ok2" expression => regcmp(".*18a307169ff8a691607846c8a19c361a37695773.*", "$(test.subout)"); + "ok3" expression => regcmp(".*79815eefd0b619c6b875edf68adc7ef0e00c1ad3.*", "$(test.subout)"); + + "ok" and => { "ok1", "ok2", "ok3" }; + +reports: + DEBUG:: + "Output from subtest: $(test.subout)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 2 diff --git a/tests/acceptance/14_reports/00_output/report_array.cf.sub b/tests/acceptance/14_reports/00_output/report_array.cf.sub new file mode 100644 index 0000000000..0bcef7380c --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_array.cf.sub @@ -0,0 +1,18 @@ +body common control +{ + bundlesequence => { set, run }; +} + +bundle agent set +{ + vars: + "array[key_scalar]" string => "9b70d955f7e126bc082e4a99eb2b3d08e4ebc29a"; + "array[key_slist]" slist => { "18a307169ff8a691607846c8a19c361a37695773", "79815eefd0b619c6b875edf68adc7ef0e00c1ad3" }; + "keys" slist => getindices(array); +} + +bundle agent run +{ + reports: + "key = $(set.keys), value = $(set.array[$(set.keys)])"; +} diff --git a/tests/acceptance/14_reports/00_output/report_nested_variable.cf b/tests/acceptance/14_reports/00_output/report_nested_variable.cf new file mode 100644 index 0000000000..54a26e4c94 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_nested_variable.cf @@ -0,0 +1,39 @@ +# Check that nested variables are reported (Redmine: https://cfengine.com/dev/issues/4247) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "any", + meta => { "redmine4247" }; + + vars: + !FAKE_PASS:: + "subout" string => execresult("$(sys.cf_agent) -b runme -Kf $(this.promise_filename).sub", "noshell"); + FAKE_PASS:: + "subout" string => execresult("$(sys.cf_agent) -b runme -Kf $(this.promise_filename).sub -DFAKE_PASS", "noshell"); +} + + +bundle agent check +{ + classes: + "ok" expression => regcmp(".*replaced-by=something_else.*", "$(test.subout)"); + + reports: + DEBUG:: + "$(test.subout)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 2 diff --git a/tests/acceptance/14_reports/00_output/report_nested_variable.cf.sub b/tests/acceptance/14_reports/00_output/report_nested_variable.cf.sub new file mode 100644 index 0000000000..f139e6755e --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_nested_variable.cf.sub @@ -0,0 +1,21 @@ +bundle agent old_bundle +{ + meta: + "tags" slist => { + "deprecated=3.6.0", + "deprecation-reason=Generic reimplementation", + "replaced-by=something_else", + }; + +} +bundle agent runme +{ + vars: + "deprecated" slist => bundlesmatching(".*", "deprecated.*"); + + reports: + !FAKE_PASS:: + "$(deprecated): $($(deprecated)_meta.tags)"; + FAKE_PASS:: + "$(deprecated): $(old_bundle_meta.tags)"; +} diff --git a/tests/acceptance/14_reports/00_output/report_once.cf b/tests/acceptance/14_reports/00_output/report_once.cf new file mode 100644 index 0000000000..0930e98564 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_once.cf @@ -0,0 +1,39 @@ +# Check that reports are printed just once (Redmine#3446 https://cfengine.com/dev/issues/3446) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + +} + + +bundle agent test +{ +vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "noshell"); +} + + +bundle agent check +{ +# If the output contains the string "ONCE" twice then we fail +classes: + "ok" not => regcmp(".*ONCE.*ONCE.*", "$(test.subout)"); + +reports: + DEBUG:: + "$(test.subout)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 2 diff --git a/tests/acceptance/14_reports/00_output/report_once.cf.sub b/tests/acceptance/14_reports/00_output/report_once.cf.sub new file mode 100644 index 0000000000..43e669680f --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_once.cf.sub @@ -0,0 +1,11 @@ +body common control +{ + bundlesequence => { run }; +} + +bundle agent run +{ + reports: + "should be printed just ONCE"; + "should be printed just ONCE"; +} diff --git a/tests/acceptance/14_reports/00_output/report_printfile_from_bot.cf b/tests/acceptance/14_reports/00_output/report_printfile_from_bot.cf new file mode 100644 index 0000000000..7f140f924a --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_printfile_from_bot.cf @@ -0,0 +1,34 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3558" } + string => "Test reports printfile with number_of_lines as negative number"; + + vars: + "actual" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "noshell"); + "expected" string => "R: Last three: +R: # Line 3 from bot +R: # Line 2 from bot +R: # Line 1 from bot"; +} + +bundle agent check +{ + classes: + "passed" expression => strcmp("$(test.expected)", "$(test.actual)"); + reports: + windows:: + "$(this.promise_filename) SFAIL/ENT-10433"; + DEBUG:: + "Expected: '$(test.expected)'"; + "Found: '$(test.actual)'"; + passed:: + "$(this.promise_filename) Pass"; + !passed:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/14_reports/00_output/report_printfile_from_bot.cf.sub b/tests/acceptance/14_reports/00_output/report_printfile_from_bot.cf.sub new file mode 100644 index 0000000000..24ed74d062 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_printfile_from_bot.cf.sub @@ -0,0 +1,21 @@ +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + reports: + "Last three:" + printfile => last_three("$(this.promise_filename)"); +} + +body printfile last_three(file) +{ + file_to_print => "$(file)"; + number_of_lines => "-3"; +} + +# Line 3 from bot +# Line 2 from bot +# Line 1 from bot diff --git a/tests/acceptance/14_reports/00_output/report_printfile_from_top.cf b/tests/acceptance/14_reports/00_output/report_printfile_from_top.cf new file mode 100644 index 0000000000..f0c0c3adf0 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_printfile_from_top.cf @@ -0,0 +1,34 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3558" } + string => "Test reports printfile with number_of_lines as positive number"; + + vars: + "actual" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub", "noshell"); + "expected" string => "R: First three: +R: # Line 1 from top +R: # Line 2 from top +R: # Line 3 from top"; +} + +bundle agent check +{ + classes: + "passed" expression => strcmp("$(test.expected)", "$(test.actual)"); + reports: + windows:: + "$(this.promise_filename) SFAIL/ENT-10433"; + DEBUG:: + "Expected: '$(test.expected)'"; + "Found: '$(test.actual)'"; + passed:: + "$(this.promise_filename) Pass"; + !passed:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/14_reports/00_output/report_printfile_from_top.cf.sub b/tests/acceptance/14_reports/00_output/report_printfile_from_top.cf.sub new file mode 100644 index 0000000000..f8240fa1d2 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/report_printfile_from_top.cf.sub @@ -0,0 +1,21 @@ +# Line 1 from top +# Line 2 from top +# Line 3 from top + +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + reports: + "First three:" + printfile => first_three("$(this.promise_filename)"); +} + +body printfile first_three(file) +{ + file_to_print => "$(file)"; + number_of_lines => "3"; +} diff --git a/tests/acceptance/14_reports/00_output/staging/report_in_common_bundle.cf b/tests/acceptance/14_reports/00_output/staging/report_in_common_bundle.cf new file mode 100644 index 0000000000..536856f0a4 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/staging/report_in_common_bundle.cf @@ -0,0 +1,38 @@ +# Check that reports are printed from common bundles (Redmine#3848 https://cfengine.com/dev/issues/3848) + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + +} + + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -b runme -Kf $(this.promise_filename).sub", "noshell"); +} + + +bundle agent check +{ + classes: + "ok" expression => regcmp(".*rhino_9d85a7796746fb3e2e2ac95f74e4b981564803de.*", "$(test.subout)"); + + reports: + DEBUG:: + "$(test.subout)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 2 diff --git a/tests/acceptance/14_reports/00_output/staging/report_in_common_bundle.cf.sub b/tests/acceptance/14_reports/00_output/staging/report_in_common_bundle.cf.sub new file mode 100644 index 0000000000..f314f5632b --- /dev/null +++ b/tests/acceptance/14_reports/00_output/staging/report_in_common_bundle.cf.sub @@ -0,0 +1,9 @@ +bundle agent runme +{ +} + +bundle common report +{ + reports: + "$(this.bundle): rhino_9d85a7796746fb3e2e2ac95f74e4b981564803de"; +} diff --git a/tests/acceptance/14_reports/00_output/unresolved_vars.cf b/tests/acceptance/14_reports/00_output/unresolved_vars.cf new file mode 100644 index 0000000000..475b773ac4 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/unresolved_vars.cf @@ -0,0 +1,57 @@ +####################################################### +# +# Test that reports promises handle unresolved var refs in a sane way +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "expected_lines" slist => { + "R: var1: val1", + "R: var2: val2", + "R: var3: val3", + "R: var4: val4", + "R: var5: $(const.dollar)(var5)" + }; + + "expected_output" string => join("$(const.n)", expected_lines); + + files: + "$(G.testfile).expected" content => "$(expected_output)$(const.n)"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3776" } + string => "Test that reports with unresolved variables are only emitted during the last pass of evaluation"; + + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + commands: + "$(sys.cf_agent) -Kf $(this.promise_filename).sub > $(G.testfile).actual" + contain => shell; +} + +body contain shell +{ + useshell => "true"; +} + + +bundle agent check +{ + methods: + "" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/14_reports/00_output/unresolved_vars.cf.sub b/tests/acceptance/14_reports/00_output/unresolved_vars.cf.sub new file mode 100644 index 0000000000..93e4c6a3f9 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/unresolved_vars.cf.sub @@ -0,0 +1,23 @@ +bundle agent __main__ { + vars: + any:: + "var1" string => "val1"; + def_var2:: + "var2" string => "val2"; + def_var3:: + "var3" string => "val3"; + def_var4:: + "var4" string => "val4"; + + classes: + "def_var2" expression => isvariable("var1"); + "def_var3" expression => isvariable("var2"); + "def_var4" expression => isvariable("var3"); + + reports: + "var1: $(var1)"; + "var2: $(var2)"; + "var3: $(var3)"; + "var4: $(var4)"; + "var5: $(var5)"; +} diff --git a/tests/acceptance/14_reports/00_output/unresolved_with.cf b/tests/acceptance/14_reports/00_output/unresolved_with.cf new file mode 100644 index 0000000000..ba6ff0480b --- /dev/null +++ b/tests/acceptance/14_reports/00_output/unresolved_with.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test that reports promises handle unresolved var refs in '$(with)' +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + vars: + "expected_output" string => 'R: y: { "@{x}" }'; + + files: + "$(G.testfile).expected" content => "$(expected_output)$(const.n)"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3776" } + string => "Test that with reports its content during the last pass even when that content has unresolved variables "; + + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + + commands: + "$(sys.cf_agent) -Kf $(this.promise_filename).sub > $(G.testfile).actual" + contain => shell; +} + +body contain shell +{ + useshell => "true"; +} + + +bundle agent check +{ + methods: + "" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/14_reports/00_output/unresolved_with.cf.sub b/tests/acceptance/14_reports/00_output/unresolved_with.cf.sub new file mode 100644 index 0000000000..dd58989738 --- /dev/null +++ b/tests/acceptance/14_reports/00_output/unresolved_with.cf.sub @@ -0,0 +1,7 @@ +bundle agent __main__ { + vars: + "y" slist => { @{x} }; + + reports: + "y: $(with)" with => format("%S", y); +} diff --git a/tests/acceptance/14_reports/failed_reports_are_not_kept.cf b/tests/acceptance/14_reports/failed_reports_are_not_kept.cf new file mode 100644 index 0000000000..b32749b0c8 --- /dev/null +++ b/tests/acceptance/14_reports/failed_reports_are_not_kept.cf @@ -0,0 +1,65 @@ +# Check that reports are printed just once (Redmine#3446 https://cfengine.com/dev/issues/3446) + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + handle => "init_testfile"; + + commands: + "/usr/bin/chattr" + args => "+i $(G.testfile)", + comment => "The file needs to be immutable so that we can accurately test it."; +} + +bundle agent test +{ + meta: + "description" + string => "Test that we do not conisder failure to report to a file a kept outcome"; + + # this test should be skipped on platforms that do not have chattr! + "test_soft_fail" + string => "any", + meta => { "redmine7833" }; + + reports: + "Hello World" + report_to_file => "$(G.testfile)", + classes => scoped_classes_generic("namespace", "report_to_file"); +} + + +bundle agent check +{ + classes: + "ok" not => "report_to_file_kept"; + + commands: + "/usr/bin/chattr" + args => "-i $(G.testfile)", + handle => "remove_immutable_bit", + comment => "So that we don't mess up other tests lets be sure to clean up + after ourselves"; + + files: + "$(G.testfile)" + depends_on => { "remove_immutable_bit" }, + delete => tidy; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} +### PROJECT_ID: core +### CATEGORY_ID: 2 diff --git a/tests/acceptance/14_reports/report_to_file_suppresses_report_to_stdout.cf b/tests/acceptance/14_reports/report_to_file_suppresses_report_to_stdout.cf new file mode 100644 index 0000000000..d00240010e --- /dev/null +++ b/tests/acceptance/14_reports/report_to_file_suppresses_report_to_stdout.cf @@ -0,0 +1,53 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common test_meta +{ + vars: + "description" string => "Test that report_to_file attribute suppresses report on stdout."; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => tidy; +} + +bundle agent test +{ + vars: + "subout" string => execresult("$(sys.cf_agent) -Kf $(this.promise_filename).sub -DAUTO", "noshell"); +} + +bundle agent check +{ + + classes: + "not_found_report_in_agent_output" + not => regcmp(".*This should not be reported to stdout.*", "$(test.subout)"); + + "sub_ok" expression => regcmp(".*Pass", "$(test.subout)"); + + "ok" and => { "sub_ok", "not_found_report_in_agent_output" }; + + reports: + DEBUG:: + "DEBUG: sub output $(const.n)$(test.subout)"; + + sub_ok.DEBUG:: + "DEBUG: subtest passed"; + + not_found_report_in_agent_output.DEBUG:: + "DEBUG: Did not find report in agent output as expected"; + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/14_reports/report_to_file_suppresses_report_to_stdout.cf.sub b/tests/acceptance/14_reports/report_to_file_suppresses_report_to_stdout.cf.sub new file mode 100644 index 0000000000..a5fd0d9999 --- /dev/null +++ b/tests/acceptance/14_reports/report_to_file_suppresses_report_to_stdout.cf.sub @@ -0,0 +1,31 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + reports: + "This should not be reported to stdout" + report_to_file => "$(G.testfile)"; +} + +bundle agent check +{ + vars: + "count_reports_in_file" + int => countlinesmatching("This should not be reported to stdout", "$(G.testfile)"); + + classes: + "ok" expression => isgreaterthan($(count_reports_in_file), 0); + + reports: + DEBUG:: + "DEBUG: Found '$(count_reports_in_file)' lines matching 'This should not be reported to stdout' in '$(G.testfile)'"; + + ok:: + "$(this.promise_filename) Pass"; +} + diff --git a/tests/acceptance/14_reports/reports_comments_emitted_in_verbose.cf b/tests/acceptance/14_reports/reports_comments_emitted_in_verbose.cf new file mode 100644 index 0000000000..f4714973b4 --- /dev/null +++ b/tests/acceptance/14_reports/reports_comments_emitted_in_verbose.cf @@ -0,0 +1,22 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent test +{ + meta: + "description" string => "Test that comments on reports are emitted in verbose output."; + +} + +####################################################### + +bundle agent check +{ + methods: + "" usebundle => dcs_passif_output(".*This is a comment about a report.*", "", "$(sys.cf_agent) -Kvf $(this.promise_filename).sub", $(this.promise_filename)); +} diff --git a/tests/acceptance/14_reports/reports_comments_emitted_in_verbose.cf.sub b/tests/acceptance/14_reports/reports_comments_emitted_in_verbose.cf.sub new file mode 100644 index 0000000000..a81bc57953 --- /dev/null +++ b/tests/acceptance/14_reports/reports_comments_emitted_in_verbose.cf.sub @@ -0,0 +1,6 @@ +bundle agent main +{ + reports: + "This is an example report" + comment => "This is a comment about a report"; +} diff --git a/tests/acceptance/15_control/01_common/001.x.cf b/tests/acceptance/15_control/01_common/001.x.cf new file mode 100644 index 0000000000..c1a1186a3b --- /dev/null +++ b/tests/acceptance/15_control/01_common/001.x.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test ignore_missing_bundles - control case +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "missing", default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/002.x.cf b/tests/acceptance/15_control/01_common/002.x.cf new file mode 100644 index 0000000000..c5e01c927d --- /dev/null +++ b/tests/acceptance/15_control/01_common/002.x.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test ignore_missing_bundles - explicit failure expected +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "missing", default("$(this.promise_filename)") }; + version => "1.0"; + ignore_missing_bundles => "off"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/003.cf b/tests/acceptance/15_control/01_common/003.cf new file mode 100644 index 0000000000..62f1e74368 --- /dev/null +++ b/tests/acceptance/15_control/01_common/003.cf @@ -0,0 +1,48 @@ +####################################################### +# +# Test ignore_missing_bundles, expect to pass +# +####################################################### + +body common control +{ + ignore_missing_bundles => "on"; + + inputs => { "../../default.cf.sub" }; + bundlesequence => { "missing", default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/004.cf b/tests/acceptance/15_control/01_common/004.cf new file mode 100644 index 0000000000..aec0e1ed4f --- /dev/null +++ b/tests/acceptance/15_control/01_common/004.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test ignore_missing_bundles, expect to pass, order independent +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "missing", default("$(this.promise_filename)") }; + version => "1.0"; + ignore_missing_bundles => "on"; # Order should not matter! +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/007.cf b/tests/acceptance/15_control/01_common/007.cf new file mode 100644 index 0000000000..bd6f387619 --- /dev/null +++ b/tests/acceptance/15_control/01_common/007.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test ignore_missing_bundles, expect to pass, methods +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + ignore_missing_bundles => "on"; +} + +####################################################### +# +# bundle agent init +# { +# vars: +# "dummy" string => "dummy"; +# } +# +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/011.x.cf b/tests/acceptance/15_control/01_common/011.x.cf new file mode 100644 index 0000000000..39d983688c --- /dev/null +++ b/tests/acceptance/15_control/01_common/011.x.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test ignore_missing_inputs - control case +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "missing" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/012.x.cf b/tests/acceptance/15_control/01_common/012.x.cf new file mode 100644 index 0000000000..df798e30bf --- /dev/null +++ b/tests/acceptance/15_control/01_common/012.x.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test ignore_missing_inputs - explicit failure expected +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "missing" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + ignore_missing_inputs => "off"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/013.cf b/tests/acceptance/15_control/01_common/013.cf new file mode 100644 index 0000000000..93a1a80118 --- /dev/null +++ b/tests/acceptance/15_control/01_common/013.cf @@ -0,0 +1,48 @@ +####################################################### +# +# Test ignore_missing_inputs, expect to pass +# +####################################################### + +body common control +{ + ignore_missing_inputs => "on"; + + inputs => { "../../default.cf.sub", "missing" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/014.cf b/tests/acceptance/15_control/01_common/014.cf new file mode 100644 index 0000000000..1ef38ab36c --- /dev/null +++ b/tests/acceptance/15_control/01_common/014.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test ignore_missing_inputs, expect to pass, order independent +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "missing" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + ignore_missing_inputs => "on"; # Order should not matter! +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/021.cf b/tests/acceptance/15_control/01_common/021.cf new file mode 100644 index 0000000000..cc8e262b6e --- /dev/null +++ b/tests/acceptance/15_control/01_common/021.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test domain +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + domain => "puppetlabs.com"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "puppetlabs_com"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/022.cf b/tests/acceptance/15_control/01_common/022.cf new file mode 100644 index 0000000000..5521860be0 --- /dev/null +++ b/tests/acceptance/15_control/01_common/022.cf @@ -0,0 +1,49 @@ +####################################################### +# +# Test domain - expect it to set the $(sys.domain) variable +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + domain => "puppetlabs.com"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => strcmp("$(sys.domain)", "puppetlabs.com"); + + reports: + DEBUG:: + "Expected $(sys.domain) to be puppetlabs.com"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/default_bundlesequence.cf b/tests/acceptance/15_control/01_common/default_bundlesequence.cf new file mode 100644 index 0000000000..07e1846579 --- /dev/null +++ b/tests/acceptance/15_control/01_common/default_bundlesequence.cf @@ -0,0 +1,18 @@ +# Redmine#4993: default bundlesequence calls main() + +body common control +{ + inputs => { "../../default.cf.sub" }; +} + +bundle agent main +{ + methods: + "" usebundle => default("$(this.promise_filename)"); +} + +bundle agent check +{ + methods: + "" usebundle => dcs_pass("$(this.promise_filename)"); +} diff --git a/tests/acceptance/15_control/01_common/default_bundlesequence_namespace.x.cf b/tests/acceptance/15_control/01_common/default_bundlesequence_namespace.x.cf new file mode 100644 index 0000000000..ba79a405d9 --- /dev/null +++ b/tests/acceptance/15_control/01_common/default_bundlesequence_namespace.x.cf @@ -0,0 +1,17 @@ +# Redmine#4993: default bundlesequence calls main() + +body common control +{ + inputs => { "../../default.cf.sub" }; +} + +body file control +{ + namespace => "abc"; +} + +bundle agent main +{ + methods: + "" usebundle => default:dcs_pass("$(this.promise_filename)"); +} diff --git a/tests/acceptance/15_control/01_common/dynamic.sub b/tests/acceptance/15_control/01_common/dynamic.sub new file mode 100644 index 0000000000..3a46a4119f --- /dev/null +++ b/tests/acceptance/15_control/01_common/dynamic.sub @@ -0,0 +1,3 @@ +bundle agent dummy +{ +} diff --git a/tests/acceptance/15_control/01_common/inputs_directory.cf b/tests/acceptance/15_control/01_common/inputs_directory.cf new file mode 100644 index 0000000000..4c1ed541cc --- /dev/null +++ b/tests/acceptance/15_control/01_common/inputs_directory.cf @@ -0,0 +1,23 @@ +# Redmine#4683: ensure directories and missing files don't abort when +# ignore_missing_inputs is set + +body common control +{ + ignore_missing_inputs => "true"; + inputs => { "/x.cf", "/", "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/15_control/01_common/inputs_from_json.cf b/tests/acceptance/15_control/01_common/inputs_from_json.cf new file mode 100644 index 0000000000..c286aebfc1 --- /dev/null +++ b/tests/acceptance/15_control/01_common/inputs_from_json.cf @@ -0,0 +1,33 @@ +# Redmine#4683: dynamic inputs should work from a data container + +body common control +{ + #ignore_missing_inputs => "true"; + inputs => { "../../default.cf.sub", "$(config.myfile)" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle common config +{ + vars: + "c" data => parsejson('{ "file": "dynamic.sub" }'); + "myfile" string => "$(c[file])"; + + reports: + DEBUG:: + "myfile $(myfile)"; +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf b/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf new file mode 100644 index 0000000000..258266d5d0 --- /dev/null +++ b/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf @@ -0,0 +1,58 @@ +# Redmine#4683: dynamic inputs should work from readstringlist + +body common control +{ + inputs => { "../../default.cf.sub", @(runlist.inputs) }; + bundlesequence => { prep_runlist, runlist, default("$(this.promise_filename)") }; +} + +# workaround: reading the data has to be separate from... +bundle common prep_runlist +{ + vars: + "generated_inputs" string => "$(this.promise_filename).txt"; + "inputs_pre" slist => readstringlist($(generated_inputs), + "\s*#[^\n]*", + "\n", + 99999, + 9999999); + reports: + EXTRA:: + "$(this.bundle) reading inputs from file $(generated_inputs)"; +} + +# ...the usage of the data +bundle common runlist +{ + vars: + "inputs" slist => maplist("$(this.promise_dirname)/$(this)", "prep_runlist.inputs_pre"); + + reports: + EXTRA:: + "$(this.bundle) got inputs '$(inputs)'"; +} + +bundle agent init +{ +} + +bundle agent test +{ +} + +bundle agent check +{ + classes: + "ok" and => { "included_info_global_class" }; + + reports: + DEBUG.!included_info_global_class:: + "BAD: The global class from the included file was not defined"; + EXTRA.included_info_global_class:: + "Good: The global class from the included file was defined"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf.sub b/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf.sub new file mode 100644 index 0000000000..1dde9cb82e --- /dev/null +++ b/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf.sub @@ -0,0 +1,5 @@ +bundle common info +{ + classes: + "included_info_global_class" expression => "any"; +} diff --git a/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf.txt b/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf.txt new file mode 100644 index 0000000000..a2590cf331 --- /dev/null +++ b/tests/acceptance/15_control/01_common/inputs_from_readstringlist.cf.txt @@ -0,0 +1 @@ +inputs_from_readstringlist.cf.sub diff --git a/tests/acceptance/15_control/01_common/missing_inputs_directory.x.cf b/tests/acceptance/15_control/01_common/missing_inputs_directory.x.cf new file mode 100644 index 0000000000..4a70dcf040 --- /dev/null +++ b/tests/acceptance/15_control/01_common/missing_inputs_directory.x.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test ignore_missing_inputs with a directory - explicit failure expected +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "/" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + ignore_missing_inputs => "off"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/staging/005.x.cf b/tests/acceptance/15_control/01_common/staging/005.x.cf new file mode 100644 index 0000000000..393f4834cb --- /dev/null +++ b/tests/acceptance/15_control/01_common/staging/005.x.cf @@ -0,0 +1,46 @@ +####################################################### +# +# Test ignore_missing_bundles - control case, methods +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### +# +# bundle agent init +# { +# vars: +# "dummy" string => "dummy"; +# } +# +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/01_common/staging/006.x.cf b/tests/acceptance/15_control/01_common/staging/006.x.cf new file mode 100644 index 0000000000..d3134d00c7 --- /dev/null +++ b/tests/acceptance/15_control/01_common/staging/006.x.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Test ignore_missing_bundles - explicit failure expected, methods +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; + ignore_missing_bundles => "off"; +} + +####################################################### +# +# bundle agent init +# { +# vars: +# "dummy" string => "dummy"; +# } +# +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/001.cf b/tests/acceptance/15_control/02_agent/001.cf new file mode 100644 index 0000000000..06a558d8b0 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/001.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Test abortclasses - control case 1, no abort +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quitquit", "quit.*" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/002.cf b/tests/acceptance/15_control/02_agent/002.cf new file mode 100644 index 0000000000..8ca3374299 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/002.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test abortclasses - control case 2, no abort +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quitquit", "quitquit.*" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "quit" expression => "any"; # Validate no submatch +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/002.x.cf b/tests/acceptance/15_control/02_agent/002.x.cf new file mode 100644 index 0000000000..e67a4f3db1 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/002.x.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test abortclasses regexp - should abort in preliminary agent bundle +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quitquit.*" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) FAIL"; + !ok:: + "$(this.promise_filename) Pass"; +} + diff --git a/tests/acceptance/15_control/02_agent/003.cf b/tests/acceptance/15_control/02_agent/003.cf new file mode 100644 index 0000000000..88ad43f2b6 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/003.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test abortclasses - control case 3, no abort +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quit" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; # Validate no submatch, reverse +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/003.x.cf b/tests/acceptance/15_control/02_agent/003.x.cf new file mode 100644 index 0000000000..0a082e6175 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/003.x.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test abortclasses regexp - should abort in same agent bundle +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quitquit.*" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + "quitquit" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) FAIL"; + !ok:: + "$(this.promise_filename) Pass"; +} + diff --git a/tests/acceptance/15_control/02_agent/004.x.cf b/tests/acceptance/15_control/02_agent/004.x.cf new file mode 100644 index 0000000000..ac6a3bf27a --- /dev/null +++ b/tests/acceptance/15_control/02_agent/004.x.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test abortclasses - should abort in common bundle +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quitquit" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/005.x.cf b/tests/acceptance/15_control/02_agent/005.x.cf new file mode 100644 index 0000000000..cf46ba573d --- /dev/null +++ b/tests/acceptance/15_control/02_agent/005.x.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test abortclasses - should abort in common bundle, regex +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quit.*" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/006.x.cf b/tests/acceptance/15_control/02_agent/006.x.cf new file mode 100644 index 0000000000..28a3af0911 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/006.x.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test abortclasses - should abort in preliminary agent bundle +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quitquit" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/007.x.cf b/tests/acceptance/15_control/02_agent/007.x.cf new file mode 100644 index 0000000000..0ffa6aaf98 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/007.x.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test abortclasses - should abort in preliminary agent bundle, regex +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quit.*" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/008.x.cf b/tests/acceptance/15_control/02_agent/008.x.cf new file mode 100644 index 0000000000..35255ff31a --- /dev/null +++ b/tests/acceptance/15_control/02_agent/008.x.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test abortclasses - should abort in same agent bundle +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quitquit" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + "quitquit" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/009.x.cf b/tests/acceptance/15_control/02_agent/009.x.cf new file mode 100644 index 0000000000..62465ac6a3 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/009.x.cf @@ -0,0 +1,22 @@ +body common control +{ + bundlesequence => { test }; +} + +bundle common g +{ +classes: + "foo" and => { "any" }; +} + +body agent control +{ + abortclasses => { "foo" }; +} + +bundle agent test { + +reports: + foo:: + "Aborting class defined"; +} diff --git a/tests/acceptance/15_control/02_agent/021.cf b/tests/acceptance/15_control/02_agent/021.cf new file mode 100644 index 0000000000..2787f5189e --- /dev/null +++ b/tests/acceptance/15_control/02_agent/021.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Test abortbundleclasses - control case 1, no abort +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortbundleclasses => { "quitquit", "quitquit.*" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/022.cf b/tests/acceptance/15_control/02_agent/022.cf new file mode 100644 index 0000000000..ef43286be2 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/022.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test abortbundleclasses - control case 2, no abort +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortbundleclasses => { "quitquit", "quitquit.*" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + "quit" expression => "any"; # Validate no submatch + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/023.cf b/tests/acceptance/15_control/02_agent/023.cf new file mode 100644 index 0000000000..bff90f71f9 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/023.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test abortbundleclasses - control case 3, no abort +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortbundleclasses => { "quit" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; # Validate no submatch, reverse +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/031.cf b/tests/acceptance/15_control/02_agent/031.cf new file mode 100644 index 0000000000..0f52ac1347 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/031.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Test addclasses +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + addclasses => { "ok" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; # Prevent reports from running + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/109.x.cf b/tests/acceptance/15_control/02_agent/109.x.cf new file mode 100644 index 0000000000..c046e1360f --- /dev/null +++ b/tests/acceptance/15_control/02_agent/109.x.cf @@ -0,0 +1,51 @@ +####################################################### +# +# Test abortclasses - should abort in same agent bundle, regex +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quit.*" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + "quitquit" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/abortbundle_classes.cf b/tests/acceptance/15_control/02_agent/abortbundle_classes.cf new file mode 100644 index 0000000000..c6c59f0001 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortbundle_classes.cf @@ -0,0 +1,28 @@ +########################################################################## +# +# Test that bundles will abort when an abortbundleclass is found to match +# exactly. Only the one bundle should be aborted, not the entire agent +# execution. +# +########################################################################## + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*aborted on defined class 'abort_bundle'.*PASS.*", ".*Should Never Reach.*", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/15_control/02_agent/abortbundle_classes.cf.sub b/tests/acceptance/15_control/02_agent/abortbundle_classes.cf.sub new file mode 100644 index 0000000000..d311f1ca06 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortbundle_classes.cf.sub @@ -0,0 +1,42 @@ +########################################################################## +# +# Test that bundles will abort when an abortbundleclass is found to match +# exactly. Only the one bundle should be aborted, not the entire agent +# execution. +# +########################################################################## + +body agent control +{ + abortbundleclasses => { "abort_bundle" }; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + classes: + "abort_bundle" expression => "any"; + + reports: + "Should Never Reach" + comment => "This report should not happen since the bundle should abort + first"; +} + +bundle agent check +{ + reports: + "PASS" + comment => "This should be reported, as only the previous bundle was + aborted not the whole agent execution"; +} diff --git a/tests/acceptance/15_control/02_agent/abortbundle_classexpression.cf b/tests/acceptance/15_control/02_agent/abortbundle_classexpression.cf new file mode 100644 index 0000000000..ba1faa66ff --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortbundle_classexpression.cf @@ -0,0 +1,29 @@ +########################################################################## +# +# Test that bundles will abort when a class expression is used with +# abortbundleclass and found to match . Only the one bundle should be aborted, +# not the entire agent execution. +# +########################################################################## + + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*Setting abort for 'abort_bundle.something_else' when setting class 'something_else'.*PASS.*", ".*Should Never Reach.*", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/15_control/02_agent/abortbundle_classexpression.cf.sub b/tests/acceptance/15_control/02_agent/abortbundle_classexpression.cf.sub new file mode 100644 index 0000000000..4518fb0269 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortbundle_classexpression.cf.sub @@ -0,0 +1,42 @@ +########################################################################## +# +# Test that bundles will abort when a class expression is used with +# abortbundleclass and found to match . Only the one bundle should be aborted, +# not the entire agent execution. +# +########################################################################## +body agent control +{ + abortbundleclasses => { "abort_bundle.something_else" }; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + classes: + "abort_bundle" expression => "any"; + "something_else" expression => "abort_bundle"; + + reports: + "Should Never Reach" + comment => "This report should not happen since the bundle should abort + first"; +} + +bundle agent check +{ + reports: + "PASS" + comment => "This should be reported, as only the previous bundle was + aborted not the whole agent execution"; +} diff --git a/tests/acceptance/15_control/02_agent/abortbundle_nomatchclassexpression.cf b/tests/acceptance/15_control/02_agent/abortbundle_nomatchclassexpression.cf new file mode 100644 index 0000000000..51f1220861 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortbundle_nomatchclassexpression.cf @@ -0,0 +1,27 @@ +########################################################################## +# +# Test that bundles will not abort when a class expression used in +# abortbundleclass does not match. +########################################################################## + + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*PASS.*", "", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/15_control/02_agent/abortbundle_nomatchclassexpression.cf.sub b/tests/acceptance/15_control/02_agent/abortbundle_nomatchclassexpression.cf.sub new file mode 100644 index 0000000000..fad72daf48 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortbundle_nomatchclassexpression.cf.sub @@ -0,0 +1,34 @@ +########################################################################## +# +# Test that bundles will not abort when a class expression used in +# abortbundleclass does not match. +########################################################################## + + +body agent control +{ + abortbundleclasses => { "abort_bundle.something_else" }; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + classes: + "abort_bundle" expression => "!any"; + "something_else" expression => "any"; + + reports: + "PASS" + comment => "This report should happen as abort_bundle.something_else is + not valid and the bundle should not abort."; +} diff --git a/tests/acceptance/15_control/02_agent/abortbundle_regex.cf b/tests/acceptance/15_control/02_agent/abortbundle_regex.cf new file mode 100644 index 0000000000..7ba8b0617b --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortbundle_regex.cf @@ -0,0 +1,29 @@ +########################################################################## +# +# Test that bundles will abort when an abortbundleclass is found to match +# a regular expression. Only the one bundle should be aborted, not the entire +# agent execution. +# +########################################################################## + + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -Kf $(this.promise_filename).sub -DAUTO"; + + methods: + "check" + usebundle => dcs_passif_output(".*aborted on defined class 'abort_bundle_please'.*PASS.*", ".*Should Never Reach.*", $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/15_control/02_agent/abortbundle_regex.cf.sub b/tests/acceptance/15_control/02_agent/abortbundle_regex.cf.sub new file mode 100644 index 0000000000..cbd20d8db9 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortbundle_regex.cf.sub @@ -0,0 +1,43 @@ +########################################################################## +# +# Test that bundles will abort when an abortbundleclass is found to match +# a regular expression. Only the one bundle should be aborted, not the entire +# agent execution. +# +########################################################################## + + +body agent control +{ + abortbundleclasses => { "abort_bundle.*" }; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + classes: + "abort_bundle_please" expression => "any"; + + reports: + "Should Never Reach" + comment => "This report should not happen since the bundle should abort + first"; +} + +bundle agent check +{ + reports: + "PASS" + comment => "This should be reported, as only the previous bundle was + aborted not the whole agent execution"; +} diff --git a/tests/acceptance/15_control/02_agent/abortclasses_even_without_classes.x.cf b/tests/acceptance/15_control/02_agent/abortclasses_even_without_classes.x.cf new file mode 100644 index 0000000000..4cc9397bd6 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortclasses_even_without_classes.x.cf @@ -0,0 +1,36 @@ +body common control +{ + bundlesequence => { init, test }; +} + +body agent control +{ + abortclasses => { "foo", "bar", "foobar" }; +} + +bundle agent init +{ + reports: + "Defining class indirectly foo" + classes => always("foo"); + "Defining class indirectly bar" + classes => always("bar"); + "Defining class indirectly foobar" + classes => always("foobar"); +} + +bundle agent test +{ + reports: + foo:: + "Aborting class defined"; +} + +body classes always(x) +{ + promise_repaired => { "$(x)" }; + promise_kept => { "$(x)" }; + repair_failed => { "$(x)" }; + repair_denied => { "$(x)" }; + repair_timeout => { "$(x)" }; +} diff --git a/tests/acceptance/15_control/02_agent/abortclasses_from_common_bundle.x.cf b/tests/acceptance/15_control/02_agent/abortclasses_from_common_bundle.x.cf new file mode 100644 index 0000000000..71b2ceb409 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/abortclasses_from_common_bundle.x.cf @@ -0,0 +1,50 @@ +####################################################### +# +# Test abortclasses regexp - should abort in preliminary common bundle +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortclasses => { "quitquit.*" }; +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle common test +{ + classes: + "quitquit" expression => "any"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; + + reports: + ok:: + "$(this.promise_filename) FAIL"; + !ok:: + "$(this.promise_filename) Pass"; +} + diff --git a/tests/acceptance/15_control/02_agent/staging/024.x.cf b/tests/acceptance/15_control/02_agent/staging/024.x.cf new file mode 100644 index 0000000000..592358d6ab --- /dev/null +++ b/tests/acceptance/15_control/02_agent/staging/024.x.cf @@ -0,0 +1,57 @@ +####################################################### +# +# Test abortbundleclasses - expect failure regex, ok should not be defined +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortbundleclasses => { "quit.*" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; # Should abort bundle + "ok" expression => "any"; # Should not be defined +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/staging/025.x.cf b/tests/acceptance/15_control/02_agent/staging/025.x.cf new file mode 100644 index 0000000000..b331e5775c --- /dev/null +++ b/tests/acceptance/15_control/02_agent/staging/025.x.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test abortbundleclasses - expect failure, report doesn't execute +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortbundleclasses => { "quitquit" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; # Globally set, but should still fail +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; # Prevent reports from running + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/staging/026.x.cf b/tests/acceptance/15_control/02_agent/staging/026.x.cf new file mode 100644 index 0000000000..f135ad74e8 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/staging/026.x.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test abortbundleclasses - expect failure, regex, report doesn't execute +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortbundleclasses => { "quit.*" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "ok" expression => "any"; # Globally set, but should still fail +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; # Prevent reports from running + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/02_agent/staging/027.x.cf b/tests/acceptance/15_control/02_agent/staging/027.x.cf new file mode 100644 index 0000000000..492a87a181 --- /dev/null +++ b/tests/acceptance/15_control/02_agent/staging/027.x.cf @@ -0,0 +1,57 @@ +####################################################### +# +# Test abortbundleclasses - expect failure, ok should not be defined +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body agent control { + abortbundleclasses => { "quitquit" }; +} + +bundle common g +{ + vars: + "dummy" string => "dummy"; + + classes: + "quitquit" expression => "any"; # Should abort bundle + "ok" expression => "any"; # Should not be defined +} + +####################################################### + +bundle agent init +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent test +{ + vars: + "dummy" string => "dummy"; +} + +####################################################### + +bundle agent check +{ + vars: + "dummy" string => "dummy"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/15_control/file/file_inputs.cf b/tests/acceptance/15_control/file/file_inputs.cf new file mode 100644 index 0000000000..43488ab8b3 --- /dev/null +++ b/tests/acceptance/15_control/file/file_inputs.cf @@ -0,0 +1,22 @@ +body common control +{ + ignore_missing_bundles => "yes"; + inputs => { "../../default.cf.sub" }; + bundlesequence => { "missing", default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +vars: + "file" string => "$(this.promise_filename)"; +} + +bundle agent test +{ +} + +body file control +{ + inputs => { "file_inputs.sub1" }; +} diff --git a/tests/acceptance/15_control/file/file_inputs.sub1 b/tests/acceptance/15_control/file/file_inputs.sub1 new file mode 100644 index 0000000000..15ef27e28d --- /dev/null +++ b/tests/acceptance/15_control/file/file_inputs.sub1 @@ -0,0 +1,13 @@ +bundle common sub1 +{ + vars: + # this is the same file twice, which should be OK with hash checks + "inputs" slist => { "$(this.promise_dirname)/file_inputs.sub2", + "$(this.promise_dirname)/./file_inputs.sub2" + }; +} + +body file control +{ + inputs => { @(sub1.inputs) }; +} diff --git a/tests/acceptance/15_control/file/file_inputs.sub2 b/tests/acceptance/15_control/file/file_inputs.sub2 new file mode 100644 index 0000000000..dfbb7d3818 --- /dev/null +++ b/tests/acceptance/15_control/file/file_inputs.sub2 @@ -0,0 +1,5 @@ +bundle agent check +{ +reports: + "$(init.file) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/001.cf b/tests/acceptance/16_cf-serverd/serial/001.cf new file mode 100644 index 0000000000..3cfa2ae037 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/001.cf @@ -0,0 +1,25 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/001.cf.sub b/tests/acceptance/16_cf-serverd/serial/001.cf.sub new file mode 100644 index 0000000000..282e9f5dc0 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/001.cf.sub @@ -0,0 +1,71 @@ +####################################################### +# +# Test cf-serverd related promises, mtime simple copy localhost +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" copy_from => copy_src_file("classic"); + "$(G.testdir)/destfile_latest" copy_from => copy_src_file("latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "mtime"; + copy_backup => "false"; + protocol_version => "$(protocol_version)"; + + portnumber => "9876"; # localhost_open + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + same_classic.same_latest:: + "$(fn[1]) Pass"; + !same_classic|!same_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/002.cf b/tests/acceptance/16_cf-serverd/serial/002.cf new file mode 100644 index 0000000000..76798cb862 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/002.cf @@ -0,0 +1,27 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + + # We generate key in-between the two files on purpose, to + # introduce latency and make sure that destination file is newer + "any" usebundle => generate_key; + + "any" usebundle => file_make("$(G.testdir)/destfile_classic", + "This is the source file to copy $(sys.date) - different!"); + "any" usebundle => file_make("$(G.testdir)/destfile_latest", + "This is the source file to copy $(sys.date) - different!"); + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/002.cf.sub b/tests/acceptance/16_cf-serverd/serial/002.cf.sub new file mode 100644 index 0000000000..7ac87c03f9 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/002.cf.sub @@ -0,0 +1,67 @@ +####################################################### +# +# mtime, newer destination, localhost, should not copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +bundle agent test + +{ + files: + + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "mtime"; + copy_backup => "false"; + + portnumber => "9876"; # localhost_open + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !copied_classic.!copied_latest:: + "$(fn[1]) Pass"; + copied_classic|copied_latest:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/003.cf b/tests/acceptance/16_cf-serverd/serial/003.cf new file mode 100644 index 0000000000..20c74fc150 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/003.cf @@ -0,0 +1,24 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_deny_connect.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_deny_connect.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/003.cf.sub b/tests/acceptance/16_cf-serverd/serial/003.cf.sub new file mode 100644 index 0000000000..a0756fe3d1 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/003.cf.sub @@ -0,0 +1,68 @@ +####################################################### +# +# mtime server copy, localhost, explicit deny connect access, should not copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "mtime"; + copy_backup => "false"; + + portnumber => "9877"; + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !copied_classic.!copied_latest:: + "$(fn[1]) Pass"; + copied_classic|copied_latest:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/005.cf b/tests/acceptance/16_cf-serverd/serial/005.cf new file mode 100644 index 0000000000..89a0b23a86 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/005.cf @@ -0,0 +1,24 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/lan_deny_connect.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/lan_deny_connect.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/005.cf.sub b/tests/acceptance/16_cf-serverd/serial/005.cf.sub new file mode 100644 index 0000000000..b1023bada0 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/005.cf.sub @@ -0,0 +1,69 @@ +####################################################### +# +# mtime server copy, for non-localhost, no connect access, should not copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + # localhost comes first but will be denied + servers => { @(sys.ip_addresses) }; + compare => "mtime"; + copy_backup => "false"; + + portnumber => "9879"; # lan_deny_connect + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !copied_classic.!copied_latest:: + "$(fn[1]) Pass"; + copied_classic|copied_latest:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/006.cf b/tests/acceptance/16_cf-serverd/serial/006.cf new file mode 100644 index 0000000000..b9d298dc7f --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/006.cf @@ -0,0 +1,24 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/006.cf.sub b/tests/acceptance/16_cf-serverd/serial/006.cf.sub new file mode 100644 index 0000000000..6fdec3c9a3 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/006.cf.sub @@ -0,0 +1,80 @@ +####################################################### +# +# mtime server copy, localhost, trustkey = false, should not copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + + # WAZA! I can't enable both protocols to be tested because the second + # will always succeed! Here is why: + + # cf-serverd and cf-agent are running in the same tree, so they have + # the same ID! (same localhost.priv,pub) So when we (the agent) + # contact the server 1st time, we drop the connection because we don't + # trust him, but *his* trust is open and he stores the proper + # MD5-xxx.pub file that identifies us. BUT THIS FILE IDENTIFIES THE + # SERVER AS WELL. So on the second (latest protocol) connection + # attempt, we (the agent) find that file and assume we have + # established trust with the server. Thus this test fails... + + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + # "$(G.testdir)/destfile_latest" + # copy_from => copy_src_file("latest"), + # classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "mtime"; + copy_backup => "false"; + #trustkey => "true"; + + portnumber => "9876"; # localhost_open + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !copied_classic.!copied_latest:: + "$(fn[1]) Pass"; + copied_classic|copied_latest:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/007.cf b/tests/acceptance/16_cf-serverd/serial/007.cf new file mode 100644 index 0000000000..1e8a9090d2 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/007.cf @@ -0,0 +1,28 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/007.cf.sub b/tests/acceptance/16_cf-serverd/serial/007.cf.sub new file mode 100644 index 0000000000..c44039d118 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/007.cf.sub @@ -0,0 +1,78 @@ +####################################################### +# +# digest server copy, localhost, newer destination, should copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test + +{ + files: + + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + + portnumber => "9876"; # localhost_open + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + same_classic.same_latest:: + "$(fn[1]) Pass"; + !same_classic|!same_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/008.cf b/tests/acceptance/16_cf-serverd/serial/008.cf new file mode 100644 index 0000000000..1e8a9090d2 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/008.cf @@ -0,0 +1,28 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/008.cf.sub b/tests/acceptance/16_cf-serverd/serial/008.cf.sub new file mode 100644 index 0000000000..31e2ddd790 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/008.cf.sub @@ -0,0 +1,78 @@ +####################################################### +# +# Test cf-serverd related promises, mtime simple copy localhost, should copy +# +# Tests secure copy (SGET) and secure stat (SSYNCH). +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +bundle agent test + +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "mtime"; + copy_backup => "false"; + + portnumber => "9876"; # localhost_open + + encrypt => "true"; + + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + same_classic.same_latest:: + "$(fn[1]) Pass"; + !same_classic|!same_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/009.cf b/tests/acceptance/16_cf-serverd/serial/009.cf new file mode 100644 index 0000000000..d657fbbbc0 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/009.cf @@ -0,0 +1,19 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + "any" usebundle => dcs_fini("$(G.testdir)/destination_file"); + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open_with_data_select.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open_with_data_select.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/009.cf.sub b/tests/acceptance/16_cf-serverd/serial/009.cf.sub new file mode 100644 index 0000000000..e44b3514a7 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/009.cf.sub @@ -0,0 +1,38 @@ +####################################################### +# +# Test cf-serverd related promises, mtime simple copy localhost +# TODO: implement +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + reports: +} + +####################################################### + +bundle agent test +{ + reports: +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/010.cf b/tests/acceptance/16_cf-serverd/serial/010.cf new file mode 100644 index 0000000000..1e8a9090d2 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/010.cf @@ -0,0 +1,28 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/010.cf.sub b/tests/acceptance/16_cf-serverd/serial/010.cf.sub new file mode 100644 index 0000000000..aa96ecda4d --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/010.cf.sub @@ -0,0 +1,77 @@ +####################################################### +# +# Test cf-serverd related promises +# +# Tests copy_from encrypted digest verify (SMD5) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + copy_backup => "false"; + + portnumber => "9876"; # localhost_open + + encrypt => "true"; + compare => "digest"; + verify => "true"; + + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + same_classic.same_latest:: + "$(fn[1]) Pass"; + !same_classic|!same_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/011.cf b/tests/acceptance/16_cf-serverd/serial/011.cf new file mode 100644 index 0000000000..1e8a9090d2 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/011.cf @@ -0,0 +1,28 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/011.cf.sub b/tests/acceptance/16_cf-serverd/serial/011.cf.sub new file mode 100644 index 0000000000..69463e98b2 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/011.cf.sub @@ -0,0 +1,77 @@ +####################################################### +# +# Test cf-serverd related promises +# +# Tests copy_from digest verify (MD5) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + copy_backup => "false"; + + portnumber => "9876"; # localhost_open + + encrypt => "false"; + compare => "digest"; + verify => "true"; + + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + same_classic.same_latest:: + "$(fn[1]) Pass"; + !same_classic|!same_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/README b/tests/acceptance/16_cf-serverd/serial/README new file mode 100644 index 0000000000..e1d204bcc7 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/README @@ -0,0 +1,8 @@ +001 - mtime simple copy, localhost +002 - mtime, newer destination, localhost, should not copy +003 - mtime server copy, localhost, explicit deny connect access, should not copy +004 - mtime server copy, for non-localhost, should copy +005 - mtime server copy, for non-localhost, no connect access, should not copy +006 - mtime server copy, localhost, no file access promise, should not copy +007 - digest server copy, localhost, newer destination, should copy +008 - mtime simple copy, localhost with encryption diff --git a/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_a.cf b/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_a.cf new file mode 100644 index 0000000000..491bf82ffc --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_a.cf @@ -0,0 +1,39 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { create_directories, default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file - on purpose in G.testroot instead of G.testdir + # since the former is admitted while the latter is denied. + "any" usebundle => file_make("$(G.testroot)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/server1_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/server1_latest"); + "any" usebundle => dcs_fini("$(G.testdir)/server2_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/server2_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_deny_one_directory.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/localhost_deny_one_directory_with_regex.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_deny_one_directory.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_deny_one_directory_with_regex.srv"); +} + +# For the access rules in cf-serverd to work recursively, the paths must exist and be directories +bundle agent create_directories +{ + files: + "$(G.testroot)/." + create => "true"; + "$(G.testdir)/." + create => "true"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_a.cf.sub b/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_a.cf.sub new file mode 100644 index 0000000000..cf3013a179 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_a.cf.sub @@ -0,0 +1,70 @@ +####################################################### +# +# We request a path from allowed path, it should succeed +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} +bundle agent init +{ +} + +####################################################### + +body copy_from copy_from_port(port, protocol_version) + +{ + portnumber => "$(port)"; + protocol_version => "$(protocol_version)"; + + # testroot dir is admitted on the server, while G.testdir is denied + source => "$(G.testroot)/source_file"; + servers => { "127.0.0.1" }; + trustkey => "true"; +} + +bundle agent test +{ + files: + # localhost_deny_one_directory + "$(G.testdir)/server1_classic" copy_from => copy_from_port("9881", "classic"); + "$(G.testdir)/server1_latest" copy_from => copy_from_port("9881", "latest"); + # localhost_deny_one_directory_with_regex + "$(G.testdir)/server2_classic" copy_from => copy_from_port("9882", "classic"); + "$(G.testdir)/server2_latest" copy_from => copy_from_port("9882", "latest"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testroot)/source_file", "$(G.testdir)/server1_classic", + "no", "same_classic1", "differ_classic1"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testroot)/source_file", "$(G.testdir)/server1_latest", + "no", "same_latest1", "differ_latest1"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testroot)/source_file", "$(G.testdir)/server2_classic", + "no", "same_classic2", "differ_classic2"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testroot)/source_file", "$(G.testdir)/server2_latest", + "no", "same_latest2", "differ_latest2"); + + reports: + + same_classic1.same_latest1.same_classic2.same_latest2:: + "$(fn[1]) Pass"; + !same_classic1|!same_latest1|!same_classic2|!same_latest2:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_b.cf b/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_b.cf new file mode 100644 index 0000000000..ea30667cac --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_b.cf @@ -0,0 +1,40 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { create_directories, default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file - G.testdir is denied. + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/server1_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/server1_latest"); + "any" usebundle => dcs_fini("$(G.testdir)/server2_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/server2_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_deny_one_directory.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/localhost_deny_one_directory_with_regex.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_deny_one_directory.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_deny_one_directory_with_regex.srv"); +} + + +# For the access rules in cf-serverd to work recursively, the paths must exist and be directories +bundle agent create_directories +{ + files: + "$(G.testroot)/." + create => "true"; + "$(G.testdir)/." + create => "true"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_b.cf.sub b/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_b.cf.sub new file mode 100644 index 0000000000..e90426172b --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/allow_path1_then_deny_path2_b.cf.sub @@ -0,0 +1,66 @@ +####################################################### +# +# We request a path from disallowed path, it should fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +####################################################### + +body copy_from copy_from_port(port, protocol_version) + +{ + portnumber => "$(port)"; + protocol_version => "$(protocol_version)"; + + # testroot dir is admitted on the server, while G.testdir is denied + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + trustkey => "true"; +} + +bundle agent test +{ + files: + # localhost_deny_one_directory + "$(G.testdir)/server1_classic" + copy_from => copy_from_port("9881", "classic"), + classes => if_repaired("copied1_classic"); + "$(G.testdir)/server1_latest" + copy_from => copy_from_port("9881", "latest"), + classes => if_repaired("copied1_latest"); + # localhost_deny_one_directory_with_regex + "$(G.testdir)/server2_classic" + copy_from => copy_from_port("9882", "classic"), + classes => if_repaired("copied2_classic"); + "$(G.testdir)/server2_latest" + copy_from => copy_from_port("9882", "latest"), + classes => if_repaired("copied2_latest"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => + regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + # All copies must fail + !copied1_classic.!copied1_latest.!copied2_classic.!copied2_latest:: + "$(fn[1]) Pass"; + copied1_classic|copied1_latest|copied2_classic|copied2_latest:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_absent.srv b/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_absent.srv new file mode 100644 index 0000000000..0dea279dcb --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_absent.srv @@ -0,0 +1,35 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9891"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Absence of this option *denies* access + # to classic protocol connections + #allowlegacyconnects => { ... }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_closed.srv b/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_closed.srv new file mode 100644 index 0000000000..9f3a1c5fba --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_closed.srv @@ -0,0 +1,34 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9892"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # DO NOT ALLOW CLASSIC PROTOCOL + allowlegacyconnects => { }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_open.srv b/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_open.srv new file mode 100644 index 0000000000..448f0e97b0 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/allowlegacyconnects_open.srv @@ -0,0 +1,34 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9890"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # ADMIT CLASSIC PROTOCOL + allowlegacyconnects => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copied_files_can_be_sparse.cf b/tests/acceptance/16_cf-serverd/serial/copied_files_can_be_sparse.cf new file mode 100644 index 0000000000..c7580061c0 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copied_files_can_be_sparse.cf @@ -0,0 +1,51 @@ +# The agent writes sparse files when detecting chunks of zeros. Here we +# verify that a NULL-filled file, is fully sparse when remotely copied. + +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + meta: + "description" string => "ENT-2769: Test that files written are sparse when possible"; + + # Various exotic filesystems don't support sparseness, see the unit + # test "files_copy_test.c" for details. + "test_skip_unsupported" string => "!linux"; + + files: + "$(G.testdir)/source_file" + delete => tidy, + handle => "init_tidy_sparse"; + + commands: + # Create a 10MB sparse file + "/bin/dd" + args => "if=/dev/zero of=$(G.testdir)/source_file seek=1024 bs=1024 count=0", + if => not( fileexists( "$(G.testdir)/source_file" )), + depends_on => { "init_tidy_sparse" }; +} + +bundle agent test +{ + methods: + # source file is created in "init" bundle + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +# "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); +# "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); +# "any" usebundle => dcs_fini("$(G.testdir)/source_file"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copied_files_can_be_sparse.cf.sub b/tests/acceptance/16_cf-serverd/serial/copied_files_can_be_sparse.cf.sub new file mode 100644 index 0000000000..3ade624815 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copied_files_can_be_sparse.cf.sub @@ -0,0 +1,93 @@ +####################################################### +# +# Test cf-serverd related promises, mtime simple copy localhost +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" copy_from => copy_src_file("classic"); + "$(G.testdir)/destfile_latest" copy_from => copy_src_file("latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "mtime"; + copy_backup => "false"; + protocol_version => "$(protocol_version)"; + + portnumber => "9876"; # localhost_open + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + vars: + + # Real file size + "size1" string => filestat( "$(G.testdir)/destfile_classic", size ), + if => fileexists( "$(G.testdir)/destfile_classic"); + "size2" string => filestat( "$(G.testdir)/destfile_latest", size ), + if => fileexists( "$(G.testdir)/destfile_latest"); + + # Disk allocated space + "alloc_size1" string => + execresult( "expr `du -k $(G.testdir)/destfile_classic | cut -f1` '*' 1024", useshell), + if => fileexists("$(G.testdir)/destfile_classic"); + "alloc_size2" string => + execresult( "expr `du -k $(G.testdir)/destfile_latest | cut -f1` '*' 1024", useshell), + if => fileexists("$(G.testdir)/destfile_latest"); + + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + "is_sparse1" expression => islessthan( "$(alloc_size1)", "$(size1)" ); + "is_sparse2" expression => islessthan( "$(alloc_size2)", "$(size2)" ); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + DEBUG.!is_sparse1:: + "ERROR destfile_classic is NOT sparse; size1=$(size1) alloc_size1=$(alloc_size1)"; + DEBUG.!is_sparse2:: + "ERROR destfile_latest is NOT sparse; size2=$(size2) alloc_size2=$(alloc_size2)"; + same_classic.same_latest.is_sparse1.is_sparse2:: + "$(fn[1]) Pass"; + !same_classic|!same_latest|!is_sparse1|!is_sparse2:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_fail.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_fail.cf new file mode 100644 index 0000000000..21678b3bde --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_fail.cf @@ -0,0 +1,26 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source file to copy, always fresh, $(sys.date)"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile1"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile2"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/nondefault_ciphers_tlsversion.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/default_ciphers_tlsversion.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/nondefault_ciphers_tlsversion.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/default_ciphers_tlsversion.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_fail.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_fail.cf.sub new file mode 100644 index 0000000000..342b53cf62 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_fail.cf.sub @@ -0,0 +1,52 @@ +####################################################### +# +# Tries to copy using TLS (which is default now), from two servers: one +# with the default TLS ciphers list and another with a non-default very +# restricted one. +# +# It should fail since we are setting "tls_ciphers" to a cipher missing +# in both servers. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + + # This cipher doesn't match neither of the two + # servers "allowciphers" setting. + tls_ciphers => "AES128-SHA"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Server with non-default, very restricted "allowciphers" + "$(G.testdir)/destfile1" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9888"), + classes => classes_generic("copy1"); + # Server with default "allowciphers" + "$(G.testdir)/destfile2" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9889"), + classes => classes_generic("copy2"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "exists1" expression => fileexists("$(G.testdir)/destfile1"); + "exists2" expression => fileexists("$(G.testdir)/destfile2"); + + reports: + + (copy1_failed.copy2_failed).(!copy1_repaired.!copy2_repaired).(!exists1.!exists2):: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_success.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_success.cf new file mode 100644 index 0000000000..9585f5cbd1 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_success.cf @@ -0,0 +1,30 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source file to copy, always fresh, $(sys.date)"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile1"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile2"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/nondefault_ciphers_tlsversion.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/default_ciphers_tlsversion.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/nondefault_ciphers_tlsversion.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/default_ciphers_tlsversion.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_success.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_success.cf.sub new file mode 100644 index 0000000000..1e734f09fc --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_ciphers_success.cf.sub @@ -0,0 +1,52 @@ +####################################################### +# +# Tries to copy using TLS (which is default now), from two servers: one +# with the default TLS ciphers list and another with a non-default very +# restricted one. +# +# It should succeed in both cases since default client-side cipherlist +# is OpenSSL's default, which is very broad. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + + # Do not set this, just use the very broad OpenSSL default. + # + # tls_ciphers => ""; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Server with non-default, very restricted "allowciphers" + "$(G.testdir)/destfile1" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9888"), + classes => classes_generic("copy1"); + # Server with default "allowciphers" + "$(G.testdir)/destfile2" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9889"), + classes => classes_generic("copy2"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "exists1" expression => fileexists("$(G.testdir)/destfile1"); + "exists2" expression => fileexists("$(G.testdir)/destfile2"); + + reports: + + (copy1_repaired.copy2_repaired).(exists1.exists2):: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_fail.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_fail.cf new file mode 100644 index 0000000000..6888dad380 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_fail.cf @@ -0,0 +1,26 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source file to copy, always fresh, $(sys.date)"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile2"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/allowlegacyconnects_closed.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/allowlegacyconnects_absent.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/allowlegacyconnects_closed.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/allowlegacyconnects_absent.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_fail.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_fail.cf.sub new file mode 100644 index 0000000000..784b3ab38c --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_fail.cf.sub @@ -0,0 +1,50 @@ +####################################################### +# +# Tries to copy using the "classic" protocol, to a server that has +# "allowlegacyconnects" closed and to one that does not have the +# setting at all. +# +# Both must fail. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + + # FORCE CLASSIC PROTOCOL + protocol_version => "classic"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Copy from server with allowlegacyconnects closed. + "$(G.testdir)/destfile" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9892"), + classes => classes_generic("copy"); + # Copy from server with allowlegacyconnects absent. + "$(G.testdir)/destfile2" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9891"), + classes => classes_generic("copy2"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "exists" expression => fileexists("$(G.testdir)/destfile"); + "exists2" expression => fileexists("$(G.testdir)/destfile2"); + + reports: + + copy_failed.!copy_repaired.!exists.copy2_failed.!copy2_repaired.!exists2:: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_success.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_success.cf new file mode 100644 index 0000000000..79bf2269ac --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_success.cf @@ -0,0 +1,23 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source file to copy, always fresh, $(sys.date)"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile1"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/allowlegacyconnects_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/allowlegacyconnects_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_success.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_success.cf.sub new file mode 100644 index 0000000000..bf2d7e6e12 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_classic_protocol_success.cf.sub @@ -0,0 +1,44 @@ +####################################################### +# +# Tries to copy using the "classic" protocol, from a server that has +# "allowlegacyconnects" open. +# +# It should succeed. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + + # FORCE CLASSIC PROTOCOL + protocol_version => "classic"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Copy from server with allowlegacyconnects open. + "$(G.testdir)/destfile1" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9890"), + classes => classes_generic("copy1"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "exists1" expression => fileexists("$(G.testdir)/destfile1"); + + reports: + + copy1_repaired.exists1.!copy1_failed:: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different.cf new file mode 100644 index 0000000000..9882be5491 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different.cf @@ -0,0 +1,26 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source and Destination are DIFFERENT_A"); + "any" usebundle => file_make("$(G.testdir)/destfile_latest", + "Source and Destination are DIFFERENT_B"); + "any" usebundle => file_make("$(G.testdir)/destfile_classic", + "Source and Destination are DIFFERENT_C"); + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different.cf.sub new file mode 100644 index 0000000000..c51846daed --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different.cf.sub @@ -0,0 +1,69 @@ +####################################################### +# +# copy_from compare=>"digest" when file is there but differs - should copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + same_classic.same_latest:: + "$(fn[1]) Pass"; + !same_classic|!same_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip.cf new file mode 100644 index 0000000000..f327f6ca7f --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip.cf @@ -0,0 +1,24 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + "any" usebundle => file_make("$(G.testdir)/127.0.0.1.txt", + "Source and Destination are different_A"); + "any" usebundle => file_make("$(G.testdir)/destination_file", + "Source and Destination are different_B"); + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip.cf.sub new file mode 100644 index 0000000000..1eed795808 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip.cf.sub @@ -0,0 +1,67 @@ +####################################################### +# +# copy_from compare=>"digest" with server-side expansion of IP address +# (TLS protocol only) when file is there but differs - should copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destination_file" + copy_from => copy_src_file, + classes => if_repaired("repaired"); +} + +######################################################### + +body copy_from copy_src_file +{ + # server-side special variable expansion + # of "127.0.0.1" to "$(connection.ip)" + source => "$(G.testdir)/127.0.0.1.txt"; + + # Special variable expansion only works with "latest" protocol. + + protocol_version => "latest"; + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected("$(G.testdir)/127.0.0.1.txt", + "$(G.testdir)/destination_file", + "no", "same_file", "differ_file"); + reports: + + same_file:: + "$(fn[1]) Pass"; + !same_file:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip_and_shortcut.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip_and_shortcut.cf new file mode 100644 index 0000000000..f327f6ca7f --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip_and_shortcut.cf @@ -0,0 +1,24 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + "any" usebundle => file_make("$(G.testdir)/127.0.0.1.txt", + "Source and Destination are different_A"); + "any" usebundle => file_make("$(G.testdir)/destination_file", + "Source and Destination are different_B"); + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip_and_shortcut.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip_and_shortcut.cf.sub new file mode 100644 index 0000000000..e8fbc3e342 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_ip_and_shortcut.cf.sub @@ -0,0 +1,67 @@ +####################################################### +# +# copy_from compare=>"digest" with server-side expansion of IP address +# (TLS protocol only) *and* shortcut expansion when file is there but +# differs - should copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destination_file" + copy_from => copy_src_file, + classes => if_repaired("repaired"); +} + +######################################################### + +body copy_from copy_src_file +{ + # server-side shortcut expansion to "$(G.testdir)/$(connection.ip).txt" + source => "expand_ip_source"; + + # Special variable expansion only works with "latest" protocol. + + protocol_version => "latest"; + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected("$(G.testdir)/127.0.0.1.txt", + "$(G.testdir)/destination_file", + "no", "same_file", "differ_file"); + reports: + + same_file:: + "$(fn[1]) Pass"; + !same_file:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_shortcut.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_shortcut.cf new file mode 100644 index 0000000000..139a1e35ba --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_shortcut.cf @@ -0,0 +1,29 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source and Destination are DIFFERENT_A"); + # destination files + "any" usebundle => file_make("$(G.testdir)/destfile_classic", + "Source and Destination are DIFFERENT_B"); + "any" usebundle => file_make("$(G.testdir)/destfile_latest", + "Source and Destination are DIFFERENT_B"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_shortcut.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_shortcut.cf.sub new file mode 100644 index 0000000000..84431e0ec4 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_different_expand_shortcut.cf.sub @@ -0,0 +1,72 @@ +####################################################### +# +# copy_from compare=>"digest" with server-side shortcut expansion +# when file is there but differs - should copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + # server-side expansion of shortcut + source => "simple_source"; + + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected("$(G.testdir)/source_file", + "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected("$(G.testdir)/source_file", + "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + same_classic.same_latest:: + "$(fn[1]) Pass"; + !same_classic|!same_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same.cf new file mode 100644 index 0000000000..cfbaa5f73a --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same.cf @@ -0,0 +1,26 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source files + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source and Destination are identical."); + # destination files + "any" usebundle => file_make("$(G.testdir)/destfile_latest", + "Source and Destination are identical."); + "any" usebundle => file_make("$(G.testdir)/destfile_classic", + "Source and Destination are identical."); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same.cf.sub new file mode 100644 index 0000000000..5ea31c1802 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same.cf.sub @@ -0,0 +1,61 @@ +####################################################### +# +# copy_from compare=>"digest" when file is already there - should not copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !copied_classic.!copied_latest:: + "$(fn[1]) Pass"; + copied_classic|copied_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip.cf new file mode 100644 index 0000000000..569c884350 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip.cf @@ -0,0 +1,20 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + "any" usebundle => file_make("$(G.testdir)/127.0.0.1.txt", + "Source and Destination are identical."); + "any" usebundle => file_make("$(G.testdir)/destination_file", + "Source and Destination are identical."); + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip.cf.sub new file mode 100644 index 0000000000..08fd9b920b --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip.cf.sub @@ -0,0 +1,59 @@ +####################################################### +# +# copy_from compare=>"digest" with server-side expansion of IP address +# (TLS protocol only) when file is already there - should not copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destination_file" + copy_from => copy_src_file, + classes => if_repaired("repaired"); +} + +######################################################### + +body copy_from copy_src_file +{ + source => "$(G.testdir)/127.0.0.1.txt"; + + protocol_version => "latest"; + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !repaired:: + "$(fn[1]) Pass"; + repaired:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip_and_shortcut.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip_and_shortcut.cf new file mode 100644 index 0000000000..569c884350 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip_and_shortcut.cf @@ -0,0 +1,20 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + "any" usebundle => file_make("$(G.testdir)/127.0.0.1.txt", + "Source and Destination are identical."); + "any" usebundle => file_make("$(G.testdir)/destination_file", + "Source and Destination are identical."); + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip_and_shortcut.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip_and_shortcut.cf.sub new file mode 100644 index 0000000000..a83355e9e9 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_ip_and_shortcut.cf.sub @@ -0,0 +1,60 @@ +####################################################### +# +# copy_from compare=>"digest" with server-side expansion of IP address +# (TLS protocol only) and shortcut expansion when file is already there +# - should not copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destination_file" + copy_from => copy_src_file, + classes => if_repaired("repaired"); +} + +######################################################### + +body copy_from copy_src_file +{ + source => "expand_ip_source"; + + protocol_version => "latest"; + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !repaired:: + "$(fn[1]) Pass"; + repaired:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_shortcut.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_shortcut.cf new file mode 100644 index 0000000000..4604789c5b --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_shortcut.cf @@ -0,0 +1,26 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source and Destination are identical"); + # destination files + "any" usebundle => file_make("$(G.testdir)/destfile_classic", + "Source and Destination are identical"); + "any" usebundle => file_make("$(G.testdir)/destfile_latest", + "Source and Destination are identical"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_shortcut.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_shortcut.cf.sub new file mode 100644 index 0000000000..7736c0663e --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_digest_same_expand_shortcut.cf.sub @@ -0,0 +1,64 @@ +####################################################### +# +# copy_from compare=>"digest" with server-side shortcut expansion (TLS +# protocol only) when file is already there - should not copy +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + # server-side expansion of shortcut + source => "simple_source"; + + servers => { "127.0.0.1" }; + compare => "digest"; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !copied_classic.!copied_latest:: + "$(fn[1]) Pass"; + copied_classic|copied_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_encrypted_md5_zero_length_file.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_encrypted_md5_zero_length_file.cf new file mode 100644 index 0000000000..b99eaa6243 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_encrypted_md5_zero_length_file.cf @@ -0,0 +1,28 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_empty("$(G.testdir)/source_file"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_encrypted_md5_zero_length_file.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_encrypted_md5_zero_length_file.cf.sub new file mode 100644 index 0000000000..da0c7f8559 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_encrypted_md5_zero_length_file.cf.sub @@ -0,0 +1,69 @@ +####################################################### +# +# Test cf-serverd related promises +# +# Tests copy_from encrypted digest verify (SMD5) zero-length file +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + copy_backup => "false"; + + portnumber => "9876"; # localhost_open + + encrypt => "true"; + compare => "digest"; + verify => "true"; + + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + "empty_classic" and => { strcmp(filesize("$(G.testdir)/destfile_classic"),"0") }; + "empty_latest" and => { strcmp(filesize("$(G.testdir)/destfile_latest"),"0") }; + + reports: + + copied_classic.copied_latest.empty_classic.empty_latest:: + "$(fn[1]) Pass"; + !copied_classic|!copied_latest|!empty_classic|!empty_latest:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_admit_directory_deny_file.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_admit_directory_deny_file.cf new file mode 100644 index 0000000000..f43712bf14 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_admit_directory_deny_file.cf @@ -0,0 +1,33 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testdir)/127.0.0.1_DIR1/." create => "true"; + "$(G.testdir)/127.0.0.1_DIR2/." create => "true"; +} + +bundle agent test +{ + methods: + "any" usebundle => file_make("$(G.testdir)/127.0.0.1_DIR1/ADMIT_FILE", + "ADMIT_FILE A CONTENTS"); + "any" usebundle => file_make("$(G.testdir)/127.0.0.1_DIR1/DENY_FILE", + "DENY_FILE A CONTENTS"); + "any" usebundle => file_make("$(G.testdir)/127.0.0.1_DIR2/ADMIT_FILE", + "ADMIT_FILE B CONTENTS"); + "any" usebundle => file_make("$(G.testdir)/127.0.0.1_DIR2/DENY_FILE", + "DENY_FILE B CONTENTS"); + "any" usebundle => dcs_fini("$(G.testdir)/destination_file1"); + "any" usebundle => dcs_fini("$(G.testdir)/destination_file2"); + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_admit_directory_deny_file.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_admit_directory_deny_file.cf.sub new file mode 100644 index 0000000000..3492e67fdc --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_admit_directory_deny_file.cf.sub @@ -0,0 +1,65 @@ +####################################################### +# +# copy_from from within recursively admitted directory that does not +# exist during daemon init time, e.g. /path/to/$(connection.ip), +# but the file itself is in "deny" list! +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + + # Directory admitted in cf-serverd using trailing slash + "$(G.testdir)/destination_file1" + copy_from => copy_src_file("$(G.testdir)/127.0.0.1_DIR1/DENY_FILE"), + classes => if_repaired("repaired1"); + + # Directory admitted in cf-serverd using trailing slashdot + "$(G.testdir)/destination_file2" + copy_from => copy_src_file("$(G.testdir)/127.0.0.1_DIR2/DENY_FILE"), + classes => if_repaired("repaired2"); +} + +######################################################### + +body copy_from copy_src_file(file) +{ + source => "$(file)"; + + protocol_version => "latest"; + servers => { "127.0.0.1" }; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + !repaired1.!repaired2:: + "$(fn[1]) Pass"; + repaired1|repaired2:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_directory.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_directory.cf new file mode 100644 index 0000000000..c79ff63c7e --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_directory.cf @@ -0,0 +1,37 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testdir)/127.0.0.1_DIR1/." create => "true"; + "$(G.testdir)/127.0.0.1_DIR2/." create => "true"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + "any" usebundle => file_make("$(G.testdir)/127.0.0.1_DIR1/ADMIT_FILE", + "ADMIT_FILE A CONTENTS"); + "any" usebundle => file_make("$(G.testdir)/127.0.0.1_DIR1/DENY_FILE", + "DENY_FILE A CONTENTS"); + "any" usebundle => file_make("$(G.testdir)/127.0.0.1_DIR2/ADMIT_FILE", + "ADMIT_FILE B CONTENTS"); + "any" usebundle => file_make("$(G.testdir)/127.0.0.1_DIR2/DENY_FILE", + "DENY_FILE B CONTENTS"); + "any" usebundle => dcs_fini("$(G.testdir)/destination_file1"); + "any" usebundle => dcs_fini("$(G.testdir)/destination_file2"); + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_directory.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_directory.cf.sub new file mode 100644 index 0000000000..4c8f3ca7cd --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_expand_ip_directory.cf.sub @@ -0,0 +1,72 @@ +####################################################### +# +# copy_from from within recursively admitted directory that does not +# exist during daemon init time, e.g. /path/to/$(connection.ip) +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + + # Admitted in cf-serverd using trailing slash + "$(G.testdir)/destination_file1" + copy_from => copy_src_file("$(G.testdir)/127.0.0.1_DIR1/ADMIT_FILE"); + + # Admitted in cf-serverd using trailing slashdot + "$(G.testdir)/destination_file2" + copy_from => copy_src_file("$(G.testdir)/127.0.0.1_DIR2/ADMIT_FILE"); +} + +######################################################### + +body copy_from copy_src_file(file) +{ + source => "$(file)"; + + # Special variable expansion only works with "latest" protocol. + + protocol_version => "latest"; + servers => { "127.0.0.1" }; + copy_backup => "false"; + trustkey => "true"; + portnumber => "9876"; # localhost_open +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected("$(G.testdir)/127.0.0.1_DIR1/ADMIT_FILE", + "$(G.testdir)/destination_file1", + "no", "same_file1", "differ_file1"); + "any" usebundle => dcs_if_diff_expected("$(G.testdir)/127.0.0.1_DIR2/ADMIT_FILE", + "$(G.testdir)/destination_file2", + "no", "same_file2", "differ_file2"); + reports: + + same_file1.same_file2:: + "$(fn[1]) Pass"; + !same_file1|!same_file2:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed-classic_protocol.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed-classic_protocol.cf new file mode 100644 index 0000000000..f3e45f0131 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed-classic_protocol.cf @@ -0,0 +1,31 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile1"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile2"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile3"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed-classic_protocol.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed-classic_protocol.cf.sub new file mode 100644 index 0000000000..779d3a9cce --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed-classic_protocol.cf.sub @@ -0,0 +1,51 @@ +####################################################### +# +# CFE-2532: failure to get one file causes all subsequent copy_from +# promises to also fail - classic protocol version +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + protocol_version => "classic"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Copy 1st file successfully + "$(G.testdir)/destfile1" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9876"), + classes => classes_generic("copy1"); + # Copy 2nd file, WILL FAIL + "$(G.testdir)/destfile2" + copy_from => dcs_remote_cp("DOES_NOT_EXIST", "127.0.0.1", "9876"), + classes => classes_generic("copy2"); + # Copy 3rd file, MUST SUCCEED DESPITE PREVIOUS FAILURE + "$(G.testdir)/destfile3" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9876"), + classes => classes_generic("copy3"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + "exists1" expression => fileexists("$(G.testdir)/destfile1"); + "exists2" expression => fileexists("$(G.testdir)/destfile2"); + "exists3" expression => fileexists("$(G.testdir)/destfile3"); + + reports: + + copy1_repaired.exists1.!copy1_failed.copy3_repaired.exists3.!copy3_failed.!copy2_repaired.!exists2.copy2_failed:: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed.cf new file mode 100644 index 0000000000..f3e45f0131 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed.cf @@ -0,0 +1,31 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile1"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile2"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile3"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed.cf.sub new file mode 100644 index 0000000000..514539fa82 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_inexistent_file_connection_closed.cf.sub @@ -0,0 +1,50 @@ +####################################################### +# +# CFE-2532: failure to get one file causes all subsequent copy_from +# promises to also fail +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Copy 1st file successfully + "$(G.testdir)/destfile1" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9876"), + classes => classes_generic("copy1"); + # Copy 2nd file, WILL FAIL + "$(G.testdir)/destfile2" + copy_from => dcs_remote_cp("DOES_NOT_EXIST", "127.0.0.1", "9876"), + classes => classes_generic("copy2"); + # Copy 3rd file, MUST SUCCEED DESPITE PREVIOUS FAILURE + "$(G.testdir)/destfile3" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9876"), + classes => classes_generic("copy3"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + "exists1" expression => fileexists("$(G.testdir)/destfile1"); + "exists2" expression => fileexists("$(G.testdir)/destfile2"); + "exists3" expression => fileexists("$(G.testdir)/destfile3"); + + reports: + + copy1_repaired.exists1.!copy1_failed.copy3_repaired.exists3.!copy3_failed.!copy2_repaired.!exists2.copy2_failed:: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_md5_zero_length_file.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_md5_zero_length_file.cf new file mode 100644 index 0000000000..b99eaa6243 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_md5_zero_length_file.cf @@ -0,0 +1,28 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_empty("$(G.testdir)/source_file"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_md5_zero_length_file.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_md5_zero_length_file.cf.sub new file mode 100644 index 0000000000..5fe7bed4f4 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_md5_zero_length_file.cf.sub @@ -0,0 +1,70 @@ +####################################################### +# +# Test cf-serverd related promises +# +# Tests copy_from digest verify (SMD5) zero-length file +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + copy_backup => "false"; + + portnumber => "9876"; # localhost_open + + encrypt => "false"; + compare => "digest"; + verify => "true"; + + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + "empty_classic" and => { strcmp(filesize("$(G.testdir)/destfile_classic"),"0") }; + "empty_latest" and => { strcmp(filesize("$(G.testdir)/destfile_latest"),"0") }; + + + reports: + + copied_classic.copied_latest.empty_classic.empty_latest:: + "$(fn[1]) Pass"; + !copied_classic|!copied_latest|!empty_classic|!empty_latest:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_reordered_ciphers_success.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_reordered_ciphers_success.cf new file mode 100644 index 0000000000..d809fc6d31 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_reordered_ciphers_success.cf @@ -0,0 +1,23 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source file to copy, always fresh, $(sys.date)"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile1"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/reordered_default_ciphers_tlsversion.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/reordered_default_ciphers_tlsversion.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_reordered_ciphers_success.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_reordered_ciphers_success.cf.sub new file mode 100644 index 0000000000..df151f01e6 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_reordered_ciphers_success.cf.sub @@ -0,0 +1,47 @@ +####################################################### +# +# Tries to copy using TLS (which is default now), from two servers: one +# with the default TLS ciphers list and another with a non-default very +# restricted one. +# +# It should succeed in both cases since default client-side cipherlist +# is OpenSSL's default, which is very broad. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + + # Do not set this, just use the very broad OpenSSL default. + # + # tls_ciphers => ""; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Server with reordered default "allowciphers" + "$(G.testdir)/destfile1" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9893"), + classes => classes_generic("copy1"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "exists1" expression => fileexists("$(G.testdir)/destfile1"); + + reports: + + copy1_repaired.exists1:: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_fail.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_fail.cf new file mode 100644 index 0000000000..f580826488 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_fail.cf @@ -0,0 +1,35 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!feature_tls_1_3"; + + "description" -> {"ENT-4617"} + string => "Test that requiring TLS 1.3 works fine"; + + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source file to copy, always fresh, $(sys.date)"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile1"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile2"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/no_tls_1_3.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/default_ciphers_tlsversion.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/no_tls_1_3.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/default_ciphers_tlsversion.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_fail.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_fail.cf.sub new file mode 100644 index 0000000000..35bcab7bb9 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_fail.cf.sub @@ -0,0 +1,48 @@ +####################################################### +# +# Tries to copy using TLS 1.3, from two servers: +# - one not supporting TLS 1.3 +# - one with the defaults +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + + # Require TLS 1.3 + tls_min_version => "1.3"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Server not supporting TLS 1.3 + "$(G.testdir)/destfile1" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9895"), + classes => classes_generic("copy1"); + # Server with default "allowciphers" and "allowtlsversion" + "$(G.testdir)/destfile2" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9889"), + classes => classes_generic("copy2"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "exists1" expression => fileexists("$(G.testdir)/destfile1"); + "exists2" expression => fileexists("$(G.testdir)/destfile2"); + + reports: + + (copy1_failed.copy2_repaired).(!exists1.exists2):: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_success.cf b/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_success.cf new file mode 100644 index 0000000000..867d21ad96 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_success.cf @@ -0,0 +1,34 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_skip_unsupported" string => "!feature_tls_1_3"; + + "description" -> {"ENT-4617"} + string => "Test that CFEngine connections over TLS 1.3 work fine"; + + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source file to copy, always fresh, $(sys.date)"); + + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/destfile1"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile2"); + + "any" usebundle => generate_key; + + "any" usebundle => start_server("$(this.promise_dirname)/tls_1_3_only.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/default_ciphers_tlsversion.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/tls_1_3_only.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/default_ciphers_tlsversion.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_success.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_success.cf.sub new file mode 100644 index 0000000000..7030b4a03a --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_from_tls_1_3_success.cf.sub @@ -0,0 +1,48 @@ +####################################################### +# +# Tries to copy using TLS 1.3, from two servers: +# - one with only TLS 1.3 enabled +# - one with the defaults +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + + # Require TLS 1.3 + tls_min_version => "1.3"; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + # Server only allowing TLS 1.3 + "$(G.testdir)/destfile1" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9894"), + classes => classes_generic("copy1"); + # Server with default "allowciphers" and "allowtlsversion" + "$(G.testdir)/destfile2" + copy_from => dcs_remote_cp("simple_source", "127.0.0.1", "9889"), + classes => classes_generic("copy2"); +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + "exists1" expression => fileexists("$(G.testdir)/destfile1"); + "exists2" expression => fileexists("$(G.testdir)/destfile2"); + + reports: + + (copy1_repaired.copy2_repaired).(exists1.exists2):: + "$(fn[1]) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_missing_ok.cf b/tests/acceptance/16_cf-serverd/serial/copy_missing_ok.cf new file mode 100644 index 0000000000..9ae428b473 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_missing_ok.cf @@ -0,0 +1,32 @@ +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10401" }; + + methods: + # ensure destination files are not there + "any" usebundle => dcs_fini("$(G.testdir)/dir"); + "any" usebundle => dcs_fini("$(G.testdir)/out"); + "any" usebundle => dcs_fini("$(G.testdir)/nodir/does_not_exist"); + + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "Source file to copy, always fresh, $(sys.date)"); + "any" usebundle => file_make("$(G.testdir)/out/dummy", + "dummy file to create output dir"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/copy_missing_ok.cf.sub b/tests/acceptance/16_cf-serverd/serial/copy_missing_ok.cf.sub new file mode 100644 index 0000000000..588fb1305a --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/copy_missing_ok.cf.sub @@ -0,0 +1,60 @@ +####################################################### +# +# Tries to copy using TLS (which is default now), from two servers: one +# with the default TLS ciphers list and another with a non-default very +# restricted one. +# +# It should fail since we are setting "tls_ciphers" to a cipher missing +# in both servers. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ +} + +bundle agent test +{ + files: + "$(G.testdir)/out/missing_ok" + classes => classes_generic("test"), + copy_from => dcs_remote_cp_X("nodir/does_not_exist", "127.0.0.1", "true"); + + # try to copy a file after the previous was not found + "$(G.testdir)/out/second_file_should_be_ok" + classes => classes_generic("test2"), + copy_from => dcs_remote_cp_X("source_file", "127.0.0.1", "false"); +} + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + test_kept.test2_repaired:: + "$(fn[1]) Pass"; + test_failed|test_repaired|test2_failed:: + "$(fn[1]) FAIL"; +} + + +body copy_from dcs_remote_cp_X(from,server, missing_ok) +# @brief Download a file from a remote server. They server is always trusted. +# +# @param from The location of the file on the remote server +# @param server The hostname or IP of the server from which to download +{ + servers => { "$(server)" }; + source => "$(G.testdir)/$(from)"; + compare => "mtime"; + trustkey => "true"; + missing_ok => "${missing_ok}"; + portnumber => "9876"; # localhost_open +} diff --git a/tests/acceptance/16_cf-serverd/serial/default_ciphers_tlsversion.srv b/tests/acceptance/16_cf-serverd/serial/default_ciphers_tlsversion.srv new file mode 100644 index 0000000000..58e2107236 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/default_ciphers_tlsversion.srv @@ -0,0 +1,36 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9889"; + +# Use the defaults, i.e. the following: +# +# allowciphers => "AES256-GCM-SHA384:AES256-SHA:TLS_AES_256_GCM_SHA384"; +# allowtlsversion => "1.1"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/lan_deny_connect.srv b/tests/acceptance/16_cf-serverd/serial/lan_deny_connect.srv new file mode 100644 index 0000000000..e4207eb7c9 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/lan_deny_connect.srv @@ -0,0 +1,32 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9879"; + + denyconnects => { @(sys.ip_addresses) }; + allowconnects => { @(sys.ip_addresses) }; + allowallconnects => { @(sys.ip_addresses) }; + trustkeysfrom => { @(sys.ip_addresses) }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "$(G.testdir)/source_file" admit => { @(sys.ip_addresses) }; +} + diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_closed_deny_hostnames.srv b/tests/acceptance/16_cf-serverd/serial/localhost_closed_deny_hostnames.srv new file mode 100644 index 0000000000..3bb5d7134f --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_closed_deny_hostnames.srv @@ -0,0 +1,35 @@ +# In access_rules, deny "localhost" instead of "127.0.0.1" + +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9887"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowlegacyconnects => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "0.0.0.0/0" }, # admit everyone but + deny_hostnames => { "localhost", "localhost.localdomain" }; # deny is stronger than admit +} + diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_closed_hostname.srv b/tests/acceptance/16_cf-serverd/serial/localhost_closed_hostname.srv new file mode 100644 index 0000000000..0ff8b17da4 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_closed_hostname.srv @@ -0,0 +1,35 @@ +# In access_rules, deny "localhost" instead of "127.0.0.1" + +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9885"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowlegacyconnects => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "0.0.0.0/0" }, # admit everyone + deny => { "localhost", "localhost.localdomain" }; # deny is stronger than admit +} + diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_deny_connect.srv b/tests/acceptance/16_cf-serverd/serial/localhost_deny_connect.srv new file mode 100644 index 0000000000..374de1665e --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_deny_connect.srv @@ -0,0 +1,33 @@ + +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9877"; + + denyconnects => { "127.0.0.1" , "::1" }; + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "$(G.testdir)/source_file" admit => { "127.0.0.1", "::1" }; +} + diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_deny_one_directory.srv b/tests/acceptance/16_cf-serverd/serial/localhost_deny_one_directory.srv new file mode 100644 index 0000000000..a04ad1a830 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_deny_one_directory.srv @@ -0,0 +1,30 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; +} + +######################################################### +# Server config +######################################################### + +body server control +{ + port => "9881"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowlegacyconnects => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() +{ + access: + "$(G.testroot)/" + admit => { "127.0.0.1", "::1" }; + "$(G.testdir)/" + deny => { "127.0.0.1", "::1" }; +} diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_deny_one_directory_with_regex.srv b/tests/acceptance/16_cf-serverd/serial/localhost_deny_one_directory_with_regex.srv new file mode 100644 index 0000000000..a4572ea153 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_deny_one_directory_with_regex.srv @@ -0,0 +1,31 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; +} + +######################################################### +# Server config +######################################################### + +body server control +{ + port => "9882"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowlegacyconnects => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() +{ + access: + "$(G.testroot)/" + admit => { "192.168.*", ".*", "10.*" }, + deny => { "127.0*" }; # this matches nothing + "$(G.testdir)/" + deny => { ".*" }; +} diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_noaccess.srv b/tests/acceptance/16_cf-serverd/serial/localhost_noaccess.srv new file mode 100644 index 0000000000..cd0fb89893 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_noaccess.srv @@ -0,0 +1,32 @@ + +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9880"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + # no access granted +} + diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_open.srv b/tests/acceptance/16_cf-serverd/serial/localhost_open.srv new file mode 100644 index 0000000000..ebcc67f990 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_open.srv @@ -0,0 +1,58 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9876"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowlegacyconnects => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; + + "$(G.testdir)/$(connection.ip).txt" + admit_ips => { "$(connection.ip)" }, + shortcut => "expand_ip_source"; + + + # Directory existing only in connection time (not in daemon init + # time), after special variables have been expanded. So we need to + # specify recursive access by appending trailing slash or slashdot. + + # Trailing slash + "$(G.testdir)/$(connection.ip)_DIR1/" + admit_ips => { "$(connection.ip)" }; + + # Trailing slashdot, should be equivalent to trailing slash + "$(G.testdir)/$(connection.ip)_DIR2/." + admit_ips => { "$(connection.ip)" }; + + + # Deny access to a file within a recursively admitted directory! + "$(G.testdir)/$(connection.ip)_DIR1/DENY_FILE" + deny_ips => { "$(connection.ip)" }; + "$(G.testdir)/$(connection.ip)_DIR2/DENY_FILE" + deny_ips => { "$(connection.ip)" }; +} + diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_open_admit_hostnames.srv b/tests/acceptance/16_cf-serverd/serial/localhost_open_admit_hostnames.srv new file mode 100644 index 0000000000..a202c7796a --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_open_admit_hostnames.srv @@ -0,0 +1,34 @@ +# In access_rules, admit "localhost" instead of "127.0.0.1" + +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9886"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowlegacyconnects => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit_hostnames => { "localhost", "localhost.localdomain" }; +} + diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_open_hostname.srv b/tests/acceptance/16_cf-serverd/serial/localhost_open_hostname.srv new file mode 100644 index 0000000000..21a9488bf7 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_open_hostname.srv @@ -0,0 +1,34 @@ +# In access_rules, admit "localhost" instead of "127.0.0.1" + +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9884"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + allowlegacyconnects => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "localhost", "localhost.localdomain" }; +} + diff --git a/tests/acceptance/16_cf-serverd/serial/localhost_open_with_data_select.srv b/tests/acceptance/16_cf-serverd/serial/localhost_open_with_data_select.srv new file mode 100644 index 0000000000..d4ec5ae3ae --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/localhost_open_with_data_select.srv @@ -0,0 +1,40 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9883"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "$(G.testdir)/source_file" admit => { "127.0.0.1", "::1" }; + + "delta" + resource_type => "query", + report_data_select => test_report_filter; +} + +body report_data_select test_report_filter +{ + monitoring_include => { "lala.*", "lalala.*" }; + monitoring_exclude => { "lala.*", "lalala.*" }; +} diff --git a/tests/acceptance/16_cf-serverd/serial/network/004.cf b/tests/acceptance/16_cf-serverd/serial/network/004.cf new file mode 100644 index 0000000000..97a28cc2dc --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/network/004.cf @@ -0,0 +1,23 @@ +# +body common control +{ + inputs => { "../../../default.cf.sub", "../../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/destfile_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/destfile_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/lan_open.srv"); + "any" usebundle => run_test("$(this.promise_filename).sub"); + "any" usebundle => stop_server("$(this.promise_dirname)/lan_open.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/network/004.cf.sub b/tests/acceptance/16_cf-serverd/serial/network/004.cf.sub new file mode 100644 index 0000000000..a267ab9bd2 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/network/004.cf.sub @@ -0,0 +1,79 @@ +####################################################### +# +# mtime server copy, for non-localhost, should copy +# +####################################################### + +body common control +{ + inputs => { "../../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + + "$(G.testdir)/destfile_classic" + copy_from => copy_src_file("classic"), + classes => if_repaired("copied_classic"); + "$(G.testdir)/destfile_latest" + copy_from => copy_src_file("latest"), + classes => if_repaired("copied_latest"); +} + +######################################################### + +body copy_from copy_src_file(protocol_version) +{ + protocol_version => "$(protocol_version)"; + + source => "$(G.testdir)/source_file"; + + # localhost comes first but will be denied + servers => { @(sys.ip_addresses) }; + compare => "mtime"; + copy_backup => "false"; + + portnumber => "9878"; # lan_open + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_classic", + "no", "same_classic", "differ_classic"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/destfile_latest", + "no", "same_latest", "differ_latest"); + + reports: + + same_classic.same_latest:: + "$(fn[1]) Pass"; + !same_classic|!same_latest:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/network/lan_open.srv b/tests/acceptance/16_cf-serverd/serial/network/lan_open.srv new file mode 100644 index 0000000000..812ccd4741 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/network/lan_open.srv @@ -0,0 +1,40 @@ +# Deny localhost, admit other interface IPs + +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9878"; + + # DENY LOCALHOST! + denyconnects => { "127.0.0.1" }; + + # "127.0.0.1" is in this variable, but deny has precedence. So + # effectively we only admit non-localhost connections + + allowconnects => { @(sys.ip_addresses) }; + allowallconnects => { @(sys.ip_addresses) }; + trustkeysfrom => { @(sys.ip_addresses) }; + allowlegacyconnects => { @(sys.ip_addresses) }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + + "$(G.testdir)/source_file" admit => { @(sys.ip_addresses) }; +} + diff --git a/tests/acceptance/16_cf-serverd/serial/no_tls_1_3.srv b/tests/acceptance/16_cf-serverd/serial/no_tls_1_3.srv new file mode 100644 index 0000000000..8663b81d10 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/no_tls_1_3.srv @@ -0,0 +1,35 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9895"; + + # Do not allow TLS 1.3 (no TLS 1.3 cipher suites listed below) + allowciphers => "AES256-GCM-SHA384:AES256-SHA"; + allowtlsversion => "1.1"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/nondefault_ciphers_tlsversion.srv b/tests/acceptance/16_cf-serverd/serial/nondefault_ciphers_tlsversion.srv new file mode 100644 index 0000000000..9dad9f2a06 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/nondefault_ciphers_tlsversion.srv @@ -0,0 +1,37 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9888"; + + # Only this cipher is to be accepted + allowciphers => "AES128-GCM-SHA256"; + + # Allow only TLSv1.1 or higher + allowtlsversion => "1.1"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/reordered_default_ciphers_tlsversion.srv b/tests/acceptance/16_cf-serverd/serial/reordered_default_ciphers_tlsversion.srv new file mode 100644 index 0000000000..d72e0de62a --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/reordered_default_ciphers_tlsversion.srv @@ -0,0 +1,34 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9893"; + + # Use the defaults, but reordered: + allowciphers => "TLS_AES_256_GCM_SHA384:AES256-GCM-SHA384:AES256-SHA"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/simple_copy_from_admit_localhost.cf b/tests/acceptance/16_cf-serverd/serial/simple_copy_from_admit_localhost.cf new file mode 100644 index 0000000000..9dcb42bba0 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/simple_copy_from_admit_localhost.cf @@ -0,0 +1,41 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine6405", "ENT-2480"}; + + # Ubuntu 20 (at least our build machines) doesn't know how to resolve + # 127.0.0.1/::1 to localhost, the reverse lookup fails and so + # 'admit => { "localhost" };' and 'admit_hostnames => { "localhost" };' + # are not enough to allow access for this test. + # Test fails on SLES / OpenSUSE 15, pending investigation. + "test_skip_unsupported" string => "(ubuntu_20|ubuntu_22|debian_12|sles_15|opensuse_leap_15)", + meta => { "ENT-2480", "ENT-7362", "ENT-9055" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/server1_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/server1_latest"); + "any" usebundle => dcs_fini("$(G.testdir)/server2_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/server2_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open_hostname.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/localhost_open_admit_hostnames.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open_hostname.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_open_admit_hostnames.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/simple_copy_from_admit_localhost.cf.sub b/tests/acceptance/16_cf-serverd/serial/simple_copy_from_admit_localhost.cf.sub new file mode 100644 index 0000000000..211b77ae68 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/simple_copy_from_admit_localhost.cf.sub @@ -0,0 +1,82 @@ +####################################################### +# +# Test cf-serverd related promises, mtime simple copy localhost +# The source server has access_rules admit=>{"localhost"} +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + # localhost_open_hostname + "$(G.testdir)/server1_classic" copy_from => copy_file("9884", "classic"); + "$(G.testdir)/server1_latest" copy_from => copy_file("9884", "latest"); + # localhost_open_admit_hostnames + "$(G.testdir)/server2_classic" copy_from => copy_file("9886", "classic"); + "$(G.testdir)/server2_latest" copy_from => copy_file("9886", "latest"); +} + +######################################################### + +body copy_from copy_file(port, protocol_version) +{ + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "mtime"; + copy_backup => "false"; + + portnumber => "$(port)"; + protocol_version => "$(protocol_version)"; + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + methods: + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/server1_classic", + "no", "same_classic1", "differ_classic1"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/server1_latest", + "no", "same_latest1", "differ_latest1"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/server2_classic", + "no", "same_classic2", "differ_classic2"); + "any" usebundle => dcs_if_diff_expected( + "$(G.testdir)/source_file", "$(G.testdir)/server2_latest", + "no", "same_latest2", "differ_latest2"); + + reports: + + same_classic1.same_latest1.same_classic2.same_latest2:: + "$(fn[1]) Pass"; + !same_classic1|!same_latest1|!same_classic2|!same_latest2:: + "$(fn[1]) FAIL"; + +} diff --git a/tests/acceptance/16_cf-serverd/serial/simple_copy_from_deny_localhost.cf b/tests/acceptance/16_cf-serverd/serial/simple_copy_from_deny_localhost.cf new file mode 100644 index 0000000000..6adaeadd97 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/simple_copy_from_deny_localhost.cf @@ -0,0 +1,41 @@ +# +body common control +{ + inputs => { "../../default.cf.sub", "../../run_with_server.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_suppress_fail" string => "windows", + meta => { "redmine6405", "ENT-2480"}; + + # Ubuntu 20 (at least our build machines) doesn't know how to resolve + # 127.0.0.1/::1 to localhost, the reverse lookup fails and so + # 'deny => { "localhost" };' and 'deny_hostnames => { "localhost" };' + # are not enough to deny access for this test. + # Test fails on SLES / OpenSUSE 15, pending investigation. + "test_skip_unsupported" string => "(ubuntu_20|ubuntu_22|debian_12|sles_15|opensuse_leap_15)", + meta => { "ENT-2480", "ENT-7362", "ENT-9055" }; + + methods: + # source file + "any" usebundle => file_make("$(G.testdir)/source_file", + "This is the source file to copy $(sys.date) - always fresh"); + # destination files + "any" usebundle => dcs_fini("$(G.testdir)/server1_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/server1_latest"); + "any" usebundle => dcs_fini("$(G.testdir)/server2_classic"); + "any" usebundle => dcs_fini("$(G.testdir)/server2_latest"); + + "any" usebundle => generate_key; + "any" usebundle => start_server("$(this.promise_dirname)/localhost_closed_hostname.srv"); + "any" usebundle => start_server("$(this.promise_dirname)/localhost_closed_deny_hostnames.srv"); + + "any" usebundle => run_test("$(this.promise_filename).sub"); + + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_closed_hostname.srv"); + "any" usebundle => stop_server("$(this.promise_dirname)/localhost_closed_deny_hostnames.srv"); +} diff --git a/tests/acceptance/16_cf-serverd/serial/simple_copy_from_deny_localhost.cf.sub b/tests/acceptance/16_cf-serverd/serial/simple_copy_from_deny_localhost.cf.sub new file mode 100644 index 0000000000..bf71d9399a --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/simple_copy_from_deny_localhost.cf.sub @@ -0,0 +1,75 @@ +####################################################### +# +# Test cf-serverd related promises, mtime simple copy localhost +# The source server has access_rules admit=>{"localhost"} +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + files: + # localhost_closed_hostname + "$(G.testdir)/server1_classic" + copy_from => copy_file("9885", "classic"), + classes => if_repaired("repaired1_classic"); + "$(G.testdir)/server1_latest" + copy_from => copy_file("9885", "latest"), + classes => if_repaired("repaired1_latest"); + # localhost_closed_deny_hostnames + "$(G.testdir)/server2_classic" + copy_from => copy_file("9887", "classic"), + classes => if_repaired("repaired2_classic"); + "$(G.testdir)/server2_latest" + copy_from => copy_file("9887", "latest"), + classes => if_repaired("repaired2_latest"); +} + +######################################################### + +body copy_from copy_file(port, protocol_version) +{ + source => "$(G.testdir)/source_file"; + servers => { "127.0.0.1" }; + compare => "mtime"; + copy_backup => "false"; + + portnumber => "$(port)"; + protocol_version => "$(protocol_version)"; + + #encrypt => "true"; + #verify => "true"; + #purge => "false"; + #type_check => "true"; + #force_ipv4 => "true"; + trustkey => "true"; +} + +####################################################### + +bundle agent check +{ + classes: + "dummy" expression => regextract("(.*)\.sub", $(this.promise_filename), "fn"); + + reports: + + !repaired1_classic.!repaired1_latest.!repaired2_classic.!repaired2_latest:: + "$(fn[1]) Pass"; + repaired1_classic|repaired1_latest|repaired2_classic|repaired2_latest:: + "$(fn[1]) FAIL"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/tcp_port_copy_from_within_range.cf b/tests/acceptance/16_cf-serverd/serial/tcp_port_copy_from_within_range.cf new file mode 100644 index 0000000000..1bdee572e6 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/tcp_port_copy_from_within_range.cf @@ -0,0 +1,22 @@ +# +# Check the parser enforces standard TCP port ( 1 <= port <= 65535) for: +# -copy_from bodies "portnumber" +# + +body common control +{ + bundlesequence => { default("$(this.promise_filename)") }; + inputs => { "../../default.cf.sub" }; + version => "1.0"; +} + +bundle agent test +{ + reports: + "$(this.promise_filename) Pass"; +} + +body copy_from dummy_copy_from +{ + portnumber => "5308"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/tcp_port_listen_outside_range.x.cf b/tests/acceptance/16_cf-serverd/serial/tcp_port_listen_outside_range.x.cf new file mode 100644 index 0000000000..08556127ea --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/tcp_port_listen_outside_range.x.cf @@ -0,0 +1,22 @@ +# +# Check the parser enforces standard TCP port ( 1 <= port <= 65535) for: +# -cf-serverd listening port +# + +body common control +{ + bundlesequence => { default("$(this.promise_filename)") }; + inputs => { "../../default.cf.sub" }; + version => "1.0"; +} + +body server control +{ + port => "65536"; +} + +bundle agent test +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/16_cf-serverd/serial/tcp_port_listen_within_range.cf b/tests/acceptance/16_cf-serverd/serial/tcp_port_listen_within_range.cf new file mode 100644 index 0000000000..42ec7dcef9 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/tcp_port_listen_within_range.cf @@ -0,0 +1,23 @@ +# +# Check the parser enforces standard TCP port ( 1 <= port <= 65535) for: +# -cf-serverd listening port +# + +body common control +{ + bundlesequence => { default("$(this.promise_filename)") }; + inputs => { "../../default.cf.sub" }; + version => "1.0"; +} + +body server control +{ + port => "5308"; +} + +bundle agent test +{ + reports: + "$(this.promise_filename) Pass"; +} + diff --git a/tests/acceptance/16_cf-serverd/serial/tls_1_3_only.srv b/tests/acceptance/16_cf-serverd/serial/tls_1_3_only.srv new file mode 100644 index 0000000000..4e73ae4f27 --- /dev/null +++ b/tests/acceptance/16_cf-serverd/serial/tls_1_3_only.srv @@ -0,0 +1,35 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "9894"; + + # Allow TLS 1.3 only + allowciphers => "TLS_AES_256_GCM_SHA384"; + allowtlsversion => "1.3"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() + +{ + access: + "$(G.testdir)/source_file" + admit => { "127.0.0.1", "::1" }, + shortcut => "simple_source"; +} diff --git a/tests/acceptance/17_users/unsafe/05_hpux_set_non_trusted_mode.cf b/tests/acceptance/17_users/unsafe/05_hpux_set_non_trusted_mode.cf new file mode 100644 index 0000000000..5fdd5b06f6 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/05_hpux_set_non_trusted_mode.cf @@ -0,0 +1,31 @@ +# Not a test, but just setting up non-trusted mode on HPUX. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "!hpux"; + + classes: + "non_trusted_mode" + not => fileexists("/etc/shadow"), + scope => "namespace"; + + commands: + !not_trusted_mode:: + "/usr/sbin/pwunconv"; +} + +bundle agent check +{ + reports: + non_trusted_mode:: + "$(this.promise_filename) Pass"; + !non_trusted_mode:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user.cf b/tests/acceptance/17_users/unsafe/10_add_user.cf new file mode 100644 index 0000000000..545c71de15 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user.cf @@ -0,0 +1,53 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_exists("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_locked.cf b/tests/acceptance/17_users/unsafe/10_add_user_locked.cf new file mode 100644 index 0000000000..0206fc867c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_locked.cf @@ -0,0 +1,63 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added locked"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "locked"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_locked("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_locked_with_password.cf b/tests/acceptance/17_users/unsafe/10_add_user_locked_with_password.cf new file mode 100644 index 0000000000..427390a5f5 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_locked_with_password.cf @@ -0,0 +1,72 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added locked with password"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + vars: + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "locked", + password => test_password; +} + +body password test_password +{ + format => "hash"; + data => "$(test.hash)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_locked("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_warn.cf b/tests/acceptance/17_users/unsafe/10_add_user_warn.cf new file mode 100644 index 0000000000..633b39f0d0 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_warn.cf @@ -0,0 +1,59 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added (dry run only)"; + "story_id" string => "5525"; + "covers" string => "dryrun_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + action => test_action, + policy => "present"; +} + +body action test_action +{ + action_policy => "warn"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_exists("johndoe", "failure", "success"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_description.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_description.cf new file mode 100644 index 0000000000..ee5ec1ac7d --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_description.cf @@ -0,0 +1,57 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with description"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + vars: + "desc_string" string => "This description should make the CFEngine test pass"; + + users: + "johndoe" + policy => "present", + description => "$(desc_string)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_has_description("johndoe", "$(test.desc_string)", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_hashed_password.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_hashed_password.cf new file mode 100644 index 0000000000..927fc15382 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_hashed_password.cf @@ -0,0 +1,74 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with password"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + vars: + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "present", + password => test_password; +} + +body password test_password +{ + format => "hash"; + data => "$(test.hash)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_has_password_hash("johndoe", "$(test.hash)", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + # Password hash unsupported on Windows. + (ok.ready)|windows:: + "$(this.promise_filename) Pass"; + (!ok.ready).!windows:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_many_attributes.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_many_attributes.cf new file mode 100644 index 0000000000..82add1c1a6 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_many_attributes.cf @@ -0,0 +1,103 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with several attributes"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + vars: + "desc_string" string => "This description should make the CFEngine test pass"; + aix:: + # AIX only allows certain shells. + "shell" string => "/bin/csh"; + !aix:: + "shell" string => "$(G.echo)"; + + users: + "johndoe" + policy => "present", + uid => "9876", + group_primary => "$(user_tests.group1)", + groups_secondary => { "$(user_tests.group2)" }, + home_dir => "/home/user-johndoe", + shell => "$(shell)", + description => "$(desc_string)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => remove_stale_groups; + + methods: + "any" usebundle => user_has_uid("johndoe", "9876", "uid_success", "uid_failure"), + classes => always("uid_methods_run"); + + "any" usebundle => user_is_in_primary_group("johndoe", "$(user_tests.group1)", "pgroup_success", "pgroup_failure"), + classes => always("pgroup_methods_run"); + + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group2)", "sgroup_success", "sgroup_failure"), + classes => always("sgroup_methods_run"); + + "any" usebundle => user_has_home_dir("johndoe", "/home/user-johndoe", "home_success", "home_failure"), + classes => always("home_methods_run"); + + "any" usebundle => user_has_shell("johndoe", "$(test.shell)", "shell_success", "shell_failure"), + classes => always("shell_methods_run"); + + "any" usebundle => user_has_description("johndoe", "$(test.desc_string)", "desc_success", "desc_failure"), + classes => always("desc_methods_run"); + + classes: + "ready" and => { "uid_methods_run", "pgroup_methods_run", "sgroup_methods_run", + "home_methods_run", "shell_methods_run", "desc_methods_run" }; + !windows:: + "unix_ok" and => { "uid_success", "!uid_failure", "shell_success", "!shell_failure", }; + windows:: + "unix_ok" expression => "any"; + any:: + "ok" and => { "pgroup_success", "!pgroup_failure", "sgroup_success", "!sgroup_failure", + "home_success", "!home_failure", "desc_success", "!desc_failure", + "unix_ok" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_many_attributes_warn.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_many_attributes_warn.cf new file mode 100644 index 0000000000..7b6e4446f9 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_many_attributes_warn.cf @@ -0,0 +1,84 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with multiple attributes (dry run only)"; + "story_id" string => "5525"; + "covers" string => "dryrun_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + vars: + "desc_string" string => "This description should make the CFEngine test pass"; + + users: + "johndoe" + action => test_action, + policy => "present", + uid => "9876", + group_primary => "$(user_tests.gid1)", + groups_secondary => { "$(user_tests.group2)" }, + password => test_password, + shell => "/bin/csh", + description => "$(desc_string)"; +} + +body action test_action +{ + action_policy => "warn"; +} + +body password test_password +{ + format => "hash"; + data => "dTloMVpjYt1w2"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_exists("johndoe", "failure", "success"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_many_empty_attributes.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_many_empty_attributes.cf new file mode 100644 index 0000000000..677b46ac5c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_many_empty_attributes.cf @@ -0,0 +1,68 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with several empty attributes"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present", + group_primary => "", + groups_secondary => { }, + description => "", + classes => if_ok("promise_ok"); +} + +####################################################### + +bundle agent check +{ + methods: + aix:: + # AIX forcibly puts a user in the staff group, even if no group is specified. + "any" usebundle => user_is_in_secondary_group("johndoe", "staff", "sgroup_success", "sgroup_failure"), + classes => always("sgroup_methods_run"); + + !aix:: + "any" usebundle => user_is_in_any_secondary_group("johndoe", "sgroup_failure", "sgroup_success"), + classes => always("sgroup_methods_run"); + + any:: + "any" usebundle => user_has_description("johndoe", "", "desc_success", "desc_failure"), + classes => always("desc_methods_run"); + + classes: + "ready" and => { "sgroup_methods_run", "desc_methods_run" }; + "ok" and => { "sgroup_success", "!sgroup_failure", + "desc_success", "!desc_failure", "promise_ok" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_password.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_password.cf new file mode 100644 index 0000000000..2db323a5d4 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_password.cf @@ -0,0 +1,82 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with password"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present", + password => test_password; +} + +body password test_password +{ + format => "plaintext"; + data => "J0hnd0eX"; +} + +####################################################### + +bundle agent check +{ + methods: + !windows:: + "any" usebundle => user_get_password_hash("johndoe"), + classes => always("methods_run"); + windows:: + "any" usebundle => user_has_password("johndoe", "J0hnd0eX", "pw_ok", "pw_fail"), + classes => always("methods_run"); + + any:: + "any" usebundle => user_does_not_need_password_update("johndoe", "no_pw_update_ok", "no_pw_update_fail"); + + classes: + "ready" expression => "methods_run"; + !windows:: + # Make sure the password field has grown to more than a few characters. + "pw_ok" expression => regcmp("^[^:][^:][^:][^:][^:][^:][^:][^:][^:][^:].*", "$(user_get_password_hash.hash)"); + + any:: + "ok" and => { "pw_ok", "!pw_fail", "no_pw_update_ok", "!no_pw_update_fail" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_primary_gid.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_primary_gid.cf new file mode 100644 index 0000000000..6709a94c34 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_primary_gid.cf @@ -0,0 +1,105 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with primary group id"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", + "user_queries.cf.sub", + "disable_sudo_tty_requirement.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + meta: + # No GIDs on Windows. + "test_skip_unsupported" string => "windows"; + + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; + + files: + "$(G.testfile)" + delete => init_delete; + + methods: + "any" usebundle => disable_sudo_tty_requirement; +} + +body delete init_delete +{ + rmdirs => "false"; +} + +####################################################### + +body contain test_contain_body +{ + useshell => "useshell"; +} + +bundle agent test +{ + users: + "johndoe" + policy => "present", + group_primary => "$(user_tests.gid1)"; + + commands: + sudo_works:: + "sudo -u johndoe $(G.touch) $(G.testfile)" + contain => test_contain_body; +} + +####################################################### + +body perms check_perms_body +{ + groups => { "$(user_tests.gid1)" }; +} + +body classes check_classes_body +{ + promise_repaired => { "perms_not_ok" }; + promise_kept => { "perms_ok" }; +} + +bundle agent check +{ + methods: + "any" usebundle => remove_stale_groups; + + methods: + "any" usebundle => user_is_in_primary_group("johndoe", "$(user_tests.group1)", "success", "failure"), + classes => always("methods_run"); + + files: + "$(G.testfile)" + perms => check_perms_body, + classes => check_classes_body; + + classes: + "ready" expression => "methods_run"; + sudo_works:: + "ok" and => { "success", "!failure", "perms_ok", "!perms_not_ok" }; + !sudo_works:: + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_primary_group.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_primary_group.cf new file mode 100644 index 0000000000..1ce89fd925 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_primary_group.cf @@ -0,0 +1,102 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with primary group"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", + "user_queries.cf.sub", + "disable_sudo_tty_requirement.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; + + files: + "$(G.testfile)" + delete => init_delete; + + methods: + "any" usebundle => disable_sudo_tty_requirement; +} + +body delete init_delete +{ + rmdirs => "false"; +} + +####################################################### + +body contain test_contain_body +{ + useshell => "useshell"; +} + +bundle agent test +{ + users: + "johndoe" + policy => "present", + group_primary => "$(user_tests.group1)"; + + commands: + sudo_works:: + "sudo -u johndoe $(G.touch) $(G.testfile)" + contain => test_contain_body; +} + +####################################################### + +body perms check_perms_body +{ + groups => { "$(user_tests.group1)" }; +} + +body classes check_classes_body +{ + promise_repaired => { "perms_not_ok" }; + promise_kept => { "perms_ok" }; +} + +bundle agent check +{ + methods: + "any" usebundle => remove_stale_groups; + + methods: + "any" usebundle => user_is_in_primary_group("johndoe", "$(user_tests.group1)", "success", "failure"), + classes => always("methods_run"); + + files: + sudo_works:: + "$(G.testfile)" + perms => check_perms_body, + classes => check_classes_body; + + classes: + "ready" expression => "methods_run"; + sudo_works:: + "ok" and => { "success", "!failure", "perms_ok", "!perms_not_ok" }; + !sudo_works:: + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_secondary_gid.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_secondary_gid.cf new file mode 100644 index 0000000000..55e917cb59 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_secondary_gid.cf @@ -0,0 +1,95 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with secondary group id"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", + "user_queries.cf.sub", + "disable_sudo_tty_requirement.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +body perms init_perms_body +{ + groups => { "$(user_tests.gid1)" }; + mode => "664"; +} + +bundle agent init +{ + # AIX useradd/usermod commands do not support numerical group arguments. + meta: + "test_soft_fail" string => "aix", + meta => { "redmine6285" }; + + # No GIDs on Windows. + "test_skip_unsupported" string => "windows"; + + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; + + files: + "$(G.testfile)" + create => "true", + perms => init_perms_body; + + methods: + "any" usebundle => disable_sudo_tty_requirement; +} + +####################################################### + +body contain test_contain_body +{ + useshell => "useshell"; +} + +bundle agent test +{ + users: + "johndoe" + policy => "present", + groups_secondary => { "$(user_tests.gid1)" }; + + commands: + sudo_works:: + "sudo -u johndoe /bin/sh -c '$(G.echo) Succeeded > $(G.testfile)'" + contain => test_contain_body; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group1)", "success", "failure"), + classes => always("methods_run"); + + classes: + "content_ok" not => strcmp("0", countlinesmatching("Succeeded", "$(G.testfile)")); + + classes: + "ready" expression => "methods_run"; + sudo_works:: + "ok" and => { "success", "!failure", "content_ok" }; + !sudo_works:: + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_secondary_group.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_secondary_group.cf new file mode 100644 index 0000000000..9c48f38ee7 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_secondary_group.cf @@ -0,0 +1,88 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with secondary group"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", + "user_queries.cf.sub", + "disable_sudo_tty_requirement.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +body perms init_perms_body +{ + groups => { "$(user_tests.group1)" }; + mode => "664"; +} + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; + + files: + "$(G.testfile)" + create => "true", + perms => init_perms_body; + + methods: + "any" usebundle => disable_sudo_tty_requirement; +} + +####################################################### + +body contain test_contain_body +{ + useshell => "useshell"; +} + +bundle agent test +{ + users: + "johndoe" + policy => "present", + groups_secondary => { "$(user_tests.group1)" }; + + commands: + sudo_works:: + "sudo -u johndoe /bin/sh -c '$(G.echo) Succeeded > $(G.testfile)'" + contain => test_contain_body; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group1)", "success", "failure"), + classes => always("methods_run"); + + classes: + sudo_works:: + "content_ok" not => strcmp("0", countlinesmatching("Succeeded", "$(G.testfile)")); + + classes: + "ready" expression => "methods_run"; + sudo_works:: + "ok" and => { "success", "!failure", "content_ok" }; + !sudo_works:: + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_several_secondary_groups.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_several_secondary_groups.cf new file mode 100644 index 0000000000..42b91e08c4 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_several_secondary_groups.cf @@ -0,0 +1,102 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with several secondary groups"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", + "user_queries.cf.sub", + "disable_sudo_tty_requirement.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +body perms init_perms_body_bin +{ + groups => { "$(user_tests.group1)" }; + mode => "664"; +} + +body perms init_perms_body_sys +{ + groups => { "$(user_tests.group2)" }; + mode => "664"; +} + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; + + files: + "$(G.testfile)" + create => "true", + perms => init_perms_body_bin; + "$(G.testfile).2" + create => "true", + perms => init_perms_body_sys; + + methods: + "any" usebundle => disable_sudo_tty_requirement; +} + +####################################################### + +body contain test_contain_body +{ + useshell => "useshell"; +} + +bundle agent test +{ + users: + "johndoe" + policy => "present", + groups_secondary => { "$(user_tests.group1)", "$(user_tests.group2)" }; + + commands: + sudo_works:: + "sudo -u johndoe /bin/sh -c '$(G.echo) Succeeded > $(G.testfile)'" + contain => test_contain_body; + "sudo -u johndoe /bin/sh -c '$(G.echo) Succeeded > $(G.testfile).2'" + contain => test_contain_body; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group1)", "bin_success", "bin_failure"), + classes => always("bin_methods_run"); + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group2)", "sys_success", "sys_failure"), + classes => always("sys_methods_run"); + + classes: + !windows:: + "1_ok" not => strcmp("0", countlinesmatching("Succeeded", "$(G.testfile)")); + "2_ok" not => strcmp("0", countlinesmatching("Succeeded", "$(G.testfile).2")); + + classes: + "ready" and => { "bin_methods_run", "sys_methods_run" }; + sudo_works:: + "ok" and => { "bin_success", "!bin_failure", "sys_success", "!sys_failure", "1_ok", "2_ok" }; + !sudo_works:: + "ok" and => { "bin_success", "!bin_failure", "sys_success", "!sys_failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_shell.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_shell.cf new file mode 100644 index 0000000000..424b3a7b92 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_shell.cf @@ -0,0 +1,76 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with a shell"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", + "user_queries.cf.sub", + "disable_sudo_tty_requirement.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; + cache_system_functions => "no"; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; + + methods: + "any" usebundle => disable_sudo_tty_requirement; +} + +####################################################### + +bundle agent test +{ + vars: + aix:: + # AIX only allows certain shells. + "shell" string => "/bin/csh"; + !aix:: + "shell" string => "$(G.cat)"; + + users: + "johndoe" + policy => "present", + shell => "$(shell)"; +} + +####################################################### + +bundle agent check +{ + vars: + "currentdir" string => dirname("$(this.promise_filename)"); + + classes: + !windows.!aix:: + "ok" expression => regcmp(".*Succeeded.*", execresult("sudo su johndoe $(currentdir)$(const.dirsep)add_user_with_shell.txt", "useshell")); + + methods: + aix:: + "any" usebundle => user_has_shell("johndoe", "$(test.shell)", "shell_success", "shell_failure"), + classes => always("shell_methods_run"); + + classes: + aix:: + "ok" and => { "shell_success", "!shell_failure" }; + + reports: + ok|windows:: + "$(this.promise_filename) Pass"; + !ok.!windows:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_add_user_with_uid.cf b/tests/acceptance/17_users/unsafe/10_add_user_with_uid.cf new file mode 100644 index 0000000000..ccf3f1b77c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_add_user_with_uid.cf @@ -0,0 +1,81 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added with uid"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; + + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + rmdirs => "false"; +} + +####################################################### + +body perms test_perms_body +{ + owners => { "johndoe" }; +} + +bundle agent test +{ + users: + "johndoe" + policy => "present", + uid => "9999"; + + files: + "$(G.testfile)" + create => "true", + perms => test_perms_body; +} + +####################################################### + +body perms check_perms_body +{ + owners => { "9999" }; +} + +body classes check_classes_body +{ + promise_repaired => { "not_ok" }; + promise_kept => { "ok" }; +} + +bundle agent check +{ + files: + "$(G.testfile)" + perms => check_perms_body, + classes => check_classes_body; + + reports: + (ok.!not_ok)|windows:: + "$(this.promise_filename) Pass"; + (!ok|not_ok).!windows:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_home_bundle_when_removing_user.cf b/tests/acceptance/17_users/unsafe/10_home_bundle_when_removing_user.cf new file mode 100644 index 0000000000..0ee06d9706 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_home_bundle_when_removing_user.cf @@ -0,0 +1,62 @@ +bundle common test_meta +{ + vars: + "description" string => "A user removed does not get the home bundle run"; + "story_id" string => "5526"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "absent", + home_bundle => home_bundle("/home/johndoe"); +} + +bundle agent home_bundle(x) +{ + files: + "$(G.testfile)" + create => "true", + edit_line => home_edit("$(x)"); +} + +bundle edit_line home_edit(x) +{ + insert_lines: + "$(x)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("0", countlinesmatching("/home/johndoe", "$(G.testfile)")); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_home_bundle_with_existing_user.cf b/tests/acceptance/17_users/unsafe/10_home_bundle_with_existing_user.cf new file mode 100644 index 0000000000..5025357718 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_home_bundle_with_existing_user.cf @@ -0,0 +1,62 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present does not get the home bundle run"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present", + home_bundle => home_bundle("/home/johndoe"); +} + +bundle agent home_bundle(x) +{ + files: + "$(G.testfile)" + create => "true", + edit_line => home_edit("$(x)"); +} + +bundle edit_line home_edit(x) +{ + insert_lines: + "$(x)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("0", countlinesmatching("/home/johndoe", "$(G.testfile)")); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_default_inherit.cf b/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_default_inherit.cf new file mode 100644 index 0000000000..40654b9fd6 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_default_inherit.cf @@ -0,0 +1,68 @@ +bundle common test_meta +{ + vars: + "description" string => "A user added gets the home bundle run"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + classes: + "should_not_prevent_edits" expression => "any"; + + users: + "johndoe" + policy => "present", + home_bundle => home_bundle("/home/johndoe"); +} + +bundle agent home_bundle(x) +{ + files: + # Class selector should have no effect since we're not supposed to inherit classes. + !should_not_prevent_edits:: + "$(G.testfile)" + create => "true", + edit_line => home_edit("$(x)"); +} + +bundle edit_line home_edit(x) +{ + insert_lines: + "$(x)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => strcmp("0", countlinesmatching("/home/johndoe", "$(G.testfile)")); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_inherit.cf b/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_inherit.cf new file mode 100644 index 0000000000..1113851da0 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_inherit.cf @@ -0,0 +1,69 @@ +bundle common test_meta +{ + vars: + "description" string => "A user added gets the home bundle run (2)"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + classes: + "should_prevent_edits" expression => "any"; + + users: + "johndoe" + policy => "present", + home_bundle => home_bundle("/home/johndoe"), + home_bundle_inherit => "true"; +} + +bundle agent home_bundle(x) +{ + files: + # Class selector should prevent edits since we're supposed to inherit classes. + !should_prevent_edits:: + "$(G.testfile)" + create => "true", + edit_line => home_edit("$(x)"); +} + +bundle edit_line home_edit(x) +{ + insert_lines: + "$(x)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => strcmp("0", countlinesmatching("/home/johndoe", "$(G.testfile)")); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_no_inherit.cf b/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_no_inherit.cf new file mode 100644 index 0000000000..e0256f017b --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_home_bundle_with_new_user_and_no_inherit.cf @@ -0,0 +1,69 @@ +bundle common test_meta +{ + vars: + "description" string => "A user added gets the home bundle run (3)"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Remove him first, should he already be present. + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + classes: + "should_not_prevent_edits" expression => "any"; + + users: + "johndoe" + policy => "present", + home_bundle => home_bundle("/home/johndoe"), + home_bundle_inherit => "false"; +} + +bundle agent home_bundle(x) +{ + files: + # Class selector should have no effect since we're not supposed to inherit classes. + !should_not_prevent_edits:: + "$(G.testfile)" + create => "true", + edit_line => home_edit("$(x)"); +} + +bundle edit_line home_edit(x) +{ + insert_lines: + "$(x)"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" not => strcmp("0", countlinesmatching("/home/johndoe", "$(G.testfile)")); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_missing_policy.cf b/tests/acceptance/17_users/unsafe/10_missing_policy.cf new file mode 100644 index 0000000000..ac9af5dbee --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_missing_policy.cf @@ -0,0 +1,51 @@ +bundle common test_meta +{ + vars: + "description" string => "A user not present gets added when there is no policy attribute"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_exists("johndoe", "failure", "success"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_lock.cf b/tests/acceptance/17_users/unsafe/10_modify_user_lock.cf new file mode 100644 index 0000000000..fffe5d37a5 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_lock.cf @@ -0,0 +1,62 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets set to locked"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "locked"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_locked("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_lock_warn.cf b/tests/acceptance/17_users/unsafe/10_modify_user_lock_warn.cf new file mode 100644 index 0000000000..d8c72e69ec --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_lock_warn.cf @@ -0,0 +1,68 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets locked (dry run)"; + "story_id" string => "5525"; + "covers" string => "dryrun_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + action => test_action, + policy => "locked"; +} + +body action test_action +{ + action_policy => "warn"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_unlocked("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_lock_with_password.cf b/tests/acceptance/17_users/unsafe/10_modify_user_lock_with_password.cf new file mode 100644 index 0000000000..1f888d2788 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_lock_with_password.cf @@ -0,0 +1,72 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets locked and the password changed"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent test +{ + vars: + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "locked", + password => test_password; +} + +body password test_password +{ + format => "hash"; + data => "$(test.hash)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_locked("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_unlock.cf b/tests/acceptance/17_users/unsafe/10_modify_user_unlock.cf new file mode 100644 index 0000000000..4ed031bc03 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_unlock.cf @@ -0,0 +1,62 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets enabled"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + users: + "johndoe" + policy => "locked"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_unlocked("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_unlock_warn.cf b/tests/acceptance/17_users/unsafe/10_modify_user_unlock_warn.cf new file mode 100644 index 0000000000..f9b6fc3331 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_unlock_warn.cf @@ -0,0 +1,68 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets enabled and the password changed (dry run)"; + "story_id" string => "5525"; + "covers" string => "dryrun_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + users: + "johndoe" + policy => "locked"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + action => test_action, + policy => "present"; +} + +body action test_action +{ + action_policy => "warn"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_locked("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_unlock_with_password.cf b/tests/acceptance/17_users/unsafe/10_modify_user_unlock_with_password.cf new file mode 100644 index 0000000000..407fe77c8c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_unlock_with_password.cf @@ -0,0 +1,82 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets enabled and the password changed"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + users: + "johndoe" + policy => "locked"; +} + +####################################################### + +bundle agent test +{ + vars: + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "present", + password => test_password; +} + +body password test_password +{ + !windows:: + format => "hash"; + data => "$(test.hash)"; + windows:: + format => "plaintext"; + data => "Unlocked0P4SSW0RD"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_unlocked("johndoe", "unlock_success", "unlock_failure"), + classes => always("unlock_methods_run"); + !windows:: + "any" usebundle => user_has_password_hash("johndoe", "$(test.hash)", "hash_success", "hash_failure"), + classes => always("hash_methods_run"); + windows:: + "any" usebundle => user_has_password("johndoe", "Unlocked0P4SSW0RD", "hash_success", "hash_failure"), + classes => always("hash_methods_run"); + + classes: + "ready" and => { "unlock_methods_run", "hash_methods_run" }; + "ok" and => { "unlock_success", "!unlock_failure", "hash_success", "!hash_failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_with_hashed_password.cf b/tests/acceptance/17_users/unsafe/10_modify_user_with_hashed_password.cf new file mode 100644 index 0000000000..b6919e188e --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_with_hashed_password.cf @@ -0,0 +1,84 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets the hashed password changed"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + users: + "johndoe" + policy => "present", + password => init_password; +} + +body password init_password +{ + format => "plaintext"; + data => "Bad0P4SSW0RD"; +} + +####################################################### + +bundle agent test +{ + vars: + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "present", + password => test_password; +} + +body password test_password +{ + format => "hash"; + data => "$(test.hash)"; +} + +####################################################### + +bundle agent check +{ + methods: + !windows:: + "any" usebundle => user_has_password_hash("johndoe", "$(test.hash)", "success", "failure"), + classes => always("methods_run"); + windows:: + # Hash not supported on Windows. Make sure it is the same as before. + "any" usebundle => user_has_password("johndoe", "Bad0P4SSW0RD", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_with_many_attributes.cf b/tests/acceptance/17_users/unsafe/10_modify_user_with_many_attributes.cf new file mode 100644 index 0000000000..655c26c84a --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_with_many_attributes.cf @@ -0,0 +1,143 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets several attributes changed"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Start out with other attributes. + users: + "johndoe" + policy => "present", + password => init_password, + uid => "8765", + group_primary => "$(user_tests.gid2)", + groups_secondary => { "$(user_tests.group1)" }, + home_dir => "/home/johndoe", + shell => "/bin/bash", + description => "Wrong!"; +} + +body password init_password +{ + format => "plaintext"; + data => "Wrong0P4SSW0RD"; +} + +####################################################### + +bundle agent test +{ + vars: + "hash" string => "dTloMVpjYt1w2"; + "desc_string" string => "This description should make the CFEngine test pass"; + + users: + "johndoe" + policy => "present", + password => test_password, + uid => "9876", + group_primary => "$(user_tests.group1)", + groups_secondary => { "$(user_tests.group2)" }, + home_dir => "/home/user-johndoe", + shell => "/bin/csh", + description => "$(desc_string)"; +} + +body password test_password +{ + !windows:: + format => "hash"; + data => "$(test.hash)"; + windows:: + format => "plaintext"; + data => "New0P4SSW0RD"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => remove_stale_groups; + + methods: + "any" usebundle => user_has_uid("johndoe", "9876", "uid_success", "uid_failure"), + classes => always("uid_methods_run"); + + "any" usebundle => user_is_in_primary_group("johndoe", "$(user_tests.group1)", "pgroup_success", "pgroup_failure"), + classes => always("pgroup_methods_run"); + + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group2)", "sgroup_success", "sgroup_failure"), + classes => always("sgroup_methods_run"); + + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group1)", "not_sgroup_failure", "not_sgroup_success"), + classes => always("not_sgroup_methods_run"); + + "any" usebundle => user_has_home_dir("johndoe", "/home/user-johndoe", "home_success", "home_failure"), + classes => always("home_methods_run"); + + "any" usebundle => user_has_shell("johndoe", "/bin/csh", "shell_success", "shell_failure"), + classes => always("shell_methods_run"); + + "any" usebundle => user_has_description("johndoe", "$(test.desc_string)", "desc_success", "desc_failure"), + classes => always("desc_methods_run"); + + !windows:: + "any" usebundle => user_has_password_hash("johndoe", "$(test.hash)", "hash_success", "hash_failure"), + classes => always("hash_methods_run"); + windows:: + "any" usebundle => user_has_password("johndoe", "New0P4SSW0RD", "hash_success", "hash_failure"), + classes => always("hash_methods_run"); + + classes: + "ready" and => { "uid_methods_run", "pgroup_methods_run", "sgroup_methods_run", "not_sgroup_methods_run", + "home_methods_run", "shell_methods_run", "desc_methods_run", "hash_methods_run" }; + !windows:: + # Note the secondary group classes here. Windows treats primary and secondary groups the same, + # and hence that test is invalid there. + "unix_ok" and => { "uid_success", "!uid_failure", "shell_success", "!shell_failure", + "not_sgroup_success", "!not_sgroup_failure", }; + windows:: + "unix_ok" expression => "any"; + sles_11:: + "ok" -> "CFE-3386" + and => { "pgroup_success", "!pgroup_failure", "!sgroup_success", "sgroup_failure", + "hash_success", "!hash_failure", + "home_success", "!home_failure", "desc_success", "!desc_failure", + "unix_ok" }; + !sles_11:: + "ok" and => { "pgroup_success", "!pgroup_failure", "sgroup_success", "!sgroup_failure", + "hash_success", "!hash_failure", + "home_success", "!home_failure", "desc_success", "!desc_failure", + "unix_ok" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_with_many_attributes_warn.cf b/tests/acceptance/17_users/unsafe/10_modify_user_with_many_attributes_warn.cf new file mode 100644 index 0000000000..5366a0953d --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_with_many_attributes_warn.cf @@ -0,0 +1,151 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets many attributes changed (dry run)"; + "story_id" string => "5525"; + "covers" string => "dryrun_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + vars: + "hash" string => "dTloMVpjYt1w2"; + + # Start out with other attributes. + users: + "johndoe" + policy => "present", + password => init_password, + uid => "8765", + group_primary => "$(user_tests.group2)", + groups_secondary => { "$(user_tests.group1)" }, + home_dir => "/johns_old_home_dir", + shell => "/bin/csh", + description => "Good user"; +} + +body password init_password +{ + !windows:: + format => "hash"; + data => "$(init.hash)"; + windows:: + format => "plaintext"; + data => "Old0P4SSW0RD"; +} + +####################################################### + +bundle agent test +{ + vars: + "desc_string" string => "This description should make the CFEngine test fail"; + + users: + "johndoe" + action => test_action, + policy => "present", + password => test_password, + uid => "9876", + group_primary => "$(user_tests.group1)", + groups_secondary => { "$(user_tests.group2)" }, + home_dir => "/johns_home_dir", + shell => "/bin/sh", + description => "$(desc_string)"; +} + +body action test_action +{ + action_policy => "warn"; +} + +body password test_password +{ + format => "plaintext"; + data => "Wrong0P4SSW0RD"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => remove_stale_groups; + + methods: + "any" usebundle => user_has_uid("johndoe", "8765", "uid_success", "uid_failure"), + classes => always("uid_methods_run"); + + "any" usebundle => user_is_in_primary_group("johndoe", "$(user_tests.group2)", "pgroup_success", "pgroup_failure"), + classes => always("pgroup_methods_run"); + + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group1)", "sgroup_success", "sgroup_failure"), + classes => always("sgroup_methods_run"); + + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group2)", "not_sgroup_failure", "not_sgroup_success"), + classes => always("not_sgroup_methods_run"); + + "any" usebundle => user_has_home_dir("johndoe", "/johns_old_home_dir", "home_success", "home_failure"), + classes => always("home_methods_run"); + + "any" usebundle => user_has_shell("johndoe", "/bin/csh", "shell_success", "shell_failure"), + classes => always("shell_methods_run"); + + "any" usebundle => user_has_description("johndoe", "Good user", "desc_success", "desc_failure"), + classes => always("desc_methods_run"); + + !windows:: + "any" usebundle => user_has_password_hash("johndoe", "$(init.hash)", "hash_success", "hash_failure"), + classes => always("hash_methods_run"); + windows:: + "any" usebundle => user_has_password("johndoe", "Wrong0P4SSW0RD", "hash_failure", "hash_success"), + classes => always("hash_methods_run"); + + classes: + "ready" and => { "uid_methods_run", "pgroup_methods_run", "sgroup_methods_run", "not_sgroup_methods_run", + "home_methods_run", "shell_methods_run", "desc_methods_run", "hash_methods_run" }; + !windows:: + # Note the secondary group classes here. Windows treats primary and secondary groups the same, + # and hence that test is invalid there. + "unix_ok" and => { "uid_success", "!uid_failure", "shell_success", "!shell_failure", + "not_sgroup_success", "!not_sgroup_failure", }; + windows:: + "unix_ok" expression => "any"; + sles_11:: + "ok" -> "CFE-3386" + and => { "pgroup_success", "!pgroup_failure", "!sgroup_success", "sgroup_failure", + "hash_success", "!hash_failure", + "home_success", "!home_failure", "desc_success", "!desc_failure", + "unix_ok" }; + !sles_11:: + "ok" and => { "pgroup_success", "!pgroup_failure", "sgroup_success", "!sgroup_failure", + "hash_success", "!hash_failure", + "home_success", "!home_failure", "desc_success", "!desc_failure", + "unix_ok" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_with_many_empty_attributes.cf b/tests/acceptance/17_users/unsafe/10_modify_user_with_many_empty_attributes.cf new file mode 100644 index 0000000000..7f9cd9a31c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_with_many_empty_attributes.cf @@ -0,0 +1,81 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets multiple empty attributes changed"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + # Create him first with some unwanted attributes + users: + "johndoe" + policy => "present", + group_primary => "$(user_tests.group1)", + groups_secondary => { "$(user_tests.group2)" }, + description => "Description"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present", + group_primary => "", + groups_secondary => { }, + description => ""; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => remove_stale_groups; + + methods: + windows:: + "any" usebundle => user_is_in_primary_group("johndoe", "$(user_tests.group1)", "pgroup_failure", "pgroup_success"), + classes => always("pgroup_methods_run"); + # Unix hosts cannot remove the primary group. + !windows:: + "any" usebundle => user_is_in_primary_group("johndoe", "$(user_tests.group1)", "pgroup_success", "pgroup_failure"), + classes => always("pgroup_methods_run"); + + aix:: + # AIX forcibly puts a user in the primary group, even if no group is specified. + "any" usebundle => user_is_in_secondary_group("johndoe", "$(user_tests.group1)", "sgroup_success", "sgroup_failure"), + classes => always("sgroup_methods_run"); + + !aix:: + "any" usebundle => user_is_in_any_secondary_group("johndoe", "sgroup_failure", "sgroup_success"), + classes => always("sgroup_methods_run"); + + any:: + "any" usebundle => user_has_description("johndoe", "", "desc_success", "desc_failure"), + classes => always("desc_methods_run"); + + classes: + "ready" and => { "pgroup_methods_run", "sgroup_methods_run", "desc_methods_run" }; + "ok" and => { "pgroup_success", "!pgroup_failure", "sgroup_success", "!sgroup_failure", + "desc_success", "!desc_failure", }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_with_password.cf b/tests/acceptance/17_users/unsafe/10_modify_user_with_password.cf new file mode 100644 index 0000000000..aca7f816f2 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_with_password.cf @@ -0,0 +1,90 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets the password changed"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + vars: + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "present", + password => init_password; +} + +body password init_password +{ + data => "$(init.hash)"; + !windows:: + format => "hash"; + windows:: + format => "plaintext"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present", + password => test_password; +} + +body password test_password +{ + format => "plaintext"; + data => "New0P4SSW0RD"; +} + +####################################################### + +bundle agent check +{ + methods: + !windows:: + # Make sure it is *not* the same as before. + "any" usebundle => user_has_password_hash("johndoe", "$(init.hash)", "failure", "success"), + classes => always("methods_run"); + windows:: + "any" usebundle => user_has_password("johndoe", "New0P4SSW0RD", "success", "failure"), + classes => always("methods_run"); + + any:: + "any" usebundle => user_does_not_need_password_update("johndoe", "no_pw_update_ok", "no_pw_update_fail"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure", "no_pw_update_ok", "!no_pw_update_fail" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_modify_user_with_same_password.cf b/tests/acceptance/17_users/unsafe/10_modify_user_with_same_password.cf new file mode 100644 index 0000000000..d335163480 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_modify_user_with_same_password.cf @@ -0,0 +1,100 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets the password set to the same as before"; + "story_id" string => "5525"; + "covers" string => "operational_kept"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Something in the Solaris chroot test environment makes it impossible + # to test matching passwords, the pam module always returns error. + # This should not happen in a production system though. + # Since the error is not on our part, and likely unsolvable, we set + # Redmine to zero. However, it would be nice to know if the problem ever + # goes away, so using soft_fail. + "test_soft_fail" string => "!hpux_trusted_mode_test.(solaris.!sunos_5_9)", + meta => { "redmine0" }; + # On Solaris 9 PAM just crashes inside chroot. + "test_skip_needs_work" string => "!hpux_trusted_mode_test.sunos_5_9"; + vars: + # This is the same password as the plaintext one further down. + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "present", + password => init_password; +} + +body password init_password +{ + !windows:: + format => "hash"; + data => "$(init.hash)"; + windows:: + format => "plaintext"; + data => "J0hnd0eX"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present", + password => test_password, + classes => if_repaired("kept_failed"); +} + +body password test_password +{ + format => "plaintext"; + data => "J0hnd0eX"; +} + +####################################################### + +bundle agent check +{ + methods: + # Make sure it is the same as before. + !windows:: + "any" usebundle => user_has_password_hash("johndoe", "$(init.hash)", "success", "failure"), + classes => always("methods_run"); + windows:: + "any" usebundle => user_has_password("johndoe", "J0hnd0eX", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure", "!kept_failed" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_newly_created_account_should_not_count_as_locked.cf b/tests/acceptance/17_users/unsafe/10_newly_created_account_should_not_count_as_locked.cf new file mode 100644 index 0000000000..f29e14e9c1 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_newly_created_account_should_not_count_as_locked.cf @@ -0,0 +1,87 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets locked"; + "story_id" string => "5525"; + "covers" string => "operational_repaired"; +} + +####################################################### +# +# Test that a newly created account does not confuse +# our locking detection. This may happen because +# "useradd" initially sets the password to '!'. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Delete user, then create. + vars: + "users[n1]" string => "johndoe"; + "users[n2]" string => "johndoe"; + "policies[n1]" string => "absent"; + "policies[n2]" string => "present"; + + "iter" slist => { "n1", "n2" }; + + users: + "$(users[$(iter)])" + policy => "$(policies[$(iter)])"; +} + +####################################################### + +bundle agent test +{ + vars: + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "locked", + password => test_password; +} + +body password test_password +{ + format => "hash"; + data => "$(test.hash)"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_is_locked("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_password_hash_is_not_cached.cf b/tests/acceptance/17_users/unsafe/10_password_hash_is_not_cached.cf new file mode 100644 index 0000000000..29aa62fd8c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_password_hash_is_not_cached.cf @@ -0,0 +1,141 @@ +bundle common test_meta +{ + vars: + "description" string => "Test that cached password hashes do not cause problems"; + "story_id" string => "5525"; + "covers" string => "operational_kept"; +} + +####################################################### + +# If the platform is using /etc/passwd to store hashes, then the hash will be +# cached in the passwd_info structure in the C code. This may cause problems +# if one part of the code (password update) tries to modify the hash, and +# another one (locking) also does it. The latter may use the cached value from +# before it was modified, but should use the updated value. + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + # Password hashes are not supported on Windows. + "test_skip_unsupported" string => "(hpux_trusted_mode_test.!hpux)|windows"; + + vars: + # "j0hnd0e" + "hash" string => "dTloMVpjYt1w2"; + + methods: + "any" usebundle => init_firstpass; + "any" usebundle => init_secondpass; +} + +bundle agent init_firstpass +{ + users: + "user1" + policy => "absent"; + "user2" + policy => "absent"; +} + +bundle agent init_secondpass +{ + users: + "user1" + policy => "present", + password => init_password; + "user2" + policy => "present", + password => init_password; +} + +body password init_password +{ + format => "hash"; + data => "$(init.hash)"; +} + +####################################################### + +bundle agent test +{ + vars: + # "N4wP4ssw" + "hash" string => "aapgPBZAGeZf6"; + + methods: + "any" usebundle => test_firstpass; + "any" usebundle => test_secondpass; +} + +bundle agent test_firstpass +{ + users: + "user1" + policy => "locked"; + "user2" + policy => "locked"; +} + +bundle agent test_secondpass +{ + users: + "user1" + policy => "present", + password => test_hash; + "user2" + policy => "present", + password => test_passwd; +} + +body password test_hash +{ + format => "hash"; + data => "$(test.hash)"; +} + +body password test_passwd +{ + format => "plaintext"; + data => "N4wP4ssw"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_has_password_hash("user1", "$(test.hash)", "user1_success", "user1_failure"), + classes => always("user1_methods_run"); + "any" usebundle => user_has_password_hash("user2", "$(init.hash)", "user2_failure", "user2_success"), + classes => always("user2_methods_run"); + + classes: + "ready" and => { "user1_methods_run", "user2_methods_run" }; + "ok" and => { "user1_success", "!user1_failure", + "user2_success", "!user2_failure" + }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_password_with_no_data.cf b/tests/acceptance/17_users/unsafe/10_password_with_no_data.cf new file mode 100644 index 0000000000..7978033d9f --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_password_with_no_data.cf @@ -0,0 +1,88 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets the password unchanged when it is unspecified"; + "story_id" string => "5525"; + "covers" string => "operational_kept"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + vars: + # "j0hnd0e" + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "present", + password => init_password; +} + +body password init_password +{ + !windows:: + format => "hash"; + data => "$(init.hash)"; + windows:: + format => "plaintext"; + data => "Old0P4SSW0RD"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present", + password => test_password; +} + +body password test_password +{ + format => "plaintext"; +} + +####################################################### + +bundle agent check +{ + methods: + # Make sure it is the same as before. + !windows:: + "any" usebundle => user_has_password_hash("johndoe", "$(init.hash)", "success", "failure"), + classes => always("methods_run"); + windows:: + "any" usebundle => user_has_password("johndoe", "Old0P4SSW0RD", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_password_with_no_format.cf b/tests/acceptance/17_users/unsafe/10_password_with_no_format.cf new file mode 100644 index 0000000000..10a01db4df --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_password_with_no_format.cf @@ -0,0 +1,88 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present has the password unchanged when password format is unspecified"; + "story_id" string => "5525"; + "covers" string => "operational_kept"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + vars: + # "j0hnd0e" + "hash" string => "dTloMVpjYt1w2"; + + users: + "johndoe" + policy => "present", + password => init_password; +} + +body password init_password +{ + !windows:: + format => "hash"; + data => "$(init.hash)"; + windows:: + format => "plaintext"; + data => "Old0P4SSW0RD"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "present", + password => test_password; +} + +body password test_password +{ + data => "New0P4SSW0RD"; +} + +####################################################### + +bundle agent check +{ + methods: + # Make sure it is the same as before. + !windows:: + "any" usebundle => user_has_password_hash("johndoe", "$(init.hash)", "success", "failure"), + classes => always("methods_run"); + windows:: + "any" usebundle => user_has_password("johndoe", "Old0P4SSW0RD", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_promise_outcomes.cf b/tests/acceptance/17_users/unsafe/10_promise_outcomes.cf new file mode 100644 index 0000000000..2dbbbeb601 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_promise_outcomes.cf @@ -0,0 +1,221 @@ +####################################################### +# +# Test that promise outcomes are set correctly. +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle common hpux_trusted +{ + classes: + "hpux_trusted_mode_test" + expression => regcmp(".*hpux_trusted.*", $(this.promise_filename)); +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "hpux_trusted_mode_test.!hpux"; + + # Something in the Solaris chroot test environment makes it impossible + # to test matching passwords, the pam module always returns error. + # This should not happen in a production system though. + # Since the error is not on our part, and likely unsolvable, we set + # Redmine to zero. However, it would be nice to know if the problem ever + # goes away, so using soft_fail. + "test_soft_fail" string => "!hpux_trusted_mode_test.(solaris.!sunos_5_9)", + meta => { "redmine0" }; + # On Solaris 9 PAM just crashes inside chroot. + "test_skip_needs_work" string => "!hpux_trusted_mode_test.sunos_5_9"; + + # AIX doesn't like long names (> 8 chars), so keep them short. + # a = absent + # p = present + # t = attributes + # w = password + # l = locked + # r = repair + users: + "akeep" + policy => "absent"; + "arepair" + policy => "present"; + "pkeep" + policy => "present"; + "prepair" + policy => "absent"; + "tkeep" + policy => "present", + uid => "9878", + group_primary => "$(user_tests.group1)", + groups_secondary => { "$(user_tests.group2)" }, + shell => "/bin/csh", + description => "Description"; + "trepair" + policy => "present", + uid => "9877", + group_primary => "$(user_tests.group1)", + groups_secondary => { "$(user_tests.group2)" }, + shell => "/bin/csh", + description => "Description"; + "wkeep" + policy => "present", + password => init_password; + "wrepair" + policy => "present", + password => init_password; + "lkeep" + policy => "locked"; + "lr_byadd" + policy => "absent"; + "lr_bymod" + policy => "present"; +} + +body password init_password +{ + format => "plaintext"; + data => "Init0P4SSW0RD"; +} + +####################################################### + +bundle agent test +{ + users: + !ok:: + "akeep" + classes => set_classes_kept_repaired("absent_keep", "not_absent_keep"), + policy => "absent"; + "arepair" + classes => set_classes_kept_repaired("not_absent_repair", "absent_repair"), + policy => "absent"; + "pkeep" + classes => set_classes_kept_repaired("present_keep", "not_present_keep"), + policy => "present"; + "prepair" + classes => set_classes_kept_repaired("not_present_repair", "present_repair"), + policy => "present"; + "tkeep" + classes => set_classes_kept_repaired("attributes_keep", "not_attributes_keep"), + policy => "present", + uid => "9878", + group_primary => "$(user_tests.group1)", + groups_secondary => { "$(user_tests.group2)" }, + shell => "/bin/csh", + description => "Description"; + "trepair" + classes => set_classes_kept_repaired("not_attributes_repair", "attributes_repair"), + policy => "present", + uid => "9877", + group_primary => "$(user_tests.group1)", + groups_secondary => { }, + shell => "/bin/ksh", + description => "Description"; + "wkeep" + classes => set_classes_kept_repaired("password_keep", "not_password_keep"), + policy => "present", + password => init_password; + "wrepair" + classes => set_classes_kept_repaired("not_password_repair", "password_repair"), + policy => "present", + password => test_password; + "lkeep" + classes => set_classes_kept_repaired("locked_keep", "not_locked_keep"), + policy => "locked"; + "lr_byadd" + classes => set_classes_kept_repaired("not_locked_repair_by_add", "locked_repair_by_add"), + policy => "locked"; + "lr_bymod" + classes => set_classes_kept_repaired("not_locked_repair_by_mod", "locked_repair_by_mod"), + policy => "locked"; + + + classes: + "and_ok" and => { "absent_keep", "absent_repair", "present_keep", "present_repair", + "attributes_keep", "attributes_repair", "password_keep", "password_repair", + "locked_keep", "locked_repair_by_add", "locked_repair_by_mod" }; + "not_ok" or => { "not_absent_keep", "not_absent_repair", "not_present_keep", "not_present_repair", + "not_attributes_keep", "not_attributes_repair", "not_password_keep", "not_password_repair", + "not_locked_keep", "not_locked_repair_by_add", "not_locked_repair_by_mod" }; + + "ok" and => { "and_ok", "!not_ok" }; + "fail" or => { "!and_ok", "not_ok" }; + + reports: + !absent_keep.DEBUG:: + "absent_keep is NOT set, but should be"; + !absent_repair.DEBUG:: + "absent_repair is NOT set, but should be"; + !present_keep.DEBUG:: + "present_keep is NOT set, but should be"; + !present_repair.DEBUG:: + "present_repair is NOT set, but should be"; + !attributes_keep.DEBUG:: + "attributes_keep is NOT set, but should be"; + !attributes_repair.DEBUG:: + "attributes_repair is NOT set, but should be"; + !password_keep.DEBUG:: + "password_keep is NOT set, but should be"; + !password_repair.DEBUG:: + "password_repair is NOT set, but should be"; + !locked_keep.DEBUG:: + "locked_keep is NOT set, but should be"; + !locked_repair_by_add.DEBUG:: + "locked_repair_by_add is NOT set, but should be"; + !locked_repair_by_mod.DEBUG:: + "locked_repair_by_mod is NOT set, but should be"; + + not_absent_keep.DEBUG:: + "not_absent_keep is SET, but shouldn't be"; + not_absent_repair.DEBUG:: + "not_absent_repair is SET, but shouldn't be"; + not_present_keep.DEBUG:: + "not_present_keep is SET, but shouldn't be"; + not_present_repair.DEBUG:: + "not_present_repair is SET, but shouldn't be"; + not_attributes_keep.DEBUG:: + "not_attributes_keep is SET, but shouldn't be"; + not_attributes_repair.DEBUG:: + "not_attributes_repair is SET, but shouldn't be"; + not_password_keep.DEBUG:: + "not_password_keep is SET, but shouldn't be"; + not_password_repair.DEBUG:: + "not_password_repair is SET, but shouldn't be"; + not_locked_keep.DEBUG:: + "not_locked_keep is SET, but shouldn't be"; + not_locked_repair_by_add.DEBUG:: + "not_locked_repair_by_add is SET, but shouldn't be"; + not_locked_repair_by_mod.DEBUG:: + "not_locked_repair_by_mod is SET, but shouldn't be"; + + ok:: + "$(this.promise_filename) Pass"; + fail|!ok:: + "$(this.promise_filename) FAIL"; +} + +body password test_password +{ + format => "plaintext"; + data => "Test0P4SSW0RD"; +} + +body classes set_classes_kept_repaired(x, y) +{ + promise_kept => { "$(x)" }; + promise_repaired => { "$(y)" }; +} + +####################################################### + +bundle agent check +{ +} diff --git a/tests/acceptance/17_users/unsafe/10_remove_user.cf b/tests/acceptance/17_users/unsafe/10_remove_user.cf new file mode 100644 index 0000000000..09388e533c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_remove_user.cf @@ -0,0 +1,52 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets removed"; + "story_id" string => "5526"; + "covers" string => "operational_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + policy => "absent"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_exists("johndoe", "failure", "success"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/10_remove_user_warn.cf b/tests/acceptance/17_users/unsafe/10_remove_user_warn.cf new file mode 100644 index 0000000000..71605e8322 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/10_remove_user_warn.cf @@ -0,0 +1,58 @@ +bundle common test_meta +{ + vars: + "description" string => "A user present gets removed (dry run)"; + "story_id" string => "5526"; + "covers" string => "dryrun_repaired"; +} + +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + users: + "johndoe" + policy => "present"; +} + +####################################################### + +bundle agent test +{ + users: + "johndoe" + action => test_action, + policy => "absent"; +} + +body action test_action +{ + action_policy => "warn"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => user_exists("johndoe", "success", "failure"), + classes => always("methods_run"); + + classes: + "ready" expression => "methods_run"; + "ok" and => { "success", "!failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/15_hpux_set_trusted_mode.cf b/tests/acceptance/17_users/unsafe/15_hpux_set_trusted_mode.cf new file mode 100644 index 0000000000..6635808385 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/15_hpux_set_trusted_mode.cf @@ -0,0 +1,32 @@ +# Not a test, but just setting up trusted mode on HPUX. + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + meta: + "test_skip_unsupported" string => "!hpux"; + + classes: + "trusted_mode" + expression => fileexists("/etc/shadow"), + scope => "namespace"; + + commands: + !trusted_mode:: + "$(G.echo) yes | /usr/sbin/pwconv" + contain => in_shell; +} + +bundle agent check +{ + reports: + trusted_mode:: + "$(this.promise_filename) Pass"; + !trusted_mode:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/20_add_user_locked_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_add_user_locked_hpux_trusted.cf new file mode 120000 index 0000000000..2febc4c273 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_add_user_locked_hpux_trusted.cf @@ -0,0 +1 @@ +10_add_user_locked.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_add_user_locked_with_password_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_add_user_locked_with_password_hpux_trusted.cf new file mode 120000 index 0000000000..c8691a84af --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_add_user_locked_with_password_hpux_trusted.cf @@ -0,0 +1 @@ +10_add_user_locked_with_password.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_add_user_with_hashed_password_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_add_user_with_hashed_password_hpux_trusted.cf new file mode 120000 index 0000000000..752d816bec --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_add_user_with_hashed_password_hpux_trusted.cf @@ -0,0 +1 @@ +10_add_user_with_hashed_password.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_add_user_with_many_attributes_warn_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_add_user_with_many_attributes_warn_hpux_trusted.cf new file mode 120000 index 0000000000..1887d7bd99 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_add_user_with_many_attributes_warn_hpux_trusted.cf @@ -0,0 +1 @@ +10_add_user_with_many_attributes_warn.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_add_user_with_password_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_add_user_with_password_hpux_trusted.cf new file mode 120000 index 0000000000..4dd91ec1e5 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_add_user_with_password_hpux_trusted.cf @@ -0,0 +1 @@ +10_add_user_with_password.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_lock_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_lock_hpux_trusted.cf new file mode 120000 index 0000000000..22f791c11c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_lock_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_lock.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_lock_warn_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_lock_warn_hpux_trusted.cf new file mode 120000 index 0000000000..44d7767f6f --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_lock_warn_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_lock_warn.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_lock_with_password_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_lock_with_password_hpux_trusted.cf new file mode 120000 index 0000000000..acaa752b94 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_lock_with_password_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_lock_with_password.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_missing_secondary_group.cf b/tests/acceptance/17_users/unsafe/20_modify_user_missing_secondary_group.cf new file mode 100644 index 0000000000..56714687eb --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_missing_secondary_group.cf @@ -0,0 +1,57 @@ +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + commands: + "$(G.sudo) $(user_tests.pw_path) $(G.userdel) testu"; + "$(G.sudo) $(user_tests.pw_path) $(G.groupdel) testu"; + "$(G.sudo) $(user_tests.pw_path) $(G.groupdel) testg1"; + "$(G.sudo) $(user_tests.pw_path) $(G.groupdel) testg2"; + "$(G.sudo) $(user_tests.pw_path) $(G.groupadd) testg1"; + "$(G.sudo) $(user_tests.pw_path) $(G.useradd) testu"; + freebsd:: + "$(G.sudo) $(user_tests.pw_path) $(G.usermod) testu -G testg1"; + !freebsd:: + "$(G.sudo) $(user_tests.pw_path) $(G.usermod) -G testg1 testu"; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-3710" } + string => "Managing a user without specifying any secondary groups attribute + should result in no changes to existing secondary groups."; + + users: + "testu" + policy => "present"; +} + +bundle agent check +{ + methods: + "any" usebundle => user_is_in_secondary_group("testu", "testg1", "testg1_success", "testg1_failure"), + classes => always("testg1_method_run"); + + "any" usebundle => user_is_in_secondary_group("testu", "testg2", "testg2_success", "testg2_failure"), + classes => always("testg2_method_run"); + + "any" usebundle => user_exists("testu", "testu_success", "testu_failure"), + classes => always("testu_method_run"); + + classes: + "ready" and => { "testg1_method_run", "testg2_method_run", "testu_method_run" }; + "ok" and => { "testg1_success", "!testg1_failure", + "!testg2_success", "testg2_failure", + "testu_success", "!testu_failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_secondary_groups.cf b/tests/acceptance/17_users/unsafe/20_modify_user_secondary_groups.cf new file mode 100644 index 0000000000..d84054ded6 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_secondary_groups.cf @@ -0,0 +1,57 @@ +body common control +{ + inputs => { "../../default.cf.sub", "user_queries.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + commands: + "$(G.sudo) $(user_tests.pw_path) $(G.userdel) testu"; + "$(G.sudo) $(user_tests.pw_path) $(G.groupdel) testu"; + "$(G.sudo) $(user_tests.pw_path) $(G.groupdel) testg1"; + "$(G.sudo) $(user_tests.pw_path) $(G.groupdel) testg2"; + "$(G.sudo) $(user_tests.pw_path) $(G.groupadd) testg1"; + "$(G.sudo) $(user_tests.pw_path) $(G.useradd) testu"; + freebsd:: + "$(G.sudo) $(user_tests.pw_path) $(G.usermod) testu -G testg1"; + !freebsd:: + "$(G.sudo) $(user_tests.pw_path) $(G.usermod) -G testg1 testu"; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-3710" } + string => "Adding a secondary group which doesn't exist should not remove existing secondary groups"; + + users: + "testu" + groups_secondary => { "testg1", "testg2" }, + policy => "present"; +} + +bundle agent check +{ + methods: + "any" usebundle => user_is_in_secondary_group("testu", "testg1", "testg1_success", "testg1_failure"), + classes => always("testg1_method_run"); + + "any" usebundle => user_is_in_secondary_group("testu", "testg2", "testg2_success", "testg2_failure"), + classes => always("testg2_method_run"); + + "any" usebundle => user_exists("testu", "testu_success", "testu_failure"), + classes => always("testu_method_run"); + + classes: + "ready" and => { "testg1_method_run", "testg2_method_run", "testu_method_run" }; + "ok" and => { "testg1_success", "!testg1_failure", + "!testg2_success", "testg2_failure", + "testu_success", "!testu_failure" }; + + reports: + ok.ready:: + "$(this.promise_filename) Pass"; + !ok.ready:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_unlock_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_unlock_hpux_trusted.cf new file mode 120000 index 0000000000..e0bb7952b5 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_unlock_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_unlock.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_unlock_warn_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_unlock_warn_hpux_trusted.cf new file mode 120000 index 0000000000..b1638ced9b --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_unlock_warn_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_unlock_warn.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_unlock_with_password_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_unlock_with_password_hpux_trusted.cf new file mode 120000 index 0000000000..2fec541e5c --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_unlock_with_password_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_unlock_with_password.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_with_hashed_password_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_with_hashed_password_hpux_trusted.cf new file mode 120000 index 0000000000..7ddaa72408 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_with_hashed_password_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_with_hashed_password.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_with_many_attributes_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_with_many_attributes_hpux_trusted.cf new file mode 120000 index 0000000000..23ba1ec905 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_with_many_attributes_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_with_many_attributes.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_with_many_attributes_warn_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_with_many_attributes_warn_hpux_trusted.cf new file mode 120000 index 0000000000..97ed5d1305 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_with_many_attributes_warn_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_with_many_attributes_warn.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_with_password_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_with_password_hpux_trusted.cf new file mode 120000 index 0000000000..7f08235fc2 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_with_password_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_with_password.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_modify_user_with_same_password_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_modify_user_with_same_password_hpux_trusted.cf new file mode 120000 index 0000000000..24862e8c4f --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_modify_user_with_same_password_hpux_trusted.cf @@ -0,0 +1 @@ +10_modify_user_with_same_password.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_newly_created_account_should_not_count_as_locked_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_newly_created_account_should_not_count_as_locked_hpux_trusted.cf new file mode 120000 index 0000000000..17e6d65632 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_newly_created_account_should_not_count_as_locked_hpux_trusted.cf @@ -0,0 +1 @@ +10_newly_created_account_should_not_count_as_locked.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_password_hash_is_not_cached_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_password_hash_is_not_cached_hpux_trusted.cf new file mode 120000 index 0000000000..c868d805f7 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_password_hash_is_not_cached_hpux_trusted.cf @@ -0,0 +1 @@ +10_password_hash_is_not_cached.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_password_with_no_data_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_password_with_no_data_hpux_trusted.cf new file mode 120000 index 0000000000..d4481da5ec --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_password_with_no_data_hpux_trusted.cf @@ -0,0 +1 @@ +10_password_with_no_data.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_password_with_no_format_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_password_with_no_format_hpux_trusted.cf new file mode 120000 index 0000000000..ffebb9cd20 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_password_with_no_format_hpux_trusted.cf @@ -0,0 +1 @@ +10_password_with_no_format.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/20_promise_outcomes_hpux_trusted.cf b/tests/acceptance/17_users/unsafe/20_promise_outcomes_hpux_trusted.cf new file mode 120000 index 0000000000..0dc9eb664a --- /dev/null +++ b/tests/acceptance/17_users/unsafe/20_promise_outcomes_hpux_trusted.cf @@ -0,0 +1 @@ +10_promise_outcomes.cf \ No newline at end of file diff --git a/tests/acceptance/17_users/unsafe/add_user_with_shell.txt b/tests/acceptance/17_users/unsafe/add_user_with_shell.txt new file mode 100644 index 0000000000..00da666934 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/add_user_with_shell.txt @@ -0,0 +1,3 @@ +#!/bin/sh +exit 0 +Succeeded diff --git a/tests/acceptance/17_users/unsafe/aix_get_shadow_field.pl b/tests/acceptance/17_users/unsafe/aix_get_shadow_field.pl new file mode 100755 index 0000000000..63c5f69f68 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/aix_get_shadow_field.pl @@ -0,0 +1,32 @@ +#!/usr/bin/perl -w + +# Takes two arguments, the field to get, and the user to get it from. +open(PASSWD, "< /etc/security/passwd") or die("Could not open password file"); + +my $in_user_section = 0; +my $hash = ""; +while () +{ + if (!$in_user_section) + { + if (/^$ARGV[1]:/) + { + $in_user_section = 1; + } + } + else + { + if (/^\S/) + { + $in_user_section = 0; + } + elsif (/$ARGV[0] *= *(\S+)/) + { + $hash = $1; + } + } +} + +print("$hash\n") if ($hash); + +close(PASSWD); diff --git a/tests/acceptance/17_users/unsafe/disable_sudo_tty_requirement.cf.sub b/tests/acceptance/17_users/unsafe/disable_sudo_tty_requirement.cf.sub new file mode 100644 index 0000000000..03898c1e62 --- /dev/null +++ b/tests/acceptance/17_users/unsafe/disable_sudo_tty_requirement.cf.sub @@ -0,0 +1,20 @@ +# Disable the requiretty setting in /etc/sudoers. + +body file control +{ + inputs => { "../../plucked.cf.sub" }; +} + +bundle agent disable_sudo_tty_requirement +{ + files: + "/etc/sudoers" + edit_line => comment_requiretty; +} + +bundle edit_line comment_requiretty +{ + replace_patterns: + "^Defaults *requiretty" + replace_with => value("#Defaults requiretty"); +} diff --git a/tests/acceptance/17_users/unsafe/user_queries.cf.sub b/tests/acceptance/17_users/unsafe/user_queries.cf.sub new file mode 100644 index 0000000000..25a57d635b --- /dev/null +++ b/tests/acceptance/17_users/unsafe/user_queries.cf.sub @@ -0,0 +1,374 @@ +bundle common user_tests +{ + vars: + !windows:: + "group1" string => "bin"; + "group2" string => "sys"; + "gid2" string => "3"; + redhat|suse.!suse_15:: + "gid1" string => "1"; + !redhat.(!suse|suse_15).!windows:: + "gid1" string => "2"; + + windows:: + "group1" string => "Users"; + "group2" string => "Administrators"; + + freebsd:: + "pw_path" string => "/usr/sbin/pw"; + "sudo_path" string => "/usr/local/bin/sudo"; + !freebsd:: + "pw_path" string => ""; + "sudo_path" string => "/usr/bin/sudo"; + + classes: + # On HPUX we use a sudo hack, which doesn't support '-u'. + !windows.!hpux:: + "sudo_works" expression => "any"; + + hpux:: + "passwords_in_shadow" expression => fileexists("/etc/shadow"); + "passwords_in_passwd" not => fileexists("/etc/shadow"); + freebsd:: + "passwords_in_shadow" expression => "!any"; + "passwords_in_passwd" expression => "!any"; + "passwords_in_master_passwd" expression => "any"; + windows|aix:: + "passwords_in_shadow" expression => "!any"; + "passwords_in_passwd" expression => "!any"; + !hpux.!windows.!aix.!freebsd:: + "passwords_in_shadow" expression => "any"; + "passwords_in_passwd" expression => "!any"; + windows|aix:: + "passwords_in_passwd_format" expression => "!any"; + !windows.!aix:: + "passwords_in_passwd_format" expression => "any"; +} + +bundle agent remove_stale_groups +{ + # When we create a user and change its primary GID afterwards, its original + # group may linger and cause problems later. + commands: + aix:: + "rmgroup johndoe" + contain => in_shell; + !aix:: + "$(user_tests.pw_path) groupdel johndoe" + contain => in_shell; +} + +bundle agent user_exists(user, true_class, false_class) +{ + commands: + !windows:: + "$(G.grep) '^$(user):' /etc/passwd" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + vars: + windows:: + "output" string => execresult("net user $(user)", "useshell"); + "reg" string => escape("$(user)"); + classes: + windows:: + "$(true_class)" expression => regcmp(".*$(reg).*", "$(output)"), + scope => "namespace"; + "$(false_class)" not => regcmp(".*$(reg).*", "$(output)"), + scope => "namespace"; +} + +bundle agent user_has_uid(user, uid, true_class, false_class) +{ + commands: + !windows:: + "$(G.grep) '^$(user):[^:]*:$(uid):' /etc/passwd" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + reports: + windows:: + "Cannot check uid on Windows!"; +} + +bundle agent user_is_in_primary_group(user, group, true_class, false_class) +{ + vars: + !windows:: + "no" int => getfields("$(group):.*", "/etc/group", ":", "gid_number"); + + commands: + !windows:: + "$(G.grep) '^$(user):[^:]*:[^:]*:$(gid_number[3]):' /etc/passwd" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + methods: + windows:: + "redirect" usebundle => user_is_in_secondary_group("$(user)", "$(group)", "$(true_class)", "$(false_class)"); +} + +bundle agent user_is_in_secondary_group(user, group, true_class, false_class) +{ + commands: + !windows:: + "$(G.egrep) '^$(group):[^:]*:[^:]*:[^:]*,?$(user)(,|$)' /etc/group" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + vars: + windows:: + "output" string => execresult("net user $(user)", "useshell"); + "reg" string => escape("$(group)"); + classes: + windows:: + "$(true_class)" expression => regcmp(".*Local Group Memberships *(\*[a-zA-Z0-9]* *)* *$(reg).*", "$(output)"), + scope => "namespace"; + "$(false_class)" not => regcmp(".*Local Group Memberships *(\*[a-zA-Z0-9]* *)* *$(reg).*", "$(output)"), + scope => "namespace"; +} + +bundle agent user_is_in_any_secondary_group(user, true_class, false_class) +{ + commands: + !windows:: + "$(G.egrep) '^[^:]*:[^:]*:[^:]*:[^:]*,?$(user)(,|$)' /etc/group" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + vars: + windows:: + "output" string => execresult("net user $(user)", "useshell"); + classes: + windows:: + "$(true_class)" expression => regcmp(".*Local Group Memberships *\*None.*", "$(output)"), + scope => "namespace"; + "$(false_class)" not => regcmp(".*Local Group Memberships *\*None.*", "$(output)"), + scope => "namespace"; +} + +bundle agent user_has_home_dir(user, home_dir, true_class, false_class) +{ + commands: + !windows:: + "$(G.grep) '^$(user):[^:]*:[^:]*:[^:]*:[^:]*:$(home_dir):' /etc/passwd" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + vars: + windows:: + "output" string => execresult("net user $(user)", "useshell"); + "reg" string => escape("$(home_dir)"); + classes: + windows:: + "$(true_class)" expression => regcmp(".*Home directory *$(reg).*", "$(output)"), + scope => "namespace"; + "$(false_class)" not => regcmp(".*Home directory *$(reg).*", "$(output)"), + scope => "namespace"; +} + +bundle agent user_has_shell(user, shell, true_class, false_class) +{ + commands: + !windows:: + "$(G.grep) '^$(user):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:$(shell)$' /etc/passwd" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + reports: + windows:: + "Cannot check shell on Windows!"; +} + +bundle agent user_has_description(user, description, true_class, false_class) +{ + commands: + !windows:: + "$(G.grep) '^$(user):[^:]*:[^:]*:[^:]*:$(description):' /etc/passwd" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + vars: + windows:: + "output" string => execresult("net user $(user)", "useshell"); + classes: + windows:: + "$(true_class)" expression => regcmp(".*Full Name *$(description).*", "$(output)"), + scope => "namespace"; + "$(false_class)" not => regcmp(".*Full Name *$(description).*", "$(output)"), + scope => "namespace"; +} + +bundle agent user_has_password(user, password, true_class, false_class) +{ + reports: + !windows:: + "Cannot test password on Unix!"; + + classes: + windows.has_psexec:: + "$(true_class)" expression => returnszero("$(G.psexec) -u $(user) -p $(password) $(sys.cf_agent) -h", "noshell"), + scope => "namespace"; + "$(false_class)" not => returnszero("$(G.psexec) -u $(user) -p $(password) $(sys.cf_agent) -h", "noshell"), + scope => "namespace"; + reports: + windows.!has_psexec:: + "Need PsExec.exe tool from PSTools to test password!"; +} + +bundle agent user_has_password_hash(user, hash, true_class, false_class) +{ + vars: + !aix:: + "escaped_hash" string => escape("$(hash)"); + aix:: + "user_hash" string => execresult("$(this.promise_dirname)/aix_get_shadow_field.pl password $(user)", "useshell"); + passwords_in_passwd:: + "passwd_file" string => "/etc/passwd"; + passwords_in_shadow:: + "passwd_file" string => "/etc/shadow"; + passwords_in_master_passwd:: + "passwd_file" string => "/etc/master.passwd"; + + classes: + aix:: + "$(true_class)" expression => strcmp($(hash), $(user_hash)), + scope => "namespace"; + "$(false_class)" not => strcmp($(hash), $(user_hash)), + scope => "namespace"; + + commands: + passwords_in_passwd_format:: + "$(G.grep) '^$(user):$(escaped_hash):' $(passwd_file)" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + reports: + windows:: + "Cannot test password hash on Windows!"; +} + +bundle agent user_get_password_hash(user) +{ + vars: + passwords_in_passwd:: + "passwd_file" string => "/etc/passwd"; + passwords_in_shadow:: + "passwd_file" string => "/etc/shadow"; + passwords_in_passwd_format:: + "hash" string => execresult("$(G.grep) '^$(user):' $(passwd_file) | $(G.sed) -e 's/[^:]*:\([^:]*\):.*/\1/'", "useshell"); + aix:: + "hash" string => execresult("$(this.promise_dirname)/aix_get_shadow_field.pl password $(user)", "useshell"); + + reports: + windows:: + "Cannot get password hash on Windows!"; +} + +bundle agent user_is_locked(user, true_class, false_class) +{ + vars: + solaris:: + # Solaris doesn't support expiry date properly (see users promise code). + "expiry_date" string => ""; + !solaris:: + # Expiry date should be something non-empty. + "expiry_date" string => "[^:]"; + aix:: + "user_hash" string => execresult("$(this.promise_dirname)/aix_get_shadow_field.pl password $(user)", "useshell"); + + classes: + aix:: + "$(true_class)" expression => regcmp("!.*", $(user_hash)), + scope => "namespace"; + "$(false_class)" not => regcmp("!.*", $(user_hash)), + scope => "namespace"; + + commands: + passwords_in_shadow:: + # Notice the [^:] without * at the end. That field (expiry) should be *something*. + "$(G.grep) '^$(user):![^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:$(expiry_date)[^:]*:' /etc/shadow" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + passwords_in_passwd:: + "$(G.grep) '^$(user):![^:]*:' /etc/passwd" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + vars: + windows:: + "output" string => execresult("net user $(user)", "useshell"); + classes: + windows:: + "$(true_class)" expression => regcmp(".*Account active *No.*", "$(output)"), + scope => "namespace"; + "$(false_class)" not => regcmp(".*Account active *No.*", "$(output)"), + scope => "namespace"; +} + +bundle agent user_is_unlocked(user, true_class, false_class) +{ + vars: + aix:: + "user_hash" string => execresult("$(this.promise_dirname)/aix_get_shadow_field.pl password $(user)", "useshell"); + + classes: + aix:: + "$(true_class)" not => regcmp("!.*", $(user_hash)), + scope => "namespace"; + "$(false_class)" expression => regcmp("!.*", $(user_hash)), + scope => "namespace"; + + commands: + passwords_in_shadow:: + # Notice the field at the end. That field (expiry) should be gone or zero. + "$(G.egrep) '^$(user):[^:!]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:(:|0:)' /etc/shadow" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + passwords_in_passwd:: + "$(G.grep) '^$(user):[^:!]*:' /etc/passwd" + contain => no_output_shell, + classes => on_success("$(true_class)", "$(false_class)"); + + vars: + windows:: + "output" string => execresult("net user $(user)", "useshell"); + classes: + windows:: + "$(true_class)" expression => regcmp(".*Account active *Yes.*", "$(output)"), + scope => "namespace"; + "$(false_class)" not => regcmp(".*Account active *Yes.*", "$(output)"), + scope => "namespace"; +} + +# Some platforms add a flag that forces the user to change the password. +bundle agent user_does_not_need_password_update(user, true_class, false_class) +{ + vars: + aix:: + "flags" string => execresult("$(this.promise_dirname)/aix_get_shadow_field.pl flags $(user)", "useshell"); + + classes: + aix:: + "$(true_class)" not => regcmp(".*ADMCHG.*", "$(flags)"), + scope => "namespace"; + "$(false_class)" expression => regcmp(".*ADMCHG.*", "$(flags)"), + scope => "namespace"; + + !aix:: + "$(true_class)" expression => "any", + scope => "namespace"; +} + +body classes on_success(true_class, false_class) +{ + promise_repaired => { "$(true_class)" }; + repair_failed => { "$(false_class)" }; + cancel_notkept => { "$(true_class)" }; + cancel_repaired => { "$(false_class)" }; +} + +body contain no_output_shell +{ + no_output => "true"; + useshell => "useshell"; +} diff --git a/tests/acceptance/19_security/other_writeable/group_write.cf b/tests/acceptance/19_security/other_writeable/group_write.cf new file mode 100644 index 0000000000..0c9a5cc8d1 --- /dev/null +++ b/tests/acceptance/19_security/other_writeable/group_write.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Create a file, check defaults +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + # This extracts the octal mode, and decimal nlink, uid, gid, size + "policy_file" string => ' + +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + reports: + "Dorothy: How do you talk if you don\'t have a brain?"; + "Scarecrow: Well, some people without brains do an awful lot of talking don\'t they?"; + +}'; + +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + edit_defaults => empty, + edit_line => insert_lines("$(g.policy_file)"), + perms => m("620"); +} + +####################################################### + +bundle agent test +{ + + vars: + "agent_output" string => execresult("$(sys.cf_agent) -f $(G.testfile)", "noshell"), + if => fileexists("$(G.testfile)"); + + classes: + "security_exception" + expression => regcmp(".*is writable by others (security exception).*", "$(agent_output)"), + comment => "It's a security risk to evaluate policy that is writeable by users other than the owner"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "!security_exception"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/19_security/other_writeable/other_write.cf b/tests/acceptance/19_security/other_writeable/other_write.cf new file mode 100644 index 0000000000..b6542593d8 --- /dev/null +++ b/tests/acceptance/19_security/other_writeable/other_write.cf @@ -0,0 +1,74 @@ +####################################################### +# +# Create a file, check defaults +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle common g +{ + vars: + # This extracts the octal mode, and decimal nlink, uid, gid, size + "policy_file" string => ' + +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + reports: + "We\'re not in KS anymore toto."; + +}'; + +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + create => "true", + edit_defaults => empty, + edit_line => insert_lines("$(g.policy_file)"), + perms => m("602"); +} + +####################################################### + +bundle agent test +{ + + vars: + "agent_output" string => execresult("$(sys.cf_agent) -f $(G.testfile)", "noshell"), + if => fileexists("$(G.testfile)"); + + + classes: + "security_exception" + expression => regcmp(".*is writable by others (security exception).*", "$(agent_output)"), + comment => "It's a security risk to evaluate policy that is writeable by users other than the owner"; +} + +####################################################### + +bundle agent check +{ + classes: + "ok" expression => "!security_exception"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/20_meta/basic.cf b/tests/acceptance/20_meta/basic.cf new file mode 100644 index 0000000000..0e02377c89 --- /dev/null +++ b/tests/acceptance/20_meta/basic.cf @@ -0,0 +1,67 @@ +####################################################### +# +# Test basics of meta promises +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ +} + +####################################################### + +bundle agent test +{ + meta: + "s" string => "Wally Walleye"; + "l" slist => { "Be", "Hg", "Pb" }; + "c" data => parsejson(' +{ + "first": 1, + "seconds": 2, + "third": [ "a", "b", "c" ], + "fourth": null +} +'); +} + +bundle agent check +{ + vars: + "actual[s]" string => $(test_meta.s); + "actual[l]" string => format("%S", "test_meta.l"); + "actual[c]" string => format("%S", "test_meta.c"); + + "expected[s]" string => "Wally Walleye"; + "expected[l]" string => '{ "Be", "Hg", "Pb" }'; + "expected[c]" string => '{ "first": 1, "seconds": 2, "third": [ "a", "b", "c" ], "fourth": null }'; + "tests" slist => getindices(expected); + + classes: + "ok_$(tests)" expression => strcmp("$(actual[$(tests)])", "$(expected[$(tests)])"); + "ok_$(tests)" not => strcmp("$(actual[$(tests)])", "$(expected[$(tests)])"); + + "ok" and => { "ok_s", "ok_l", "ok_c" }; + + reports: + DEBUG:: + "OK: $(tests) got $(actual[$(tests)])" + if => "ok_$(tests)"; + + "FAIL: $(tests) got $(actual[$(tests)]) != $(expected[$(tests)])" + if => "not_ok_$(tests)"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/20_meta/this_bundle_meta_expands.cf b/tests/acceptance/20_meta/this_bundle_meta_expands.cf new file mode 100644 index 0000000000..2941fba8d2 --- /dev/null +++ b/tests/acceptance/20_meta/this_bundle_meta_expands.cf @@ -0,0 +1,76 @@ +####################################################### +# +# Test basics of meta promises +# +####################################################### +bundle common test_meta +{ + vars: + "description" string => "Can dereference meta vars."; +} + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + meta: + "test_soft_fail" + string => "any", + meta => { "redmine7803" }; + + "string" string => "value"; + "list" slist => { "element1", "element2" }; + + classes: + "can_deref_this_bundle_meta_string" + expression => strcmp("$($(this.bundle)_meta.string)", "value"); + + "can_deref_direct_meta_list_element1" + #expression => strcmp("$($(this.bundle)_meta.list)", "element1"); + expression => strcmp("$(check_meta.list)", "element1"); + + "can_deref_direct_meta_list_element2" + #expression => strcmp("$($(this.bundle)_meta.list)", "element2"); + expression => strcmp("$(check_meta.list)", "element2"); + + "can_deref_this_bundle_meta_list_element1" + expression => strcmp("$($(this.bundle)_meta.list)", "element1"); + + "can_deref_this_bundle_meta_list_element2" + expression => strcmp("$($(this.bundle)_meta.list)", "element2"); + + "ok" and => { + "can_deref_this_bundle_meta_string", + "can_deref_direct_meta_list_element1", + "can_deref_direct_meta_list_element2", + "can_deref_this_bundle_meta_list_element1", + "can_deref_this_bundle_meta_list_element2", + }; + + reports: + DEBUG:: + "DEBUG $(this.bundle): meta var = '$($(this.bundle)_meta.string)'"; + "DEBUG $(this.bundle): meta list direct = '$(check_meta.list)'"; + "DEBUG $(this.bundle): meta list this.bundle = '$($(this.bundle)_meta.list)'"; + + "DEBUG $(this.bundle): Can't dereference meta string var with this.bundle" + unless => "can_deref_this_bundle_meta_string"; + "DEBUG $(this.bundle): Can't dereference meta list directly" + unless => "can_deref_direct_meta_list_element1.can_deref_direct_meta_list_element2"; + "DEBUG $(this.bundle): Can't dereference meta list with this.bundle" + unless => "can_deref_this_bundle_meta_list_element1.can_deref_this_bundle_meta_list_element2"; + + + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/21_methods/array_reset.cf b/tests/acceptance/21_methods/array_reset.cf new file mode 100644 index 0000000000..806d231a16 --- /dev/null +++ b/tests/acceptance/21_methods/array_reset.cf @@ -0,0 +1,47 @@ +####################################################### +# +# Redmine 6369 - make sure that arrays don't accumulate +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + vars: + "key_value" slist => { 1, 2, 3 }; + + methods: + "in" usebundle => test_method($(key_value), $(key_value)); +} + +bundle agent test_method(key, value) +{ + vars: + "array[$(key)]" string => "$(value)"; +} + +bundle agent check +{ + vars: + "indices" slist => getindices("test_method.array"); + "dim" int => length("indices"); + + classes: + "ok" expression => strcmp("$(dim)", "1"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG.!ok:: + "test_method.array has $(dim) entries instead of 1"; +} + diff --git a/tests/acceptance/21_methods/call_methods_using_array_expansion.cf b/tests/acceptance/21_methods/call_methods_using_array_expansion.cf new file mode 100644 index 0000000000..dfa945a782 --- /dev/null +++ b/tests/acceptance/21_methods/call_methods_using_array_expansion.cf @@ -0,0 +1,70 @@ +####################################################### +# +# Redmine 6369 - make sure that arrays don't accumulate +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + commands: + windows:: + "$(G.dos2unix) $(this.promise_filename).expected.txt" -> { "ENT-10433" }; + + files: + "$(G.testdir)/reports.txt" + delete => tidy; +} +bundle agent test +{ + meta: + + "description" -> { "CFE-636" } + string => "Check that methods can be called by expanding classic + arrays"; + + vars: + + "b1" string => "b1"; + "b2" string => "b2"; + "b[b2]" string => "b2"; + + methods: + + "${b1}" + usebundle => ${b1}; + "${b[b2]}" + usebundle => ${b[b2]}("param"); + "${b[b2]}" + usebundle => ${b[${b2}]}("param2"); +} + +bundle agent check +{ + methods: + + "Pass/Fail" + usebundle => dcs_check_diff( "$(G.testdir)/reports.txt", + "$(this.promise_filename).expected.txt", + $(this.promise_filename)); +} + +bundle agent b1{ + reports: + cfengine:: + "Bundle b1" + report_to_file => "$(G.testdir)/reports.txt"; +} + +bundle agent b2(ref){ + reports: + cfengine:: + "Bundle b2 ${ref}" + report_to_file => "$(G.testdir)/reports.txt"; +} diff --git a/tests/acceptance/21_methods/call_methods_using_array_expansion.cf.expected.txt b/tests/acceptance/21_methods/call_methods_using_array_expansion.cf.expected.txt new file mode 100644 index 0000000000..4cbc7ca112 --- /dev/null +++ b/tests/acceptance/21_methods/call_methods_using_array_expansion.cf.expected.txt @@ -0,0 +1,3 @@ +Bundle b1 +Bundle b2 param +Bundle b2 param2 diff --git a/tests/acceptance/21_methods/callers/callers_directly.cf b/tests/acceptance/21_methods/callers/callers_directly.cf new file mode 100644 index 0000000000..0ddd755446 --- /dev/null +++ b/tests/acceptance/21_methods/callers/callers_directly.cf @@ -0,0 +1,59 @@ +####################################################### +# +# Test the variable this.callers_promisers with one bundle +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init { +} + +bundle agent test { + methods: + "test" usebundle => "caller"; +} + +bundle agent check { + vars: + "callers_expect" string => "agent"; + # Due to the 4K variable limit we cannot test the whole structure, so + # just test one element within it. At least it's not completely broken + # if we find it. + "callers_actual" string => "$(dummy.callers[0][bundle][bundleType])"; + + classes: + "success" expression => strcmp("${callers_expect}", "${callers_actual}"), + scope => "namespace"; + + reports: + success:: + "$(this.promise_filename) Pass"; + !success:: + "$(this.promise_filename) FAIL"; + + methods: + "any" usebundle => file_make($(G.testfile), $(callers_actual)); + + reports: + DEBUG:: + "EXPECT: callers_string = ${callers_expect}"; + "ACTUAL: callers_string = $(callers_actual)"; +} + +bundle agent caller { + methods: + "first call" usebundle => dummy; +} + +bundle agent dummy { + vars: + "callers" data => callstack_callers(); +} diff --git a/tests/acceptance/21_methods/callers/promisers_directly.cf b/tests/acceptance/21_methods/callers/promisers_directly.cf new file mode 100644 index 0000000000..14edfd48cd --- /dev/null +++ b/tests/acceptance/21_methods/callers/promisers_directly.cf @@ -0,0 +1,53 @@ +####################################################### +# +# Test the variable this.callers_promisers with one bundle +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init { +} + +bundle agent test { + methods: + "test" usebundle => "caller"; +} + +bundle agent check { + reports: + success:: + "$(this.promise_filename) Pass"; + !success:: + "$(this.promise_filename) FAIL"; +} + +bundle agent caller { + methods: + "first call" usebundle => dummy; +} + +bundle agent dummy { + vars: + "callers_promisers" slist => callstack_promisers(); + + "callers_promisers_expect" string => "any, any, test, first call"; + "callers_promisers_actual" string => join(", ", "callers_promisers"); + + classes: + "success" expression => strcmp("${callers_promisers_expect}", "${callers_promisers_actual}"), + scope => "namespace"; + + reports: + DEBUG:: + "EXPECT: callers_promisers_string = ${callers_promisers_expect}"; + "ACTUAL: callers_promisers_string = ${callers_promisers_actual}"; +} + diff --git a/tests/acceptance/21_methods/callers/promisers_indirectly.cf b/tests/acceptance/21_methods/callers/promisers_indirectly.cf new file mode 100644 index 0000000000..4e145766b7 --- /dev/null +++ b/tests/acceptance/21_methods/callers/promisers_indirectly.cf @@ -0,0 +1,71 @@ +####################################################### +# +# Test the variable this.callers_promisers with one bundle called twice +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init { +} + +bundle agent test { + methods: + "test" usebundle => "caller"; +} + +bundle agent check { + reports: + success_first.success_second:: + "$(this.promise_filename) Pass"; + !(success_first.success_second):: + "$(this.promise_filename) FAIL"; +} + +bundle agent caller { + + methods: + "first call" usebundle => dummy; + "second call" usebundle => dummy_inter; + +} + +bundle agent dummy_inter { + + methods: + "inter" usebundle => dummy; + +} + +bundle agent dummy { + + vars: + # This bundle gets called twice, once directly, and once via dummy_inter + "callers_promisers" slist => callstack_promisers(); + + "callers_promisers_expect_first" string => "any, any, test, first call"; + "callers_promisers_expect_second" string => "any, any, test, second call, inter"; + "callers_promisers_actual" string => join(", ", "callers_promisers"); + + classes: + "success_first" expression => strcmp("${callers_promisers_expect_first}", "${callers_promisers_actual}"), + scope => "namespace"; + "success_second" expression => strcmp("${callers_promisers_expect_second}", "${callers_promisers_actual}"), + scope => "namespace"; + + reports: + DEBUG:: + "EXPECT (first in ${this.bundle}): callers_promisers_string = ${callers_promisers_expect_first}"; + "ACTUAL (first in ${this.bundle}): callers_promisers_string = ${callers_promisers_actual}"; + + "EXPECT (second in ${this.bundle}): callers_promisers_string = ${callers_promisers_expect_second}"; + "ACTUAL (second in ${this.bundle}): callers_promisers_string = ${callers_promisers_actual}"; +} + diff --git a/tests/acceptance/21_methods/method_outcomes.cf b/tests/acceptance/21_methods/method_outcomes.cf new file mode 100644 index 0000000000..66b0ad25c9 --- /dev/null +++ b/tests/acceptance/21_methods/method_outcomes.cf @@ -0,0 +1,144 @@ +# Test method outcomes as expected +# Worst promise outcome inside bundle results in that outcome for entire +# bundle. +body file control +{ + inputs => { "../default.cf.sub" }; +} + +bundle agent main +{ + methods: + "init" + usebundle => init; + "check" + usebundle => check; +} + +bundle agent init +{ + classes: + # "method_FORCE_UNEXPECTED_FAIL" expression => "any"; + + vars: + "method_outcome_classes" + slist => classesmatching("method_.*"); + + "sorted_method_outcome_classes" + slist => sort(method_outcome_classes, lex); + + methods: + "test kept bundle" + usebundle => method_kept, + classes => scoped_classes_generic("namespace", "method_kept_outcome"); + + "test repaired bundle" + usebundle => method_repaired, + classes => scoped_classes_generic("namespace", "method_repaired_outcome"); + + "test repaired bundle" + usebundle => method_not_kept, + classes => scoped_classes_generic("namespace", "method_not_kept_outcome"); + + "test repaired bundle" + usebundle => method_worst, + classes => scoped_classes_generic("namespace", "method_worst_outcome"); + + + reports: + "$(sorted_method_outcome_classes)"; +} + +bundle agent check +{ + vars: + "expected_classes" + slist => { + "method_kept_outcome_kept", + "method_kept_outcome_ok", + "method_kept_outcome_reached", + "method_not_kept_outcome_error", + "method_not_kept_outcome_failed", + "method_not_kept_outcome_not_kept", + "method_not_kept_outcome_not_ok", + "method_not_kept_outcome_reached", + "method_repaired_outcome_ok", + "method_repaired_outcome_reached", + "method_repaired_outcome_repaired", + "method_worst_outcome_error", + "method_worst_outcome_failed", + "method_worst_outcome_not_kept", + "method_worst_outcome_not_ok", + "method_worst_outcome_reached", + #"method_FORCE_UNEXPECTED_FAILURE", + }; + + # Find classes that we expect to find but do not + "missing_expected_classes" + slist => difference( "expected_classes", "init.method_outcome_classes"); + + # Find classes that we did not expect to find + "unexpected_classes" + slist => difference( "init.method_outcome_classes", "expected_classes" ); + + # Count up the number of missing and extra classes + "num_missing" + int => length("missing_expected_classes"); + "num_unexpected" + int => length("unexpected_classes"); + + classes: + # Fail if the counts are not as expected + "fail" + expression => isgreaterthan( "$(num_missing)", "0"); + "fail" + expression => isgreaterthan( "$(num_unexpected)", "0"); + + # Pass if we found all expected classes and did not fail + "ok" and => { @(expected_classes), "!fail" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + fail:: + "$(this.promise_filename) FAIL"; + +} + +bundle agent method_kept +{ + reports: + "$(this.bundle)" + comment => "This promsie should have a kept outcome"; +} + +bundle agent method_repaired +{ +# This bundle runs a command that returns 0 and is by default considered +# repaired, so the bundle will be considered repaired + commands: + "$(G.true)" + comment => "This promsie should have a repaired outcome"; +} + +bundle agent method_not_kept +{ + commands: + "$(G.false)" + comment => "This promise should have a not_kept outcome"; +} + +bundle agent method_worst +# @brief This bundle activates a promise that is repaired (/bin/true), a promise that is not kept (/bin/false), and a promise that is kept (report) +{ + commands: + "$(G.true)" + comment => "This promsie should have a repaired outcome"; + + "$(G.false)" + comment => "This promise should have a not_kept outcome"; + + reports: + "$(this.bundle)" + comment => "This promsie should have a kept outcome"; +} diff --git a/tests/acceptance/21_methods/reachable/bundle_promiser_call_by_arg_defined.cf b/tests/acceptance/21_methods/reachable/bundle_promiser_call_by_arg_defined.cf new file mode 100644 index 0000000000..037ccf689e --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_promiser_call_by_arg_defined.cf @@ -0,0 +1,27 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent baz +{ + reports: + "$(this.promise_filename) Pass"; +} + +bundle agent bar(x) +{ + methods: + "$(x)"; # Tries to call baz, exists, works +} + +bundle agent main +{ + methods: + "foo" + usebundle => bar("baz"); +} diff --git a/tests/acceptance/21_methods/reachable/bundle_promiser_call_by_arg_undefined.x.cf b/tests/acceptance/21_methods/reachable/bundle_promiser_call_by_arg_undefined.x.cf new file mode 100644 index 0000000000..65273c6d09 --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_promiser_call_by_arg_undefined.x.cf @@ -0,0 +1,27 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent baz +{ + reports: + "$(this.promise_filename) Pass"; +} + +bundle agent bar(x) +{ + methods: + "$(x)"; # Tries to call bazz, doesn't exist, shouldn't work +} + +bundle agent main +{ + methods: + "foo" + usebundle => bar("bazz"); +} diff --git a/tests/acceptance/21_methods/reachable/bundle_promiser_defined.cf b/tests/acceptance/21_methods/reachable/bundle_promiser_defined.cf new file mode 100644 index 0000000000..e01cf79995 --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_promiser_defined.cf @@ -0,0 +1,20 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent foo +{ + reports: + "$(this.promise_filename) Pass"; +} + +bundle agent main +{ + methods: + "foo"; +} diff --git a/tests/acceptance/21_methods/reachable/bundle_promiser_undefined.x.cf b/tests/acceptance/21_methods/reachable/bundle_promiser_undefined.x.cf new file mode 100644 index 0000000000..20db06ea41 --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_promiser_undefined.x.cf @@ -0,0 +1,14 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent main +{ + methods: + "foo"; # Should error because foo does not exist +} diff --git a/tests/acceptance/21_methods/reachable/bundle_promiser_unreachable.cf b/tests/acceptance/21_methods/reachable/bundle_promiser_unreachable.cf new file mode 100644 index 0000000000..b0847e75f4 --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_promiser_unreachable.cf @@ -0,0 +1,23 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent bar +{ + reports: + "$(this.promise_filename) Pass"; +} + +bundle agent main +{ + methods: + out_of_context:: + "foo"; # Should not error because it's unreachable + any:: + "bar"; +} diff --git a/tests/acceptance/21_methods/reachable/bundle_reversed_usebundle_parameters_defined.cf b/tests/acceptance/21_methods/reachable/bundle_reversed_usebundle_parameters_defined.cf new file mode 100644 index 0000000000..e0373ba2be --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_reversed_usebundle_parameters_defined.cf @@ -0,0 +1,26 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +# reversed tests are just to show it should behave the same +# if the called bundle comes after instead of before + +bundle agent main +{ + methods: + "foo" + usebundle => bar("baz"); +} + +bundle agent bar(x) +{ + # bar exists and it's the bundle we want, so this will pass: + reports: + "$(x)"; + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/21_methods/reachable/bundle_reversed_usebundle_parameters_undefined.x.cf b/tests/acceptance/21_methods/reachable/bundle_reversed_usebundle_parameters_undefined.x.cf new file mode 100644 index 0000000000..81fefaaf4e --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_reversed_usebundle_parameters_undefined.x.cf @@ -0,0 +1,26 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +# reversed tests are just to show it should behave the same +# if the called bundle comes after instead of before + +bundle agent main +{ + methods: + "foo" + usebundle => bar("baz"); # Should error because bar does not exist +} + +bundle agent foo(x) +{ + # foo exists, but it's not the bundle we are looking for + reports: + "$(x)"; + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/21_methods/reachable/bundle_usebundle_call_by_arg_defined.cf b/tests/acceptance/21_methods/reachable/bundle_usebundle_call_by_arg_defined.cf new file mode 100644 index 0000000000..f586391d0f --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_usebundle_call_by_arg_defined.cf @@ -0,0 +1,28 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent baz +{ + reports: + "$(this.promise_filename) Pass"; +} + +bundle agent bar(x) +{ + methods: + "foo" + usebundle => "$(x)"; # Will call baz, which exists, should work +} + +bundle agent main +{ + methods: + "foo" + usebundle => bar("baz"); +} diff --git a/tests/acceptance/21_methods/reachable/bundle_usebundle_call_by_arg_undefined.x.cf b/tests/acceptance/21_methods/reachable/bundle_usebundle_call_by_arg_undefined.x.cf new file mode 100644 index 0000000000..20118e4897 --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_usebundle_call_by_arg_undefined.x.cf @@ -0,0 +1,28 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent baz +{ + reports: + "$(this.promise_filename) Pass"; +} + +bundle agent bar(x) +{ + methods: + "foo" + usebundle => "$(x)"; # Tries to call bazz, doesn't exist, shouldn't work +} + +bundle agent main +{ + methods: + "foo" + usebundle => bar("bazz"); +} diff --git a/tests/acceptance/21_methods/reachable/bundle_usebundle_defined.cf b/tests/acceptance/21_methods/reachable/bundle_usebundle_defined.cf new file mode 100644 index 0000000000..e4d1136a92 --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_usebundle_defined.cf @@ -0,0 +1,21 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent bar +{ + reports: + "$(this.promise_filename) Pass"; +} + +bundle agent main +{ + methods: + "foo" + usebundle => bar; +} diff --git a/tests/acceptance/21_methods/reachable/bundle_usebundle_parameters_defined.cf b/tests/acceptance/21_methods/reachable/bundle_usebundle_parameters_defined.cf new file mode 100644 index 0000000000..8eee0f8e1c --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_usebundle_parameters_defined.cf @@ -0,0 +1,23 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent bar(x) +{ + # bar exists and it's the bundle we want, so this will pass: + reports: + "$(x)"; + "$(this.promise_filename) Pass"; +} + +bundle agent main +{ + methods: + "foo" + usebundle => bar("baz"); +} diff --git a/tests/acceptance/21_methods/reachable/bundle_usebundle_parameters_undefined.x.cf b/tests/acceptance/21_methods/reachable/bundle_usebundle_parameters_undefined.x.cf new file mode 100644 index 0000000000..3c5fc1de5b --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_usebundle_parameters_undefined.x.cf @@ -0,0 +1,23 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent foo(x) +{ + # foo exists, but it's not the bundle we are looking for + reports: + "$(x)"; + "$(this.promise_filename) Pass"; +} + +bundle agent main +{ + methods: + "foo" + usebundle => bar("baz"); # Should error because bar does not exist +} diff --git a/tests/acceptance/21_methods/reachable/bundle_usebundle_undefined.x.cf b/tests/acceptance/21_methods/reachable/bundle_usebundle_undefined.x.cf new file mode 100644 index 0000000000..a44fdb0d3c --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_usebundle_undefined.x.cf @@ -0,0 +1,20 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent foo +{ + # foo exists but it's not the bundle we are looking for +} + +bundle agent main +{ + methods: + "foo" + usebundle => bar; # Should error because bar does not exist +} diff --git a/tests/acceptance/21_methods/reachable/bundle_usebundle_unreachable.cf b/tests/acceptance/21_methods/reachable/bundle_usebundle_unreachable.cf new file mode 100644 index 0000000000..3fd33f4a97 --- /dev/null +++ b/tests/acceptance/21_methods/reachable/bundle_usebundle_unreachable.cf @@ -0,0 +1,23 @@ +# Reachable bundle tests verify that you are allowed have references +# to undefined bundles, as long as they are not actually called. + +# These tests are left very simple on purpose, +# they are not using the test framework. +# They should be seen together, note that defined vs undefined variants are +# almost identical, just replacing one detail to show what should work +# and what should error. + +bundle agent baz +{ + reports: + "$(this.promise_filename) Pass"; +} + +bundle agent main +{ + methods: + "foo" + if => "out_of_context", + usebundle => "bar"; # Should not error because it's unreachable + "baz"; +} diff --git a/tests/acceptance/21_methods/reporting/kept.cf b/tests/acceptance/21_methods/reporting/kept.cf new file mode 100644 index 0000000000..24590e98bc --- /dev/null +++ b/tests/acceptance/21_methods/reporting/kept.cf @@ -0,0 +1,69 @@ +####################################################### +# +# Redmine#4852: test kept methods reporting +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + methods: + "create" usebundle => test_create_hello; +} + +####################################################### + +bundle agent test +{ + methods: + + "verify" usebundle => test_create_hello, + classes => test_verify_method; + + reports: + OK:: + "OK"; + FAIL:: + "FAIL"; +} + +bundle agent test_create_hello +{ + files: + "$(G.testfile)" + create => "true", + perms => test_m("000"), + action => test_immediate; +} + +body perms test_m(mode) +{ + mode => $(mode); +} + +body action test_immediate +{ + ifelapsed => 0; +} + +body classes test_verify_method +{ + promise_kept => {"ok"}; +} + +bundle agent check +{ + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/21_methods/usebundle_with_or_without.cf b/tests/acceptance/21_methods/usebundle_with_or_without.cf new file mode 100644 index 0000000000..24eabccb59 --- /dev/null +++ b/tests/acceptance/21_methods/usebundle_with_or_without.cf @@ -0,0 +1,92 @@ +# Test that methods promises work correctly both with and without usebundle. + +body common control +{ + inputs => { '../default.cf.sub' }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent test +{ + vars: + "test_var7" string => "7"; + "test_var8" string => "8"; + + methods: + "check1"; + "check2" usebundle => check2; + "bad_check3" usebundle => check3; + #"checkN(4)"; # Not currently working. + "checkN(5)" usebundle => checkN(5); + "bad_checkN(6)" usebundle => checkN(6); + "checkN(bad)" usebundle => checkN(good); + "check$(test_var7)"; + #"checkN($(test_var8))"; # Not currently working. +} + +bundle agent check1 +{ + classes: + "ok_1" expression => "any", + scope => "namespace"; +} +bundle agent check2 +{ + classes: + "ok_2" expression => "any", + scope => "namespace"; +} +bundle agent check3 +{ + classes: + "ok_3" expression => "any", + scope => "namespace"; +} +bundle agent bad_check3 +{ + classes: + "bad_3" expression => "any", + scope => "namespace"; +} +bundle agent checkN(X) +{ + classes: + "ok_$(X)" expression => "any", + scope => "namespace"; +} +bundle agent bad_checkN(X) +{ + classes: + "bad_$(X)" expression => "any", + scope => "namespace"; +} +bundle agent check7 +{ + classes: + "ok_7" expression => "any", + scope => "namespace"; +} + +bundle agent check +{ + vars: + "cases" slist => { 1, 2, 3, 5, 6, 7, "good" }; + "positive" slist => maplist("ok_$(this)", "cases"); + "negative" slist => { maplist("bad_$(this)", "cases"), "ok_bad" }; + + classes: + "positive_match" and => { @(positive) }; + "negative_match" or => { @(negative) }; + "ok" expression => "positive_match.!negative_match"; + + reports: + DEBUG:: + "Classes not set that should be: $(positive)" + if => "!$(positive)"; + "Classes set that should not be: $(negative)" + if => "$(negative)"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/21_methods/warn_only.cf b/tests/acceptance/21_methods/warn_only.cf new file mode 100644 index 0000000000..a5741d35ca --- /dev/null +++ b/tests/acceptance/21_methods/warn_only.cf @@ -0,0 +1,48 @@ +####################################################### +# +# Redmine#4852: test kept methods reporting +# +####################################################### + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +body action warnonly +{ + action_policy => "warn"; +} + +####################################################### + +bundle agent test +{ + methods: + "verify" + usebundle => test_method, + action => warnonly, + classes => if_else("method_kept", "method_notkept"); +} + +bundle agent test_method +{ + reports: + "unwanted sideeffect!" classes => if_else("report_kept", "report_notkept"); +} + +bundle agent check +{ + classes: + "ok" expression => "method_notkept.!report_kept.!report_notkept"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/22_cf-runagent/serial/allroles_nonroot_role2_root_allowed.22009.srv b/tests/acceptance/22_cf-runagent/serial/allroles_nonroot_role2_root_allowed.22009.srv new file mode 100644 index 0000000000..421f097e64 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/allroles_nonroot_role2_root_allowed.22009.srv @@ -0,0 +1,44 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22009"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to only activate classes "role1" and "role2" + roles: + ".*" authorize => { "nonroot" }; + "role2" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/allroles_root_allowed.22006.srv b/tests/acceptance/22_cf-runagent/serial/allroles_root_allowed.22006.srv new file mode 100644 index 0000000000..5271db6c15 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/allroles_root_allowed.22006.srv @@ -0,0 +1,43 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22006"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to activate all classes + roles: + ".*" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/bundle2_role2_only_allowed.22012.srv b/tests/acceptance/22_cf-runagent/serial/bundle2_role2_only_allowed.22012.srv new file mode 100644 index 0000000000..e1fec6e320 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/bundle2_role2_only_allowed.22012.srv @@ -0,0 +1,47 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22012"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + "bundle2" + admit_ips => { "127.0.0.1", "::1" }, + resource_type => "bundle"; + + # Authorize "root" user to activate only "role2" class + roles: + "role2" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed.22010.srv b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed.22010.srv new file mode 100644 index 0000000000..08e16535ae --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed.22010.srv @@ -0,0 +1,43 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22010"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + ".*" + admit_ips => { "127.0.0.1", "::1" }, + resource_type => "bundle"; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed.22013.srv b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed.22013.srv new file mode 100644 index 0000000000..80ab439edd --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed.22013.srv @@ -0,0 +1,47 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22013"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + ".*" + admit_ips => { "127.0.0.1", "::1" }, + resource_type => "bundle"; + + "bundle[12]" + deny_ips => { "0.0.0.0/0" }, + resource_type => "bundle"; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed_admitted.cf b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed_admitted.cf new file mode 100644 index 0000000000..84a92cb499 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed_admitted.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "--bundlesequence bundle"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundles_all_allowed_regex_disallowed.22013.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22013 is bundles_all_allowed_regex_disallowed.22013.srv + run_runagent("-H 127.0.0.1:22013 --remote-bundles bundle $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundles_all_allowed_regex_disallowed.22013.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed_denied.cf b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed_denied.cf new file mode 100644 index 0000000000..ac8cfbc69c --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_regex_disallowed_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundles_all_allowed_regex_disallowed.22013.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22013 is bundles_all_allowed_regex_disallowed.22013.srv + run_runagent("-H 127.0.0.1:22013 --remote-bundles bundle1 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundles_all_allowed_regex_disallowed.22013.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_roles_disallowed_denied.cf b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_roles_disallowed_denied.cf new file mode 100644 index 0000000000..7d6bd1bd5d --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/bundles_all_allowed_roles_disallowed_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundles_all_allowed.22010.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22010 is bundles_all_allowed.22010.srv + run_runagent("-H 127.0.0.1:22010 --remote-bundles bundle1 -D role $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundles_all_allowed.22010.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/bundles_roles_all_allowed.22011.srv b/tests/acceptance/22_cf-runagent/serial/bundles_roles_all_allowed.22011.srv new file mode 100644 index 0000000000..1b09aaad2b --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/bundles_roles_all_allowed.22011.srv @@ -0,0 +1,47 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22011"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + ".*" + admit_ips => { "127.0.0.1", "::1" }, + resource_type => "bundle"; + + # Authorize "root" users to activate all classes + roles: + ".*" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_argv0_quoted.22014.srv b/tests/acceptance/22_cf-runagent/serial/cfruncommand_argv0_quoted.22014.srv new file mode 100644 index 0000000000..b5d3213a09 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_argv0_quoted.22014.srv @@ -0,0 +1,46 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22014"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + + # Make sure that if argv0 is quoted, it still is executed properly. + + cfruncommand => + "\"$(G.write_args_sh)\" $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to activate all classes + # roles: + # ".*" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_argv0_quoted.cf b/tests/acceptance/22_cf-runagent/serial/cfruncommand_argv0_quoted.cf new file mode 100644 index 0000000000..83901b0d19 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_argv0_quoted.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + ""); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_argv0_quoted.22014.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22014 is cfruncommand_argv0_quoted.22014.srv + run_runagent("-H 127.0.0.1:22014 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_argv0_quoted.22014.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_deny_root.22001.srv b/tests/acceptance/22_cf-runagent/serial/cfruncommand_deny_root.22001.srv new file mode 100644 index 0000000000..d4a1ce0ac0 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_deny_root.22001.srv @@ -0,0 +1,39 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22001"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Allow user "blah", disallow "root" user to execute cfruncommand + allowusers => { "blah" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_deny_root.cf b/tests/acceptance/22_cf-runagent/serial/cfruncommand_deny_root.cf new file mode 100644 index 0000000000..37cd4e8152 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_deny_root.cf @@ -0,0 +1,44 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_deny_root.22001.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + run_runagent("-H 127.0.0.1:22001 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_deny_root.22001.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22002.srv b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22002.srv new file mode 100644 index 0000000000..3c67ba1d0e --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22002.srv @@ -0,0 +1,45 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22002"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + # ACCESS IS DENIED + + # "$(G.write_args_sh)" + # admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to activate all classes + # roles: + # ".*" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22003.srv b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22003.srv new file mode 100644 index 0000000000..32659e350c --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22003.srv @@ -0,0 +1,45 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22003"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + # ACCESS IS DENIED to 127.0.0.1 + + "$(G.write_args_sh)" + admit_ips => { "1.2.3.4" }; + + # Authorize "root" users to activate all classes + # roles: + # ".*" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22004.srv b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22004.srv new file mode 100644 index 0000000000..e1507126ec --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access.22004.srv @@ -0,0 +1,45 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22004"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + # ACCESS IS DENIED + + "$(G.write_args_sh)" + deny => { "127.0.0.1", "::1" }; + + # Authorize "root" users to activate all classes + # roles: + # ".*" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_1.cf b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_1.cf new file mode 100644 index 0000000000..554515f035 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_1.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_no_access.22002.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22002 is cfruncommand_no_access.22002.srv + run_runagent("-H 127.0.0.1:22002 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there, since cfruncommand + # invocation from cf-serverd should have been denied. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_no_access.22002.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_2.cf b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_2.cf new file mode 100644 index 0000000000..04968f30a1 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_2.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_no_access.22003.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22003 is cfruncommand_no_access.22003.srv + run_runagent("-H 127.0.0.1:22003 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there, since cfruncommand + # invocation from cf-serverd should have been denied. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_no_access.22003.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_3.cf b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_3.cf new file mode 100644 index 0000000000..3e9f7467d1 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_no_access_3.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_no_access.22004.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22004 is cfruncommand_no_access.22004.srv + run_runagent("-H 127.0.0.1:22004 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there, since cfruncommand + # invocation from cf-serverd should have been denied. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_no_access.22004.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_open.22000.srv b/tests/acceptance/22_cf-runagent/serial/cfruncommand_open.22000.srv new file mode 100644 index 0000000000..23de49073d --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_open.22000.srv @@ -0,0 +1,43 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22000"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to activate all classes + # roles: + # ".*" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_open.cf b/tests/acceptance/22_cf-runagent/serial/cfruncommand_open.cf new file mode 100644 index 0000000000..ea6acae5c3 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_open.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + ""); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_open.22000.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22000 is cfruncommand_open.22000.srv + run_runagent("-H 127.0.0.1:22000 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_open.22000.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/cfruncommand_relative_paths_disallowed.srv b/tests/acceptance/22_cf-runagent/serial/cfruncommand_relative_paths_disallowed.srv new file mode 100644 index 0000000000..9d7dcaf914 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/cfruncommand_relative_paths_disallowed.srv @@ -0,0 +1,43 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22001"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "root" }; + cfruncommand => + "../../../../../../../../../bin/echo blah"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "/bin/echo" + admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to activate all classes + # roles: + # ".*" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/empty_config.runagent.cf.sub b/tests/acceptance/22_cf-runagent/serial/empty_config.runagent.cf.sub new file mode 100644 index 0000000000..c5f05c3c24 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/empty_config.runagent.cf.sub @@ -0,0 +1,17 @@ +# +# Uncomment if you want to test the classic protocol. The code paths for +# EXEC on cf-serverd have been unified so it should not make any +# difference. +# +#body common control +#{ +# protocol_version => "classic"; +#} + + +body runagent control +{ + # A list of hosts to contact when using cf-runagent. + # In the acceptance tests we give the list of hosts on the command line. + hosts => { }; +} diff --git a/tests/acceptance/22_cf-runagent/serial/ns2_bundle2_ns1_role2_only_allowed.22015.srv b/tests/acceptance/22_cf-runagent/serial/ns2_bundle2_ns1_role2_only_allowed.22015.srv new file mode 100644 index 0000000000..9cebd898d7 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/ns2_bundle2_ns1_role2_only_allowed.22015.srv @@ -0,0 +1,47 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22015"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + "ns2:bundle2" + admit_ips => { "127.0.0.1", "::1" }, + resource_type => "bundle"; + + # Authorize "root" user to activate only "role2" class + roles: + "ns1:role2" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/role12_root_allowed.22007.srv b/tests/acceptance/22_cf-runagent/serial/role12_root_allowed.22007.srv new file mode 100644 index 0000000000..e6921f7f42 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/role12_root_allowed.22007.srv @@ -0,0 +1,43 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22007"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to only activate class "role12" + roles: + "role12" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/role1_role2_root_allowed.22008.srv b/tests/acceptance/22_cf-runagent/serial/role1_role2_root_allowed.22008.srv new file mode 100644 index 0000000000..34a3410916 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/role1_role2_root_allowed.22008.srv @@ -0,0 +1,44 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22008"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to only activate classes "role1" and "role2" + roles: + "role1" authorize => { "root" }; + "role2" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/role1_root_allowed.22005.srv b/tests/acceptance/22_cf-runagent/serial/role1_root_allowed.22005.srv new file mode 100644 index 0000000000..7884cf48b9 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/role1_root_allowed.22005.srv @@ -0,0 +1,43 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control + +{ + port => "22005"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; + + # Authorize "root" users to execute cfruncommand + allowusers => { "blah", "root" }; + cfruncommand => + "$(G.write_args_sh) $(G.testdir)/exec_args.txt"; +} + +######################################################### + +bundle server access_rules() + +{ + + access: + + "$(G.write_args_sh)" + admit_ips => { "127.0.0.1", "::1" }; + + # Authorize "root" users to only activate class "role1" + roles: + "role1" authorize => { "root" }; + +} + diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_admitted_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_admitted_1.cf new file mode 100644 index 0000000000..66cc006c40 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_admitted_1.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "--bundlesequence bundle1"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundles_all_allowed.22010.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22010 is bundles_all_allowed.22010.srv + run_runagent("-H 127.0.0.1:22010 --remote-bundles bundle1 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundles_all_allowed.22010.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_bundle2_admitted_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_bundle2_admitted_1.cf new file mode 100644 index 0000000000..d9f0ab8898 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_bundle2_admitted_1.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "--bundlesequence bundle1,bundle2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundles_all_allowed.22010.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22010 is bundles_all_allowed.22010.srv + run_runagent("-H 127.0.0.1:22010 --remote-bundles bundle1,bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundles_all_allowed.22010.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_bundle2_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_bundle2_denied.cf new file mode 100644 index 0000000000..41e5654f94 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_bundle2_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22012 is bundle2_role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22012 --remote-bundles bundle1,bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_denied_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_denied_1.cf new file mode 100644 index 0000000000..a677b3285f --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle1_denied_1.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_open.22000.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22000 is cfruncommand_open.22000.srv + run_runagent("-H 127.0.0.1:22000 --remote-bundles bundle1 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_open.22000.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle2_admitted.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle2_admitted.cf new file mode 100644 index 0000000000..421913028a --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle2_admitted.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "--bundlesequence bundle2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22012 is bundle2_role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22012 --remote-bundles bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle2_bundle1_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle2_bundle1_denied.cf new file mode 100644 index 0000000000..b549ebb6dd --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle2_bundle1_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22012 is bundle2_role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22012 --remote-bundles bundle2,bundle1 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle_denied.cf new file mode 100644 index 0000000000..0384bcba1c --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-B_bundle_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22012 is bundle2_role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22012 --remote-bundles bundle $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-B_specialchars_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-B_specialchars_denied.cf new file mode 100644 index 0000000000..e6195f3d99 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-B_specialchars_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundles_roles_all_allowed.22011.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22011 is bundles_roles_all_allowed.22011.srv + run_runagent("-H 127.0.0.1:22011 --remote-bundles bundle@ $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundles_roles_all_allowed.22011.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_bundle2_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_bundle2_denied.cf new file mode 100644 index 0000000000..c2ee447390 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_bundle2_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/ns2_bundle2_ns1_role2_only_allowed.22015.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22015 is ns2_bundle2_ns1_role2_only_allowed.22015.srv + run_runagent("-H 127.0.0.1:22015 -D ns1:role2 --remote-bundles bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/ns2_bundle2_ns1_role2_only_allowed.22015.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_ns2_bundle2_admitted.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_ns2_bundle2_admitted.cf new file mode 100644 index 0000000000..9c2b39bf4d --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_ns2_bundle2_admitted.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D ns1:role2 --bundlesequence ns2:bundle2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/ns2_bundle2_ns1_role2_only_allowed.22015.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22015 is ns2:bundle2_ns1:role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22015 -D ns1:role2 --remote-bundles ns2:bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/ns2_bundle2_ns1_role2_only_allowed.22015.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_ns2_bundle2_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_ns2_bundle2_denied.cf new file mode 100644 index 0000000000..5e1d790c18 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns1_role2_-B_ns2_bundle2_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22012 is bundle2_role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22012 -D ns1:role2 --remote-bundles ns2:bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns2_role2_-B_ns2_bundle2_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns2_role2_-B_ns2_bundle2_denied.cf new file mode 100644 index 0000000000..2ae80e3053 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_ns2_role2_-B_ns2_bundle2_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/ns2_bundle2_ns1_role2_only_allowed.22015.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22015 is ns2_bundle2_ns1_role2_only_allowed.22015.srv + run_runagent("-H 127.0.0.1:22015 -D ns2:role2 --remote-bundles ns2:bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/ns2_bundle2_ns1_role2_only_allowed.22015.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1-B_bundle1_admitted_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1-B_bundle1_admitted_1.cf new file mode 100644 index 0000000000..cbaa462bcd --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1-B_bundle1_admitted_1.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role1 --bundlesequence bundle1"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundles_roles_all_allowed.22011.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22010 is bundles_roles_all_allowed.22011.srv + run_runagent("-H 127.0.0.1:22011 -D role1 --remote-bundles bundle1 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundles_roles_all_allowed.22011.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role12_denied_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role12_denied_1.cf new file mode 100644 index 0000000000..4057dec620 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role12_denied_1.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role1_root_allowed.22005.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22005 is role1_root_allowed.22005.srv + # It allows "role1" but should disallow "role12" + run_runagent("-H 127.0.0.1:22005 -D role12 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role1_root_allowed.22005.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_-B_bundle2_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_-B_bundle2_denied.cf new file mode 100644 index 0000000000..3fb009b85d --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_-B_bundle2_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22012 is bundle2_role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22012 -D role1 --remote-bundles bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_-D_role2_admitted.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_-D_role2_admitted.cf new file mode 100644 index 0000000000..2182a5ca01 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_-D_role2_admitted.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role1,role2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role1_role2_root_allowed.22008.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22008 is role1_role2_root_allowed.22008.srv + run_runagent("-H 127.0.0.1:22008 -D role1 -D role2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role1_role2_root_allowed.22008.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_1.cf new file mode 100644 index 0000000000..4f4fd72db2 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_1.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role1"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role1_root_allowed.22005.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22005 is role1_root_allowed.22005.srv + run_runagent("-H 127.0.0.1:22005 -D role1 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role1_root_allowed.22005.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_2.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_2.cf new file mode 100644 index 0000000000..0b0cb1f4f2 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_2.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role1"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/allroles_root_allowed.22006.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22006 is allroles_root_allowed.22006.srv + run_runagent("-H 127.0.0.1:22006 -D role1 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/allroles_root_allowed.22006.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_3.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_3.cf new file mode 100644 index 0000000000..070d03b888 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_admitted_3.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role1"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role1_role2_root_allowed.22008.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22008 is role1_role2_root_allowed.22008.srv + run_runagent("-H 127.0.0.1:22008 -D role1 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role1_role2_root_allowed.22008.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_1.cf new file mode 100644 index 0000000000..10d5b2a049 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_1.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_open.22000.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22000 is cfruncommand_open.22000.srv + run_runagent("-H 127.0.0.1:22000 -D role1 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_open.22000.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_2.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_2.cf new file mode 100644 index 0000000000..fb39815231 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_2.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role12_root_allowed.22007.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22007 is role12_root_allowed.22007.srv + # It allows "role12" but should disallow "role1" + run_runagent("-H 127.0.0.1:22007 -D role1 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role12_root_allowed.22007.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_3.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_3.cf new file mode 100644 index 0000000000..0238dd0c87 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_denied_3.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/allroles_nonroot_role2_root_allowed.22009.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22009 is allroles_nonroot_role2_root_allowed.22009.srv + # It allows "role1" but only to user "nonroot" so it should fail + run_runagent("-H 127.0.0.1:22009 -D role1 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/allroles_nonroot_role2_root_allowed.22009.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_admitted_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_admitted_1.cf new file mode 100644 index 0000000000..bd587cb715 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_admitted_1.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role1,role2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/allroles_root_allowed.22006.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22006 is allroles_root_allowed.22006.srv + run_runagent("-H 127.0.0.1:22006 -D role1,role2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/allroles_root_allowed.22006.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_admitted_2.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_admitted_2.cf new file mode 100644 index 0000000000..3314a10f09 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_admitted_2.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role1,role2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role1_role2_root_allowed.22008.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22008 is role1_role2_root_allowed.22008.srv + run_runagent("-H 127.0.0.1:22008 -D role1,role2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role1_role2_root_allowed.22008.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_1.cf new file mode 100644 index 0000000000..e4a41eec0d --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_1.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role1_root_allowed.22005.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22005 is role1_root_allowed.22005.srv + # It allows "role1" but should disallow "role2" + run_runagent("-H 127.0.0.1:22005 -D role1,role2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role1_root_allowed.22005.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_2.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_2.cf new file mode 100644 index 0000000000..9b84fa9dd5 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_2.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role12_root_allowed.22007.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22007 is role12_root_allowed.22007.srv + # It allows "role12" but should disallow both "role1" and "role2" + run_runagent("-H 127.0.0.1:22007 -D role1,role2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role12_root_allowed.22007.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_3.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_3.cf new file mode 100644 index 0000000000..b5625c7e11 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role1_role2_denied_3.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/allroles_nonroot_role2_root_allowed.22009.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22009 is allroles_nonroot_role2_root_allowed.22009.srv + run_runagent("-H 127.0.0.1:22009 -D role1,role2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/allroles_nonroot_role2_root_allowed.22009.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_-B_bundle1_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_-B_bundle1_denied.cf new file mode 100644 index 0000000000..7b426c9e6d --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_-B_bundle1_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22012 is bundle2_role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22012 -D role2 --remote-bundles bundle1 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_-B_bundle2_admitted.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_-B_bundle2_admitted.cf new file mode 100644 index 0000000000..0cc93ef624 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_-B_bundle2_admitted.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role2 --bundlesequence bundle2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22012 is bundle2_role2_only_allowed.22012.srv + run_runagent("-H 127.0.0.1:22012 -D role2 --remote-bundles bundle2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundle2_role2_only_allowed.22012.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_admitted_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_admitted_1.cf new file mode 100644 index 0000000000..e8ec9e91cf --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_admitted_1.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role1_role2_root_allowed.22008.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22008 is role1_role2_root_allowed.22008.srv + run_runagent("-H 127.0.0.1:22008 -D role2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role1_role2_root_allowed.22008.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_admitted_2.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_admitted_2.cf new file mode 100644 index 0000000000..6b9724740a --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_admitted_2.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Expected output in the exec_args.txt file + "any" usebundle => file_make("$(G.testdir)/expected_args.txt", + "-D role2"); + # Ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/allroles_nonroot_role2_root_allowed.22009.srv"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10404" }; + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22009 is allroles_nonroot_role2_root_allowed.22009.srv + run_runagent("-H 127.0.0.1:22009 -D role2 $(runagent_cf)"); +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testdir)/expected_args.txt", + "$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/allroles_nonroot_role2_root_allowed.22009.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_denied_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_denied_1.cf new file mode 100644 index 0000000000..bda39dca4c --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_denied_1.cf @@ -0,0 +1,46 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/role1_root_allowed.22005.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22005 is role1_root_allowed.22005.srv + # It allows "role1" but should disallow "role2" + run_runagent("-H 127.0.0.1:22005 -D role2 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/role1_root_allowed.22005.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_role1_denied_1.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_role1_denied_1.cf new file mode 100644 index 0000000000..1368868453 --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_role2_role1_denied_1.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/allroles_nonroot_role2_root_allowed.22009.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22009 is allroles_nonroot_role2_root_allowed.22009.srv + run_runagent("-H 127.0.0.1:22009 -D role2,role1 $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/allroles_nonroot_role2_root_allowed.22009.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-D_specialchars_denied.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-D_specialchars_denied.cf new file mode 100644 index 0000000000..30f74e208f --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-D_specialchars_denied.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/bundles_roles_all_allowed.22011.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22011 is bundles_roles_all_allowed.22011.srv + run_runagent("-H 127.0.0.1:22011 -D role@ $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/bundles_roles_all_allowed.22011.srv"); +} diff --git a/tests/acceptance/22_cf-runagent/serial/runagent_-o_deprecated.cf b/tests/acceptance/22_cf-runagent/serial/runagent_-o_deprecated.cf new file mode 100644 index 0000000000..1e12badc0d --- /dev/null +++ b/tests/acceptance/22_cf-runagent/serial/runagent_-o_deprecated.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # ensure execution output file is not there + "any" usebundle => dcs_fini("$(G.testdir)/exec_args.txt"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/cfruncommand_open.22000.srv"); +} + +bundle agent test +{ + vars: + "runagent_cf" string => + "$(this.promise_dirname)/empty_config.runagent.cf.sub"; + methods: + "any" usebundle => + # Port 22000 is cfruncommand_open.22000.srv + run_runagent("-H 127.0.0.1:22000 -o custom_option $(runagent_cf)"); +} + +bundle agent check +{ + # Execution output file should still not be there. + methods: + "any" usebundle => dcs_passif_file_absent("$(G.testdir)/exec_args.txt", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/cfruncommand_open.22000.srv"); +} diff --git a/tests/acceptance/23_failsafe/failsafe_created_on_parse_error.cf b/tests/acceptance/23_failsafe/failsafe_created_on_parse_error.cf new file mode 100644 index 0000000000..f5f2fb504b --- /dev/null +++ b/tests/acceptance/23_failsafe/failsafe_created_on_parse_error.cf @@ -0,0 +1,41 @@ +# Test that failsafe.cf is created when dosn't exist + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Remove any failsafe.cf + "any" usebundle => dcs_fini("$(sys.inputdir)/failsafe.cf"); + classes: + "failsafe_before_test_not_exists" not => + fileexists("$(sys.inputdir)/failsafe.cf"), + scope => "namespace"; +} + +bundle agent test +{ + commands: + "$(sys.cf_agent) -f $(this.promise_dirname)/invalid_syntax.cf.sub"; + + # TODO this sub-agent fails to run invalid_syntax.cf and writes + # failsafe.cf, which then executes. The execute bit is + # redundant, can we avoid it? +} + +bundle agent check +{ + classes: + "failsafe_after_test_exists" expression => fileexists("$(sys.inputdir)/failsafe.cf"); + "ok" expression => "failsafe_after_test_exists.failsafe_before_test_not_exists"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/23_failsafe/invalid_syntax.cf.sub b/tests/acceptance/23_failsafe/invalid_syntax.cf.sub new file mode 100644 index 0000000000..0e6d18fb02 --- /dev/null +++ b/tests/acceptance/23_failsafe/invalid_syntax.cf.sub @@ -0,0 +1,13 @@ +#INVALID POLICY SYNTAX! + + +body common control +{ + bundlesequence => { "test" }; +} + +bundle agent test +{ + my_non_existing_promise_type: + "non_existing" => "not_there"; +} diff --git a/tests/acceptance/23_failsafe/preexisting_failsafe_preserved.cf b/tests/acceptance/23_failsafe/preexisting_failsafe_preserved.cf new file mode 100644 index 0000000000..3c17546b02 --- /dev/null +++ b/tests/acceptance/23_failsafe/preexisting_failsafe_preserved.cf @@ -0,0 +1,36 @@ +# Test that failsafe.cf is not created when it exists + + +body common control +{ + inputs => { "../default.cf.sub", "../plucked.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + # Remove the custom failsafe output file + "any" usebundle => dcs_fini("$(sys.inputdir)/failsafe_output.txt"); + files: + "$(sys.inputdir)/failsafe.cf" + create => "true", + perms => m("600"), + copy_from => dcs_sync("$(this.promise_dirname)/preexisting_failsafe_preserved.failsafe.cf.sub"); +} + +bundle agent test +{ + commands: + "$(sys.cf_agent) -f $(this.promise_dirname)/invalid_syntax.cf.sub"; +} + +bundle agent check +{ + methods: + "any" usebundle => + # Verify that the custom failsafe.cf did run and created the + # file that we removed earlier. + dcs_passif_fileexists("$(sys.inputdir)/failsafe_output.txt", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/23_failsafe/preexisting_failsafe_preserved.failsafe.cf.sub b/tests/acceptance/23_failsafe/preexisting_failsafe_preserved.failsafe.cf.sub new file mode 100644 index 0000000000..72c71f2335 --- /dev/null +++ b/tests/acceptance/23_failsafe/preexisting_failsafe_preserved.failsafe.cf.sub @@ -0,0 +1,16 @@ +# My custom minimal failsafe, only touches a file in $(sys.inputdir) + + +body common control +{ + bundlesequence => { "main" }; +} + +bundle agent main +{ + vars: + "outfile" string => "$(sys.inputdir)/failsafe_output.txt"; + + files: + "$(outfile)" create => true; +} diff --git a/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf b/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf new file mode 100644 index 0000000000..d073ded8f5 --- /dev/null +++ b/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf @@ -0,0 +1,41 @@ +# Tests that the '-p' option works properly in all its modes. + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent init +{ + vars: + "testdir" data => '{ testdir: "$(this.promise_dirname)" }'; + + files: + "$(G.testdir)/output.expected" + create => "true", + edit_template => "$(this.promise_filename).template", + template_method => "mustache", + template_data => @(testdir); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; + vars: + "arg_list" slist => { "none", "cf", "json", "cf-full", "json-full" }; + + commands: + "$(sys.cf_promises) -f $(this.promise_filename).sub -p $(arg_list) >> $(G.testdir)/output.actual" + contain => in_shell; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_diff("$(G.testdir)/output.actual", + "$(G.testdir)/output.expected", + $(this.promise_filename)); +} diff --git a/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.sub b/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.sub new file mode 100644 index 0000000000..0084e62de4 --- /dev/null +++ b/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.sub @@ -0,0 +1,12 @@ +body common control +{ + inputs => { "parsed_policy.cf.sub.sub" }; + bundlesequence => { "test" }; +} + +bundle agent test +{ + vars: + "testvar" string => "string"; + "testlist" slist => { "list" }; +} diff --git a/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.sub.sub b/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.sub.sub new file mode 100644 index 0000000000..dcf3e6444a --- /dev/null +++ b/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.sub.sub @@ -0,0 +1,5 @@ +bundle common extra +{ + vars: + "extra_var" string => "extra"; +} diff --git a/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.template b/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.template new file mode 100644 index 0000000000..e30fce3927 --- /dev/null +++ b/tests/acceptance/24_cmd_line_arguments/parsed_policy.cf.template @@ -0,0 +1,279 @@ +bundle agent test() +{ +vars: + any:: + "testvar" string => "string"; + "testlist" slist => {"list"}; + + +} + +body common control() +{ + inputs => {"parsed_policy.cf.sub.sub"}; + bundlesequence => {"test"}; + +} + +{ + "bodies": [ + { + "arguments": [], + "bodyType": "common", + "contexts": [ + { + "attributes": [ + { + "line": 3, + "lval": "inputs", + "rval": { + "type": "list", + "value": [ + { + "type": "string", + "value": "parsed_policy.cf.sub.sub" + } + ] + } + }, + { + "line": 4, + "lval": "bundlesequence", + "rval": { + "type": "list", + "value": [ + { + "type": "string", + "value": "test" + } + ] + } + } + ], + "name": "any" + } + ], + "line": 1, + "name": "control", + "namespace": "default", + "sourcePath": "{{testdir}}/parsed_policy.cf.sub" + } + ], + "bundles": [ + { + "arguments": [], + "bundleType": "agent", + "line": 7, + "name": "test", + "namespace": "default", + "promiseTypes": [ + { + "contexts": [ + { + "name": "any", + "promises": [ + { + "attributes": [ + { + "line": 10, + "lval": "string", + "rval": { + "type": "string", + "value": "string" + } + } + ], + "line": 10, + "promiser": "testvar" + }, + { + "attributes": [ + { + "line": 11, + "lval": "slist", + "rval": { + "type": "list", + "value": [ + { + "type": "string", + "value": "list" + } + ] + } + } + ], + "line": 11, + "promiser": "testlist" + } + ] + } + ], + "line": 9, + "name": "vars" + } + ], + "sourcePath": "{{testdir}}/parsed_policy.cf.sub" + } + ] + }bundle agent test() +{ +vars: + any:: + "testvar" string => "string"; + "testlist" slist => {"list"}; + + +} + +bundle common extra() +{ +vars: + any:: + "extra_var" string => "extra"; + + +} + +body common control() +{ + inputs => {"parsed_policy.cf.sub.sub"}; + bundlesequence => {"test"}; + +} + +{ + "bodies": [ + { + "arguments": [], + "bodyType": "common", + "contexts": [ + { + "attributes": [ + { + "line": 3, + "lval": "inputs", + "rval": { + "type": "list", + "value": [ + { + "type": "string", + "value": "parsed_policy.cf.sub.sub" + } + ] + } + }, + { + "line": 4, + "lval": "bundlesequence", + "rval": { + "type": "list", + "value": [ + { + "type": "string", + "value": "test" + } + ] + } + } + ], + "name": "any" + } + ], + "line": 1, + "name": "control", + "namespace": "default", + "sourcePath": "{{testdir}}/parsed_policy.cf.sub" + } + ], + "bundles": [ + { + "arguments": [], + "bundleType": "agent", + "line": 7, + "name": "test", + "namespace": "default", + "promiseTypes": [ + { + "contexts": [ + { + "name": "any", + "promises": [ + { + "attributes": [ + { + "line": 10, + "lval": "string", + "rval": { + "type": "string", + "value": "string" + } + } + ], + "line": 10, + "promiser": "testvar" + }, + { + "attributes": [ + { + "line": 11, + "lval": "slist", + "rval": { + "type": "list", + "value": [ + { + "type": "string", + "value": "list" + } + ] + } + } + ], + "line": 11, + "promiser": "testlist" + } + ] + } + ], + "line": 9, + "name": "vars" + } + ], + "sourcePath": "{{testdir}}/parsed_policy.cf.sub" + }, + { + "arguments": [], + "bundleType": "common", + "line": 1, + "name": "extra", + "namespace": "default", + "promiseTypes": [ + { + "contexts": [ + { + "name": "any", + "promises": [ + { + "attributes": [ + { + "line": 4, + "lval": "string", + "rval": { + "type": "string", + "value": "extra" + } + } + ], + "line": 4, + "promiser": "extra_var" + } + ] + } + ], + "line": 3, + "name": "vars" + } + ], + "sourcePath": "{{testdir}}/parsed_policy.cf.sub.sub" + } + ] + } \ No newline at end of file diff --git a/tests/acceptance/25_cf-execd/.gitignore b/tests/acceptance/25_cf-execd/.gitignore new file mode 100644 index 0000000000..7bd0105dce --- /dev/null +++ b/tests/acceptance/25_cf-execd/.gitignore @@ -0,0 +1 @@ +/cf-execd-test diff --git a/tests/acceptance/25_cf-execd/Makefile.am b/tests/acceptance/25_cf-execd/Makefile.am new file mode 100644 index 0000000000..dd2290e1cf --- /dev/null +++ b/tests/acceptance/25_cf-execd/Makefile.am @@ -0,0 +1,46 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +AM_CPPFLAGS = \ + $(OPENSSL_CPPFLAGS) \ + -I$(srcdir)/../../../libpromises \ + -I$(srcdir)/../../../libntech/libutils \ + -I$(srcdir)/../../../libcfnet \ + -I$(srcdir)/../../../libenv \ + -I$(srcdir)/../../../cf-execd \ + $(PCRE2_CPPFLAGS) \ + $(ENTERPRISE_CPPFLAGS) + +AM_CFLAGS = \ + $(PCRE2_CFLAGS) \ + $(OPENSSL_CFLAGS) \ + $(ENTERPRISE_CFLAGS) + +if !NT +noinst_PROGRAMS = cf-execd-test + +cf_execd_test_SOURCES = cf-execd-rpl-functions.c +cf_execd_test_LDADD = ../../../cf-execd/libcf-execd-test.la + +endif diff --git a/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c b/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c new file mode 100644 index 0000000000..9b6c3f445f --- /dev/null +++ b/tests/acceptance/25_cf-execd/cf-execd-rpl-functions.c @@ -0,0 +1,118 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +/* + * A test source file with alternative implementations of some functions. Those + * that are affected are generally ifdef'ed out in the original source file by + * TEST_CF_EXECD macro. + */ + +#include +#include + +static void *FeedSmtpDirectives(void *data) +{ + int socket = *(int *)data; + +#define COND_SEND(X) if (send(socket, X, strlen(X), 0) == -1) \ + { \ + Log(LOG_LEVEL_ERR, "Failed to write SMTP directive in %s:%i", __FILE__, __LINE__); \ + return NULL; \ + } \ + else \ + { \ + fwrite(X, strlen(X), 1, stdout); \ + } + +#define COND_RECV(X) if ((rcvd = recv(socket, X, sizeof(X), 0)) == -1) \ + { \ + Log(LOG_LEVEL_ERR, "Failed to read SMTP response in %s:%i", __FILE__, __LINE__); \ + return NULL; \ + } \ + else \ + { \ + fwrite(X, rcvd, 1, stdout); \ + } + + char recvbuf[CF_BUFSIZE]; + int rcvd; + COND_SEND("220 test.com\r\n"); + COND_RECV(recvbuf); + COND_SEND("250 Hello test.com, pleased to meet you\r\n"); + COND_RECV(recvbuf); + COND_SEND("250 from@test.com... Sender ok\r\n"); + COND_RECV(recvbuf); + COND_SEND("250 to@test.com... Recipient ok\r\n"); + COND_RECV(recvbuf); + COND_SEND("354 Enter mail, end with \".\" on a line by itself\r\n"); + while (true) + { + COND_RECV(recvbuf); + if ((rcvd == 3 && memcmp(recvbuf + rcvd - 3, ".\r\n", 3) == 0) || + (rcvd > 3 && memcmp(recvbuf + rcvd - 4, "\n.\r\n", 4) == 0)) + { + break; + } + } + COND_SEND("250 Message accepted for delivery\r\n"); + COND_RECV(recvbuf); + COND_SEND("221 test.com closing connection\r\n"); + +#undef COND_SEND +#undef COND_RECV + + cf_closesocket(socket); + free(data); + + return NULL; +} + +int ConnectToSmtpSocket(ARG_UNUSED const ExecConfig *config) +{ + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) + { + return -1; + } + + int *thread_socket = xmalloc(sizeof(int)); + *thread_socket = sockets[1]; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_t thread; + int ret = pthread_create(&thread, &attr, &FeedSmtpDirectives, thread_socket); + pthread_attr_destroy(&attr); + + if (ret != 0) + { + free(thread_socket); + cf_closesocket(sockets[0]); + cf_closesocket(sockets[1]); + return -1; + } + + return sockets[0]; +} diff --git a/tests/acceptance/25_cf-execd/mail_1st_run_empty_new_log.cf b/tests/acceptance/25_cf-execd/mail_1st_run_empty_new_log.cf new file mode 100644 index 0000000000..accdd80788 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mail_1st_run_empty_new_log.cf @@ -0,0 +1,44 @@ +# Tests that no mail is sent by cf-execd on the first run (hence the old log +# doesn't exist) and if the new log is empty. + +body common control +{ + inputs => { "../default.cf.sub", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promiser_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { + }; + + # No mail is expected at all, since everything is filtered. + "expected" slist => { }; + "unexpected" slist => { ".+" }; + + "includes" slist => { + }; + "excludes" slist => { + }; + + methods: + "any" usebundle => prepare_mailfilter_test(@(reports), + @(includes), + @(excludes)); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + methods: + "any" usebundle => check_cf_execd_mail(@(init.expected), + @(init.unexpected), + $(this.promise_filename)); +} diff --git a/tests/acceptance/25_cf-execd/mail_empty_old_and_new_log.cf b/tests/acceptance/25_cf-execd/mail_empty_old_and_new_log.cf new file mode 100644 index 0000000000..627a891db7 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mail_empty_old_and_new_log.cf @@ -0,0 +1,49 @@ +# Tests that no mail is sent by cf-execd if the old and new logs are empty. + +body common control +{ + inputs => { "../default.cf.sub", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promiser_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { + }; + + # No mail is expected at all, since everything is filtered. + "expected" slist => { }; + "unexpected" slist => { ".+" }; + + "includes" slist => { + }; + "excludes" slist => { + }; + + methods: + "any" usebundle => prepare_mailfilter_test(@(reports), + @(includes), + @(excludes)); + + files: + "$(sys.workdir)/outputs/old.log" + create => "true"; + "$(sys.workdir)/outputs/previous" + link_from => linkfrom("$(sys.workdir)/outputs/old.log", "symlink"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + methods: + "any" usebundle => check_cf_execd_mail(@(init.expected), + @(init.unexpected), + $(this.promise_filename)); +} diff --git a/tests/acceptance/25_cf-execd/mail_exclude_filter.cf b/tests/acceptance/25_cf-execd/mail_exclude_filter.cf new file mode 100644 index 0000000000..62e868752d --- /dev/null +++ b/tests/acceptance/25_cf-execd/mail_exclude_filter.cf @@ -0,0 +1,45 @@ +# Tests that exclude mail filter causes excluded lines not to be mailed by +# cf-execd. + +body common control +{ + inputs => { "../default.cf.sub", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { + "CorrectString1", + "CorrectString2", + "WrongString1", + "WrongString2", + }; + "includes" slist => { }; + "excludes" slist => { "R: WrongString1", ".*WrongString[2]" }; + + methods: + "any" usebundle => prepare_mailfilter_test(@(reports), + @(includes), + @(excludes)); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + vars: + "expected" slist => { "CorrectString1", "CorrectString2" }; + "not_expected" slist => { "WrongString1", "WrongString2" }; + + methods: + "any" usebundle => check_cf_execd_mail(@(expected), + @(not_expected), + $(this.promise_filename)); +} diff --git a/tests/acceptance/25_cf-execd/mail_include_and_exclude_filters.cf b/tests/acceptance/25_cf-execd/mail_include_and_exclude_filters.cf new file mode 100644 index 0000000000..26ecb478d3 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mail_include_and_exclude_filters.cf @@ -0,0 +1,54 @@ +# Tests that combining include and exclude mail filters works, IOW only lines +# matching the include filter should be printed, unless they also match the +# exclude filter. + +body common control +{ + inputs => { "../default.cf.sub", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { + "CorrectString1", + "CorrectString2", + "WrongString1", + "WrongString2", + "WrongString3", + }; + "includes" slist => { + "R: CorrectString1", ".*CorrectString[2]", + "R: WrongString1", ".*WrongString[2]" + }; + "excludes" slist => { + "R: WrongString1", ".*WrongString[2]", + # Tests anchoring. This is only a part of the string + # and should not match. + "orrectString1" + }; + methods: + "any" usebundle => prepare_mailfilter_test(@(reports), + @(includes), + @(excludes)); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + vars: + "expected" slist => { "CorrectString1", "CorrectString2" }; + "not_expected" slist => { "WrongString1", "WrongString2", "WrongString3" }; + + methods: + "any" usebundle => check_cf_execd_mail(@(expected), + @(not_expected), + $(this.promise_filename)); +} diff --git a/tests/acceptance/25_cf-execd/mail_include_filter.cf b/tests/acceptance/25_cf-execd/mail_include_filter.cf new file mode 100644 index 0000000000..b5532d33da --- /dev/null +++ b/tests/acceptance/25_cf-execd/mail_include_filter.cf @@ -0,0 +1,45 @@ +# Tests that include mail filter causes only matched lines to be mailed by +# cf-execd. + +body common control +{ + inputs => { "../default.cf.sub", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { + "CorrectString1", + "CorrectString2", + "WrongString1", + "WrongString2", + }; + "includes" slist => { "R: CorrectString1", ".*CorrectString[2]" }; + "excludes" slist => { }; + + methods: + "any" usebundle => prepare_mailfilter_test(@(reports), + @(includes), + @(excludes)); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + vars: + "expected" slist => { "CorrectString1", "CorrectString2" }; + "not_expected" slist => { "WrongString1", "WrongString2" }; + + methods: + "any" usebundle => check_cf_execd_mail(@(expected), + @(not_expected), + $(this.promise_filename)); +} diff --git a/tests/acceptance/25_cf-execd/mail_no_filter.cf b/tests/acceptance/25_cf-execd/mail_no_filter.cf new file mode 100644 index 0000000000..c26b9bd775 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mail_no_filter.cf @@ -0,0 +1,40 @@ +# Tests that without mail filters, everything is mailed by cf-execd. + +body common control +{ + inputs => { "../default.cf.sub", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promise_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { + "CorrectString1", + "CorrectString2", + }; + "includes" slist => { }; + "excludes" slist => { }; + + methods: + "any" usebundle => prepare_mailfilter_test(@(reports), + @(includes), + @(excludes)); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + vars: + "empty_list" slist => { }; + methods: + "any" usebundle => check_cf_execd_mail(@(init.reports), + @(empty_list), + $(this.promise_filename)); +} diff --git a/tests/acceptance/25_cf-execd/mailfilter_1st_run_everything_filtered.cf b/tests/acceptance/25_cf-execd/mailfilter_1st_run_everything_filtered.cf new file mode 100644 index 0000000000..4dec4da34e --- /dev/null +++ b/tests/acceptance/25_cf-execd/mailfilter_1st_run_everything_filtered.cf @@ -0,0 +1,48 @@ +# Tests that no mail is sent by cf-execd if everything is filtered on the first +# run (hence the old log doesn't exist). + +body common control +{ + inputs => { "../default.cf.sub", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promiser_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { + "WrongString1", + "WrongString2", + "WrongString3", + }; + + # No mail is expected at all, since everything is filtered. + "expected" slist => { }; + "unexpected" slist => { ".+" }; + + "includes" slist => { + }; + "excludes" slist => { + ".*WrongString.*" + }; + + methods: + "any" usebundle => prepare_mailfilter_test(@(reports), + @(includes), + @(excludes)); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + methods: + "any" usebundle => check_cf_execd_mail(@(init.expected), + @(init.unexpected), + $(this.promise_filename)); +} diff --git a/tests/acceptance/25_cf-execd/mailfilter_cf_execd_control.template b/tests/acceptance/25_cf-execd/mailfilter_cf_execd_control.template new file mode 100644 index 0000000000..564477bb58 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mailfilter_cf_execd_control.template @@ -0,0 +1,18 @@ +body executor control +{ + mailto => "to@test.com"; + mailfrom => "from@test.com"; + smtpserver => "dummy"; + mailfilter_include => { + {{#includes}} + "{{.}}", + {{/includes}} + }; + mailfilter_exclude => { + {{#excludes}} + "{{.}}", + {{/excludes}} + }; + + exec_command => "$(sys.cf_agent) -K"; +} diff --git a/tests/acceptance/25_cf-execd/mailfilter_common.cf.sub b/tests/acceptance/25_cf-execd/mailfilter_common.cf.sub new file mode 100644 index 0000000000..410cbd6cb4 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mailfilter_common.cf.sub @@ -0,0 +1,178 @@ +# Common stuff for mailfilter tests. + +bundle agent prepare_mailfilter_test(reports, includes, excludes) +{ + vars: + "data_keys[reports]" slist => { @(reports) }; + "data_keys[includes]" slist => { @(includes) }; + "data_keys[excludes]" slist => { @(excludes) }; + "data" data => mergedata("data_keys"); + "data_print" string => format("%S", "data"); + + # promises.cf + + files: + "$(sys.inputdir)/promises.cf" + create => "true", + edit_template => "$(this.promise_dirname)/mailfilter_promises_cf.template", + template_method => "mustache", + template_data => @(data); + + # cf-execd.cf + + files: + "$(sys.inputdir)/controls/cf_execd.cf" + create => "true", + edit_template => "$(this.promise_dirname)/mailfilter_cf_execd_control.template", + template_method => "mustache", + template_data => @(data); + + # Keys. + + methods: + "generate_key"; +} + +bundle agent run_cf_execd(wanted, unwanted) +{ + vars: + "cmd" string => "$(this.promise_dirname)/cf-execd-test --once --with-runagent-socket no"; + + commands: + "$(cmd) >> $(G.testfile).output" + contain => in_shell; + + methods: + "any" usebundle => store_expected_regex_from_run(@(wanted), @(unwanted)); +} + +bundle agent store_expected_regex_from_run(wanted, unwanted) +{ + classes: + "wanted_not_empty" expression => some(".*", "wanted"); + "unwanted_not_empty" expression => some(".*", "unwanted"); + + vars: + !wanted_not_empty:: + "wanted_regex" string => ""; + wanted_not_empty:: + # Notice that "Date" is optionally matched, due to it being ifdef'ed on + # some platforms. + "wanted_regex" string => concat( + +'220 test\.com\r +HELO [^\r\n]+\r +250 Hello test\.com, pleased to meet you\r +MAIL FROM: \r +250 from@test\.com\.\.\. Sender ok\r +RCPT TO: \r +250 to@test\.com\.\.\. Recipient ok\r +DATA\r +354 Enter mail, end with "\." on a line by itself\r +Subject: \[[^]]+\]\r +X-CFEngine: vfqhost="[^"]+";ip-addresses="[^"]+";policyhub="[^"]*";pkhash="MD5=617eb383deffef843ea856b129d0a423"\r +(Date: [^\r\n]+\r +)?From: from@test\.com\r +To: to@test\.com\r +\r +R: ' + , join( + +'\r +R: ', "wanted"), + +'\r +\.\r +250 Message accepted for delivery\r +QUIT\r( +221 test\.com closing connection\r)*'); +# ^ Note that this last string is optionally matched. The reason is that +# cf-execd may finish everything and quit before the mock thread has time to +# finish writing that string. It does not mean there's an error. +# Moreover, sometimes it might be printed more than once, and since this +# regexp is treated as ancored, we're prepared to face as many of repetitions +# as necessary. + + unwanted_not_empty:: + "unwanted_regex" string => concat(".*(", join("|", "unwanted"), ").*"); + + files: + "$(G.testfile).wanted" + create => "true"; + wanted_not_empty:: + "$(G.testfile).wanted" + edit_line => append_regex($(wanted_regex)); + + any:: + "$(G.testfile).unwanted" + create => "true"; + unwanted_not_empty:: + "$(G.testfile).unwanted" + edit_line => append_regex($(unwanted_regex)); +} + +bundle edit_line append_regex(regex) +{ + insert_lines: + "$(regex)" + insert_type => "preserve_all_lines"; +} + +bundle agent compare_cf_execd_mail(test) +{ + classes: + "test_not_empty" expression => isvariable("test"); + "unwanted_content" not => strcmp(filestat("$(G.testfile).unwanted", "size"), "0"); + + vars: + test_not_empty:: + "cmd" string => "$(G.cat) $(G.testfile).output"; + "wanted_regex" string => readfile("$(G.testfile).wanted", 0); + "unwanted_regex_list" slist => readstringlist("$(G.testfile).unwanted", + "", + "\n", + "1000000", + "1000000"); + + "unwanted_regex" string => join("|", "unwanted_regex_list"); + + "output" string => readfile("$(G.testfile).output", 0); + + classes: + "wanted_ok" expression => regcmp($(wanted_regex), $(output)); + "unwanted_ok" not => regcmp($(unwanted_regex), $(output)); + "ok" and => { "wanted_ok", "unwanted_ok" }; + + reports: + "unwanted_regex_list = $(unwanted_regex_list)"; + "wanted_regex = '$(wanted_regex)'"; + "unwanted_regex = '$(unwanted_regex)'"; + "output = '$(output)'"; + wanted_ok:: + "wanted_ok is set"; + !wanted_ok:: + "wanted_ok is NOT set"; + unwanted_ok:: + "unwanted_ok is set"; + !unwanted_ok:: + "unwanted_ok is NOT set"; + + reports: + ok:: + "$(test) Pass"; + !ok:: + "$(test) FAIL"; +} + +bundle agent check_cf_execd_mail(wanted_arg, unwanted_arg, test) +{ + vars: + # Workaround: The parser doesn't see bundle arguments as truly being in + # the scope of the bundle, and therefore they can't be passed on into + # other bundles. Therefore we make a copy. + "wanted" slist => { @(wanted_arg) }; + "unwanted" slist => { @(unwanted_arg) }; + methods: + "any" usebundle => run_cf_execd(@(wanted), @(unwanted)); + "any" usebundle => compare_cf_execd_mail($(test)); +} diff --git a/tests/acceptance/25_cf-execd/mailfilter_empty_old_log_everything_filtered.cf b/tests/acceptance/25_cf-execd/mailfilter_empty_old_log_everything_filtered.cf new file mode 100644 index 0000000000..bd01a2f260 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mailfilter_empty_old_log_everything_filtered.cf @@ -0,0 +1,54 @@ +# Tests that no mail is sent by cf-execd if everything is filtered and the old +# log is empty. + +body common control +{ + inputs => { "../default.cf.sub", "mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promiser_filename)) }; +} + +bundle agent init +{ + vars: + "reports" slist => { + "WrongString1", + "WrongString2", + "WrongString3", + }; + + # No mail is expected at all, since everything is filtered. + "expected" slist => { }; + "unexpected" slist => { ".+" }; + + "includes" slist => { + }; + "excludes" slist => { + ".*WrongString.*" + }; + + methods: + "any" usebundle => prepare_mailfilter_test(@(reports), + @(includes), + @(excludes)); + + files: + "$(sys.workdir)/outputs/old.log" + create => "true"; + "$(sys.workdir)/outputs/previous" + link_from => linkfrom("$(sys.workdir)/outputs/old.log", "symlink"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + methods: + "any" usebundle => check_cf_execd_mail(@(init.expected), + @(init.unexpected), + $(this.promise_filename)); +} diff --git a/tests/acceptance/25_cf-execd/mailfilter_promises_cf.template b/tests/acceptance/25_cf-execd/mailfilter_promises_cf.template new file mode 100644 index 0000000000..4651f00dc5 --- /dev/null +++ b/tests/acceptance/25_cf-execd/mailfilter_promises_cf.template @@ -0,0 +1,13 @@ +body common control +{ + inputs => { "controls/cf_execd.cf" }; + bundlesequence => { "test" }; +} + +bundle agent test +{ + reports: + {{#reports}} + "{{.}}"; + {{/reports}} +} diff --git a/tests/acceptance/25_cf-execd/slow/dies_in_time.cf b/tests/acceptance/25_cf-execd/slow/dies_in_time.cf new file mode 100644 index 0000000000..e95942b2f6 --- /dev/null +++ b/tests/acceptance/25_cf-execd/slow/dies_in_time.cf @@ -0,0 +1,159 @@ +# ENT-3147, zd#3157 + +# Ensure that cf-execd dies within 3 seconds after being signalled. + + +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + vars: + + "exec_command_script" string => +"#!/bin/sh -x + +sleep 10 + +CF_EXECD_PID=`cat $(sys.piddir)/cf-execd.pid` + +# Send TERM +kill -15 $CF_EXECD_PID + +# Give the process 2 (plus 1 just to be sure) seconds to die gracefully +sleep 3 + +# Is it dead? +if kill -0 $CF_EXECD_PID +then + echo ALIVE > $(G.testdir)/cf_execd.status +else + echo DEAD > $(G.testdir)/cf_execd.status +fi + +# Touch a file to trigger the policy to move on and check the cf_execd.status file +touch $(G.testdir)/EXECUTOR_WAS_SIGNALLED + +# Keep this child running to test that cf-execd still dies instantly despite that +sleep 10 + +# Clean up that useless executor if TERM failed +# NOTE: using `ps` command fails under fakeroot! + +if kill -0 $CF_EXECD_PID +then + echo 'cf-execd is still alive, killing it with FIRE!!!' + kill -9 $CF_EXECD_PID + exit 1 +fi + +# Verify that this shell script reaches completion +# despite parent cf-execd being dead +echo DONE > $(G.testdir)/exec_command.status + +"; + + files: + "$(G.testdir)/exec_command.sh" + create => "true", + perms => m(777), + edit_line => insert_lines("$(exec_command_script)"); + + methods: + "any" usebundle => dcs_fini("$(sys.piddir)/cf-execd.pid"); + "any" usebundle => dcs_fini("$(G.testdir)/EXECUTOR_WAS_SIGNALLED"); + "any" usebundle => dcs_fini("$(G.testdir)/cf_execd.status"); + "any" usebundle => dcs_fini("$(G.testdir)/exec_command.status"); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; + + commands: + + "$(sys.cf_execd) -f $(this.promise_dirname)/kill_myself.execd.srv" + classes => if_repaired("executor_started"); + + executor_started:: + # Make sure it started fully + "$(G.sleep) 5" + classes => if_repaired("waited"); + + executor_started.waited:: + + # Ensure executor PID is up + "kill -0 `cat $(sys.piddir)/cf-execd.pid`" + contain => in_shell, + classes => if_repaired("executor_started_ok"); + + executor_started_ok:: + # Wait until cf-execd is signalled by its own child; + # more than 60s which is CFPULSETIME + " +i=0 +while [ $i -lt 120 ] +do + i=`expr $i + 1` + echo $i + sleep 1 + if test -f $(G.testdir)/EXECUTOR_WAS_SIGNALLED + then + exit 0 + fi +done +exit 1 # Executor was never signalled! +" + contain => in_shell, + classes => if_repaired("executor_was_signalled"); + + executor_was_signalled:: + # Wait again until executor runs the signal handler and exits gracefully + "$(G.sleep) 3" + classes => if_repaired("waited_for_cleanup"); + + waited_for_cleanup:: + # and another 10s for exec_command.sh script to exit + "$(G.sleep) 10" + classes => if_repaired("script_should_have_exited"); + + classes: + "test_bundle_done" expression => "script_should_have_exited", + scope => "namespace"; + + reports: + + !executor_was_signalled:: + "$(this.promise_filename) FAIL"; +} + +# PASS only if cf_execd.status is DEAD and exec_command.status is DONE +bundle agent check +{ + classes: + test_bundle_done:: + "executor_is_dead" expression => + returnszero("$(G.grep) DEAD $(G.testdir)/cf_execd.status", + "useshell"); + "exec_command_finished" expression => + returnszero("$(G.grep) DONE $(G.testdir)/exec_command.status", + "useshell"); + + methods: + "" usebundle => + dcs_passif_expected("executor_is_dead,exec_command_finished","", + "$(this.promise_filename)"), + inherit => "true"; + + reports: + !executor_is_dead.DEBUG:: + "FAIL: cf-execd did not die within 3s after TERM signal!"; +} diff --git a/tests/acceptance/25_cf-execd/slow/kill_myself.execd.srv b/tests/acceptance/25_cf-execd/slow/kill_myself.execd.srv new file mode 100644 index 0000000000..d1e73063df --- /dev/null +++ b/tests/acceptance/25_cf-execd/slow/kill_myself.execd.srv @@ -0,0 +1,16 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +body executor control +{ + # Every 1 min + schedule => { "any" }; + + # redirect all file descriptors so that the shell script + # stays alive after cf-execd is killed + exec_command => + "$(G.testdir)/exec_command.sh > $(G.testdir)/exec_command.sh.log 2>&1 { "../../default.cf.sub", "../mailfilter_common.cf.sub" }; + bundlesequence => { default($(this.promiser_filename)) }; +} + +bundle agent init +{ + vars: + # Positions of WrongString inside the report should not matter, since it + # is filtered out. + "reports1" slist => { + "CorrectString1", + "CorrectString2", + "WrongString1", + "WrongString2", + "WrongString3", + }; + "expected1" slist => { + "CorrectString1", + "CorrectString2", + }; + + "reports2" slist => { + "WrongString1", + "CorrectString1", + "WrongString2", + "CorrectString2", + "WrongString3", + }; + "expected2" slist => { + # Nothing expected, since it is identical to + # previous run. + }; + + "reports3" slist => { + }; + "expected3" slist => { + # Nothing expected, because the log is empty. + }; + + "reports4" slist => { + "WrongString1", + "WrongString2", + "WrongString3", + }; + "expected4" slist => { + # Nothing expected, because everything is filtered. + }; + + "reports5" slist => { + "WrongString2", + "WrongString1", + "CorrectString2", + "CorrectString1", + "WrongString3", + }; + "expected5" slist => { + "CorrectString2", + "CorrectString1", + }; + + "unexpected" slist => { ".*WrongString.*" }; + + "includes" slist => { + "R: CorrectString1", ".*CorrectString[2]", + "R: WrongString1", ".*WrongString[2]" + }; + "excludes" slist => { + "R: WrongString1", ".*WrongString[2]", + # Tests anchoring. + "orrectString1" + }; + + methods: + test_pass_1:: + "any" usebundle => prepare_mailfilter_test(@(reports1), + @(includes), + @(excludes)); + "any" usebundle => run_cf_execd(@(expected1), @(unexpected)); + + test_pass_2:: + "any" usebundle => prepare_mailfilter_test(@(reports2), + @(includes), + @(excludes)); + "any" usebundle => run_cf_execd(@(expected2), @(unexpected)); + + test_pass_3:: + "any" usebundle => prepare_mailfilter_test(@(reports3), + @(includes), + @(excludes)); + "any" usebundle => run_cf_execd(@(expected3), @(unexpected)); + + test_pass_4:: + "any" usebundle => prepare_mailfilter_test(@(reports4), + @(includes), + @(excludes)); + "any" usebundle => run_cf_execd(@(expected4), @(unexpected)); + + test_pass_5:: + "any" usebundle => prepare_mailfilter_test(@(reports5), + @(includes), + @(excludes)); + "any" usebundle => run_cf_execd(@(expected5), @(unexpected)); +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10215" }; +} + +bundle agent check +{ + methods: + test_pass_1|test_pass_2|test_pass_3|test_pass_4:: + "any" usebundle => dcs_wait($(this.promise_filename), 1); + test_pass_5:: + "any" usebundle => compare_cf_execd_mail($(this.promise_filename)); +} diff --git a/tests/acceptance/26_cf-net/serial/cf-net_connect.cf b/tests/acceptance/26_cf-net/serial/cf-net_connect.cf new file mode 100644 index 0000000000..033183ac17 --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/cf-net_connect.cf @@ -0,0 +1,62 @@ +body contain myshell +{ + !windows:: + useshell => "useshell"; + windows:: + useshell => "powershell"; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); +} + +bundle agent test +{ + commands: + "$(sys.cf_net)" + args => " -H 127.0.0.1:15000 connect > $(G.testfile) 2>&1", + contain => myshell; + meta: + "description" string => "Connect and authenticate with cf-serverd"; + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_diff("$(G.testfile)", + "$(this.promise_filename).expected", + "$(this.promise_filename)"); + vars: + DEBUG|EXTRA:: + "actual_output" string => readfile("$(G.testfile)"); + "expected_output" string => readfile("$(this.promise_filename).expected"); + reports: + DEBUG|EXTRA:: + "expected_output: $(expected_output)"; + "actual_output: $(actual_output)"; +} + +bundle agent destroy +{ + methods: + "any" + usebundle => stop_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); +} diff --git a/tests/acceptance/26_cf-net/serial/cf-net_connect.cf.expected b/tests/acceptance/26_cf-net/serial/cf-net_connect.cf.expected new file mode 100644 index 0000000000..f3b2bcff6f --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/cf-net_connect.cf.expected @@ -0,0 +1 @@ +Connected & authenticated successfully to '127.0.0.1:15000' diff --git a/tests/acceptance/26_cf-net/serial/cf-net_get.cf b/tests/acceptance/26_cf-net/serial/cf-net_get.cf new file mode 100644 index 0000000000..3d0d78ee18 --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/cf-net_get.cf @@ -0,0 +1,54 @@ +body contain myshell +{ + !windows:: + useshell => "useshell"; + windows:: + useshell => "powershell"; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); +} + +bundle agent test +{ + commands: + "$(sys.cf_net)" + args => " -H 127.0.0.1:15000 get -o $(G.testfile) $(this.promise_filename)", + contain => myshell; + meta: + "description" string => "STAT+GET policy file from cf-serverd"; + "test_skip_needs_work" string => "windows", + comment => "for some reason, softfailing this test is not enough", + meta => { "ENT-10254" }; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_diff("$(G.testfile)", + "$(this.promise_filename)", + "$(this.promise_filename)"); +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); +} diff --git a/tests/acceptance/26_cf-net/serial/cf-net_help.cf b/tests/acceptance/26_cf-net/serial/cf-net_help.cf new file mode 100644 index 0000000000..ebfbe74a8e --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/cf-net_help.cf @@ -0,0 +1,42 @@ +body contain myshell +{ + !windows:: + useshell => "useshell"; + windows:: + useshell => "powershell"; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); +} + +bundle agent test +{ + commands: + # Run "cf-net help help" + "$(sys.cf_net)" + args => " help help > $(G.testfile) 2>&1", + contain => myshell; + meta: + "description" string => "Argument parsing and help output"; + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_diff("$(G.testfile)", + "$(this.promise_filename).expected", + $(this.promise_filename)); +} diff --git a/tests/acceptance/26_cf-net/serial/cf-net_help.cf.expected b/tests/acceptance/26_cf-net/serial/cf-net_help.cf.expected new file mode 100644 index 0000000000..3ec8503a0a --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/cf-net_help.cf.expected @@ -0,0 +1,5 @@ +Command: help +Usage: cf-net help [command] +Description: Prints general help or per topic + +You did it, you used the help command! diff --git a/tests/acceptance/26_cf-net/serial/cf-net_stat.cf b/tests/acceptance/26_cf-net/serial/cf-net_stat.cf new file mode 100644 index 0000000000..2fdb01740e --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/cf-net_stat.cf @@ -0,0 +1,65 @@ +body contain myshell +{ + !windows:: + useshell => "useshell"; + windows:: + useshell => "powershell"; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); + commands: + "echo" + args => " \"127.0.0.1:15000:'$(this.promise_dirname)/cf-net_stat.cf' is a regular file\" > $(G.testfile).expected", + contain => myshell; +} + +bundle agent test +{ + commands: + "$(sys.cf_net)" + args => " -H 127.0.0.1:15000 stat $(this.promise_dirname)/cf-net_stat.cf > $(G.testfile) 2>&1", + contain => myshell; + meta: + "description" string => "Stat a regular file (this policy file)"; + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_diff("$(G.testfile)", + "$(G.testfile).expected", + "$(this.promise_filename)"); + vars: + DEBUG|EXTRA:: + "actual_output" string => readfile("$(G.testfile)"); + "expected_output" string => readfile("$(G.testfile).expected"); + reports: + DEBUG|EXTRA:: + "expected_output: $(expected_output)"; + "actual_output: $(actual_output)"; +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); +} diff --git a/tests/acceptance/26_cf-net/serial/cf-net_stat_deny.cf b/tests/acceptance/26_cf-net/serial/cf-net_stat_deny.cf new file mode 100644 index 0000000000..ecea92f941 --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/cf-net_stat_deny.cf @@ -0,0 +1,65 @@ +body contain myshell +{ + !windows:: + useshell => "useshell"; + windows:: + useshell => "powershell"; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); + commands: + "echo" + args => "\"Could not stat: '$(this.promise_dirname)/cf-net_connect.cf'\" > $(G.testfile).expected", + contain => myshell; +} + +bundle agent test +{ + commands: + "$(sys.cf_net)" + args => "--log-level ERROR -H 127.0.0.1:15000 stat $(this.promise_dirname)/cf-net_connect.cf > $(G.testfile) 2>&1", + contain => myshell; + meta: + "description" string => "Attempt to stat file without permission"; + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_diff("$(G.testfile)", + "$(G.testfile).expected", + "$(this.promise_filename)"); + vars: + DEBUG|EXTRA:: + "actual_output" string => readfile("$(G.testfile)"); + "expected_output" string => readfile("$(G.testfile).expected"); + reports: + DEBUG|EXTRA:: + "expected_output: $(expected_output)"; + "actual_output: $(actual_output)"; +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); +} diff --git a/tests/acceptance/26_cf-net/serial/cf-net_stat_dir.cf b/tests/acceptance/26_cf-net/serial/cf-net_stat_dir.cf new file mode 100644 index 0000000000..685beb317c --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/cf-net_stat_dir.cf @@ -0,0 +1,65 @@ +body contain myshell +{ + !windows:: + useshell => "useshell"; + windows:: + useshell => "powershell"; +} + +body common control +{ + inputs => { + "../../default.cf.sub", + "../../run_with_server.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile)"); + + "any" usebundle => generate_key; + "any" usebundle => trust_key; + + "any" usebundle => start_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); + commands: + "echo" + args => " \"127.0.0.1:15000:'$(G.testdir)' is a directory\" > $(G.testfile).expected", + contain => myshell; +} + +bundle agent test +{ + commands: + "$(sys.cf_net)" + args => " -H 127.0.0.1:15000 stat $(G.testdir) > $(G.testfile) 2>&1", + contain => myshell; + meta: + "description" string => "Stat a directory"; + "test_soft_fail" string => "windows", + meta => { "ENT-10254" }; +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_diff("$(G.testfile)", + "$(G.testfile).expected", + "$(this.promise_filename)"); + vars: + DEBUG|EXTRA:: + "actual_output" string => readfile("$(G.testfile)"); + "expected_output" string => readfile("$(G.testfile).expected"); + reports: + DEBUG|EXTRA:: + "expected_output: $(expected_output)"; + "actual_output: $(actual_output)"; +} + +bundle agent destroy +{ + methods: + "any" usebundle => stop_server("$(this.promise_dirname)/serverd_admit_localhost_15000.cf.srv"); +} diff --git a/tests/acceptance/26_cf-net/serial/serverd_admit_localhost_15000.cf.srv b/tests/acceptance/26_cf-net/serial/serverd_admit_localhost_15000.cf.srv new file mode 100644 index 0000000000..188ad12d73 --- /dev/null +++ b/tests/acceptance/26_cf-net/serial/serverd_admit_localhost_15000.cf.srv @@ -0,0 +1,32 @@ +body common control +{ + bundlesequence => { "access_rules" }; + inputs => { "../../default.cf.sub" }; + +} + +######################################################### +# Server config +######################################################### + +body server control +{ + port => "15000"; + + allowconnects => { "127.0.0.1" , "::1" }; + allowallconnects => { "127.0.0.1" , "::1" }; + trustkeysfrom => { "127.0.0.1" , "::1" }; +} + +######################################################### + +bundle server access_rules() +{ + access: + "$(this.promise_dirname)/cf-net_stat.cf" + admit_ips => { "127.0.0.1", "::1" }; + "$(this.promise_dirname)/cf-net_get.cf" + admit_ips => { "127.0.0.1", "::1" }; + "$(G.testdir)" + admit_ips => { "127.0.0.1", "::1" }; +} diff --git a/tests/acceptance/27_cf-secret/decrypt.cf b/tests/acceptance/27_cf-secret/decrypt.cf new file mode 100644 index 0000000000..0baf3c522c --- /dev/null +++ b/tests/acceptance/27_cf-secret/decrypt.cf @@ -0,0 +1,28 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" + string => "Test that cf-secret can still decrypt content encrypted at the time of initial implementation"; + "test_soft_fail" string => "windows", + meta => { "ENT-10405" }; + + commands: + "$(sys.cf_secret)" + args => "decrypt -k $(this.promise_dirname)/testkey.priv -o $(G.testfile) $(this.promise_dirname)/encrypted"; +} + +bundle agent check +{ + methods: + "any" + usebundle => dcs_check_diff("$(this.promise_dirname)/plaintext", + "$(G.testfile)", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/27_cf-secret/encrypt-decrypt-args.cf b/tests/acceptance/27_cf-secret/encrypt-decrypt-args.cf new file mode 100644 index 0000000000..7d4c34349b --- /dev/null +++ b/tests/acceptance/27_cf-secret/encrypt-decrypt-args.cf @@ -0,0 +1,52 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).plain"); + "any" usebundle => dcs_fini("$(G.testfile).encrypted"); + "any" usebundle => dcs_fini("$(G.testfile).decrypted"); + "any" usebundle => generate_key; + "any" usebundle => trust_key; +} + +bundle agent test +{ + meta: + "description" + string => "Test cf-secret with different arguments/order"; + + vars: + "text" string => "This secret sauce should be encrypted and decrypted."; + + files: + "$(G.testfile).plain" + create => "true", + edit_defaults => empty, + edit_line => insert_lines( "$(text)" ); + + commands: + "$(sys.cf_secret)" + args => "encrypt --key $(sys.workdir)/ppkeys/localhost.pub --output $(G.testfile).encrypted $(G.testfile).plain"; + "$(sys.cf_secret)" + args => "decrypt --key $(sys.workdir)/ppkeys/localhost.priv -o $(G.testfile).decrypted $(G.testfile).encrypted"; + reports: + "Binaries/folders:"; + "$(sys.cf_secret)"; + "$(sys.cf_agent)"; + "$(sys.workdir)"; +} + +bundle agent check +{ + methods: + "any" + usebundle => dcs_check_diff("$(G.testfile).plain", + "$(G.testfile).decrypted", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/27_cf-secret/encrypt-decrypt.cf b/tests/acceptance/27_cf-secret/encrypt-decrypt.cf new file mode 100644 index 0000000000..57a77623f5 --- /dev/null +++ b/tests/acceptance/27_cf-secret/encrypt-decrypt.cf @@ -0,0 +1,57 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + methods: + "any" usebundle => dcs_fini("$(G.testfile).plain"); + "any" usebundle => dcs_fini("$(G.testfile).encrypted"); + "any" usebundle => dcs_fini("$(G.testfile).decrypted"); + "any" usebundle => generate_key; + "any" usebundle => trust_key; +} + +bundle agent test +{ + meta: + "description" + string => "Test that cf-secret basic key based encryption and decryption work"; + + "test_soft_fail" string => "windows", + meta => { "ENT-10405" }; + + vars: + "text" + string => "This secret sauce should be encrypted and decrypted."; + + files: + "$(G.testfile).plain" + create => "true", + edit_defaults => empty, + edit_line => insert_lines( "$(text)" ); + + commands: + "$(sys.cf_secret)" + args => "encrypt -k $(sys.workdir)/ppkeys/localhost.pub -o $(G.testfile).encrypted $(G.testfile).plain"; + "$(sys.cf_secret)" + args => "decrypt -k $(sys.workdir)/ppkeys/localhost.priv -o $(G.testfile).decrypted $(G.testfile).encrypted"; + + reports: + "Binaries/folders:"; + "$(sys.cf_secret)"; + "$(sys.cf_agent)"; + "$(sys.workdir)"; +} + +bundle agent check +{ + methods: + "any" + usebundle => dcs_check_diff("$(G.testfile).plain", + "$(G.testfile).decrypted", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/27_cf-secret/encrypt-no-key.cf b/tests/acceptance/27_cf-secret/encrypt-no-key.cf new file mode 100644 index 0000000000..ce84d42363 --- /dev/null +++ b/tests/acceptance/27_cf-secret/encrypt-no-key.cf @@ -0,0 +1,72 @@ +############################################################################## +# +# Test that encrypting with no host- / key argument defaults to encrypting for +# localhost. +# +############################################################################## + +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +bundle agent init +{ + methods: + "any" + usebundle => generate_key; + "any" + usebundle => trust_key; + + files: + # Create plain text file + "$(G.testfile).plaintext" + create => "true", + content => "Hello World!"; + + # Delete encrypted file + "$(G.testfile).encrypted" + delete => init_delete; + + # Delete decrypted file + "$(G.testfile).decrypted" + delete => init_delete; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3874" } + string => "Test that encrypting with no host- / key argument defaults to encrypting for localhost."; + + "test_soft_fail" string => "windows", + meta => { "ENT-10405" }; + + commands: + # Encrypt test file + "$(sys.cf_secret)" + args => "encrypt -o $(G.testfile).encrypted $(G.testfile).plaintext", + handle => "file-is-encrypted"; + + # Decrypt test file + "$(sys.cf_secret)" + args => "decrypt -o $(G.testfile).decrypted $(G.testfile).encrypted", + depends_on => { "file-is-encrypted" }; +} + +bundle agent check +{ + methods: + "any" + usebundle => dcs_check_diff("$(G.testfile).plaintext", + "$(G.testfile).decrypted", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/27_cf-secret/encrypt.x.cf b/tests/acceptance/27_cf-secret/encrypt.x.cf new file mode 100644 index 0000000000..dca6dfe767 --- /dev/null +++ b/tests/acceptance/27_cf-secret/encrypt.x.cf @@ -0,0 +1,28 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" + string => "Test that cf-secret encryption uses random padding"; + + commands: + "$(sys.cf_secret)" + args => "encrypt -k $(this.promise_dirname)/testkey.pub -o $(G.testfile) $(this.promise_dirname)/plaintext"; + "$(sys.cf_secret)" + args => "encrypt -k $(this.promise_dirname)/testkey.pub -o $(G.testfile).2 $(this.promise_dirname)/plaintext"; +} + +bundle agent check +{ + methods: + "any" + usebundle => dcs_check_diff("$(G.testfile)", + "$(G.testfile).2", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/27_cf-secret/encrypted b/tests/acceptance/27_cf-secret/encrypted new file mode 100644 index 0000000000..d37e09ef6b Binary files /dev/null and b/tests/acceptance/27_cf-secret/encrypted differ diff --git a/tests/acceptance/27_cf-secret/plaintext b/tests/acceptance/27_cf-secret/plaintext new file mode 100644 index 0000000000..ab9c75451f --- /dev/null +++ b/tests/acceptance/27_cf-secret/plaintext @@ -0,0 +1 @@ +Super secret message is here diff --git a/tests/acceptance/27_cf-secret/testkey.priv b/tests/acceptance/27_cf-secret/testkey.priv new file mode 100644 index 0000000000..9057519827 --- /dev/null +++ b/tests/acceptance/27_cf-secret/testkey.priv @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,C213C4387710A6A4 + +L1J7NLoZ0DogIHAQXb0MR62kSGyIqRkZX5mLDKAf76mP/ejKGo27s8gwV7wVioFC +uTAY5j9066/XsUdRN2vjBmX+/VM9SPciRrXwsKFUxqd23swxEEG9FjTWmp8z5mKK +VNUh3C1CcSMVYjigSKInQP3M4/iMZ5EvzXgCsPxgfLgIf0j2+lF3Qxsdy0uzBCxz +t19IjgvG4LCn26Vd1tm8QomPBK5y1e6OEuxh7Ke8TcOh/93nACIpuo/hGw1FtJ6Z +zQXNdrDklP96FwrzsUdaDd6tuHTTTiqHkviaF9jkEUbfkIZJkWzqAFGhcW0So+28 +OiCzpfESTDTaj3aVagc9UovJ2/S+AnlbHxrf24VCo5tD0dQzk9h5Pjg8Ze5N5el8 +tmGkPMcvSDwr1RkPm1Xqipa1FbPGnfnebRNxc95vRw1JIf0eF0PCvVS0f4XjncBG +QhUVeM6XKsqxoq3GC6ab/6cC3ZiVZNQpcptzt6Qy9+cDz0Et21Y8RJPdOT8wR+Sr +SQST/wKW+H/xNh4sld+gLF3v6MACq5Wk2trAZYS+MdmelVps7LSQBnHPD1oDOkkq +cL21cdevScDMjvbRnL2oj2054320rGBXS2VkT4XI7Qw/JeqNfT0Ue/9n+TABx60u +pbPKpCfJzxQIErE5XEQU27+2IVYu5fK+BVi6s7UYUU40NePRlWFTfLRCVskrm63u +UnZ4o8nKz+5DLtCdtTlRGOZDCwYQYOFawCfwLN9wToHqQZxC1wzqRTsqXoTEVmby +hp+Qji55e93dFgLpIKB54flj2v3Wdbk8FPx3mjzf9MCxRaaGhsuX0WHQyHzIDb9K +raFWYcZJ1S8DUz5o7Q8otJaxj+nPGLCKTpBw/UyfXi1tXkGSRSIDo1dUtMaE2MpL +J44u4BuokKSOZbOi/piJy5wR0GSN6FHiDJo3yNbz/ZFXCq8l4ZQsVFNtB+9BzKX1 +ZkTSe8JX1guGLo3+MKsLOpriBzmjDJ2JvziXa1S4P9l3SziwCfreDqMM3dw5+N2z +rmtruWDMCURiVQXyuUToKRvzxiroOWIUofHrBoCG23Ztz3R+qlpXtYNy93z9tcg1 +R8CeFXymXq8q2TpUorhLrZGf7Q6fZxLV66tWfkYje4oicH4qo+SFqN8T1/T6kJd4 +kVj28Vb1RB4Phz7dkOyjKNo7qYXcedUbygUiAepLfnFgk7L9NEldn5CR6e1TxJoZ +fD0ib/naOuUKdesc2YF2EAoS0Un9O4/YgZMCBNBUI412MGteiqOWwn0K6MNgiIKr +0ToBtNRsEXM/7p5/q32NDBuWOfL1/82ptMShRIlCMemZAlswbibuJnA6KI6smUIi +LD4fti2143FqLQg3pkvjc6PEuAHUFVxfUSx3Kn/8mhKNbZxJuU8V/OfTEnfPKo+3 +DyesHX7fTvnea5K+XJZY4fZRMxuzXtCcbIfq2wc5D4XPUy32wGqfsV9fmiEGqG7/ +ul1uquFUg4qfBhKqbFdIt7x7XSDiCSzdQsC0kQ9XI5OiIQBrPIctXEsQxfsvGzx5 +tI9xJ40eiNbFZfOLNyPOZ9RS5E7SHe0YeOQGkcbb1/+yY8GjaxTTYg== +-----END RSA PRIVATE KEY----- diff --git a/tests/acceptance/27_cf-secret/testkey.pub b/tests/acceptance/27_cf-secret/testkey.pub new file mode 100644 index 0000000000..4fc29eff00 --- /dev/null +++ b/tests/acceptance/27_cf-secret/testkey.pub @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAt96mvMUSXyJSr4jjcfrADkIm+8uqLGp0pHL4zrJLo8o/o8WnQyKl +hK614sAA/XlQmOIZ1KZ8x5Nw3fd/EFxEJ9wugz2iMbQTyRNgyJ4y68wrlBumKDgL +rtVDgb1ozIVh9Q5SEdGuV6MgHwNI7ubaLyuLetn+mlLcJXbzs6V75yWvCdfPfAt+ +aVONUChFRi1HydH34KEmEOgEca8YQW3FxjRYOnzeU81B9C3UGv3aFs8UQtNBvvd5 +15GQGnjimkZ3Ukpp0M/CrpcpoAU7X4AywvZLB3X5/JhZVw4wgFlprIdbbL4wd9ov +Q+4KoG+3BaMf5Y6NKvLRshONh5e3j7V4kQIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/tests/acceptance/28_inform_testing/01_files/changes01.cf b/tests/acceptance/28_inform_testing/01_files/changes01.cf new file mode 100644 index 0000000000..b82b88f30c --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/changes01.cf @@ -0,0 +1,31 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + changes => example; +} + +body changes example +{ + hash => "best"; + update_hashes => "true"; + report_changes => "content"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/changes01.cf.expected b/tests/acceptance/28_inform_testing/01_files/changes01.cf.expected new file mode 100644 index 0000000000..5f30ec8e07 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/changes01.cf.expected @@ -0,0 +1,2 @@ + info: Stored md5 hash for '/tmp/TEST.cfengine' (MD5=d41d8cd98f00b204e9800998ecf8427e) + info: Stored sha1 hash for '/tmp/TEST.cfengine' (SHA=da39a3ee5e6b4b0d3255bfef95601890afd80709) diff --git a/tests/acceptance/28_inform_testing/01_files/changes02.cf b/tests/acceptance/28_inform_testing/01_files/changes02.cf new file mode 100644 index 0000000000..79598d03f0 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/changes02.cf @@ -0,0 +1,24 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => insert_lines("foobar"); +} diff --git a/tests/acceptance/28_inform_testing/01_files/changes02.cf.expected b/tests/acceptance/28_inform_testing/01_files/changes02.cf.expected new file mode 100644 index 0000000000..420156e0c9 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/changes02.cf.expected @@ -0,0 +1,3 @@ + info: Inserted the promised line 'foobar' into '/tmp/TEST.cfengine' after locator + info: insert_lines promise 'foobar' repaired + info: Edited file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/copy_from01.cf b/tests/acceptance/28_inform_testing/01_files/copy_from01.cf new file mode 100644 index 0000000000..32ee890b36 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/copy_from01.cf @@ -0,0 +1,42 @@ +body file control +{ + hpux.ia64:: + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; + !(hpux.ia64):: + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testroot)/TEST.source" + depth_search => recurse("inf"), + perms => system_owned("0644"), + copy_from => example("$(this.promise_dirname)/test_files"); + +} + +bundle agent main +{ + files: + "$(G.testroot)/TEST.destination/subdir/." + create => "true"; + "$(G.testroot)/TEST.destination/file-perms-644" + copy_from => example("$(G.testroot)/TEST.source/file-perms-644"); + "$(G.testroot)/TEST.destination/subdir/subfile" + copy_from => example("$(G.testroot)/TEST.source/subdir/subdir-file"); +} + +body copy_from example(from) +{ + source => "$(from)"; + compare => "digest"; + preserve => "true"; +} + diff --git a/tests/acceptance/28_inform_testing/01_files/copy_from01.cf.expected b/tests/acceptance/28_inform_testing/01_files/copy_from01.cf.expected new file mode 100644 index 0000000000..8529b2deff --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/copy_from01.cf.expected @@ -0,0 +1,9 @@ + info: Created directory '/tmp/TEST.destination/subdir/.', mode 0700 + info: Copied file '/tmp/TEST.source/file-perms-644' to '/tmp/TEST.destination/file-perms-644.cfnew' (permissions preserved) + info: Moved '/tmp/TEST.destination/file-perms-644.cfnew' to '/tmp/TEST.destination/file-perms-644' + info: Regular file '/tmp/TEST.destination/file-perms-644' had permissions 0600, changed it to 0644 + info: Updated file '/tmp/TEST.destination/file-perms-644' from 'localhost:/tmp/TEST.source/file-perms-644' + info: Copied file '/tmp/TEST.source/subdir/subdir-file' to '/tmp/TEST.destination/subdir/subfile.cfnew' (permissions preserved) + info: Moved '/tmp/TEST.destination/subdir/subfile.cfnew' to '/tmp/TEST.destination/subdir/subfile' + info: Regular file '/tmp/TEST.destination/subdir/subfile' had permissions 0600, changed it to 0644 + info: Updated file '/tmp/TEST.destination/subdir/subfile' from 'localhost:/tmp/TEST.source/subdir/subdir-file' diff --git a/tests/acceptance/28_inform_testing/01_files/copy_from02.cf b/tests/acceptance/28_inform_testing/01_files/copy_from02.cf new file mode 100644 index 0000000000..d0b487be03 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/copy_from02.cf @@ -0,0 +1,44 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testroot)/TEST.source" + depth_search => recurse("inf"), + copy_from => example("$(this.promise_dirname)/test_files"); +} + +bundle agent main +{ + files: + "$(G.testroot)/TEST.destination/subdir/." + create => "true"; + "$(G.testroot)/TEST.destination/file-perms-644" + copy_from => example2("$(G.testroot)/TEST.source/file-perms-644"); + "$(G.testroot)/TEST.destination/subdir/subfile" + copy_from => example2("$(G.testroot)/TEST.source/subdir/subdir-file"); +} + +body copy_from example(from) +{ + source => "$(from)"; + compare => "digest"; + preserve => "true"; +} + +body copy_from example2(from) +{ + source => "$(from)"; + compare => "mtime"; + check_root => "true"; + copy_backup => "timestamp"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/copy_from02.cf.expected b/tests/acceptance/28_inform_testing/01_files/copy_from02.cf.expected new file mode 100644 index 0000000000..39c5c0b160 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/copy_from02.cf.expected @@ -0,0 +1,7 @@ + info: Created directory '/tmp/TEST.destination/subdir/.', mode 0700 + info: Copied file '/tmp/TEST.source/file-perms-644' to '/tmp/TEST.destination/file-perms-644.cfnew' (mode '600') + info: Moved '/tmp/TEST.destination/file-perms-644.cfnew' to '/tmp/TEST.destination/file-perms-644' + info: Updated file '/tmp/TEST.destination/file-perms-644' from 'localhost:/tmp/TEST.source/file-perms-644' + info: Copied file '/tmp/TEST.source/subdir/subdir-file' to '/tmp/TEST.destination/subdir/subfile.cfnew' (mode '600') + info: Moved '/tmp/TEST.destination/subdir/subfile.cfnew' to '/tmp/TEST.destination/subdir/subfile' + info: Updated file '/tmp/TEST.destination/subdir/subfile' from 'localhost:/tmp/TEST.source/subdir/subdir-file' diff --git a/tests/acceptance/28_inform_testing/01_files/copy_from03.cf b/tests/acceptance/28_inform_testing/01_files/copy_from03.cf new file mode 100644 index 0000000000..7e7f43035c --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/copy_from03.cf @@ -0,0 +1,52 @@ +body file control +{ + hpux.ia64:: + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; + !(hpux.ia64):: + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testroot)/TEST.source" + depth_search => recurse("inf"), + perms => system_owned("0644"), + copy_from => example("$(this.promise_dirname)/test_files"); + +} + +bundle agent main +{ + files: + "$(G.testroot)/TEST.destination/subdir/purgeme" + create => "true", + handle => "step_one"; + "$(G.testroot)/TEST.destination/file-perms-644" + copy_from => example("$(G.testroot)/TEST.source/file-perms-644"); + "$(G.testroot)/TEST.destination/subdir/." + depends_on => { "step_one" }, + depth_search => recurse("inf"), + move_obstructions => "true", + copy_from => example3("$(G.testroot)/TEST.source/subdir/."); +} + +body copy_from example(from) +{ + source => "$(from)"; + compare => "digest"; + preserve => "true"; +} + +body copy_from example3(from) +{ + source => "$(from)"; + linkcopy_patterns => { ".*644" }; + purge => "true"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/copy_from03.cf.expected b/tests/acceptance/28_inform_testing/01_files/copy_from03.cf.expected new file mode 100644 index 0000000000..b253a69ab4 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/copy_from03.cf.expected @@ -0,0 +1,10 @@ + info: Created directory for '/tmp/TEST.destination/subdir/purgeme' + info: Created file '/tmp/TEST.destination/subdir/purgeme', mode 0600 + info: Copied file '/tmp/TEST.source/file-perms-644' to '/tmp/TEST.destination/file-perms-644.cfnew' (permissions preserved) + info: Moved '/tmp/TEST.destination/file-perms-644.cfnew' to '/tmp/TEST.destination/file-perms-644' + info: Regular file '/tmp/TEST.destination/file-perms-644' had permissions 0600, changed it to 0644 + info: Updated file '/tmp/TEST.destination/file-perms-644' from 'localhost:/tmp/TEST.source/file-perms-644' + info: Copied file '/tmp/TEST.source/subdir/./subdir-file' to '/tmp/TEST.destination/subdir/./subdir-file.cfnew' (mode '600') + info: Moved '/tmp/TEST.destination/subdir/./subdir-file.cfnew' to '/tmp/TEST.destination/subdir/./subdir-file' + info: Updated file '/tmp/TEST.destination/subdir/./subdir-file' from 'localhost:/tmp/TEST.source/subdir/./subdir-file' + info: Purged '/tmp/TEST.destination/subdir/./purgeme' copy dest directory diff --git a/tests/acceptance/28_inform_testing/01_files/create.cf b/tests/acceptance/28_inform_testing/01_files/create.cf new file mode 100644 index 0000000000..f4a08afea6 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/create.cf @@ -0,0 +1,17 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + create => "true"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/create.cf.expected b/tests/acceptance/28_inform_testing/01_files/create.cf.expected new file mode 100644 index 0000000000..8be6838241 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/create.cf.expected @@ -0,0 +1 @@ + info: Created file '/tmp/TEST.cfengine', mode 0600 diff --git a/tests/acceptance/28_inform_testing/01_files/delete-dir.cf b/tests/acceptance/28_inform_testing/01_files/delete-dir.cf new file mode 100644 index 0000000000..42e56638df --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/delete-dir.cf @@ -0,0 +1,24 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testdir)/sub-directory/." + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testdir)/sub-directory/." + delete => init_delete; +} diff --git a/tests/acceptance/28_inform_testing/01_files/delete-dir.cf.expected b/tests/acceptance/28_inform_testing/01_files/delete-dir.cf.expected new file mode 100644 index 0000000000..2819230250 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/delete-dir.cf.expected @@ -0,0 +1 @@ + info: Deleted directory '/tmp/TESTDIR.cfengine/sub-directory' diff --git a/tests/acceptance/28_inform_testing/01_files/delete.cf b/tests/acceptance/28_inform_testing/01_files/delete.cf new file mode 100644 index 0000000000..6b8dea0542 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/delete.cf @@ -0,0 +1,24 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + delete => init_delete; +} diff --git a/tests/acceptance/28_inform_testing/01_files/delete.cf.expected b/tests/acceptance/28_inform_testing/01_files/delete.cf.expected new file mode 100644 index 0000000000..e78141aec5 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/delete.cf.expected @@ -0,0 +1 @@ + info: Deleted file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line01.cf b/tests/acceptance/28_inform_testing/01_files/edit_line01.cf new file mode 100644 index 0000000000..0501c8ff78 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line01.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => set_colon_field("me","6","/my/new/shell"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line01.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_line01.cf.expected new file mode 100644 index 0000000000..7f13f52543 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line01.cf.expected @@ -0,0 +1,3 @@ + info: Set field sub-value '/my/new/shell' in '/tmp/TEST.cfengine' + info: fields_edit promise 'me:.*' repaired + info: Edited file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line02.cf b/tests/acceptance/28_inform_testing/01_files/edit_line02.cf new file mode 100644 index 0000000000..a2c72b99fb --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line02.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => delete_lines_matching("foo 1"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line02.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_line02.cf.expected new file mode 100644 index 0000000000..90425b5457 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line02.cf.expected @@ -0,0 +1,3 @@ + info: Deleted the promised line 1 'foo 1' from /tmp/TEST.cfengine + info: delete_lines promise 'foo 1' repaired + info: Edited file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line03.cf b/tests/acceptance/28_inform_testing/01_files/edit_line03.cf new file mode 100644 index 0000000000..3f7c46de3a --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line03.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => insert_lines("amazing!"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line03.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_line03.cf.expected new file mode 100644 index 0000000000..fefda97d1e --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line03.cf.expected @@ -0,0 +1,3 @@ + info: Inserted the promised line 'amazing!' into '/tmp/TEST.cfengine' after locator + info: insert_lines promise 'amazing!' repaired + info: Edited file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line04.cf b/tests/acceptance/28_inform_testing/01_files/edit_line04.cf new file mode 100644 index 0000000000..d88a526e61 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line04.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => regex_replace("bar","BAR"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line04.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_line04.cf.expected new file mode 100644 index 0000000000..c003767151 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line04.cf.expected @@ -0,0 +1,3 @@ + info: Replaced pattern 'bar' in '/tmp/TEST.cfengine' + info: replace_patterns promise 'bar' repaired + info: Edited file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line05.cf b/tests/acceptance/28_inform_testing/01_files/edit_line05.cf new file mode 100644 index 0000000000..6e9ab0fec0 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line05.cf @@ -0,0 +1,49 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + template_method => "inline_mustache", + edit_template_string => "Hi {{{username}}}!", + template_data => '{ "username": "agent" }'; +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/01_files/edit_line05.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_line05.cf.expected new file mode 100644 index 0000000000..5cc567859b --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_line05.cf.expected @@ -0,0 +1 @@ + info: Updated rendering of '/tmp/TEST.cfengine' from mustache template 'inline' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml01.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml01.cf new file mode 100644 index 0000000000..39417b7a97 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml01.cf @@ -0,0 +1,45 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => build_xpath("/Server/Service/Engine/Host"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml build_xpath(xpath) +{ + build_xpath: + "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml01.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml01.cf.expected new file mode 100644 index 0000000000..fd8cf41977 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml01.cf.expected @@ -0,0 +1,3 @@ + info: Built XPath '/Server/Service/Engine/Host' into an empty XML document '/tmp/TEST.cfengine' + info: build_xpath promise '/Server/Service/Engine/Host' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml02.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml02.cf new file mode 100644 index 0000000000..9d885775cc --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml02.cf @@ -0,0 +1,57 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + vars: + "xml1" string => "y"; + "xml2" string => "text content"; + + files: + "$(G.testfile)" edit_xml => xml_insert_tree("$(xml1)", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_insert_tree(treestring, xpath) +{ + insert_tree: + '$(treestring)' select_xpath => "$(xpath)"; +} + diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml02.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml02.cf.expected new file mode 100644 index 0000000000..73f82d7424 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml02.cf.expected @@ -0,0 +1,3 @@ + info: Inserted tree 'y' at XPath '//Root' in XML document '/tmp/TEST.cfengine' + info: insert_tree promise 'y' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml03.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml03.cf new file mode 100644 index 0000000000..aef1bbb9dc --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml03.cf @@ -0,0 +1,57 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + vars: + "xml" string => "y"; + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath("$(xml)"), + create => "true"; +} + +bundle agent main +{ + vars: + "xml2" string => "text content"; + + files: + "$(G.testfile)" edit_xml => xml_insert_tree("$(xml2)", "//x"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_insert_tree(treestring, xpath) +{ + insert_tree: + '$(treestring)' select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml03.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml03.cf.expected new file mode 100644 index 0000000000..0249418250 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml03.cf.expected @@ -0,0 +1,3 @@ + info: Inserted tree 'text content' at XPath '//x' in XML document '/tmp/TEST.cfengine' + info: insert_tree promise 'text content' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml04.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml04.cf new file mode 100644 index 0000000000..b44c02c8e9 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml04.cf @@ -0,0 +1,53 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_set_value("foobar", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_set_value(value, xpath) +{ + set_text: + "$(value)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml04.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml04.cf.expected new file mode 100644 index 0000000000..a22ee85965 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml04.cf.expected @@ -0,0 +1,3 @@ + info: Set text 'foobar' at XPath '//Root' in XML document '/tmp/TEST.cfengine' + info: set_text promise 'foobar' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml05.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml05.cf new file mode 100644 index 0000000000..0c47a920ea --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml05.cf @@ -0,0 +1,56 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_set_attribute("my_attr", "my_value", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_set_attribute(attr, value, xpath) +{ + set_attribute: + "$(attr)" + attribute_value => "$(value)", + select_xpath => "$(xpath)"; + +} + diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml05.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml05.cf.expected new file mode 100644 index 0000000000..91037d98cb --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml05.cf.expected @@ -0,0 +1,3 @@ + info: Set attribute with name 'my_attr' to value 'my_value' at XPath '//Root' in XML document '/tmp/TEST.cfengine' + info: set_attribute promise 'my_attr' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml06.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml06.cf new file mode 100644 index 0000000000..3fa1da2630 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml06.cf @@ -0,0 +1,53 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_delete_attribute("foo", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_delete_attribute(attr, xpath) +{ + delete_attribute: + "$(attr)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml06.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml06.cf.expected new file mode 100644 index 0000000000..90a73aa444 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml06.cf.expected @@ -0,0 +1,3 @@ + info: Deleted attribute 'foo' at XPath '//Root' in the XML document '/tmp/TEST.cfengine' + info: delete_attribute promise 'foo' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml07.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml07.cf new file mode 100644 index 0000000000..7474433e40 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml07.cf @@ -0,0 +1,56 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + vars: + "xml" string => "text content"; + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath($(xml)), + create => "true"; +} + +bundle agent main +{ + + files: + "$(G.testfile)" edit_xml => xml_delete_text("text content", "//c"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_delete_text(match, xpath) +{ + delete_text: + "$(match)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml07.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml07.cf.expected new file mode 100644 index 0000000000..e946dd3a04 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml07.cf.expected @@ -0,0 +1,3 @@ + info: Deleted text 'text content' at XPath '//c' in the XML document '/tmp/TEST.cfengine' + info: delete_text promise 'text content' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml08.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml08.cf new file mode 100644 index 0000000000..87eda8e2e2 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml08.cf @@ -0,0 +1,53 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_insert_text("Tuesday", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_insert_text(text, xpath) +{ + insert_text: + "$(text)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml08.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml08.cf.expected new file mode 100644 index 0000000000..5f6760a2f7 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml08.cf.expected @@ -0,0 +1,3 @@ + info: Inserted text 'Tuesday' at XPath '//Root' in the XML document '/tmp/TEST.cfengine' + info: insert_text promise 'Tuesday' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml09.cf b/tests/acceptance/28_inform_testing/01_files/edit_xml09.cf new file mode 100644 index 0000000000..a4a8f9d4a3 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml09.cf @@ -0,0 +1,53 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath("text content"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_delete_tree("text content", "//b"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_delete_tree(subtree, xpath) +{ + delete_tree: + "$(subtree)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/edit_xml09.cf.expected b/tests/acceptance/28_inform_testing/01_files/edit_xml09.cf.expected new file mode 100644 index 0000000000..fdb0edb3b0 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/edit_xml09.cf.expected @@ -0,0 +1,3 @@ + info: Deleted tree 'text content' at XPath '//b' in XML document '/tmp/TEST.cfengine' + info: delete_tree promise 'text content' repaired + info: Edited xml file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/insert_lines_noop.cf b/tests/acceptance/28_inform_testing/01_files/insert_lines_noop.cf new file mode 100644 index 0000000000..c6fedbc36d --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/insert_lines_noop.cf @@ -0,0 +1,48 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + meta: + "description" -> { "CFE-3708" } + string => "empty+insert should not cause any changes being logged because the file is already as expected"; + + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines("this one line"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => insert_lines("this one line"), + create => "true", + edit_defaults => empty; +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/01_files/insert_lines_noop.cf.expected b/tests/acceptance/28_inform_testing/01_files/insert_lines_noop.cf.expected new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/28_inform_testing/01_files/perms.cf b/tests/acceptance/28_inform_testing/01_files/perms.cf new file mode 100644 index 0000000000..f2978c5226 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/perms.cf @@ -0,0 +1,49 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +body link_from link_info(source) +{ + source => "$(source)"; + link_type => "symlink"; +} + +bundle agent setup +{ + files: + "$(G.testdir)/foo" + create => "true", + comment => "A regular file"; + + "$(G.testdir)/bar" + create => "true", + comment => "A regular file symlink target"; + + "$(G.testdir)/baz" + link_from => link_info("$(G.testdir)/bar"), + comment => "A symbolic link to a regular file"; + + "$(G.testdir)/foobar/." + create => "true", + comment => "A directory"; +} + +bundle agent main +{ + files: + "$(G.testdir)/foo" + perms => m(777); + + "$(G.testdir)/baz" + perms => m(777); + + "$(G.testdir)/foobar" + perms => m(777); +} diff --git a/tests/acceptance/28_inform_testing/01_files/perms.cf.expected b/tests/acceptance/28_inform_testing/01_files/perms.cf.expected new file mode 100644 index 0000000000..c45c216d86 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/perms.cf.expected @@ -0,0 +1,2 @@ + info: Regular file '/tmp/TESTDIR.cfengine/foo' had permissions 0600, changed it to 0777 + info: Directory '/tmp/TESTDIR.cfengine/foobar' had permissions 0700, changed it to 0777 diff --git a/tests/acceptance/28_inform_testing/01_files/rename_newname.cf b/tests/acceptance/28_inform_testing/01_files/rename_newname.cf new file mode 100644 index 0000000000..ff61a41c0a --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/rename_newname.cf @@ -0,0 +1,31 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; + "$(G.testroot)/TEST.foobar" + delete => init_delete; +} + +bundle agent main +{ + files: + "$(G.testfile)" + rename => newname; +} + +body rename newname +{ + newname => "TEST.foobar"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/rename_newname.cf.expected b/tests/acceptance/28_inform_testing/01_files/rename_newname.cf.expected new file mode 100644 index 0000000000..fc86468280 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/rename_newname.cf.expected @@ -0,0 +1 @@ + info: Renamed file '/tmp/TEST.cfengine' to '/tmp/TEST.foobar' diff --git a/tests/acceptance/28_inform_testing/01_files/rename_rotate.cf b/tests/acceptance/28_inform_testing/01_files/rename_rotate.cf new file mode 100644 index 0000000000..c03cc5d838 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/rename_rotate.cf @@ -0,0 +1,29 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + rename => rotate; +} + +body rename rotate +{ + rotate => "4"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/rename_rotate.cf.expected b/tests/acceptance/28_inform_testing/01_files/rename_rotate.cf.expected new file mode 100644 index 0000000000..e2e01031ac --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/rename_rotate.cf.expected @@ -0,0 +1,2 @@ + info: Rotated file '/tmp/TEST.cfengine' in 4 fifo + info: Rotated file '/tmp/TEST.cfengine' in 4 fifo diff --git a/tests/acceptance/28_inform_testing/01_files/test_files/file-perms-644 b/tests/acceptance/28_inform_testing/01_files/test_files/file-perms-644 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/28_inform_testing/01_files/test_files/subdir/subdir-file b/tests/acceptance/28_inform_testing/01_files/test_files/subdir/subdir-file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/28_inform_testing/01_files/touch.cf b/tests/acceptance/28_inform_testing/01_files/touch.cf new file mode 100644 index 0000000000..3bacb09d78 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/touch.cf @@ -0,0 +1,17 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + touch => "true"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/touch.cf.expected b/tests/acceptance/28_inform_testing/01_files/touch.cf.expected new file mode 100644 index 0000000000..419cbabf17 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/touch.cf.expected @@ -0,0 +1,2 @@ + info: Created file '/tmp/TEST.cfengine', mode 0600 + info: Touched (updated time stamps) for path '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/01_files/transformer.cf b/tests/acceptance/28_inform_testing/01_files/transformer.cf new file mode 100644 index 0000000000..d6435a4529 --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/transformer.cf @@ -0,0 +1,31 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true", + edit_line => insert_lines(" +zebra +apple +dog +cat +banana +"); +} + +bundle agent main +{ + files: + "$(G.testfile)" + transformer => "$(G.gzip) $(G.testfile)"; +} diff --git a/tests/acceptance/28_inform_testing/01_files/transformer.cf.expected b/tests/acceptance/28_inform_testing/01_files/transformer.cf.expected new file mode 100644 index 0000000000..1f30c60c6b --- /dev/null +++ b/tests/acceptance/28_inform_testing/01_files/transformer.cf.expected @@ -0,0 +1,3 @@ + info: Transforming '/tmp/TEST.cfengine' with '/bin/gzip /tmp/TEST.cfengine' + info: Transformed '/tmp/TEST.cfengine' with '/bin/gzip /tmp/TEST.cfengine' + info: Transformer '/tmp/TEST.cfengine' => '/bin/gzip /tmp/TEST.cfengine' seemed to work ok diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/changes01.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/changes01.cf new file mode 100644 index 0000000000..2445ea3ccd --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/changes01.cf @@ -0,0 +1,31 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + changes => example; +} + +body changes example +{ + hash => "best"; + update_hashes => "true"; + report_changes => "content"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/changes01.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/changes01.cf.expected new file mode 100644 index 0000000000..f4621c6941 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/changes01.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, changes => example + info: Detected changes for '/tmp/TEST.cfengine', md5 hash 'MD5=d41d8cd98f00b204e9800998ecf8427e', sha1 hash 'SHA=da39a3ee5e6b4b0d3255bfef95601890afd80709' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/changes02.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/changes02.cf new file mode 100644 index 0000000000..4babb946a5 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/changes02.cf @@ -0,0 +1,24 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => insert_lines("foobar"); +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/changes02.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/changes02.cf.expected new file mode 100644 index 0000000000..22e21495fb --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/changes02.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_line => insert_lines('foobar') + info: Inserted line 'foobar' into '/tmp/TEST.cfengine' at end of file diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from01.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from01.cf new file mode 100644 index 0000000000..ef726d3917 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from01.cf @@ -0,0 +1,34 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testroot)/TEST.source" + depth_search => recurse("inf"), + copy_from => example("$(this.promise_dirname)/test_files"); +} + +bundle agent main +{ + files: + "$(G.testroot)/TEST.destination" + depth_search => recurse("inf"), + copy_from => example("$(G.testroot)/TEST.source"); +} + +body copy_from example(from) +{ + source => "$(from)"; + compare => "digest"; + preserve => "true"; +} + diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from01.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from01.cf.expected new file mode 100644 index 0000000000..252827bcc6 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from01.cf.expected @@ -0,0 +1,3 @@ + info: files promise '/tmp/TEST.destination' repaired, copy_from => example('/tmp/TEST.source'), depth_search => recurse("inf") + info: Replaced file '/tmp/TEST.destination/subdir/subdir-file' from 'localhost:/tmp/TEST.source/subdir/subdir-file' + info: Replaced file '/tmp/TEST.destination/file-perms-644' from 'localhost:/tmp/TEST.source/file-perms-644' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from02.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from02.cf new file mode 100644 index 0000000000..d244bcfa81 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from02.cf @@ -0,0 +1,41 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testroot)/TEST.source" + depth_search => recurse("inf"), + copy_from => example("$(this.promise_dirname)/test_files"); +} + +bundle agent main +{ + files: + "$(G.testroot)/TEST.destination" + depth_search => recurse("inf"), + copy_from => example2("$(G.testroot)/TEST.source"); +} + +body copy_from example(from) +{ + source => "$(from)"; + compare => "digest"; + preserve => "true"; +} + +body copy_from example2(from) +{ + source => "$(from)"; + compare => "mtime"; + check_root => "true"; + copy_backup => "timestamp"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from02.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from02.cf.expected new file mode 100644 index 0000000000..27676041c1 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from02.cf.expected @@ -0,0 +1,3 @@ + info: files promise '/tmp/TEST.destination' repaired, depth_search => recurse("inf"), copy_from => example2("$(G.testroot)/TEST.source") + info: Replaced file '/tmp/TEST.destination/subdir/subdir-file' from 'localhost:/tmp/TEST.source/subdir/subdir-file' + info: Replaced file '/tmp/TEST.destination/file-perms-644' from 'localhost:/tmp/TEST.source/file-perms-644' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from03.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from03.cf new file mode 100644 index 0000000000..992bb63000 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from03.cf @@ -0,0 +1,45 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testroot)/TEST.source" + depth_search => recurse("inf"), + copy_from => example("$(this.promise_dirname)/test_files"); +} + +bundle agent main +{ + files: + "$(G.testroot)/TEST.destination/purgeme" + create => "true", + handle => "step_one"; + "$(G.testroot)/TEST.destination" + depends_on => { "step_one" }, + depth_search => recurse("inf"), + move_obstructions => "true", + copy_from => example3("$(G.testroot)/TEST.source"); +} + +body copy_from example(from) +{ + source => "$(from)"; + compare => "digest"; + preserve => "true"; +} + +body copy_from example3(from) +{ + source => "$(from)"; + linkcopy_patterns => { ".*644" }; + purge => "true"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from03.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from03.cf.expected new file mode 100644 index 0000000000..8b4418d5a8 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/copy_from03.cf.expected @@ -0,0 +1,5 @@ + info: files promise '/tmp/TEST.destination/purgeme' repaired, create => "true", handle => "step_one" + info: Created file '/tmp/TEST.destination/purgeme', mode 0600 + info: files promise '/tmp/TEST.destination' repaired, depth_search => recurse("inf"), move_obstructions => "true", copy_from => example3("$(G.testroot)/TEST.source") + info: Purged '/tmp/TEST.destination/purgeme' + info: Replaced '/tmp/TEST.destination/subdir/subdir-file' from 'localhost:/tmp/TEST.source/subdir/subdir-file' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/create.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/create.cf new file mode 100644 index 0000000000..b5b335b00b --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/create.cf @@ -0,0 +1,17 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + create => "true"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/create.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/create.cf.expected new file mode 100644 index 0000000000..f06da5a3ec --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/create.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, create => "true" + info: Created file '/tmp/TEST.cfengine', mode 0600 diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/delete.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/delete.cf new file mode 100644 index 0000000000..f8111dfafe --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/delete.cf @@ -0,0 +1,24 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + delete => init_delete; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/delete.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/delete.cf.expected new file mode 100644 index 0000000000..4c3c953b04 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/delete.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, delete => init_delete + info: Deleted file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line01.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line01.cf new file mode 100644 index 0000000000..3e07d0c1fa --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line01.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => set_colon_field("me","6","/my/new/shell"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line01.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line01.cf.expected new file mode 100644 index 0000000000..5cfce86f19 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line01.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_line => set_colon_field("me","6","/my/new/shell"); + info: Set field 6 with separator ':' to value '/my/new/shell' on line 4 of '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line02.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line02.cf new file mode 100644 index 0000000000..2b0cf5758b --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line02.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => delete_lines_matching("foo 1"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line02.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line02.cf.expected new file mode 100644 index 0000000000..01957d0b61 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line02.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_line => delete_lines_matching("foo 1"); + info: Deleted line 'foo 1' from '/tmp/TEST.cfengine' at line 1 diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line03.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line03.cf new file mode 100644 index 0000000000..6754bf98de --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line03.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => insert_lines("amazing!"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line03.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line03.cf.expected new file mode 100644 index 0000000000..d860a1dc87 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line03.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_line => insert_lines("amazing!"); + info: Inserted line 'amazing!' into '/tmp/TEST.cfengine' at end of file diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line04.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line04.cf new file mode 100644 index 0000000000..5099e850d7 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line04.cf @@ -0,0 +1,47 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + edit_line => regex_replace("bar","BAR"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line04.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line04.cf.expected new file mode 100644 index 0000000000..08f3ba896d --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line04.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_line => regex_replace("bar","BAR"); + info: Replaced pattern 'bar' in '/tmp/TEST.cfengine' at line 2 diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line05.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line05.cf new file mode 100644 index 0000000000..2152aef491 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line05.cf @@ -0,0 +1,49 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + edit_line => insert_lines(" +foo 1 +bar 2 +baz 3 +me:x:1001:1001::/home/me:/bin/sh +"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + template_method => "inline_mustache", + edit_template_string => "Hi {{{username}}}!", + template_data => '{ "username": "agent" }'; +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line05.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line05.cf.expected new file mode 100644 index 0000000000..f865e17aa8 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_line05.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, template_method => "inline_mustache", edit_template_string => "Hi {{{username}}}!", template_data => '{ "username": "agent" }' + info: Updated rendering of '/tmp/TEST.cfengine' from mustache template 'Hi {{{username}}}!' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml01.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml01.cf new file mode 100644 index 0000000000..066fb6b159 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml01.cf @@ -0,0 +1,45 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => build_xpath("/Server/Service/Engine/Host"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml build_xpath(xpath) +{ + build_xpath: + "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml01.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml01.cf.expected new file mode 100644 index 0000000000..51016c243b --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml01.cf.expected @@ -0,0 +1,3 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_xml => build_xpath("/Server/Service/Engine/Host"); + info: Built XPath '/Server/Service/Engine/Host' into an empty XML document +'/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml02.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml02.cf new file mode 100644 index 0000000000..0196431d46 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml02.cf @@ -0,0 +1,57 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + vars: + "xml1" string => "y"; + "xml2" string => "text content"; + + files: + "$(G.testfile)" edit_xml => xml_insert_tree("$(xml1)", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_insert_tree(treestring, xpath) +{ + insert_tree: + '$(treestring)' select_xpath => "$(xpath)"; +} + diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml02.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml02.cf.expected new file mode 100644 index 0000000000..4df9d25f65 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml02.cf.expected @@ -0,0 +1,2 @@ + info: Inserted tree 'y' at XPath '//Root' in XML document'/tmp/TEST.cfengine' + info: files promise '/tmp/TEST.cfengine' repaired, edit_xml => xml_insert_tree("$(xml1)", "//Root") diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml03.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml03.cf new file mode 100644 index 0000000000..4944bcf0da --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml03.cf @@ -0,0 +1,57 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + vars: + "xml" string => "y"; + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath("$(xml)"), + create => "true"; +} + +bundle agent main +{ + vars: + "xml2" string => "text content"; + + files: + "$(G.testfile)" edit_xml => xml_insert_tree("$(xml2)", "//x"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_insert_tree(treestring, xpath) +{ + insert_tree: + '$(treestring)' select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml03.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml03.cf.expected new file mode 100644 index 0000000000..57da9dc888 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml03.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, xml_insert_tree("$(xml2)", "//x") + info: Inserted tree 'text content' at XPath '//x' in XML document '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml04.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml04.cf new file mode 100644 index 0000000000..c1b807c5c5 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml04.cf @@ -0,0 +1,53 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_set_value("foobar", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_set_value(value, xpath) +{ + set_text: + "$(value)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml04.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml04.cf.expected new file mode 100644 index 0000000000..0afb5d53ce --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml04.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_xml => xml_set_value("foobar", "//Root") + info: Set text 'foobar' at XPath '//Root' in XML document '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml05.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml05.cf new file mode 100644 index 0000000000..76360e5a99 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml05.cf @@ -0,0 +1,56 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_set_attribute("my_attr", "my_value", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_set_attribute(attr, value, xpath) +{ + set_attribute: + "$(attr)" + attribute_value => "$(value)", + select_xpath => "$(xpath)"; + +} + diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml05.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml05.cf.expected new file mode 100644 index 0000000000..6dd6a90779 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml05.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_xml => xml_set_attribute("my_attr", "my_value", "//Root") + info: Set attribute with name 'my_attr' to value 'my_value' at XPath '//Root' in XML document '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml06.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml06.cf new file mode 100644 index 0000000000..eed4d1b99a --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml06.cf @@ -0,0 +1,53 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_delete_attribute("foo", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_delete_attribute(attr, xpath) +{ + delete_attribute: + "$(attr)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml06.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml06.cf.expected new file mode 100644 index 0000000000..105258e494 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml06.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_xml => xml_delete_attribute("foo", "//Root") + info: Deleted attribute 'foo' at XPath '//Root' in the XML document '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml07.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml07.cf new file mode 100644 index 0000000000..457c27cd46 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml07.cf @@ -0,0 +1,56 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + vars: + "xml" string => "text content"; + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath($(xml)), + create => "true"; +} + +bundle agent main +{ + + files: + "$(G.testfile)" edit_xml => xml_delete_text("text content", "//c"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_delete_text(match, xpath) +{ + delete_text: + "$(match)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml07.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml07.cf.expected new file mode 100644 index 0000000000..640e09ad1d --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml07.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_xml => xml_delete_text("text content", "//c") + info: Deleted text 'text content' at XPath '//c' in the XML document '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml08.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml08.cf new file mode 100644 index 0000000000..207bde9790 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml08.cf @@ -0,0 +1,53 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath(""), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_insert_text("Tuesday", "//Root"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_insert_text(text, xpath) +{ + insert_text: + "$(text)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml08.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml08.cf.expected new file mode 100644 index 0000000000..6292f7fa39 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml08.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_xml => xml_insert_text("Tuesday", "//Root") + info: Inserted text 'Tuesday' at XPath '//Root' in the XML document '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml09.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml09.cf new file mode 100644 index 0000000000..cdec05d839 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml09.cf @@ -0,0 +1,53 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + + +bundle agent setup +{ + + files: + "$(G.testfile)" edit_xml => xml_insert_tree_nopath("text content"), + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" edit_xml => xml_delete_tree("text content", "//b"); +} + +bundle agent teardown +{ + reports: + "Edited file has contents: " + printfile => my_cat( $(G.testfile) ); +} + +body printfile my_cat(file) +# @brief Report the contents of a file +# @param file The full path of the file to report +{ + file_to_print => "$(file)"; + number_of_lines => "inf"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} + +bundle edit_xml xml_delete_tree(subtree, xpath) +{ + delete_tree: + "$(subtree)" + select_xpath => "$(xpath)"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml09.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml09.cf.expected new file mode 100644 index 0000000000..885a888ea6 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/edit_xml09.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, edit_xml => xml_delete_tree("text content", "//b") + info: Deleted tree 'text content' at XPath '//b' in XML document '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/perms.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/perms.cf new file mode 100644 index 0000000000..1dda939581 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/perms.cf @@ -0,0 +1,24 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + perms => m(000); +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/perms.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/perms.cf.expected new file mode 100644 index 0000000000..51af59987e --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/perms.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, perms => m(000) + info: Changed permissions of '/tmp/TEST.cfengine' from 0600 to 0000 diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_newname.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_newname.cf new file mode 100644 index 0000000000..33d37d68ca --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_newname.cf @@ -0,0 +1,31 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; + "$(G.testroot)/TEST.foobar" + delete => init_delete; +} + +bundle agent main +{ + files: + "$(G.testfile)" + rename => newname; +} + +body rename newname +{ + newname => "TEST.foobar"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_newname.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_newname.cf.expected new file mode 100644 index 0000000000..c607ec4016 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_newname.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, rename => newname + info: Renamed file '/tmp/TEST.cfengine' to 'TEST.foobar' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_rotate.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_rotate.cf new file mode 100644 index 0000000000..c4e91b7a9e --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_rotate.cf @@ -0,0 +1,29 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + rename => rotate; +} + +body rename rotate +{ + rotate => "4"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_rotate.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_rotate.cf.expected new file mode 100644 index 0000000000..b6c35d3f9a --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/rename_rotate.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, rename => rotate + info: Rotated file '/tmp/TEST.cfengine' in 4 fifo to '/tmp/TEST.cfengine.1' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/test_files/file-perms-644 b/tests/acceptance/28_inform_testing/02_soft_fail_files/test_files/file-perms-644 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/test_files/subdir/subdir-file b/tests/acceptance/28_inform_testing/02_soft_fail_files/test_files/subdir/subdir-file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/touch.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/touch.cf new file mode 100644 index 0000000000..beeecc3059 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/touch.cf @@ -0,0 +1,17 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent main +{ + files: + "$(G.testfile)" + touch => "true"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/touch.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/touch.cf.expected new file mode 100644 index 0000000000..afb5892340 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/touch.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, touch => "true" + info: Created and Touched (updated time stamps) file '/tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/transformer.cf b/tests/acceptance/28_inform_testing/02_soft_fail_files/transformer.cf new file mode 100644 index 0000000000..5a5487b8a3 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/transformer.cf @@ -0,0 +1,31 @@ +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common_soft_fail.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +bundle agent setup +{ + files: + "$(G.testfile)" + create => "true", + edit_line => insert_lines(" +zebra +apple +dog +cat +banana +"); +} + +bundle agent main +{ + files: + "$(G.testfile)" + transformer => "$(G.gzip) $(G.testfile)"; +} diff --git a/tests/acceptance/28_inform_testing/02_soft_fail_files/transformer.cf.expected b/tests/acceptance/28_inform_testing/02_soft_fail_files/transformer.cf.expected new file mode 100644 index 0000000000..e6c8cef8a5 --- /dev/null +++ b/tests/acceptance/28_inform_testing/02_soft_fail_files/transformer.cf.expected @@ -0,0 +1,2 @@ + info: files promise '/tmp/TEST.cfengine' repaired, transformer => "$(G.gzip) $(G.testfile)" + info: Transformed '/tmp/TEST.cfengine' with '/bin/bzip /tmp/TEST.cfengine' diff --git a/tests/acceptance/28_inform_testing/README.md b/tests/acceptance/28_inform_testing/README.md new file mode 100644 index 0000000000..5559f29b83 --- /dev/null +++ b/tests/acceptance/28_inform_testing/README.md @@ -0,0 +1,47 @@ +==================== +Inform Logging Tests +==================== + +These tests turn the model upside down a bit to make things easier. + +Two bits are needed at the top of each .cf test file: + +```cf3 +body file control +{ + inputs => { "$(sys.policy_entry_dirname)/../common.cf.sub" }; +} + +bundle common testcase +{ + vars: + "filename" string => "$(this.promise_filename)"; +} + +``` + +This is the boiler-plate to drive the tests. + +The framework automatically cleans up `$(G.testfile)` and `$(G.testdir)`. + +For the simplest case you can just include a `main` bundle to do the operation you want to perform. + +```cf3 +bundle agent main +{ + files: + "$(G.testfile)" + create => "true"; +} +``` + +And when the test is run it will generate a `.actual` and check it against +a `.expected`. + +There are temporary files created during the test involving the results. +If the test fails, these files will be kept, if the test passes, they will be removed. + + +The little framework also supports a `setup` and `teardown` optional bundle for things you might need in that regard. + +Next to common.cf.sub there is a shell script called accept-actual.sh which will mv the .actual files to .expected and add them via `git`. diff --git a/tests/acceptance/28_inform_testing/accept-actual.sh b/tests/acceptance/28_inform_testing/accept-actual.sh new file mode 100755 index 0000000000..25cca2f754 --- /dev/null +++ b/tests/acceptance/28_inform_testing/accept-actual.sh @@ -0,0 +1,2 @@ +for f in *.actual; do mv $f $(basename $f .actual).expected; done +git add *.expected diff --git a/tests/acceptance/28_inform_testing/common.cf.sub b/tests/acceptance/28_inform_testing/common.cf.sub new file mode 100644 index 0000000000..2d031bf945 --- /dev/null +++ b/tests/acceptance/28_inform_testing/common.cf.sub @@ -0,0 +1,96 @@ +bundle common inform_tests +{ + vars: + "inputs" slist => { "$(this.promise_dirname)/../default.cf.sub" }; +} + +body common control +{ + inputs => { "@(inform_tests.inputs)" }; + version => "1.0"; + + # RUNDIRECT is used later to only run the actual test code with cf-agent + !RUNDIRECT:: + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + classes: + "expected_file_exists" expression => fileexists("$(testcase.filename).expected"), + scope => "namespace"; + "has_setup" expression => isgreaterthan(length(bundlesmatching("default:setup")), "0"), + scope => "namespace"; + "has_teardown" expression => isgreaterthan(length(bundlesmatching("default:teardown")), "0"), + scope => "namespace"; + + files: + "$(G.testfile)" + delete => init_delete, + handle => "file_deleted"; + "$(G.testdir)" + delete => init_delete, + handle => "dir_deleted"; + + methods: + has_setup:: + "setup" + depends_on => { "dir_deleted", "file_deleted" }; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10406" }, + if => not( strcmp( "insert_lines_noop.cf", "$(sys.policy_entry_basename)" ) ); + vars: + "cf_agent" string => ifelse(isvariable("sys.cf_agent"), "$(sys.cf_agent)", "/var/cfengine/bin/cf-agent"); + "filter_list" slist => { " -e '/filestat bailing/d' ", + " -e s,$(G.testroot),/tmp,g ", + " -e s,/usr/local/bin/gzip,/bin/gzip, "}; + "filters" string => join("", filter_list); + commands: + # NOTE: easy debug, if you change `>` to `|tee` in all below then you can see results in the execution log + "$(cf_agent) -D RUNDIRECT -KIf $(testcase.filename) > $(testcase.filename).log" + contain => useshell; + "$(G.sed) $(filters) $(testcase.filename).log > $(testcase.filename).actual" + contain => useshell; +} + +body contain useshell +{ + useshell => "useshell"; +} + +####################################################### + +bundle agent check +{ + methods: + has_teardown:: + "teardown"; # optional tear down step contained in the test.cf file + expected_file_exists:: + "any" usebundle => dcs_if_diff("$(testcase.filename).actual", "$(testcase.filename).expected", "ok", "not_ok"); + + files: + ok:: + "$(testcase.filename).(log|actual)" + delete => tidy; + + reports: + ok:: + "$(testcase.filename) Pass"; + !ok:: + "$(testcase.filename) FAIL"; +} diff --git a/tests/acceptance/28_inform_testing/common_soft_fail.cf.sub b/tests/acceptance/28_inform_testing/common_soft_fail.cf.sub new file mode 100644 index 0000000000..8074a446ca --- /dev/null +++ b/tests/acceptance/28_inform_testing/common_soft_fail.cf.sub @@ -0,0 +1,93 @@ +bundle common inform_tests +{ + vars: + "inputs" slist => { "$(this.promise_dirname)/../default.cf.sub" }; +} + +body common control +{ + inputs => { "@(inform_tests.inputs)" }; + version => "1.0"; + + # RUNDIRECT is used later to only run the actual test code with cf-agent + !RUNDIRECT:: + bundlesequence => { default("$(this.promise_filename)") }; +} + +####################################################### + +bundle agent init +{ + classes: + "expected_file_exists" expression => fileexists("$(testcase.filename).expected"), + scope => "namespace"; + "has_setup" expression => isgreaterthan(length(bundlesmatching("default:setup")), "0"), + scope => "namespace"; + "has_teardown" expression => isgreaterthan(length(bundlesmatching("default:teardown")), "0"), + scope => "namespace"; + + files: + "$(G.testfile)" + delete => init_delete, + handle => "file_deleted"; + "$(G.testdir)" + delete => init_delete, + handle => "dir_deleted"; + + methods: + has_setup:: + "setup" + depends_on => { "dir_deleted", "file_deleted" }; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +bundle agent test +{ + meta: + "test_soft_fail" + string => "any", + meta => { "ENT-3787" }; + vars: + "cf_agent" string => ifelse(isvariable("sys.cf_agent"), "$(sys.cf_agent)", "/var/cfengine/bin/cf-agent"); + commands: + # NOTE: easy debug, if you change `>` to `|tee` in all below then you can see results in the execution log + "$(cf_agent) -D RUNDIRECT -KIf $(testcase.filename) > $(testcase.filename).log" + contain => useshell; + "$(G.sed) 's,$(G.testroot),/tmp,g' $(testcase.filename).log > $(testcase.filename).actual" + contain => useshell; +} + +body contain useshell +{ + useshell => "useshell"; +} + +####################################################### + +bundle agent check +{ + methods: + has_teardown:: + "teardown"; # optional tear down step contained in the test.cf file + expected_file_exists:: + "any" usebundle => dcs_if_diff("$(testcase.filename).actual", "$(testcase.filename).expected", "ok", "not_ok"); + + files: + # delete if not ok, since we expect these tests to fail + !ok:: + "$(testcase.filename).(log|sorted|actual)" + delete => tidy; + + reports: + ok:: + "$(testcase.filename) Pass"; + !ok:: + "$(testcase.filename) FAIL"; +} diff --git a/tests/acceptance/29_simulate_mode/diff_mode.cf b/tests/acceptance/29_simulate_mode/diff_mode.cf new file mode 100644 index 0000000000..1d031b2e80 --- /dev/null +++ b/tests/acceptance/29_simulate_mode/diff_mode.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../default.cf.sub", + "./prepare_files_for_simulate_tests.cf.sub", + "./normalize_agent_output.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle agent init +{ + methods: + "prepare_files_for_simulate_tests"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "(solaris|aix|hpux|windows)", + meta => { "ENT-6540,ENT-10254" }; + # ENT-6540 exotics fail to delete chroot + # ENT-10254 tests fail on Windows due to CRLF + + "description" -> { "ENT-5302" } + string => "Test that files promises in --simulate=diff mode produce proper output and only make changes in chroot"; + + commands: + # add --verbose here and look at the .actual log for debugging sub policy runs + "$(sys.cf_agent) -Kf $(this.promise_dirname)$(const.dirsep)promises.cf.sub --simulate=diff > $(this.promise_filename).temp 2>&1" + contain => in_shell, + comment => "Run sub policy in manifest mode and capture output to $(this.promise_filename).actual file."; +} + +bundle agent check +{ + methods: + "normalize_agent_results" usebundle => normalize_agent_results("$(this.promise_filename).temp", + "$(this.promise_filename).actual"); + "check" usebundle => dcs_check_diff("$(this.promise_filename).actual", + "$(this.promise_filename).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/29_simulate_mode/diff_mode.cf.expected b/tests/acceptance/29_simulate_mode/diff_mode.cf.expected new file mode 100644 index 0000000000..df7344380f --- /dev/null +++ b/tests/acceptance/29_simulate_mode/diff_mode.cf.expected @@ -0,0 +1,124 @@ + warning: All changes in files will be made in the 'WORKDIR/state/PID.changes' chroot +=========================================================================== +'WORKDIR/tmp/rename-newname' is the new name of 'WORKDIR/tmp/rename-me' +=========================================================================== +'WORKDIR/tmp/source-file' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +These are my source file contents. +\no newline at the end of file +=========================================================================== +'WORKDIR/tmp/create-true' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +=========================================================================== +'WORKDIR/tmp/insert-lines' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +foobar +=========================================================================== +'WORKDIR/tmp/SUBDIR' is a directory +Size: SIZE +Access: (0700/rwx------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Directory contents: +=========================================================================== +'WORKDIR/tmp/copy-from' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +These are my source file contents. +\no newline at the end of file +=========================================================================== +'WORKDIR/tmp/delete-me' no longer exists +=========================================================================== +'WORKDIR/tmp/sub-dir/./sub-file' no longer exists +=========================================================================== +Only in WORKDIR/tmp/sub-dir/.: sub-file +=========================================================================== +--- original WORKDIR/tmp/set-colon-field ++++ changed WORKDIR/tmp/set-colon-field +@@ -1 +1 @@ +-me:x:1001:1001::/home/me:/bin/sh +\ No newline at end of file ++me:x:1001:1001::/my/new/shell:/bin/sh +=========================================================================== +--- original WORKDIR/tmp/delete-lines-matching ++++ changed WORKDIR/tmp/delete-lines-matching +@@ -1,5 +1,4 @@ + +-foo 1 + bar 2 + baz 3 + me:x:101:doo +=========================================================================== +--- original WORKDIR/tmp/regex-replace ++++ changed WORKDIR/tmp/regex-replace +@@ -1 +1 @@ +-I dare you to change my policy foo +\ No newline at end of file ++I dare you to change my policy FOO! +=========================================================================== +--- original WORKDIR/tmp/edit-template-string ++++ changed WORKDIR/tmp/edit-template-string +@@ -1 +1 @@ +-From a particular point of view +\ No newline at end of file ++Darth Vader killed your father +\ No newline at end of file +=========================================================================== +--- original WORKDIR/tmp/build-xpath ++++ changed WORKDIR/tmp/build-xpath +@@ -0,0 +1,2 @@ ++ ++ +=========================================================================== +--- original WORKDIR/tmp/xml-insert-tree ++++ changed WORKDIR/tmp/xml-insert-tree +@@ -1,2 +1,2 @@ + +- ++y +=========================================================================== +=========================================================================== +'WORKDIR/tmp/transformer' no longer exists +=========================================================================== +'WORKDIR/tmp/hardlink' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +=========================================================================== +'WORKDIR/tmp/link' is a symbolic link +Size: SIZE +Access: (0777/rwxrwxrwx) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Target: 'WORKDIR/tmp/already-created' diff --git a/tests/acceptance/29_simulate_mode/manifest_full_mode.cf b/tests/acceptance/29_simulate_mode/manifest_full_mode.cf new file mode 100644 index 0000000000..c4906d5406 --- /dev/null +++ b/tests/acceptance/29_simulate_mode/manifest_full_mode.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../default.cf.sub", + "./prepare_files_for_simulate_tests.cf.sub", + "./normalize_agent_output.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle agent init +{ + methods: + "prepare_files_for_simulate_tests"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "(solaris|aix|hpux|windows)", + meta => { "ENT-6540,ENT-10254" }; + # ENT-6540 exotics fail to delete chroot + # ENT-10254 tests fail on Windows due to CRLF + + "description" -> { "ENT-5301" } + string => "Test that files promises in --simulate=manifest-full mode produce proper output and only make changes in chroot"; + + commands: + # add --verbose here and look at the .actual log for debugging sub policy runs + "$(sys.cf_agent) -Kf $(this.promise_dirname)$(const.dirsep)promises.cf.sub --simulate=manifest-full > $(this.promise_filename).temp 2>&1" + contain => in_shell, + comment => "Run sub policy in manifest mode and capture output to $(this.promise_filename).actual file."; +} + +bundle agent check +{ + methods: + "normalize_agent_results" usebundle => normalize_agent_results("$(this.promise_filename).temp", + "$(this.promise_filename).actual"); + "check" usebundle => dcs_check_diff("$(this.promise_filename).actual", + "$(this.promise_filename).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/29_simulate_mode/manifest_full_mode.cf.expected b/tests/acceptance/29_simulate_mode/manifest_full_mode.cf.expected new file mode 100644 index 0000000000..3e220c35ab --- /dev/null +++ b/tests/acceptance/29_simulate_mode/manifest_full_mode.cf.expected @@ -0,0 +1,170 @@ + warning: All changes in files will be made in the 'WORKDIR/state/PID.changes' chroot +=========================================================================== +'WORKDIR/tmp/rename-newname' is the new name of 'WORKDIR/tmp/rename-me' +=========================================================================== +'WORKDIR/tmp/source-file' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +These are my source file contents. +\no newline at the end of file +=========================================================================== +'WORKDIR/tmp/create-true' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +=========================================================================== +'WORKDIR/tmp/insert-lines' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +foobar +=========================================================================== +'WORKDIR/tmp/SUBDIR' is a directory +Size: SIZE +Access: (0700/rwx------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Directory contents: +=========================================================================== +'WORKDIR/tmp/copy-from' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +These are my source file contents. +\no newline at the end of file +=========================================================================== +'WORKDIR/tmp/delete-me' no longer exists +=========================================================================== +'WORKDIR/tmp/sub-dir/./sub-file' no longer exists +=========================================================================== +'WORKDIR/tmp/sub-dir/.' is a directory +Size: SIZE +Access: (0700/rwx------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Directory contents: +=========================================================================== +'WORKDIR/tmp/set-colon-field' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +me:x:1001:1001::/my/new/shell:/bin/sh +=========================================================================== +'WORKDIR/tmp/delete-lines-matching' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: + +bar 2 +baz 3 +me:x:101:doo +=========================================================================== +'WORKDIR/tmp/regex-replace' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +I dare you to change my policy FOO! +=========================================================================== +'WORKDIR/tmp/edit-template-string' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +Darth Vader killed your father +\no newline at the end of file +=========================================================================== +'WORKDIR/tmp/build-xpath' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: + + +=========================================================================== +'WORKDIR/tmp/xml-insert-tree' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: + +y +=========================================================================== +'WORKDIR/tmp/perms' is a regular file +Size: SIZE +Access: (0000/---------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +=========================================================================== +'WORKDIR/tmp/transformer' no longer exists +=========================================================================== +'WORKDIR/tmp/hardlink' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +=========================================================================== +'WORKDIR/tmp/link' is a symbolic link +Size: SIZE +Access: (0777/rwxrwxrwx) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Target: 'WORKDIR/tmp/already-created' +=========================================================================== +'WORKDIR/tmp/already-created' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: diff --git a/tests/acceptance/29_simulate_mode/manifest_mode.cf b/tests/acceptance/29_simulate_mode/manifest_mode.cf new file mode 100644 index 0000000000..dda53e7f42 --- /dev/null +++ b/tests/acceptance/29_simulate_mode/manifest_mode.cf @@ -0,0 +1,45 @@ +body common control +{ + inputs => { + "../default.cf.sub", + "./prepare_files_for_simulate_tests.cf.sub", + "./normalize_agent_output.cf.sub" + }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + + +bundle agent init +{ + methods: + "prepare_files_for_simulate_tests"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "(solaris|aix|hpux|windows)", + meta => { "ENT-6540,ENT-10254" }; + # ENT-6540 exotics fail to delete chroot + # ENT-10254 tests fail on Windows due to CRLF + + "description" -> { "ENT-5301" } + string => "Test that files promises in --simulate=manifest mode produce proper output and only make changes in chroot"; + + commands: + # add --verbose here and look at the .actual log for debugging sub policy runs + "$(sys.cf_agent) -Kf $(this.promise_dirname)$(const.dirsep)promises.cf.sub --simulate=manifest > $(this.promise_filename).temp 2>&1" + contain => in_shell, + comment => "Run sub policy in manifest mode and capture output to $(this.promise_filename).actual file."; +} + +bundle agent check +{ + methods: + "normalize_agent_results" usebundle => normalize_agent_results("$(this.promise_filename).temp", + "$(this.promise_filename).actual"); + "check" usebundle => dcs_check_diff("$(this.promise_filename).actual", + "$(this.promise_filename).expected", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/29_simulate_mode/manifest_mode.cf.expected b/tests/acceptance/29_simulate_mode/manifest_mode.cf.expected new file mode 100644 index 0000000000..4f810bc8b2 --- /dev/null +++ b/tests/acceptance/29_simulate_mode/manifest_mode.cf.expected @@ -0,0 +1,161 @@ + warning: All changes in files will be made in the 'WORKDIR/state/PID.changes' chroot +=========================================================================== +'WORKDIR/tmp/rename-newname' is the new name of 'WORKDIR/tmp/rename-me' +=========================================================================== +'WORKDIR/tmp/source-file' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +These are my source file contents. +\no newline at the end of file +=========================================================================== +'WORKDIR/tmp/create-true' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +=========================================================================== +'WORKDIR/tmp/insert-lines' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +foobar +=========================================================================== +'WORKDIR/tmp/SUBDIR' is a directory +Size: SIZE +Access: (0700/rwx------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Directory contents: +=========================================================================== +'WORKDIR/tmp/copy-from' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +These are my source file contents. +\no newline at the end of file +=========================================================================== +'WORKDIR/tmp/delete-me' no longer exists +=========================================================================== +'WORKDIR/tmp/sub-dir/./sub-file' no longer exists +=========================================================================== +'WORKDIR/tmp/sub-dir/.' is a directory +Size: SIZE +Access: (0700/rwx------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Directory contents: +=========================================================================== +'WORKDIR/tmp/set-colon-field' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +me:x:1001:1001::/my/new/shell:/bin/sh +=========================================================================== +'WORKDIR/tmp/delete-lines-matching' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: + +bar 2 +baz 3 +me:x:101:doo +=========================================================================== +'WORKDIR/tmp/regex-replace' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +I dare you to change my policy FOO! +=========================================================================== +'WORKDIR/tmp/edit-template-string' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +Darth Vader killed your father +\no newline at the end of file +=========================================================================== +'WORKDIR/tmp/build-xpath' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: + + +=========================================================================== +'WORKDIR/tmp/xml-insert-tree' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: + +y +=========================================================================== +'WORKDIR/tmp/perms' is a regular file +Size: SIZE +Access: (0000/---------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +=========================================================================== +'WORKDIR/tmp/transformer' no longer exists +=========================================================================== +'WORKDIR/tmp/hardlink' is a regular file +Size: SIZE +Access: (0600/rw-------) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Contents of the file: +=========================================================================== +'WORKDIR/tmp/link' is a symbolic link +Size: SIZE +Access: (0777/rwxrwxrwx) Uid: (0/root) Gid: (0/root) +Access: TIMESTAMP +Modify: TIMESTAMP +Change: TIMESTAMP + +Target: 'WORKDIR/tmp/already-created' diff --git a/tests/acceptance/29_simulate_mode/normalize_agent_output.cf.sub b/tests/acceptance/29_simulate_mode/normalize_agent_output.cf.sub new file mode 100644 index 0000000000..c0e5ac3279 --- /dev/null +++ b/tests/acceptance/29_simulate_mode/normalize_agent_output.cf.sub @@ -0,0 +1,13 @@ +bundle agent normalize_agent_results(original, normalized) +{ + commands: + "$(G.sed)" + args => " \ +-e 's,$(sys.workdir),WORKDIR,g' \ +-e 's/[0-9]*\.changes/PID.changes/' \ +-e 's/[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[[:blank:]][0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}[[:blank:]][-+][0-9]\{4\}/TIMESTAMP/' \ +-e 's/Size: [0-9]*/Size: SIZE/' \ +$(original) > $(normalized)", + contain => in_shell, + comment => "Normalize pid, timestamp and file sizes so that we can compare to an expected output."; +} diff --git a/tests/acceptance/29_simulate_mode/prepare_files_for_simulate_tests.cf.sub b/tests/acceptance/29_simulate_mode/prepare_files_for_simulate_tests.cf.sub new file mode 100644 index 0000000000..7401d0342b --- /dev/null +++ b/tests/acceptance/29_simulate_mode/prepare_files_for_simulate_tests.cf.sub @@ -0,0 +1,46 @@ +bundle agent prepare_files_for_simulate_tests +{ + files: + "$(G.testroot)$(const.dirsep)already-created" + create => "true"; + "$(G.testroot)$(const.dirsep)delete-me" + create => "true"; + "$(G.testroot)$(const.dirsep)sub-dir/." + create => "true"; + "$(G.testroot)$(const.dirsep)sub-dir/sub-file" + create => "true"; + "$(G.testroot)$(const.dirsep)set-colon-field" + create => "true", + content => "me:x:1001:1001::/home/me:/bin/sh"; + "$(G.testroot)$(const.dirsep)delete-lines-matching" + create => "true", + content => " +foo 1 +bar 2 +baz 3 +me:x:101:doo +"; + "$(G.testroot)$(const.dirsep)regex-replace" + create => "true", + content => "I dare you to change my policy foo"; + "$(G.testroot)$(const.dirsep)edit-template-string" + create => "true", + content => "From a particular point of view"; + "$(G.testroot)$(const.dirsep)xml-insert-tree" + create => "true", + edit_xml => xml_insert_tree_nopath(""); + "$(G.testroot)$(const.dirsep)build-xpath" + create => "true"; + "$(G.testroot)$(const.dirsep)perms" + create => "true"; + "$(G.testroot)$(const.dirsep)rename-me" + create => "true"; + "$(G.testroot)$(const.dirsep)transformer" + create => "true"; +} + +bundle edit_xml xml_insert_tree_nopath(treestring) +{ + insert_tree: + '$(treestring)'; +} diff --git a/tests/acceptance/29_simulate_mode/promises.cf.sub b/tests/acceptance/29_simulate_mode/promises.cf.sub new file mode 100644 index 0000000000..7c3e202912 --- /dev/null +++ b/tests/acceptance/29_simulate_mode/promises.cf.sub @@ -0,0 +1,106 @@ +bundle common simulation_files_promises +{ + vars: + "inputs" slist => { "$(this.promise_dirname)/../default.cf.sub" }; +} + +body common control +{ + inputs => { "@(simulation_files_promises.inputs)" }; +} + +bundle agent init +{ + files: + "$(G.testroot)$(const.dirsep)source-file" + create => "true", + content => "These are my source file contents."; +} + +bundle agent test +{ + methods: + "hardlink" usebundle => file_hardlink("$(G.testroot)$(const.dirsep)already-created", "$(G.testroot)$(const.dirsep)hardlink"); + "link" usebundle => file_link("$(G.testroot)$(const.dirsep)already-created", "$(G.testroot)$(const.dirsep)link"); + files: + "$(G.testroot)$(const.dirsep)already-created" + create => "true"; + "$(G.testroot)$(const.dirsep)create-true" + create => "true"; + "$(G.testroot)$(const.dirsep)insert-lines" + create => "true", + edit_line => insert_lines("foobar"); + "$(G.testroot)$(const.dirsep)SUBDIR/." + create => "true"; + "$(G.testroot)$(const.dirsep)copy-from" + copy_from => example("$(G.testroot)$(const.dirsep)source-file"); + "$(G.testroot)$(const.dirsep)delete-me" + delete => tidy; + "$(G.testroot)$(const.dirsep)sub-dir/." + delete => tidy, + file_select => all, + depth_search => recurse("inf"); + "$(G.testroot)$(const.dirsep)set-colon-field" + edit_line => set_colon_field("me","6","/my/new/shell"); + "$(G.testroot)$(const.dirsep)delete-lines-matching" + edit_line => delete_lines_matching("foo 1"); + "$(G.testroot)$(const.dirsep)regex-replace" + edit_line => regex_replace("foo","FOO!"); + "$(G.testroot)$(const.dirsep)edit-template-string" + template_method => "inline_mustache", + edit_template_string => "{{{name}}} killed your father", + template_data => '{ "name": "Darth Vader" }'; + "$(G.testroot)$(const.dirsep)build-xpath" + edit_xml => build_xpath("/Server/Service/Engine/Host"); + "$(G.testroot)$(const.dirsep)xml-insert-tree" + edit_xml => xml_insert_tree("y", "//Root"); + "$(G.testroot)$(const.dirsep)perms" + perms => m(000); + "$(G.testroot)$(const.dirsep)rename-me" + rename => newname("rename-newname"); + "$(G.testroot)$(const.dirsep)transformer" + transformer => "$(G.gzip) $(this.promiser)"; +} + + +# TODO list the contents of the chroot so we can see transformer.gz for example? +#bundle agent check +#{ +# vars: +# "result" string => execresult("$(G.ls) -laR $(sys.workdir)/state/", "noshell"), +# meta => { "simulate_safe" }; +# reports: +# "chroot files: $(result)"; +#} + +bundle agent main +{ + methods: + "init"; + "test"; +# "check"; +} + +body copy_from example(from) +{ + source => "$(from)"; + compare => "digest"; + preserve => "true"; +} + +bundle edit_xml xml_insert_tree(treestring, xpath) +{ + insert_tree: + '$(treestring)' select_xpath => "$(xpath)"; +} + +bundle edit_xml build_xpath(xpath) +{ + build_xpath: + "$(xpath)"; +} + +body rename newname(name) +{ + newname => "$(name)"; +} diff --git a/tests/acceptance/29_simulate_mode/simulate_safe_functions.cf b/tests/acceptance/29_simulate_mode/simulate_safe_functions.cf new file mode 100644 index 0000000000..ecdeb3e323 --- /dev/null +++ b/tests/acceptance/29_simulate_mode/simulate_safe_functions.cf @@ -0,0 +1,51 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + vars: + "usemodule_unsafe_module_path" string => "$(sys.workdir)$(const.dirsep)modules$(const.dirsep)usemodule_unsafe.sh"; + "usemodule_safe_module_path" string => "$(sys.workdir)$(const.dirsep)modules$(const.dirsep)usemodule_safe.sh"; + + files: + "$(sys.statedir)/*safe" + delete => tidy; + "$(usemodule_unsafe_module_path)" + create => "true", + perms => m(700), + content => "#!/bin/bash +touch $(sys.statedir)/usemodule_unsafe +"; + "$(usemodule_safe_module_path)" + create => "true", + perms => m(700), + content => "#!/bin/bash +touch $(sys.statedir)/usemodule_safe +"; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-5299" } + string => "Test that unsafe functions are only evaluated in --simulate mode when tagged as simulate_safe"; + + vars: + "cf_agent" string => ifelse(isvariable("sys.cf_agent"), "$(sys.cf_agent)", "/var/cfengine/bin/cf-agent"); + + commands: + "$(cf_agent) --timestamp --debug --simulate=manifest -Kf $(this.promise_filename).sub >$(sys.workdir)$(const.dirsep)state$(const.dirsep)agent.log" + contain => in_shell; +} + +bundle agent check +{ + vars: + "result" string => readfile("$(sys.workdir)$(const.dirsep)state$(const.dirsep)agent.log"); + + methods: + "Pass/FAIL" usebundle => dcs_check_strcmp( "$(result)", "FAIL", $(this.promise_filename), "yes"); +} diff --git a/tests/acceptance/29_simulate_mode/simulate_safe_functions.cf.sub b/tests/acceptance/29_simulate_mode/simulate_safe_functions.cf.sub new file mode 100644 index 0000000000..ee877de7c5 --- /dev/null +++ b/tests/acceptance/29_simulate_mode/simulate_safe_functions.cf.sub @@ -0,0 +1,52 @@ +body common control +{ + inputs => { "../plucked.cf.sub" }; +} + +bundle agent test +{ + vars: + "unsafe_functions" slist => { + "execresult", + "execresult_as_data", + "returnszero", + "usemodule" + }; + + "test_1" string => execresult("touch $(sys.statedir)$(const.dirsep)execresult_safe", useshell), + meta => { "simulate_safe" }; + "test_2" string => execresult("touch $(sys.statedir)$(const.dirsep)execresult_unsafe", useshell); + "test_3" data => execresult_as_data("touch $(sys.statedir)$(const.dirsep)execresult_as_data_safe", useshell), + meta => { "simulate_safe" }; + "test_4" data => execresult_as_data("touch $(sys.statedir)$(const.dirsep)execresult_as_data_unsafe", useshell); + + classes: + "test_5" expression => returnszero("touch $(sys.statedir)$(const.dirsep)returnszero_safe", useshell), + meta => { "simulate_safe" }; + "test_6" expression => returnszero("touch $(sys.statedir)$(const.dirsep)returnszero_unsafe", useshell); + "test_7" expression => usemodule("usemodule_safe.sh", useshell), + meta => { "simulate_safe" }; + "test_8" expression => usemodule("usemodule_unsafe.sh", useshell); + +} + +bundle agent check +{ + classes: + "pass_$(test.unsafe_functions)" expression => not(fileexists("$(sys.statedir)$(const.dirsep)$(test.unsafe_functions)_unsafe")); + "fail_$(test.unsafe_functions)" expression => fileexists("$(sys.statedir)$(const.dirsep)$(test.unsafe_functions)_unsafe"); + + reports: + "$(this.promise_filename) FAIL" + if => classmatch("fail_.*"); + "$(this.promise_filename) Pass" + if => strcmp(countclassesmatching("fail.*"), 0); +} + +bundle agent __main__ +{ + methods: + "init"; + "test"; + "check"; +} diff --git a/tests/acceptance/30_custom_promise_types/01_basic_module.cf b/tests/acceptance/30_custom_promise_types/01_basic_module.cf new file mode 100644 index 0000000000..812c362b7f --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/01_basic_module.cf @@ -0,0 +1,99 @@ +###################################################### +# +# Basic test of custom promise types / promise modules +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +body classes example +{ + promise_repaired => { "example_promise_repaired" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3443" } + string => "Test that you can add a promise module and evaluate a custom promise"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + + vars: + "test_string" + string => "hello, modules"; + + example: + cfengine:: + "$(G.testfile)" + message => "$(test_string)", + classes => example; + + classes: + "file_created" + expression => canonify("$(G.testfile)_created"), + scope => "namespace"; + "file_updated" + expression => canonify("$(G.testfile)_content_updated"), + scope => "namespace"; + "file_update_failed" + expression => canonify("$(G.testfile)_content_update_failed"), + scope => "namespace"; +} + +####################################################### + +bundle agent check +{ + classes: + "file_ok" + expression => strcmp("$(test.test_string)", readfile("$(G.testfile)")), + if => fileexists("$(G.testfile)"); + + "ok" expression => "file_ok.file_created.file_updated.(!file_update_failed).example_promise_repaired"; + + reports: + DEBUG.file_ok:: + "file_ok"; + DEBUG.file_created:: + "file_created"; + DEBUG.file_updated:: + "file_updated"; + DEBUG.file_update_failed:: + "file_update_failed"; + DEBUG.example_promise_repaired:: + "example_promise_repaired"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/02_if.cf b/tests/acceptance/30_custom_promise_types/02_if.cf new file mode 100644 index 0000000000..6c3ae9c0e9 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/02_if.cf @@ -0,0 +1,131 @@ +###################################################### +# +# Test that custom promises can use if attribute +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)-0" + delete => init_delete; + "$(G.testfile)-1" + delete => init_delete; + "$(G.testfile)-2" + delete => init_delete; + "$(G.testfile)-3" + delete => init_delete; + "$(G.testfile)-4" + delete => init_delete; + "$(G.testfile)-5" + delete => init_delete; + "$(G.testfile)-6" + delete => init_delete; + "$(G.testfile)-7" + delete => init_delete; + "$(G.testfile)-8" + delete => init_delete; + "$(G.testfile)-9" + delete => init_delete; + "$(G.testfile)-10" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3391" } + string => "Test that custom promises work with if attribute"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "false_variable" + string => "cfengine.(!cfengine)"; + "true_variable" + string => "cfengine|(!cfengine)"; + + example: + cfengine:: + "$(G.testfile)-0" # Created - no condition + message => "x"; + "$(G.testfile)-1" # Created - true condition + message => "x", + if => "cfengine"; + "$(G.testfile)-2" # Created - true condition from variable + message => "x", + if => "$(true_variable)"; + "$(G.testfile)-3" # Created - true condition (inverted false variable) + message => "x", + if => "!($(false_variable))"; + "$(G.testfile)-4" # Created - not function call of something false + message => "x", + if => not("$(false_variable)"); + "$(G.testfile)-5" # Not created - false condition + message => "x", + if => "!cfengine"; + "$(G.testfile)-6" # Not created - false condition from variable + message => "x", + if => "$(false_variable)"; + "$(G.testfile)-7" # Not created - false condition (inverted true variable) + message => "x", + if => "!($(true_variable))"; + "$(G.testfile)-8" # Not created - not function call of something true + message => "x", + if => not("$(true_variable)"); + "$(G.testfile)-9" # Not created - undefined variable in if + message => "x", + if => "$(undefined_variable)"; + "$(G.testfile)-10" # Not created - unresolved function call in if + message => "x", + if => not("$(undefined_variable)"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" + and => { + fileexists("$(G.testfile)-0"), + fileexists("$(G.testfile)-1"), + fileexists("$(G.testfile)-2"), + fileexists("$(G.testfile)-3"), + fileexists("$(G.testfile)-4"), + not(fileexists("$(G.testfile)-5")), + not(fileexists("$(G.testfile)-6")), + not(fileexists("$(G.testfile)-7")), + not(fileexists("$(G.testfile)-8")), + not(fileexists("$(G.testfile)-9")), + not(fileexists("$(G.testfile)-10")), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/03_ifvarclass.x.cf b/tests/acceptance/30_custom_promise_types/03_ifvarclass.x.cf new file mode 100644 index 0000000000..62e448727c --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/03_ifvarclass.x.cf @@ -0,0 +1,26 @@ +###################################################### +# +# Test that ifvarclass causes syntax / validation error +# +##################################################### + +# This policy is as minimal as possible to reduce +# the chance of something else causing an error + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +bundle agent main +{ + meta: + "description" -> { "CFE-3391" } + string => "Test that ifvarclass causes syntax / validation error"; + + example: + "/some/absolute/path" + message => "x", + ifvarclass => "any"; +} diff --git a/tests/acceptance/30_custom_promise_types/04_unknown_promise_type.error.cf b/tests/acceptance/30_custom_promise_types/04_unknown_promise_type.error.cf new file mode 100644 index 0000000000..8761ac2f7c --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/04_unknown_promise_type.error.cf @@ -0,0 +1,5 @@ +bundle agent main +{ + unknown: + "promise"; +} diff --git a/tests/acceptance/30_custom_promise_types/05_meta_attr.cf b/tests/acceptance/30_custom_promise_types/05_meta_attr.cf new file mode 100644 index 0000000000..7e38995dce --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/05_meta_attr.cf @@ -0,0 +1,101 @@ +###################################################### +# +# Basic test of custom promise types / promise modules checking that using a +# 'meta' attribute in combination with a custom promise works (i.e. doesn't +# crash). +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +body classes example +{ + promise_repaired => { "example_promise_repaired" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3440" } + string => "Test that you can use a meta attribute with a custom promise"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "test_string" + string => "hello, modules"; + + example: + cfengine:: + "$(G.testfile)" + message => "$(test_string)", + classes => example, + meta => { "tag1", "tag2" }; + + classes: + "file_created" + expression => canonify("$(G.testfile)_created"), + scope => "namespace"; + "file_updated" + expression => canonify("$(G.testfile)_content_updated"), + scope => "namespace"; + "file_update_failed" + expression => canonify("$(G.testfile)_content_update_failed"), + scope => "namespace"; +} + +####################################################### + +bundle agent check +{ + classes: + "file_ok" + expression => strcmp("$(test.test_string)", readfile("$(G.testfile)")), + if => fileexists("$(G.testfile)"); + + "ok" expression => "file_ok.file_created.file_updated.(!file_update_failed).example_promise_repaired"; + + reports: + DEBUG.file_ok:: + "file_ok"; + DEBUG.file_created:: + "file_created"; + DEBUG.file_updated:: + "file_updated"; + DEBUG.file_update_failed:: + "file_update_failed"; + DEBUG.example_promise_repaired:: + "example_promise_repaired"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/11_unless.cf b/tests/acceptance/30_custom_promise_types/11_unless.cf new file mode 100644 index 0000000000..8ca6cdf629 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/11_unless.cf @@ -0,0 +1,131 @@ +###################################################### +# +# Test that custom promises can use unless attribute +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)-0" + delete => init_delete; + "$(G.testfile)-1" + delete => init_delete; + "$(G.testfile)-2" + delete => init_delete; + "$(G.testfile)-3" + delete => init_delete; + "$(G.testfile)-4" + delete => init_delete; + "$(G.testfile)-5" + delete => init_delete; + "$(G.testfile)-6" + delete => init_delete; + "$(G.testfile)-7" + delete => init_delete; + "$(G.testfile)-8" + delete => init_delete; + "$(G.testfile)-9" + delete => init_delete; + "$(G.testfile)-10" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3431" } + string => "Test that custom promises work with unless attribute"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "false_variable" + string => "cfengine.(!cfengine)"; + "true_variable" + string => "cfengine|(!cfengine)"; + + example: + cfengine:: + "$(G.testfile)-0" # Created - no condition + message => "x"; + "$(G.testfile)-1" # Not created - true condition + message => "x", + unless => "cfengine"; + "$(G.testfile)-2" # Not created - true condition from variable + message => "x", + unless => "$(true_variable)"; + "$(G.testfile)-3" # Not created - true condition (inverted false variable) + message => "x", + unless => "!($(false_variable))"; + "$(G.testfile)-4" # NOT - not function call of something false + message => "x", + unless => not("$(false_variable)"); + "$(G.testfile)-5" # Created - false condition + message => "x", + unless => "!cfengine"; + "$(G.testfile)-6" # Created - false condition from variable + message => "x", + unless => "$(false_variable)"; + "$(G.testfile)-7" # Created - false condition (inverted true variable) + message => "x", + unless => "!($(true_variable))"; + "$(G.testfile)-8" # Created - not function call of something true + message => "x", + unless => not("$(true_variable)"); + "$(G.testfile)-9" # Created - undefined variable in unless + message => "x", + unless => "$(undefined_variable)"; + "$(G.testfile)-10" # Created - unresolved function call in unless + message => "x", + unless => not("$(undefined_variable)"); +} + +####################################################### + +bundle agent check +{ + classes: + "ok" + and => { + fileexists("$(G.testfile)-0"), + not(fileexists("$(G.testfile)-1")), + not(fileexists("$(G.testfile)-2")), + not(fileexists("$(G.testfile)-3")), + not(fileexists("$(G.testfile)-4")), + fileexists("$(G.testfile)-5"), + fileexists("$(G.testfile)-6"), + fileexists("$(G.testfile)-7"), + fileexists("$(G.testfile)-8"), + fileexists("$(G.testfile)-9"), + fileexists("$(G.testfile)-10"), + }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/12_multiple_promises.cf b/tests/acceptance/30_custom_promise_types/12_multiple_promises.cf new file mode 100644 index 0000000000..17fea8d49b --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/12_multiple_promises.cf @@ -0,0 +1,135 @@ +###################################################### +# +# Basic test of custom promise types / promise modules with multiple custom +# promises of the same type +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + "$(G.testfile)2" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +body classes example +{ + promise_repaired => { "example_promise_repaired" }; +} + +body classes example2 +{ + promise_repaired => { "example2_promise_repaired" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3443" } + string => "Test that you can evaluate multiple custom promises of the same type"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "test_string" + string => "hello, modules"; + + example: + cfengine:: + "$(G.testfile)" + message => "$(test_string)", + classes => example; + "$(G.testfile)2" + message => "$(test_string)", + classes => example2; + + classes: + "file_created" + expression => canonify("$(G.testfile)_created"), + scope => "namespace"; + "file_updated" + expression => canonify("$(G.testfile)_content_updated"), + scope => "namespace"; + "file_update_failed" + expression => canonify("$(G.testfile)_content_update_failed"), + scope => "namespace"; + "file2_created" + expression => canonify("$(G.testfile)2_created"), + scope => "namespace"; + "file2_updated" + expression => canonify("$(G.testfile)2_content_updated"), + scope => "namespace"; + "file2_update_failed" + expression => canonify("$(G.testfile)2_content_update_failed"), + scope => "namespace"; +} + +####################################################### + +bundle agent check +{ + classes: + "file_ok" + expression => strcmp("$(test.test_string)", readfile("$(G.testfile)")), + if => fileexists("$(G.testfile)"); + "file2_ok" + expression => strcmp("$(test.test_string)", readfile("$(G.testfile)2")), + if => fileexists("$(G.testfile)2"); + + "example_ok" expression => "file_ok.file_created.file_updated.(!file_update_failed).example_promise_repaired"; + "example2_ok" expression => "file2_ok.file2_created.file2_updated.(!file2_update_failed).example2_promise_repaired"; + + "ok" expression => "example_ok.example2_ok"; + + reports: + DEBUG.file_ok:: + "file_ok"; + DEBUG.file_created:: + "file_created"; + DEBUG.file_updated:: + "file_updated"; + DEBUG.file_update_failed:: + "file_update_failed"; + DEBUG.example_promise_repaired:: + "example_promise_repaired"; + + DEBUG.file2_ok:: + "file2_ok"; + DEBUG.file2_created:: + "file2_created"; + DEBUG.file2_updated:: + "file2_updated"; + DEBUG.file2_update_failed:: + "file2_update_failed"; + DEBUG.example2_promise_repaired:: + "example_promise_repaired"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/13_binary_path.cf b/tests/acceptance/30_custom_promise_types/13_binary_path.cf new file mode 100644 index 0000000000..bf85e2bb73 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/13_binary_path.cf @@ -0,0 +1,30 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +promise agent binary +{ + path => "$(G.cwd)/custom_promise_binary"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + binary: + "foobar" + classes => if_ok("binary_ok"); +} + +bundle agent check +{ + reports: + binary_ok:: + "$(this.promise_filename) Pass"; + !binary_ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/14_multiple_promise_types_same_module.cf b/tests/acceptance/30_custom_promise_types/14_multiple_promise_types_same_module.cf new file mode 100644 index 0000000000..bc8e134598 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/14_multiple_promise_types_same_module.cf @@ -0,0 +1,144 @@ +###################################################### +# +# Basic test of custom promise types / promise modules with a custom promise +# module used for two promise types. +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + "$(G.testfile)2" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +promise agent example_two +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +body classes example +{ + promise_repaired => { "example_promise_repaired" }; +} + +body classes example2 +{ + promise_repaired => { "example2_promise_repaired" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3443" } + string => "Test that you can use the same custom module for two promise types"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "test_string" + string => "hello, modules"; + + example: + cfengine:: + "$(G.testfile)" + message => "$(test_string)", + classes => example; + + example_two: + cfengine:: + "$(G.testfile)2" + message => "$(test_string)", + classes => example2; + + classes: + "file_created" + expression => canonify("$(G.testfile)_created"), + scope => "namespace"; + "file_updated" + expression => canonify("$(G.testfile)_content_updated"), + scope => "namespace"; + "file_update_failed" + expression => canonify("$(G.testfile)_content_update_failed"), + scope => "namespace"; + "file2_created" + expression => canonify("$(G.testfile)2_created"), + scope => "namespace"; + "file2_updated" + expression => canonify("$(G.testfile)2_content_updated"), + scope => "namespace"; + "file2_update_failed" + expression => canonify("$(G.testfile)2_content_update_failed"), + scope => "namespace"; +} + +####################################################### + +bundle agent check +{ + classes: + "file_ok" + expression => strcmp("$(test.test_string)", readfile("$(G.testfile)")), + if => fileexists("$(G.testfile)"); + "file2_ok" + expression => strcmp("$(test.test_string)", readfile("$(G.testfile)2")), + if => fileexists("$(G.testfile)2"); + + "example_ok" expression => "file_ok.file_created.file_updated.(!file_update_failed).example_promise_repaired"; + "example2_ok" expression => "file2_ok.file2_created.file2_updated.(!file2_update_failed).example2_promise_repaired"; + + "ok" expression => "example_ok.example2_ok"; + + reports: + DEBUG.file_ok:: + "file_ok"; + DEBUG.file_created:: + "file_created"; + DEBUG.file_updated:: + "file_updated"; + DEBUG.file_update_failed:: + "file_update_failed"; + DEBUG.example_promise_repaired:: + "example_promise_repaired"; + + DEBUG.file2_ok:: + "file2_ok"; + DEBUG.file2_created:: + "file2_created"; + DEBUG.file2_updated:: + "file2_updated"; + DEBUG.file2_update_failed:: + "file2_update_failed"; + DEBUG.example2_promise_repaired:: + "example_promise_repaired"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/15_conflicting_interpreters.cf b/tests/acceptance/30_custom_promise_types/15_conflicting_interpreters.cf new file mode 100644 index 0000000000..410a7d76c0 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/15_conflicting_interpreters.cf @@ -0,0 +1,145 @@ +###################################################### +# +# Basic test of custom promise types / promise modules with a custom promise +# module used for two promise types with conflicting interpreter +# specifications. +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; + "$(G.testfile)2" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +promise agent example_two +{ + interpreter => "/bin/bash_noexist"; + path => "$(this.promise_dirname)/example_module.sh"; +} + +body classes example +{ + promise_repaired => { "example_promise_repaired" }; +} + +body classes example2 +{ + promise_repaired => { "example2_promise_repaired" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3443" } + string => "Test that you cannot use a promise module with two different interpreters"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + vars: + "test_string" + string => "hello, modules"; + + example: + cfengine:: + "$(G.testfile)" + message => "$(test_string)", + classes => example; + + example_two: + cfengine:: + "$(G.testfile)2" + message => "$(test_string)", + classes => example2; + + classes: + "file_created" + expression => canonify("$(G.testfile)_created"), + scope => "namespace"; + "file_updated" + expression => canonify("$(G.testfile)_content_updated"), + scope => "namespace"; + "file_update_failed" + expression => canonify("$(G.testfile)_content_update_failed"), + scope => "namespace"; + "file2_created" + expression => canonify("$(G.testfile)2_created"), + scope => "namespace"; + "file2_updated" + expression => canonify("$(G.testfile)2_content_updated"), + scope => "namespace"; + "file2_update_failed" + expression => canonify("$(G.testfile)2_content_update_failed"), + scope => "namespace"; +} + +####################################################### + +bundle agent check +{ + classes: + "file_ok" + expression => strcmp("$(test.test_string)", readfile("$(G.testfile)")), + if => fileexists("$(G.testfile)"); + "file2_ok" + expression => strcmp("$(test.test_string)", readfile("$(G.testfile)2")), + if => fileexists("$(G.testfile)2"); + + "example_ok" expression => "file_ok.file_created.file_updated.(!file_update_failed).example_promise_repaired"; + "example2_ok" expression => "!file2_ok.!file2_created.!file2_updated.!file2_update_failed.!example2_promise_repaired"; + + "ok" expression => "example_ok.example2_ok"; + + reports: + DEBUG.file_ok:: + "file_ok"; + DEBUG.file_created:: + "file_created"; + DEBUG.file_updated:: + "file_updated"; + DEBUG.file_update_failed:: + "file_update_failed"; + DEBUG.example_promise_repaired:: + "example_promise_repaired"; + + DEBUG.file2_ok:: + "file2_ok"; + DEBUG.file2_created:: + "file2_created"; + DEBUG.file2_updated:: + "file2_updated"; + DEBUG.file2_update_failed:: + "file2_update_failed"; + DEBUG.example2_promise_repaired:: + "example_promise_repaired"; + + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/17_early_module_failure.cf b/tests/acceptance/30_custom_promise_types/17_early_module_failure.cf new file mode 100644 index 0000000000..cfa6bc4a76 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/17_early_module_failure.cf @@ -0,0 +1,24 @@ +###################################################### +# +# Test of a module which fails early +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -D AUTO -KIf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output(".*error:.*", ".*(Assertion.*failed|fault).*", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/30_custom_promise_types/17_early_module_failure.cf.sub b/tests/acceptance/30_custom_promise_types/17_early_module_failure.cf.sub new file mode 100644 index 0000000000..4cdc5595ad --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/17_early_module_failure.cf.sub @@ -0,0 +1,22 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +promise agent bad +{ + interpreter => "/usr/bin/python3"; + path => "$(this.promise_dirname)/bad_import.py"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3651" } + string => "Test that an early-failing custom module is hanled properly"; + + bad: + "doesn't matter what I put here"; +} diff --git a/tests/acceptance/30_custom_promise_types/18_early_module_failure_classes.cf b/tests/acceptance/30_custom_promise_types/18_early_module_failure_classes.cf new file mode 100644 index 0000000000..1b3b8ed7b3 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/18_early_module_failure_classes.cf @@ -0,0 +1,48 @@ +###################################################### +# +# Test of classes being set for a promise using a module which fails early +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +promise agent bad +{ + interpreter => "/usr/bin/python3"; + path => "$(this.promise_dirname)/bad_import.py"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3645" } + string => "Test that a promise using an early-failing custom module still sets result classes"; + + bad: + "doesn't matter what I put here" + classes => test; +} + +body classes test +{ + promise_repaired => { "test_promise_repaired" }; + repair_failed => { "test_promise_failed" }; +} + +bundle agent check +{ + classes: + "ok" expression => "test_promise_failed"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/30_custom_promise_types/22_what_module_gets.cf b/tests/acceptance/30_custom_promise_types/22_what_module_gets.cf new file mode 100644 index 0000000000..8ad75fd22f --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/22_what_module_gets.cf @@ -0,0 +1,59 @@ +###################################################### +# +# Test which checks what custom module gets from the agent +# +##################################################### +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent init +{ + files: + "/tmp/module.log" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +####################################################### + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/cat_module.sh"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3723" } + string => "Test that input from cf-agent to promise module matches expected data"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + + example: + cfengine:: + "Promiser" + attributeName => "attributeValue"; +} + +####################################################### + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("/tmp/module.log", + "$(this.promise_filename).expected", + "$(this.promise_filename)"); +} + diff --git a/tests/acceptance/30_custom_promise_types/22_what_module_gets.cf.expected b/tests/acceptance/30_custom_promise_types/22_what_module_gets.cf.expected new file mode 100644 index 0000000000..d06f1bc7c9 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/22_what_module_gets.cf.expected @@ -0,0 +1,18 @@ +cf-agent xxx v1 + +operation=validate_promise +log_level=info +promise_type=example +promiser=Promiser +line_number=46 +filename=./30_custom_promise_types/22_what_module_gets.cf +attribute_attributeName=attributeValue + +operation=evaluate_promise +log_level=info +promise_type=example +promiser=Promiser +line_number=46 +filename=./30_custom_promise_types/22_what_module_gets.cf +attribute_attributeName=attributeValue + diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_supported.cf b/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_supported.cf new file mode 100644 index 0000000000..dbcdbd4f5c --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_supported.cf @@ -0,0 +1,34 @@ +###################################################### +# +# Test of a custom promise with '--dry-run' and a module that +# implements action_policy handling. +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3433" } + string => "Test that dry-run can be used with custom promise modules supporting it"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) --dry-run -D AUTO -KIf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output(".*warning: Should.*", ".*CRITICAL:.*", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_supported.cf.sub b/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_supported.cf.sub new file mode 100644 index 0000000000..dcf66d92fe --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_supported.cf.sub @@ -0,0 +1,44 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module_with_action_policy.sh"; +} + + +bundle agent test +{ + vars: + "test_string" + string => "hello, modules"; + + example: + "$(G.testfile)" + message => "$(test_string)", + action => policy("warn"); +} + +body action policy(pol) +{ + action_policy => "${pol}"; +} + diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_unsupported.cf b/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_unsupported.cf new file mode 100644 index 0000000000..01cb2f2431 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_unsupported.cf @@ -0,0 +1,34 @@ +###################################################### +# +# Test of a custom promise with '--dry-run' and a module that +# doesn't implement action_policy handling. +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3433" } + string => "Test that --dry-run produces errors when used with custom promise modules not supporting it"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) --dry-run -D AUTO -KIf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output(".*error: Not making changes.*", ".*CRITICAL:.*", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_unsupported.cf.sub b/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_unsupported.cf.sub new file mode 100644 index 0000000000..43ca335d53 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/dryrun_unsupported.cf.sub @@ -0,0 +1,44 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module_without_action_policy.sh"; +} + + +bundle agent test +{ + vars: + "test_string" + string => "hello, modules"; + + example: + "$(G.testfile)" + message => "$(test_string)", + action => policy("warn"); +} + +body action policy(pol) +{ + action_policy => "${pol}"; +} + diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_with_action_policy.sh b/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_with_action_policy.sh new file mode 100644 index 0000000000..bc7886b44b --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_with_action_policy.sh @@ -0,0 +1,187 @@ +log() { + if [ "$#" != 2 ] ; then + echo "log_critical=Error in promise module (log must be used with 2 arguments, level and message)" + exit 1 + fi + level="$1" + message="$2" + echo "log_$level=$message" +} + +reset_state() { + # Set global variables before we begin another request + + # Variables parsed directly from request: + request_operation="" + request_log_level="" + request_promise_type="" + request_promiser="" + request_attribute_message="" + action_policy="" + + # Variables to put into response: + response_result="" + + # Other state: + saw_unknown_key="no" + saw_unknown_attribute="no" +} + +handle_input_line() { + + # Split the line of input on the first '=' into 2 - key and value + IFS='=' read -r key value <<< "$1" + + case "$key" in + operation) + request_operation="$value" ;; + log_level) + request_log_level="$value" ;; + promise_type) + request_promise_type="$value" ;; + promiser) + request_promiser="$value" ;; + attribute_message) + request_attribute_message="$value" ;; + attribute_action_policy) + action_policy="$value" ;; + attribute_*) + attribute_name=${key#"attribute_"} + log error "Unknown attribute: '$attribute_name'" + saw_unknown_attribute="yes" ;; + *) + saw_unknown_key="yes" ;; + esac +} + +receive_request() { + # Read lines from input until empty line + # Call handle_input_line for each non-empty line + while IFS='$\n' read -r line; do + if [ "x$line" = "x" ] ; then + break + fi + handle_input_line "$line" # Parses a key=value pair + done +} + +write_response() { + echo "operation=$request_operation" + echo "result=$response_result" + echo "" +} + +operation_terminate() { + response_result="success" + write_response + exit 0 +} + +operation_validate() { + response_result="valid" + if [ "$saw_unknown_attribute" != "no" ] ; then + response_result="invalid" + fi + + if [ "$request_promiser" = "" ] ; then + log error "Promiser must be non-empty" + response_result="invalid" + fi + + if [ "$request_attribute_message" = "" ] ; then + log error "Attribute 'message' is missing or empty" + response_result="invalid" + fi + + write_response +} + +operation_evaluate() { + local safe_promiser="$(echo "$request_promiser" | sed 's/,/_/g')" + local classes="" + + local existed_before=0 + if [ -f "$request_promiser" ]; then + existed_before=1 + fi + + if grep -q "$request_attribute_message" "$request_promiser" 2>/dev/null ; then + response_result="kept" + classes="${safe_promiser}_content_as_promised" + elif [ x"$action_policy" = x"warn" ]; then + printf "log_warning=Should update file '%s' with content '%s', but only warning promised\n" "$request_promiser" "$request_attribute_message" + response_result="not_kept" + else + response_result="repaired" + echo "$request_attribute_message" > "$request_promiser" && { + printf "log_info=Updated file '%s' with content '%s'\n" "$request_promiser" "$request_attribute_message" + if [ $existed_before = 0 ]; then + classes="${safe_promiser}_created,${safe_promiser}_content_updated" + else + classes="${safe_promiser}_content_updated" + fi + } || response_result="not_kept" + + if ! grep -q "$request_attribute_message" "$request_promiser" 2>/dev/null ; then + response_result="not_kept" + if [ -z "$classes" ]; then + classes="${safe_promiser}_content_update_failed" + else + classes="${classes},${safe_promiser}_content_update_failed" + fi + fi + fi + + if [ -n "$classes" ]; then + echo "result_classes=$classes" + fi + write_response +} + +operation_unknown() { + response_result="error" + log error "Promise module received unexpected operation: $request_operation" + write_response +} + +perform_operation() { + case "$request_operation" in + validate_promise) + operation_validate ;; + evaluate_promise) + operation_evaluate ;; + terminate) + operation_terminate ;; + *) + operation_unknown ;; + esac +} + +handle_request() { + reset_state # 1. Reset global variables + receive_request # 2. Receive / parse an operation from agent + perform_operation # 3. Perform operation (validate, evaluate, terminate) +} + +skip_header() { + # Skip until (and including) the first empty line + while IFS='$\n' read -r line; do + if [ "x$line" = "x" ] ; then + return; + fi + done +} + +# Skip the protocol header given by agent: +skip_header + +# Write our header to request line based protocol: +echo "example_promises 0.0.1 v1 line_based action_policy" +echo "" + +# Loop indefinitely, handling requests: +while true; do + handle_request +done + +# Should never get here. diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_with_fake_action_policy.sh b/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_with_fake_action_policy.sh new file mode 100644 index 0000000000..925827ae77 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_with_fake_action_policy.sh @@ -0,0 +1,187 @@ +log() { + if [ "$#" != 2 ] ; then + echo "log_critical=Error in promise module (log must be used with 2 arguments, level and message)" + exit 1 + fi + level="$1" + message="$2" + echo "log_$level=$message" +} + +reset_state() { + # Set global variables before we begin another request + + # Variables parsed directly from request: + request_operation="" + request_log_level="" + request_promise_type="" + request_promiser="" + request_attribute_message="" + action_policy="" + + # Variables to put into response: + response_result="" + + # Other state: + saw_unknown_key="no" + saw_unknown_attribute="no" +} + +handle_input_line() { + + # Split the line of input on the first '=' into 2 - key and value + IFS='=' read -r key value <<< "$1" + + case "$key" in + operation) + request_operation="$value" ;; + log_level) + request_log_level="$value" ;; + promise_type) + request_promise_type="$value" ;; + promiser) + request_promiser="$value" ;; + attribute_message) + request_attribute_message="$value" ;; + attribute_action_policy) + action_policy="$value" ;; + attribute_*) + attribute_name=${key#"attribute_"} + log error "Unknown attribute: '$attribute_name'" + saw_unknown_attribute="yes" ;; + *) + saw_unknown_key="yes" ;; + esac +} + +receive_request() { + # Read lines from input until empty line + # Call handle_input_line for each non-empty line + while IFS='$\n' read -r line; do + if [ "x$line" = "x" ] ; then + break + fi + handle_input_line "$line" # Parses a key=value pair + done +} + +write_response() { + echo "operation=$request_operation" + echo "result=$response_result" + echo "" +} + +operation_terminate() { + response_result="success" + write_response + exit 0 +} + +operation_validate() { + response_result="valid" + if [ "$saw_unknown_attribute" != "no" ] ; then + response_result="invalid" + fi + + if [ "$request_promiser" = "" ] ; then + log error "Promiser must be non-empty" + response_result="invalid" + fi + + if [ "$request_attribute_message" = "" ] ; then + log error "Attribute 'message' is missing or empty" + response_result="invalid" + fi + + write_response +} + +operation_evaluate() { + local safe_promiser="$(echo "$request_promiser" | sed 's/,/_/g')" + local classes="" + + local existed_before=0 + if [ -f "$request_promiser" ]; then + existed_before=1 + fi + + if grep -q "$request_attribute_message" "$request_promiser" 2>/dev/null ; then + response_result="kept" + classes="${safe_promiser}_content_as_promised" + # elif [ x"$action_policy" = x"warn" ]; then + # printf "log_warning=Should update file '%s' with content '%s', but only warning promised\n" "$request_promiser" "$request_attribute_message" + # response_result="not_kept" + else + response_result="repaired" + echo "$request_attribute_message" > "$request_promiser" && { + printf "log_info=Updated file '%s' with content '%s'\n" "$request_promiser" "$request_attribute_message" + if [ $existed_before = 0 ]; then + classes="${safe_promiser}_created,${safe_promiser}_content_updated" + else + classes="${safe_promiser}_content_updated" + fi + } || response_result="not_kept" + + if ! grep -q "$request_attribute_message" "$request_promiser" 2>/dev/null ; then + response_result="not_kept" + if [ -z "$classes" ]; then + classes="${safe_promiser}_content_update_failed" + else + classes="${classes},${safe_promiser}_content_update_failed" + fi + fi + fi + + if [ -n "$classes" ]; then + echo "result_classes=$classes" + fi + write_response +} + +operation_unknown() { + response_result="error" + log error "Promise module received unexpected operation: $request_operation" + write_response +} + +perform_operation() { + case "$request_operation" in + validate_promise) + operation_validate ;; + evaluate_promise) + operation_evaluate ;; + terminate) + operation_terminate ;; + *) + operation_unknown ;; + esac +} + +handle_request() { + reset_state # 1. Reset global variables + receive_request # 2. Receive / parse an operation from agent + perform_operation # 3. Perform operation (validate, evaluate, terminate) +} + +skip_header() { + # Skip until (and including) the first empty line + while IFS='$\n' read -r line; do + if [ "x$line" = "x" ] ; then + return; + fi + done +} + +# Skip the protocol header given by agent: +skip_header + +# Write our header to request line based protocol: +echo "example_promises 0.0.1 v1 line_based action_policy" +echo "" + +# Loop indefinitely, handling requests: +while true; do + handle_request +done + +# Should never get here. diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_without_action_policy.sh b/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_without_action_policy.sh new file mode 100644 index 0000000000..52a1e7c02f --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/example_module_without_action_policy.sh @@ -0,0 +1,171 @@ +reset_state() { + # Set global variables before we begin another request + + # Variables parsed directly from request: + request_operation="" + request_log_level="" + request_promise_type="" + request_promiser="" + request_attribute_message="" + + # Variables to put into response: + response_result="" + + # Other state: + saw_unknown_key="no" + saw_unknown_attribute="no" +} + +handle_input_line() { + + # Split the line of input on the first '=' into 2 - key and value + IFS='=' read -r key value <<< "$1" + + case "$key" in + operation) + request_operation="$value" ;; + log_level) + request_log_level="$value" ;; + promise_type) + request_promise_type="$value" ;; + promiser) + request_promiser="$value" ;; + attribute_message) + request_attribute_message="$value" ;; + attribute_*) + attribute_name=${key#"attribute_"} + log error "Unknown attribute: '$attribute_name'" + saw_unknown_attribute="yes" ;; + *) + saw_unknown_key="yes" ;; + esac +} + +receive_request() { + # Read lines from input until empty line + # Call handle_input_line for each non-empty line + while IFS='$\n' read -r line; do + if [ "x$line" = "x" ] ; then + break + fi + handle_input_line "$line" # Parses a key=value pair + done +} + +write_response() { + echo "operation=$request_operation" + echo "result=$response_result" + echo "" +} + +operation_terminate() { + response_result="success" + write_response + exit 0 +} + +operation_validate() { + response_result="valid" + if [ "$saw_unknown_attribute" != "no" ] ; then + response_result="invalid" + fi + + if [ "$request_promiser" = "" ] ; then + log error "Promiser must be non-empty" + response_result="invalid" + fi + + if [ "$request_attribute_message" = "" ] ; then + log error "Attribute 'message' is missing or empty" + response_result="invalid" + fi + + write_response +} + +operation_evaluate() { + local safe_promiser="$(echo "$request_promiser" | sed 's/,/_/g')" + local classes="" + + local existed_before=0 + if [ -f "$request_promiser" ]; then + existed_before=1 + fi + + if grep -q "$request_attribute_message" "$request_promiser" 2>/dev/null ; then + response_result="kept" + classes="${safe_promiser}_content_as_promised" + else + response_result="repaired" + echo "$request_attribute_message" > "$request_promiser" && { + printf "log_info=Updated file '%s' with content '%s'\n" "$request_promiser" "$request_attribute_message" + if [ $existed_before = 0 ]; then + classes="${safe_promiser}_created,${safe_promiser}_content_updated" + else + classes="${safe_promiser}_content_updated" + fi + } || response_result="not_kept" + fi + + if ! grep -q "$request_attribute_message" "$request_promiser" 2>/dev/null ; then + response_result="not_kept" + if [ -z "$classes" ]; then + classes="${safe_promiser}_content_update_failed" + else + classes="${classes},${safe_promiser}_content_update_failed" + fi + fi + + if [ -n "$classes" ]; then + echo "result_classes=$classes" + fi + write_response +} + +operation_unknown() { + response_result="error" + log error "Promise module received unexpected operation: $request_operation" + write_response +} + +perform_operation() { + case "$request_operation" in + validate_promise) + operation_validate ;; + evaluate_promise) + operation_evaluate ;; + terminate) + operation_terminate ;; + *) + operation_unknown ;; + esac +} + +handle_request() { + reset_state # 1. Reset global variables + receive_request # 2. Receive / parse an operation from agent + perform_operation # 3. Perform operation (validate, evaluate, terminate) +} + +skip_header() { + # Skip until (and including) the first empty line + while IFS='$\n' read -r line; do + if [ "x$line" = "x" ] ; then + return; + fi + done +} + +# Skip the protocol header given by agent: +skip_header + +# Write our header to request line based protocol: +echo "example_promises 0.0.1 v1 line_based" +echo "" + +# Loop indefinitely, handling requests: +while true; do + handle_request +done + +# Should never get here. diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_fake_supported.cf b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_fake_supported.cf new file mode 100644 index 0000000000..7a0ba4207f --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_fake_supported.cf @@ -0,0 +1,34 @@ +###################################################### +# +# Test of a custom promise with 'action_policy => "warn"' and a module that +# doesn't implement action_policy handling, but advertises it as supported. +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3433" } + string => "Test that a bug in a module is reported if it advertises action_policy feature, but doesn't properly implement it"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -D AUTO -KIf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output(".*CRITICAL: Bug in promise module.*", "", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_fake_supported.cf.sub b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_fake_supported.cf.sub new file mode 100644 index 0000000000..f845833d9b --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_fake_supported.cf.sub @@ -0,0 +1,44 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module_with_fake_action_policy.sh"; +} + + +bundle agent test +{ + vars: + "test_string" + string => "hello, modules"; + + example: + "$(G.testfile)" + message => "$(test_string)", + action => policy("warn"); +} + +body action policy(pol) +{ + action_policy => "${pol}"; +} + diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_supported.cf b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_supported.cf new file mode 100644 index 0000000000..9472c4d3a3 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_supported.cf @@ -0,0 +1,34 @@ +###################################################### +# +# Test of a custom promise with 'action_policy => "warn"' and a module that +# implements action_policy handling. +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3433" } + string => "Test that action_policy can be used with custom promise modules supporting it"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -D AUTO -KIf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output(".*warning: Should.*", ".*CRITICAL:.*", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_supported.cf.sub b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_supported.cf.sub new file mode 100644 index 0000000000..dcf66d92fe --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_supported.cf.sub @@ -0,0 +1,44 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module_with_action_policy.sh"; +} + + +bundle agent test +{ + vars: + "test_string" + string => "hello, modules"; + + example: + "$(G.testfile)" + message => "$(test_string)", + action => policy("warn"); +} + +body action policy(pol) +{ + action_policy => "${pol}"; +} + diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_unsupported.cf b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_unsupported.cf new file mode 100644 index 0000000000..52b33ccc1d --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_unsupported.cf @@ -0,0 +1,34 @@ +###################################################### +# +# Test of a custom promise with 'action_policy => "warn"' and a module that +# doesn't implement action_policy handling. +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3433" } + string => "Test that action_policy produces errors when used with custom promise modules not supporting it"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) -D AUTO -KIf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output(".*error: Not making changes.*", ".*CRITICAL:.*", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_unsupported.cf.sub b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_unsupported.cf.sub new file mode 100644 index 0000000000..43ca335d53 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/explicit_warn_unsupported.cf.sub @@ -0,0 +1,44 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module_without_action_policy.sh"; +} + + +bundle agent test +{ + vars: + "test_string" + string => "hello, modules"; + + example: + "$(G.testfile)" + message => "$(test_string)", + action => policy("warn"); +} + +body action policy(pol) +{ + action_policy => "${pol}"; +} + diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_supported.cf b/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_supported.cf new file mode 100644 index 0000000000..b8dd3d28ba --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_supported.cf @@ -0,0 +1,34 @@ +###################################################### +# +# Test of a custom promise with '--simulate' and a module that +# implements action_policy handling. +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3433" } + string => "Test that --simulate can be used with custom promise modules supporting it"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) --simulate manifest -D AUTO -KIf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output(".*warning: Should.*", ".*CRITICAL:.*", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_supported.cf.sub b/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_supported.cf.sub new file mode 100644 index 0000000000..dcf66d92fe --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_supported.cf.sub @@ -0,0 +1,44 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module_with_action_policy.sh"; +} + + +bundle agent test +{ + vars: + "test_string" + string => "hello, modules"; + + example: + "$(G.testfile)" + message => "$(test_string)", + action => policy("warn"); +} + +body action policy(pol) +{ + action_policy => "${pol}"; +} + diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_unsupported.cf b/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_unsupported.cf new file mode 100644 index 0000000000..1232467af5 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_unsupported.cf @@ -0,0 +1,34 @@ +###################################################### +# +# Test of a custom promise with '--simulate' and a module that +# doesn't implement action_policy handling. +# +##################################################### +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### + +bundle agent test +{ + meta: + "description" -> { "CFE-3433" } + string => "Test that --simulate produces errors when used with custom promise modules not supporting it"; + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; +} + +bundle agent check +{ + vars: + "command" string => "$(sys.cf_agent) --simulate manifest -D AUTO -KIf $(this.promise_filename).sub"; + + methods: + "check" + usebundle => dcs_passif_output(".*error: Not making changes.*", ".*CRITICAL:.*", + $(command), $(this.promise_filename)); +} diff --git a/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_unsupported.cf.sub b/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_unsupported.cf.sub new file mode 100644 index 0000000000..43ca335d53 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/23_action_policy/simulate_unsupported.cf.sub @@ -0,0 +1,44 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent init +{ + files: + "$(G.testfile)" + delete => init_delete; +} + +body delete init_delete +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +promise agent example +{ + interpreter => "/bin/bash"; + path => "$(this.promise_dirname)/example_module_without_action_policy.sh"; +} + + +bundle agent test +{ + vars: + "test_string" + string => "hello, modules"; + + example: + "$(G.testfile)" + message => "$(test_string)", + action => policy("warn"); +} + +body action policy(pol) +{ + action_policy => "${pol}"; +} + diff --git a/tests/acceptance/30_custom_promise_types/bad_import.py b/tests/acceptance/30_custom_promise_types/bad_import.py new file mode 100644 index 0000000000..b344ed1e01 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/bad_import.py @@ -0,0 +1 @@ +import non_existing diff --git a/tests/acceptance/30_custom_promise_types/cat_module.sh b/tests/acceptance/30_custom_promise_types/cat_module.sh new file mode 100644 index 0000000000..53f0a3c073 --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/cat_module.sh @@ -0,0 +1,86 @@ +log() { + echo "$1" >>/tmp/module.log +} + +reset_operation() { + # reset operation name to default value + request_operation="" +} + +handle_input_line() { + # Split the line of input on the first '=' into 2 - key and value + IFS='=' read -r key value <<< "$1" + + # save operation name, if that's what the line is about + if [ "$key" = operation ]; then + request_operation="$value" + fi +} + +receive_request() { + # Read lines from input until empty line + # Call handle_input_line for each non-empty line + while IFS='$\n' read -r line; do + # Different CIs (Travis and Jenkins) run cf-agent with different verbosity settings. + # This test accepts two verbosity settings, replacing one of them with another. + log "`echo "$line" | sed 's/^log_level=notice$/log_level=info/'`" + if [ "x$line" = "x" ] ; then + break + fi + handle_input_line "$line" # Parses a key=value pair + done +} + +write_response() { + echo "operation=$request_operation" + echo "result=$response_result" + echo "" +} + +perform_operation() { + case "$request_operation" in + validate_promise) + response_result="valid" ;; + evaluate_promise) + response_result="kept" ;; + terminate) + response_result="success" + write_response + exit 0 + ;; + *) + response_result="error" ;; + esac + write_response +} + +handle_request() { + reset_state # 1. Reset global variables + receive_request # 2. Receive / parse an operation from agent + perform_operation # 3. Perform operation (validate, evaluate, terminate) +} + +skip_header() { + # Skip until (and including) the first empty line + while IFS='$\n' read -r line; do + # save header to the log, stripping CFEngine version + log "`echo "$line" | sed 's/ 3\.[0-9][0-9]\.[0-9][^ ]* / xxx /'`" + if [ "x$line" = "x" ] ; then + return; + fi + done +} + +# Skip the protocol header given by agent: +skip_header + +# Write our header to request line based protocol: +echo "example_promises 0.0.1 v1 line_based" +echo "" + +# Loop indefinitely, handling requests: +while true; do + handle_request +done + +# Should never get here. diff --git a/tests/acceptance/30_custom_promise_types/custom_promise_binary.c b/tests/acceptance/30_custom_promise_types/custom_promise_binary.c new file mode 100644 index 0000000000..089e39a28b --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/custom_promise_binary.c @@ -0,0 +1,29 @@ +#include + +static inline void eatline(FILE *stream) +{ + int c; + while ((c = getc(stream)) != EOF && c != '\n') + ; +} + +int main() +{ + printf("binary 0.0.1 v1 line_based\n"); + printf("\n"); + + eatline(stdin); + + printf("operation=validate_promise\n"); + printf("promiser=foobar\n"); + printf("result=valid\n"); + printf("\n"); + + eatline(stdin); + + printf("operation=evaluate_promise\n"); + printf("promiser=foobar\n"); + printf("result=kept\n"); + printf("\n"); + return 0; +} diff --git a/tests/acceptance/30_custom_promise_types/example_module.sh b/tests/acceptance/30_custom_promise_types/example_module.sh new file mode 100644 index 0000000000..52a1e7c02f --- /dev/null +++ b/tests/acceptance/30_custom_promise_types/example_module.sh @@ -0,0 +1,171 @@ +reset_state() { + # Set global variables before we begin another request + + # Variables parsed directly from request: + request_operation="" + request_log_level="" + request_promise_type="" + request_promiser="" + request_attribute_message="" + + # Variables to put into response: + response_result="" + + # Other state: + saw_unknown_key="no" + saw_unknown_attribute="no" +} + +handle_input_line() { + + # Split the line of input on the first '=' into 2 - key and value + IFS='=' read -r key value <<< "$1" + + case "$key" in + operation) + request_operation="$value" ;; + log_level) + request_log_level="$value" ;; + promise_type) + request_promise_type="$value" ;; + promiser) + request_promiser="$value" ;; + attribute_message) + request_attribute_message="$value" ;; + attribute_*) + attribute_name=${key#"attribute_"} + log error "Unknown attribute: '$attribute_name'" + saw_unknown_attribute="yes" ;; + *) + saw_unknown_key="yes" ;; + esac +} + +receive_request() { + # Read lines from input until empty line + # Call handle_input_line for each non-empty line + while IFS='$\n' read -r line; do + if [ "x$line" = "x" ] ; then + break + fi + handle_input_line "$line" # Parses a key=value pair + done +} + +write_response() { + echo "operation=$request_operation" + echo "result=$response_result" + echo "" +} + +operation_terminate() { + response_result="success" + write_response + exit 0 +} + +operation_validate() { + response_result="valid" + if [ "$saw_unknown_attribute" != "no" ] ; then + response_result="invalid" + fi + + if [ "$request_promiser" = "" ] ; then + log error "Promiser must be non-empty" + response_result="invalid" + fi + + if [ "$request_attribute_message" = "" ] ; then + log error "Attribute 'message' is missing or empty" + response_result="invalid" + fi + + write_response +} + +operation_evaluate() { + local safe_promiser="$(echo "$request_promiser" | sed 's/,/_/g')" + local classes="" + + local existed_before=0 + if [ -f "$request_promiser" ]; then + existed_before=1 + fi + + if grep -q "$request_attribute_message" "$request_promiser" 2>/dev/null ; then + response_result="kept" + classes="${safe_promiser}_content_as_promised" + else + response_result="repaired" + echo "$request_attribute_message" > "$request_promiser" && { + printf "log_info=Updated file '%s' with content '%s'\n" "$request_promiser" "$request_attribute_message" + if [ $existed_before = 0 ]; then + classes="${safe_promiser}_created,${safe_promiser}_content_updated" + else + classes="${safe_promiser}_content_updated" + fi + } || response_result="not_kept" + fi + + if ! grep -q "$request_attribute_message" "$request_promiser" 2>/dev/null ; then + response_result="not_kept" + if [ -z "$classes" ]; then + classes="${safe_promiser}_content_update_failed" + else + classes="${classes},${safe_promiser}_content_update_failed" + fi + fi + + if [ -n "$classes" ]; then + echo "result_classes=$classes" + fi + write_response +} + +operation_unknown() { + response_result="error" + log error "Promise module received unexpected operation: $request_operation" + write_response +} + +perform_operation() { + case "$request_operation" in + validate_promise) + operation_validate ;; + evaluate_promise) + operation_evaluate ;; + terminate) + operation_terminate ;; + *) + operation_unknown ;; + esac +} + +handle_request() { + reset_state # 1. Reset global variables + receive_request # 2. Receive / parse an operation from agent + perform_operation # 3. Perform operation (validate, evaluate, terminate) +} + +skip_header() { + # Skip until (and including) the first empty line + while IFS='$\n' read -r line; do + if [ "x$line" = "x" ] ; then + return; + fi + done +} + +# Skip the protocol header given by agent: +skip_header + +# Write our header to request line based protocol: +echo "example_promises 0.0.1 v1 line_based" +echo "" + +# Loop indefinitely, handling requests: +while true; do + handle_request +done + +# Should never get here. diff --git a/tests/acceptance/31_tickets/CFE-2367/1/test.cf b/tests/acceptance/31_tickets/CFE-2367/1/test.cf new file mode 100644 index 0000000000..8edf53bf64 --- /dev/null +++ b/tests/acceptance/31_tickets/CFE-2367/1/test.cf @@ -0,0 +1,34 @@ +body file control +{ + inputs => { + "../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} +bundle agent test +{ + meta: + "description" -> { "CFE-2367" } + string => "cf-agent should not fatally error when fileexists() is given an undefined variable as a parameter"; + + files: + "$(G.testfile)" + content => "CFE-2367", + unless => fileexists( $(testfile) ); # testfile is not defined, in the + # original ticket filed this caused + # a fatal error +} + +bundle agent check +{ + methods: + # Since this was testing for the absence of a fatal error, if we get to + # this point, we pass. + "Pass" + usebundle => dcs_pass("$(this.promise_filename)" ); +} diff --git a/tests/acceptance/31_tickets/CFE-2367/2/test.cf b/tests/acceptance/31_tickets/CFE-2367/2/test.cf new file mode 100644 index 0000000000..928450022c --- /dev/null +++ b/tests/acceptance/31_tickets/CFE-2367/2/test.cf @@ -0,0 +1,40 @@ +body file control +{ + inputs => { + "../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} +bundle agent test +{ + meta: + "description" -> { "CFE-2367" } + string => "cf-agent should not fatally error when fileexists() is given an undefined variable as a parameter"; + + files: + "$(G.testfile)" + content => "CFE-2367", + unless => and( fileexists( $(testfile) ) ); # testfile is not defined, + # in the original ticket + # filed this caused a fatal + # error and the workaround + # was to wrap the function + # call with and(), so we + # simply test that as in the + # original case, there is no + # fatal error. +} + +bundle agent check +{ + methods: + # Since this was testing for the absence of a fatal error, if we get to + # this point, we pass. + "Pass" + usebundle => dcs_pass("$(this.promise_filename)" ); +} diff --git a/tests/acceptance/31_tickets/CFE-3987/1/before_test.xml.txt b/tests/acceptance/31_tickets/CFE-3987/1/before_test.xml.txt new file mode 100644 index 0000000000..e757010baa --- /dev/null +++ b/tests/acceptance/31_tickets/CFE-3987/1/before_test.xml.txt @@ -0,0 +1,49 @@ + + + + Atlassian JIRA Web Application + The Atlassian JIRA web application - see http://www.atlassian.com/software/jira for more information + + + + + + + + + + JiraImportProgressFilter + com.atlassian.jira.web.filters.JiraImportProgressFilter + + + + + + JiraFirstFilter + com.atlassian.jira.web.filters.JiraFirstFilter + + + + + + JiraLastFilter + com.atlassian.jira.web.filters.JiraLastFilter + + + + + diff --git a/tests/acceptance/31_tickets/CFE-3987/1/test.cf b/tests/acceptance/31_tickets/CFE-3987/1/test.cf new file mode 100644 index 0000000000..d6411fe156 --- /dev/null +++ b/tests/acceptance/31_tickets/CFE-3987/1/test.cf @@ -0,0 +1,72 @@ +body file control +{ + inputs => { + "../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent init +{ + files: + # Seed the file we will exercise the test on + "$(G.testfile)" + copy_from => local_dcp( "$(this.promise_dirname)/before_test.xml.txt" ); +} +bundle agent test +{ + meta: + "description" + string => "edit_line doesn't insert text immediately before a selected region if a location body only explicitly sets before_after => 'before'", + meta => { "CFE-3987" }; + + "test_soft_fail" + string => "any", + meta => { "CFE-3987" }; + + vars: + "seed_file" string => "$(this.promise_dirname)/before_test.xml.txt"; + "test_file" string => "$(G.testfile)"; + + files: + "$(test_file)" + edit_line => CFE_3987; +} + +bundle agent check +{ + methods: + "Pass/FAIL" + usebundle => dcs_check_regcmp( + ".*INSERT\sME\R\s+ + Atlassian JIRA Web Application + The Atlassian JIRA web application - see http://www.atlassian.com/software/jira for more information + + + + + + + + + + JiraImportProgressFilter + com.atlassian.jira.web.filters.JiraImportProgressFilter + + + + + + JiraFirstFilter + com.atlassian.jira.web.filters.JiraFirstFilter + + + + + + JiraLastFilter + com.atlassian.jira.web.filters.JiraLastFilter + + + + + diff --git a/tests/acceptance/31_tickets/CFE-3987/2/test.cf b/tests/acceptance/31_tickets/CFE-3987/2/test.cf new file mode 100644 index 0000000000..87b9ea0721 --- /dev/null +++ b/tests/acceptance/31_tickets/CFE-3987/2/test.cf @@ -0,0 +1,69 @@ +body file control +{ + inputs => { + "../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent init +{ + files: + # Seed the file we will exercise the test on + "$(G.testfile)" + copy_from => local_dcp( "$(this.promise_dirname)/before_test.xml.txt" ); +} +bundle agent test +{ + meta: + "description" + string => "Workaround for bug where edit_line doesn't insert text immediately before a selected region if a location body only explicitly sets before_after => 'before'", + meta => { "CFE-3987" }; + + vars: + "seed_file" string => "$(this.promise_dirname)/before_test.xml.txt"; + "test_file" string => "$(G.testfile)"; + + files: + "$(test_file)" + edit_line => CFE_3987; +} + +bundle agent check +{ + methods: + "Pass/FAIL" + usebundle => dcs_check_regcmp( + ".*INSERT\sME\R\s+ + Atlassian JIRA Web Application + The Atlassian JIRA web application - see http://www.atlassian.com/software/jira for more information + + + + + + + + + + JiraImportProgressFilter + com.atlassian.jira.web.filters.JiraImportProgressFilter + + + + + + JiraFirstFilter + com.atlassian.jira.web.filters.JiraFirstFilter + + + + + + JiraLastFilter + com.atlassian.jira.web.filters.JiraLastFilter + + + + + diff --git a/tests/acceptance/31_tickets/CFE-3988/1/test.cf b/tests/acceptance/31_tickets/CFE-3988/1/test.cf new file mode 100644 index 0000000000..f67749e6ed --- /dev/null +++ b/tests/acceptance/31_tickets/CFE-3988/1/test.cf @@ -0,0 +1,80 @@ +body file control +{ + inputs => { + "../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent init +{ + files: + # Seed the file we will exercise the test on + "$(G.testfile)" + copy_from => local_dcp( "$(this.promise_dirname)/before_test.xml.txt" ); +} +bundle agent test +{ + meta: + "description" + string => "edit_line should insert after the last matched line when selecting a region where the end delimiter is included", + meta => { "CFE-3988" }; + + "test_soft_fail" + string => "any", + meta => { "CFE-3988" }; + + vars: + "seed_file" string => "$(this.promise_dirname)/before_test.xml.txt"; + "test_file" string => "$(G.testfile)"; + + files: + "$(test_file)" + edit_line => CFE_3988; +} + +bundle agent check +{ + methods: + # We expect to find lines that look something like this: + # THIS MUST BE THE LAST FILTER IN THE DEFINED CHAIN + # ===================================================== --> + #INSERT ME + + "Pass/FAIL" + usebundle => dcs_check_regcmp( + ".*\s+THIS MUST BE THE LAST FILTER IN THE DEFINED CHAIN\R\s+=+\s+-->\RINSERT\sME.*", + readfile( "$(test.test_file)" ), + $(this.promise_filename), + "no" + ); +} + +bundle edit_line CFE_3988 +{ + insert_lines: + "INSERT ME" + select_region => my_comment_last_filter, + location => my_location_after_comment_last_filter; +} +body location my_location_after_comment_last_filter +# @brief Editing occurs after the last line in the selected region +{ + before_after => "after"; + first_last => "last"; + select_line_matching => ".*"; +} + +body select_region my_comment_last_filter +{ + select_start => "\s+THIS MUST BE THE LAST FILTER IN THE DEFINED CHAIN"; + select_end => "\s+=+\s+-->"; + include_start_delimiter => "true"; + include_end_delimiter => "true"; + select_end_match_eof => "false"; +} diff --git a/tests/acceptance/31_tickets/ENT-8788/1/desired-result.xml.txt b/tests/acceptance/31_tickets/ENT-8788/1/desired-result.xml.txt new file mode 100644 index 0000000000..ae78051030 --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8788/1/desired-result.xml.txt @@ -0,0 +1,189 @@ + + + + Atlassian JIRA Web Application + The Atlassian JIRA web application - see http://www.atlassian.com/software/jira for more information + + + + + + + + + + JiraImportProgressFilter + com.atlassian.jira.web.filters.JiraImportProgressFilter + + + + SetupProgressFilter + com.atlassian.jira.web.filters.SetupProgressFilter + + + + StartupProgressFilter + com.atlassian.jira.web.filters.StartupProgressFilter + + + + + JiraFirstFilter + com.atlassian.jira.web.filters.JiraFirstFilter + + + + MetricsCollectorFilter + com.atlassian.jira.servermetrics.MetricsCollectorFilter + + + + MultipartBoundaryCheckFilter + com.atlassian.jira.web.filters.MultipartBoundaryCheckFilter + + + + filter-plugin-dispatcher-before-dispatch-include + com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter + + location + before-dispatch + + + dispatcher + INCLUDE + + + + + filter-plugin-dispatcher-before-dispatch-error + com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter + + location + before-dispatch + + + dispatcher + ERROR + + + + + + + MyLoginFilter + com.atlassian.jira.authenticator.my.MyLoginFilter + + + + + MyLoginFilter + /* + REQUEST + FORWARD + + + + JiraLastFilter + com.atlassian.jira.web.filters.JiraLastFilter + + + + + + JiraImportProgressFilter + /importprogress + + + + + + + + + com.atlassian.jira.web.ServletContextProviderListener + + + + + appstatus + com.atlassian.jira.servlet.ApplicationStatusServlet + + + + jsp.func.service.service_002dexecutor_jsp + /func/service/service-executor.jsp + + + + + 600 + + + + + + wsdl + text/xml + + + + + default.jsp + index.html + + + + + 401 + /display-error + + + + + + webwork + /WEB-INF/tld/webwork.tld + + + + sitemesh-page + /WEB-INF/tld/sitemesh-page.tld + + + sitemesh-decorator + /WEB-INF/tld/sitemesh-decorator.tld + + + jiratags + /WEB-INF/tld/atlassian-jira-tags.tld + + + + + + diff --git a/tests/acceptance/31_tickets/ENT-8788/1/main.cf b/tests/acceptance/31_tickets/ENT-8788/1/main.cf new file mode 100644 index 0000000000..d02bf2652b --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8788/1/main.cf @@ -0,0 +1,75 @@ +body file control +{ + inputs => { + "../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent init +{ + vars: + "original_file" string => "$(this.promise_dirname)/start.xml.txt"; + "test_file" string => "$(sys.workdir)/start.xml.test"; + files: + "$(test_file)" + copy_from => local_cp("${original_file}"); +} + +bundle agent test +{ + meta: + "description" + string => "Inserting content that contains blank lines with file_preserve_block before select_line_matching does not split inserted content before and after select_line_matching", + meta => { "ENT-8788" }; + + vars: + "new_content_file" string => "$(this.promise_dirname)/newcontent.xml.txt"; + + files: + "$(init.test_file)" + create => "false", + edit_defaults => size("500k"), + edit_line => insert_file_as_block_relative_to_first_or_last_line( + "$(new_content_file)", # File with content to insert if not found + "before", # Relative position to insert content (before|after) + "last", # which line match will we insert based on? + "\s+" # Regex to match the line we want to insert relative to + ); +} + +bundle agent check +{ + methods: + + "Pass/Fail" usebundle => dcs_check_diff($(init.test_file), + "$(this.promise_dirname)/desired-result.xml.txt", + $(this.promise_filename)); +} + +bundle edit_line insert_file_as_block_relative_to_first_or_last_line(templatefile, relative_location, first_or_last, location_line_regex) +{ + insert_lines: + + "$(templatefile)" + comment => "Insert the template file into the file being edited", + insert_type => "file_preserve_block", + location => location_before_after_first_or_last_line( $(relative_location), $(first_or_last), $(location_line_regex) ); +} +body location location_before_after_first_or_last_line(relative_position, first_or_last, regex_matching_line) +{ + before_after => "$(relative_position)"; + first_last => "$(first_or_last)"; + select_line_matching => "$(regex_matching_line)"; +} + +body edit_defaults size(bigenough) +{ + max_file_size => "$(bigenough)"; +} + diff --git a/tests/acceptance/31_tickets/ENT-8788/1/newcontent.xml.txt b/tests/acceptance/31_tickets/ENT-8788/1/newcontent.xml.txt new file mode 100644 index 0000000000..b77d8f8770 --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8788/1/newcontent.xml.txt @@ -0,0 +1,14 @@ + + + MyLoginFilter + com.atlassian.jira.authenticator.my.MyLoginFilter + + + + + MyLoginFilter + /* + REQUEST + FORWARD + + diff --git a/tests/acceptance/31_tickets/ENT-8788/1/start.xml.txt b/tests/acceptance/31_tickets/ENT-8788/1/start.xml.txt new file mode 100644 index 0000000000..f779044aeb --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8788/1/start.xml.txt @@ -0,0 +1,175 @@ + + + + Atlassian JIRA Web Application + The Atlassian JIRA web application - see http://www.atlassian.com/software/jira for more information + + + + + + + + + + JiraImportProgressFilter + com.atlassian.jira.web.filters.JiraImportProgressFilter + + + + SetupProgressFilter + com.atlassian.jira.web.filters.SetupProgressFilter + + + + StartupProgressFilter + com.atlassian.jira.web.filters.StartupProgressFilter + + + + + JiraFirstFilter + com.atlassian.jira.web.filters.JiraFirstFilter + + + + MetricsCollectorFilter + com.atlassian.jira.servermetrics.MetricsCollectorFilter + + + + MultipartBoundaryCheckFilter + com.atlassian.jira.web.filters.MultipartBoundaryCheckFilter + + + + filter-plugin-dispatcher-before-dispatch-include + com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter + + location + before-dispatch + + + dispatcher + INCLUDE + + + + + filter-plugin-dispatcher-before-dispatch-error + com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter + + location + before-dispatch + + + dispatcher + ERROR + + + + + + JiraLastFilter + com.atlassian.jira.web.filters.JiraLastFilter + + + + + + JiraImportProgressFilter + /importprogress + + + + + + + + + com.atlassian.jira.web.ServletContextProviderListener + + + + + appstatus + com.atlassian.jira.servlet.ApplicationStatusServlet + + + + jsp.func.service.service_002dexecutor_jsp + /func/service/service-executor.jsp + + + + + 600 + + + + + + wsdl + text/xml + + + + + default.jsp + index.html + + + + + 401 + /display-error + + + + + + webwork + /WEB-INF/tld/webwork.tld + + + + sitemesh-page + /WEB-INF/tld/sitemesh-page.tld + + + sitemesh-decorator + /WEB-INF/tld/sitemesh-decorator.tld + + + jiratags + /WEB-INF/tld/atlassian-jira-tags.tld + + + + + + diff --git a/tests/acceptance/31_tickets/ENT-8788/2/desired-result.xml.txt b/tests/acceptance/31_tickets/ENT-8788/2/desired-result.xml.txt new file mode 100644 index 0000000000..df38b2e941 --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8788/2/desired-result.xml.txt @@ -0,0 +1,187 @@ + + + + Atlassian JIRA Web Application + The Atlassian JIRA web application - see http://www.atlassian.com/software/jira for more information + + + + + + + + + + JiraImportProgressFilter + com.atlassian.jira.web.filters.JiraImportProgressFilter + + + + SetupProgressFilter + com.atlassian.jira.web.filters.SetupProgressFilter + + + + StartupProgressFilter + com.atlassian.jira.web.filters.StartupProgressFilter + + + + + JiraFirstFilter + com.atlassian.jira.web.filters.JiraFirstFilter + + + + MetricsCollectorFilter + com.atlassian.jira.servermetrics.MetricsCollectorFilter + + + + MultipartBoundaryCheckFilter + com.atlassian.jira.web.filters.MultipartBoundaryCheckFilter + + + + filter-plugin-dispatcher-before-dispatch-include + com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter + + location + before-dispatch + + + dispatcher + INCLUDE + + + + + filter-plugin-dispatcher-before-dispatch-error + com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter + + location + before-dispatch + + + dispatcher + ERROR + + + + + + + MyLoginFilter + com.atlassian.jira.authenticator.my.MyLoginFilter + + + MyLoginFilter + /* + REQUEST + FORWARD + + + + JiraLastFilter + com.atlassian.jira.web.filters.JiraLastFilter + + + + + + JiraImportProgressFilter + /importprogress + + + + + + + + + com.atlassian.jira.web.ServletContextProviderListener + + + + + appstatus + com.atlassian.jira.servlet.ApplicationStatusServlet + + + + jsp.func.service.service_002dexecutor_jsp + /func/service/service-executor.jsp + + + + + 600 + + + + + + wsdl + text/xml + + + + + default.jsp + index.html + + + + + 401 + /display-error + + + + + + webwork + /WEB-INF/tld/webwork.tld + + + + sitemesh-page + /WEB-INF/tld/sitemesh-page.tld + + + sitemesh-decorator + /WEB-INF/tld/sitemesh-decorator.tld + + + jiratags + /WEB-INF/tld/atlassian-jira-tags.tld + + + + + + diff --git a/tests/acceptance/31_tickets/ENT-8788/2/main.cf b/tests/acceptance/31_tickets/ENT-8788/2/main.cf new file mode 100644 index 0000000000..0dd281f65b --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8788/2/main.cf @@ -0,0 +1,75 @@ +body file control +{ + inputs => { + "../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent init +{ + vars: + "original_file" string => "$(this.promise_dirname)/start.xml.txt"; + "test_file" string => "$(sys.workdir)/start.xml.test"; + files: + "$(test_file)" + copy_from => local_cp("${original_file}"); +} + +bundle agent test +{ + meta: + "description" + string => "Inserting content that does not contain blank lines with file_preserve_block before select_line_matching works as expected", + meta => { "ENT-8788" }; + + vars: + "new_content_file" string => "$(this.promise_dirname)/newcontent.xml.txt"; + + files: + "$(init.test_file)" + create => "false", + edit_defaults => size("500k"), + edit_line => insert_file_as_block_relative_to_first_or_last_line( + "$(new_content_file)", # File with content to insert if not found + "before", # Relative position to insert content (before|after) + "last", # which line match will we insert based on? + "\s+" # Regex to match the line we want to insert relative to + ); +} + +bundle agent check +{ + methods: + + "Pass/Fail" usebundle => dcs_check_diff($(init.test_file), + "$(this.promise_dirname)/desired-result.xml.txt", + $(this.promise_filename)); +} + +bundle edit_line insert_file_as_block_relative_to_first_or_last_line(templatefile, relative_location, first_or_last, location_line_regex) +{ + insert_lines: + + "$(templatefile)" + comment => "Insert the template file into the file being edited", + insert_type => "file_preserve_block", + location => location_before_after_first_or_last_line( $(relative_location), $(first_or_last), $(location_line_regex) ); +} +body location location_before_after_first_or_last_line(relative_position, first_or_last, regex_matching_line) +{ + before_after => "$(relative_position)"; + first_last => "$(first_or_last)"; + select_line_matching => "$(regex_matching_line)"; +} + +body edit_defaults size(bigenough) +{ + max_file_size => "$(bigenough)"; +} + diff --git a/tests/acceptance/31_tickets/ENT-8788/2/newcontent.xml.txt b/tests/acceptance/31_tickets/ENT-8788/2/newcontent.xml.txt new file mode 100644 index 0000000000..1b0224d437 --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8788/2/newcontent.xml.txt @@ -0,0 +1,12 @@ + + + MyLoginFilter + com.atlassian.jira.authenticator.my.MyLoginFilter + + + MyLoginFilter + /* + REQUEST + FORWARD + + diff --git a/tests/acceptance/31_tickets/ENT-8788/2/start.xml.txt b/tests/acceptance/31_tickets/ENT-8788/2/start.xml.txt new file mode 100644 index 0000000000..f779044aeb --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8788/2/start.xml.txt @@ -0,0 +1,175 @@ + + + + Atlassian JIRA Web Application + The Atlassian JIRA web application - see http://www.atlassian.com/software/jira for more information + + + + + + + + + + JiraImportProgressFilter + com.atlassian.jira.web.filters.JiraImportProgressFilter + + + + SetupProgressFilter + com.atlassian.jira.web.filters.SetupProgressFilter + + + + StartupProgressFilter + com.atlassian.jira.web.filters.StartupProgressFilter + + + + + JiraFirstFilter + com.atlassian.jira.web.filters.JiraFirstFilter + + + + MetricsCollectorFilter + com.atlassian.jira.servermetrics.MetricsCollectorFilter + + + + MultipartBoundaryCheckFilter + com.atlassian.jira.web.filters.MultipartBoundaryCheckFilter + + + + filter-plugin-dispatcher-before-dispatch-include + com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter + + location + before-dispatch + + + dispatcher + INCLUDE + + + + + filter-plugin-dispatcher-before-dispatch-error + com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter + + location + before-dispatch + + + dispatcher + ERROR + + + + + + JiraLastFilter + com.atlassian.jira.web.filters.JiraLastFilter + + + + + + JiraImportProgressFilter + /importprogress + + + + + + + + + com.atlassian.jira.web.ServletContextProviderListener + + + + + appstatus + com.atlassian.jira.servlet.ApplicationStatusServlet + + + + jsp.func.service.service_002dexecutor_jsp + /func/service/service-executor.jsp + + + + + 600 + + + + + + wsdl + text/xml + + + + + default.jsp + index.html + + + + + 401 + /display-error + + + + + + webwork + /WEB-INF/tld/webwork.tld + + + + sitemesh-page + /WEB-INF/tld/sitemesh-page.tld + + + sitemesh-decorator + /WEB-INF/tld/sitemesh-decorator.tld + + + jiratags + /WEB-INF/tld/atlassian-jira-tags.tld + + + + + + diff --git a/tests/acceptance/31_tickets/ENT-8818/1/README.org b/tests/acceptance/31_tickets/ENT-8818/1/README.org new file mode 100644 index 0000000000..c2a06cd1e4 --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8818/1/README.org @@ -0,0 +1,5 @@ +#+title: ENT-8818 + +This test reproduces a crash (assert). Our test system does not support suppression of crashing tests, instead they must live in staging directories and be moved back out when they are fixed. This is unfortunate as it gives us little ability to run crahsing tests contunually and spot cases when they stop crashing un-expectedly. + +So, when a fix for this is ready, move staging/main.cf up to the level of this readme and delete this readme. diff --git a/tests/acceptance/31_tickets/ENT-8818/1/staging/main.cf b/tests/acceptance/31_tickets/ENT-8818/1/staging/main.cf new file mode 100644 index 0000000000..bcf6bc4968 --- /dev/null +++ b/tests/acceptance/31_tickets/ENT-8818/1/staging/main.cf @@ -0,0 +1,33 @@ +# NOTE: This test file must have it's be renamed from .x.cf to .cf when the bug is fixed. +body file control +{ + inputs => { + "../../../default.cf.sub", + }; +} +bundle agent __main__ +# If this is the policy entry (cf-agent --file) then this bundle will be run by default. +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent test +{ + meta: + "description" -> { "ENT-8818" } + string => "Referencing variables in a namespace that does not exist should not cause the agent to crash"; + + reports: + # NOTE: Here in the last variable I misspelled default as defult which is how I originally found this bug. + + "In $(this.namespace):$(this.bundle) $(const.dollar)(default:sys.cf_version_major) == $(defult:sys.cf_version_major)"; +} + +bundle agent check +{ + methods: + + # If we make ti this far we pass. + "Pass" usebundle => dcs_pass( $(this.promise_filename) ); +} diff --git a/tests/acceptance/DCS.org b/tests/acceptance/DCS.org new file mode 100644 index 0000000000..9d9f92d246 --- /dev/null +++ b/tests/acceptance/DCS.org @@ -0,0 +1,68 @@ +#+Title: Policy Test Framework + +The policy testing framework loaded by [[file:default.cf.sub][=default.cf.sub=]]. For full support of the +test framework this policy file should be included in each test and the +=bundlesequence= should be set to =default("$(this.promise_filename)")=. + +NOTE: Be sure the relative path to =default.cf.sub= +#+Caption: Common control template to be included in each test +#+BEGIN_SRC cfengine3 +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} +#+END_SRC + +[[file:dcs.cf.sub][=dcs.cf.sub=]] contains the majority of the policy testing framework. The +following bundles are useful for common test patterns. In all cases =test= +should be passed =$(this.promise_filename)=. + +- bundle agent =dcs_pass(test)= :: Pass the test. + +- bundle agent =dcs_fail(test)= :: FAIL the test. + +- bundle agent =dcs_passif(classname, test)= :: Pass if =classname= is defined. + +- bundle agent =dcs_passif_fileexists(file, test)= :: Pass if =file= exists. + +- bundle agent =dcs_passif_file_absent(file, test)= :: Pass if =file= is absent. + +- bundle agent =dcs_check_strcmp(strA, strB, test, expected_difference)= :: Pass/FAIL + based on =expected_difference= (=yes=/=no=) when the string =strA= is the + same as string =strB=. + +- bundle agent =dcs_check_regcmp(regex, thestring, test, expected_mismatch)= :: Pass/FAIL + based on =expected_mismatch= (=yes=/=no=) when the regular expression + =regex= matches the string =thestring=. + +- bundle agent =dcs_passif_output1(wanted, command, test)= :: Pass if =command= + output contains =wanted= string. + +- bundle agent =dcs_passif_output(wanted, unwanted, command, test)= :: Pass if + the regular expression =wanted= AND the regular expression =unwanted= + matches the =command= output. + +- bundle agent =dcs_check_diff(file1, file2, test)= :: Pass if =file1= and + =file2= do not differ. + +- bundle agent =dcs_check_diff_elements(set1, set2, test, expected_difference)= :: Pass if =set1= and + =set2= do not have different elements (order of elements not matter) + +- bundle agent =dcs_passif_expected(expected, not_expected, test)= :: =expected= + and =not_expected= can be a string of comma (without spaces) separated + classes. Pass if all expected classes are defined and none of the + unexpected classes are defined. + +- bundle agent =sorted_check_diff(file1, file2, test)= :: Lexically sort =file1= + and =file2=, pass if they are not different. This can be useful for testing + data that is not guaranteed to be returned in a specific order (like JSON). + +- bundle agent =dcs_check_diff_expected(file1, file2, test, expected_difference)= :: Pass/FAIL + based on =expected_difference= (=yes=/=no=) between =file1= and + =file2=. + +- bundle agent =xml_check_diff(file1, file2, test, expected_difference)= :: Pass/FAIL + based on =expected_difference= (=yes=/=no=) between xml =file1= and + xml =file2=. + diff --git a/tests/acceptance/Makefile.am b/tests/acceptance/Makefile.am new file mode 100644 index 0000000000..cc77519761 --- /dev/null +++ b/tests/acceptance/Makefile.am @@ -0,0 +1,113 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +SUBDIRS = 25_cf-execd + +AM_CPPFLAGS = \ + -I$(top_srcdir)/libntech/libutils \ + -I$(top_srcdir)/libcfecompat \ + -I$(top_srcdir)/libpromises \ + -I$(top_srcdir)/libcfnet \ + $(CORE_CPPFLAGS) $(ENTERPRISE_CPPFLAGS) + +AM_CFLAGS = $(CORE_CFLAGS) +AM_LDFLAGS = $(CORE_LDFLAGS) +LIBS = $(CORE_LIBS) + + +noinst_PROGRAMS = + +noinst_LTLIBRARIES = libmock_package_manager.la +libmock_package_manager_la_SOURCES = mock_package_manager.c +libmock_package_manager_la_LIBADD = ../../libpromises/libpromises.la + +if !WINDOWS +noinst_PROGRAMS += mock_package_manager + +mock_package_manager_SOURCES = +mock_package_manager_LDADD = libmock_package_manager.la + +noinst_PROGRAMS += no_fds +no_fds_LDADD = ../../libntech/libutils/libutils.la + +noinst_PROGRAMS += custom_promise_binary +custom_promise_binary_SOURCES = 30_custom_promise_types/custom_promise_binary.c + +endif # !WINDOWS + +if HAVE_LIBXML2 +noinst_PROGRAMS += xml-c14nize + +xml_c14nize_CPPFLAGS = \ + -I$(top_srcdir)/libpromises \ + -I$(srcdir)/../../libntech/libutils \ + $(LIBXML2_CPPFLAGS) + +xml_c14nize_CFLAGS = $(LIBXML2_CFLAGS) +xml_c14nize_LDFLAGS = $(LIBXML2_LDFLAGS) +xml_c14nize_LDADD = ../../libntech/libutils/libutils.la $(LIBXML2_LIBS) +endif + +TESTS = +TESTS_ENVIRONMENT = env \ + AGENT=`pwd`/../../cf-agent/cf-agent \ + CF_PROMISES=`pwd`/../../cf-promises/cf-promises \ + CF_SERVERD=`pwd`/../../cf-serverd/cf-serverd \ + CF_KEY=`pwd`/../../cf-key/cf-key \ + CF_NET=`pwd`/../../cf-net/cf-net \ + CF_CHECK=`pwd`/../../cf-check/cf-check \ + RPMVERCMP=`pwd`/../../ext/rpmvercmp \ + MOCK_PACKAGE_MANAGER=`pwd`/mock_package_manager + +if !HAVE_LIBXML2 +TESTS_ENVIRONMENT += LIBXML2_TESTS=0 +endif + +if !HAVE_LIBCURL +TESTS_ENVIRONMENT += LIBCURL_TESTS=0 +endif + +# Keep the '+' in front for the command, needed for the sub-make +# to run in parallel; TODO fix "make -n" not working: +check-local: + + $(TESTS_ENVIRONMENT) MAKE=$(MAKE) $(srcdir)/testall + + +EXTRA_DIST = default.cf.sub dcs.cf.sub plucked.cf.sub run_with_server.cf.sub \ + testall dummy_etc write_args.sh \ + root-MD5=617eb383deffef843ea856b129d0a423.pub \ + root-MD5=617eb383deffef843ea856b129d0a423.priv + +# Recursively include all tests in the dist tarball and set proper permissions +EXTRA_DIST += $(wildcard [0-9]*) + +dist-hook: + chmod -R go-w $(distdir) + + +CLEANFILES = *.gcno *.gcda Makefile.testall + +pluck: + echo '### This is an auto-generated file, see Makefile.am and `make pluck` ###' > plucked.cf.sub + ../../contrib/cf-locate/cf-locate -f -p '( if_|run_|warn_only|INI_section|kept_successful_command|edit_line|set_user_field|set_variable_values\(|edit_field|set_line_based|location|replace_with|_cp|_dcp|empty|shell|immediate|perms| m\b|recurse|tidy| all|classes_generic|results| start|always|^value|edit_field line|insert_lines|(link|copy)from| file_mustache| (file|dir)_(copy|tidy|sync|make|empty|link|hardlink))' ../../../masterfiles/lib/*.cf >> plucked.cf.sub diff --git a/tests/acceptance/README b/tests/acceptance/README new file mode 100644 index 0000000000..1e61ad598c --- /dev/null +++ b/tests/acceptance/README @@ -0,0 +1,373 @@ +============================================================================== +CFEngine acceptance testsuite +============================================================================== + +CFEngine has an extensive testsuite which covers lot of functionality which can +be tested as a series of cf-agent runs. + +You are encouraged to run this testsuite on any new software/hardware +configuration, in order to + * verify that CFEngine functions correctly + * provide developers with reproducible way to fix any problems encountered in + new configurations / environment + +In case you find a bug you are encouraged to create tests in format of testsuite +which demonstrate bug found, so the test could be added to this testsuite and +checked for in the future. + +Note that the testall script generates JUnit style XML output for parsing by CI systems. + +https://llg.cubic.org/docs/junit/ + + +------------------------------------------------------------------------------ +Preparing for running tests +------------------------------------------------------------------------------ + +* Compile CFEngine. + - It is advised to use Tokyo Cabinet as it gives much better performance in + test suite over Berkeley DB. + +* Install fakeroot(1). If this tool is not available for your operating system, + you may use any other "fake root" environment or even sudo(1). Alternative + tools are specified by --gainroot option of `testall' script. Note that if you + use the --unsafe option (can damage your system), you may have to use + --gainroot=sudo in order to get correct results. + +* If you want output in color, set CFENGINE_COLOR to 1. If you want + diff output colorized, you also need to install the colordiff + utility. + +* If you plan to use `git bisect` to hunt for a bug, try + tests/acceptance/bisect-acceptance.pl, contributed by the + indomitable Chris Dituri + +------------------------------------------------------------------------------ +Running testsuite +------------------------------------------------------------------------------ + +All tests ought only to create files and directories in /tmp, and ought not to +modify other files. The exception to this rule are so called unsafe tests, which +reside in a special directory. More on unsafe tests below. + +Run + + ./testall --agent=$workdir/bin/cf-agent + +e.g. + + ./testall --agent=/var/cfengine/bin/cf-agent + +Testing will start. For every test case the name and result (failed / passed) +will be produced. At the end testing summary will be provided. + +Test runner creates the following log files: + + * test.log contains detailed information about each test case (name, standard + output/error contents, return code, and test status). + * summary.log contains summary information, like the one displayed during + testsuite run. + +Also a directory .succeeded will be created, containing stamps for each passed +test case, so test cases which passed before and failing in subsequent testsuite +run will be additionally marked in output as "(UNEXPECTED FAILURE)". + +You might run a subset of tests by passing either filenames: + + ./testall --agent=$workdir/bin/cf-agent 01_vars/01_basic/sysvars.cf + +or directories to 'testall': + + ./testall --agent=$workdir/bin/cf-agent 01_vars + +------------------------------------------------------------------------------ +Creating/editing test cases +------------------------------------------------------------------------------ + +Each test should be 100% standalone. If you include "default.cf.sub" in +inputs, then the bundlesequence is automatically defined, and your file +must contain at least 1 of the main bundles: + +* init - setup, create initial and hoped-for final states +* test - the actual test code +* check - the comparison of expected and actual results +* destroy - anything you want to do at the end like killing processes etc + +However if the test is really simple you might want to skip including +"default.cf.sub" in inputs. Then you should define your own +bundlesequence, e.g. {"test","check"}. It's recommended to avoid +including "default.cf.sub" when not needed, since the test will run much +faster. + +Look in default.cf for some standard check bundles (for example, to compare +files when testing file edits, or for cleaning up temporary files). + +For a test named XYZ, if the test runner finds the file XYZ.def.json, +that file will be copied to the input directory so it will be loaded +on startup. That lets you set hard classes and def variables and add +inputs before the test ever runs. + +Tests should be named with short names describing what they test, using lower- +case letters and underscores only. If the test is expected to generate an error +(that is, if they contan syntax errors or other faults), it should have an +additional '.x' suffix before '.cf' (e.g. 'string_vars.x.cf'). A crash will +still cause a failure. + +Tests which are not expected to pass yet (e.g. there is a bug in code which +prevents tests from passing) should be placed in 'staging' subdirectory in the +test directory where they belong. Such test cases will be only run if --staging +argument to ./testall is passed. + +Tests which modify the system outside of /tmp are so called unsafe tests, and +should be placed in the 'unsafe' subdirectory in the directory where they +belong. Such test cases have the potential to damage the host system and will +only be run if the --unsafe argument is given to ./testall. For the user's +protection, this option is needed even if the test file name is specified +directly on the command line. + +Tests which need network connectivity should be placed to 'network' +subdirectories. Those tests may be disabled by passing --no-network option to +'testall'. + +Tests which cannot be run in parallel with other tests should be put in a +'serial' subdirectory. This is necessary if the test depends on for example +a certain number of cf-agent processes running, or to open a network port that +may be shared with other tests. Unsafe tests are always carried out serially, +and do not need to be in a 'serial' subdirectory. You can also give individual +tests a name that contains "serial" as a word. + +Serial tests enforce strictly sorted order when they are encountered, so you can +use them to initialize some precondition for a set a of tests by sorting it +lexicographically before the others and giving it a name containing "serial". +The other tests can then be executed in parallel, unless they need to be serial +for other reasons. For example, you can use two serial tests, one to set up a +cf-serverd, and one to tear it down, and in between you put normal tests. Note +that timed tests (see below) have one small exception: If a test that is both +timed and serial is executed in the same "block" as other serial tests (that is, +when one or more serials tests follow another), it will always be executed +first. So put it in a timed directory as well, if you want to be absolutely +sure. + +NOTE: Since the class 'ok' is used in most tests, never create a persistent +class called 'ok' in any test. Persistent classes are cleaned up between test +runs, but better safe than sorry. + +All tests should contain three bundles: init, test and check. In the +"body common control" this file should be included, and bundlesequence +should be set to default("$(this.promise_filename)"). + +Output "$(this.promise_filename) Pass" for passing + and "$(this.promise_filename) FAIL" for failing. + +If you want to use tools like grep, diff, touch, etc, please use the +$(G.grep) format so that the correct path to the tool is chosen by +the test template. If a tool is missing you can add it to dcs.cf.sub. + +------------------------------------------------------------------------------ +Waiting in tests +------------------------------------------------------------------------------ + +If your test needs to wait for a significant amount of time, for example in +order for locks to expire, you should use the wait functionality in the test +suite. It requires three parts: + + 1. Your test needs to be put in a "timed" directory. + 2. Whenever you want to wait, use the + "dcs_wait($(this.promise_filename), )" method to wait the + specified number of seconds. + 3. Each test invocation will have a predefined class set, + "test_pass_", where is the current pass number starting + from one. This means you can wait several times. + +The test suite will keep track of time, and run other tests while your test is +waiting. Some things to look out for though: + + - During the wait time, your test is no longer running, so you cannot for + example do polling. + - You cannot leave daemons running while waiting, because it may interfere + with other tests. If you need that you will have to wait the traditional + way, by introducing sleeps in the policy itself. + - The timing is not guaranteed to be accurate to the second. The test will be + resumed as soon as the current test has finished running, but if it takes + a long time, this will add to the wait time. + +------------------------------------------------------------------------------ +Handling different platforms +------------------------------------------------------------------------------ + +For tests that need to be skipped on certain platforms, you can add +special meta variables to the *test* bundle. These are the +possible variable names: + + - test_skip_unsupported + Skips a test because it makes no sense on that platform (e.g. + symbolic links on Windows). + + - test_skip_needs_work + Skips a test because the test itself is not adapted to the + platform (even if the functionality exists). + + - test_soft_fail + - test_suppress_fail + - test_flakey_fail + Runs the test, but will accept failure. Use this when there is a + real failure, but it is acceptable for the time being. This + variable requires a meta tag on the variable set to + "redmine", where is a Redmine issue number. + There is a subtle difference between the three. Soft failures will + not be reported as a failure in the XML output, and is + appropriate for test cases that document incoming bug reports. + Suppressed failures will count as a failure, but it won't block + the build, and is appropriate for regressions or bad test + failures. + Flakey failures count in a category by themselves and won't block + the build. If any are present then a different exit code will be + produced from the test run so that CI runners can react accordingly. + +Additionally, a *description* meta variable can be added to the test to describe +its function. + + meta: + "description" -> { "CFE-2971" } + string => "Test that class expressions can be used to define classes via augments"; + +The rule of thumb is: + +* If you are writing an acceptance test for a (not yet fixed) bug in + Redmine, use test_soft_fail. + +* If you need the build to work, but the bug you are suppressing cannot + stay unfixed for long, use test_suppress_fail. + +In all cases, the variable is expected to be set to a class expression +suitable for ifvarclass, where a positive match means that the test +will be skipped. So the expression '"test_skip_needs_work" string => +"hpux|aix";' will skip the test on HP-UX and AIX, and nowhere else. + +Example: + bundle agent test + { + meta: + + # Indicate that this test should be skipped on hpux because the + # functionality is unsupported on that platform. + "test_skip_unsupported" string => "hpux"; + } + + bundle agent test + { + meta: + + # Indicates that the test should be skipped on hpux because the + # test needs to be adapted for the platform. + "test_skip_needs_work" string => "hpux"; + } + + bundle agent test + { + meta: + + # Indicate the test is expected to fail on hpux and aix, but + # should not cause the build to change from green. This is + # appropriate for test cases that document incoming bug reports. + "test_soft_fail" + string => "hpux|aix", + meta => { "redmine1234" }; + } + + bundle agent test + { + meta: + + # Indicate the test is expected to fail on hpux but will not + # block the build. This is appropriate for regressions or very + # bad bad test failures. + + "test_suppress_fail" + string => "hpux", + meta => { "redmine1234" }; + } + + +------------------------------------------------------------------------------ +Glossary +------------------------------------------------------------------------------ + +For purposes of testing, here is what our terms mean: + +Pass: the test did what we expected (whether that was setting a variable, +editing a file, killing or starting a process, or correctly failing to do +these actions in the light of existing conditions or attributes). Note that +in the case of tests that end in an 'x', a Pass is generated when the test +abnormally terminates and we wanted it to do that. + +FAIL: not doing what we wanted: either test finished and returned "FAIL" from +check bundle, or something went wrong - cf-agent might have dropped core, +cf-promises may have denied execution of the promises, etc. + +Soft fail: the test failed as expected by a "test_soft_fail" promise. + +Skipped: test is skipped due to be either explicitly disabled or being +Nova-specific and being run on Community cf-agent. + +------------------------------------------------------------------------------ +Example Test Skeleton +------------------------------------------------------------------------------ +body file control +{ + # Feel free to avoid including "default.cf.sub" and define your + # own bundlesequence for simple tests + + inputs => { "../default.cf.sub" }; +} + +####################################################### + +bundle agent init +# Initialize the environment and prepare for test +{ + +} + +bundle agent test +# Activate policy for behaviour you wish to inspect +{ + meta: + "description" -> { "CFE-1234" } + string => "What does this test?"; + + "test_soft_fail" + string => "Class_Expression|here", + meta => { "CFE-1234" }; +} + +bundle agent check +# Check the result of the test +{ + + # Pass/Fail requires a specific format: + # reports: "$(this.promise_filename) Pass"; + # reports: "$(this.promise_filename) FAIL"; + + # Consider using one of the dcs bundles + # methods: "Pass/Fail" usebundle => dcs_passif( "Namespace_Scoped_Class_Indicating_Test_Passed", $(this.promise_filename) ); + +} +bundle agent __main__ +# @brief The test entry +# Note: The testall script runs the agent with the acceptance test as the entry point, +##+begin_src sh :results output :exports both :dir ~/CFEngine/core/tests/acceptance +# pwd | sed 's/\/.*CFEngine\///' +# find . -name "*.cf*" | xargs chmod 600 +# cf-agent -Kf ./01_vars/02_functions/basename_1.cf --define AUTO +##+end_src sh +# +##+RESULTS: +#: core/tests/acceptance +#: R: /home/nickanderson/Northern.Tech/CFEngine/core/tests/acceptance/./01_vars/02_functions/basename_1.cf Pass +{ + methods: + "Run the default test bundle sequence (init,test,check,cleanup) if defined" + usebundle => default( $(this.promise_filename) ); +} +------------------------------------------------------------------------------ diff --git a/tests/acceptance/bisect-acceptance.pl b/tests/acceptance/bisect-acceptance.pl new file mode 100755 index 0000000000..103baee5e6 --- /dev/null +++ b/tests/acceptance/bisect-acceptance.pl @@ -0,0 +1,103 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Data::Dumper; +use Getopt::Long; +use File::Basename; +use Sys::Hostname; + +$|=1; # autoflush + +my %options = ( + help => 0, + fixup => 0, + make => "make -j3", + repo_root => "../../", + ); + +GetOptions(\%options, + "help|h!", + "fixup|f!", + "good=s", + "bad=s", + "make=s", + ); + +usage() if ($options{help}); + +unless (exists $options{good} && exists $options{bad}) +{ + usage(); +} + +my $rc = 0; +my $test = ""; +my $file = shift @ARGV; + +chdir $options{repo_root}; + +print qx/git bisect start/; + +if ($options{fixup}) +{ + $test = <<"TESTSTR"; +(cd ./tests/acceptance/; ./testall ${file} | grep -q "^.*\.cf Pass\$" && (test ! \$? && exit \$?) || exit 1) +TESTSTR + + # invert; we're trying to find a commit that fixed a bug. + print qx/git bisect good $options{bad}/; + print qx/git bisect bad $options{good}/; +} +else +{ + $test = <<"TESTSTR"; +(cd ./tests/acceptance/; ./testall ${file} | grep -q "^.*\.cf Pass\$" && exit \$? || exit 1) +TESTSTR + + print qx/git bisect good $options{good}/; + print qx/git bisect bad $options{bad}/; +} + +my $m = $options{make}; +my $script = "($m clean && ./autogen.sh && ./configure --enable-debug && $m || exit 125) && " . $test; + +system("git bisect run sh -c '$script'"); + +exit $rc; + +############ + +sub usage { + print < --bad= TEST.cf + +Automatically git bisect against the specified CFEngine acceptance test; +cleaning artifacts, regenerating autotools files, and rebuilding binaries +along the way. You need to provide a good and a bad commit SHA. + +By default hunts for the commit that introduced a bug. If you want the commit +that fixed a bug, use `--fixup` + + This forces the program to respect git's good and bad more like bzr's + notion of yes -vs- no... much less confusing when hunting for a commit + that _fixed_ an issue rather than introducing one. + +[-g|--good]: the commit-sha whose build produces known good results. +[-b|--bad]: the commit-sha whose build produces known bad results. +[-m|--make]: a make command (default $options{make}) + +This script expects to be run from inside tests/acceptance/. + +--- + +For the final argument, supply a CFEngine acceptance test to check against. + +Example invocation: + + ./bisect-acceptance.pl --fixup --good=HEAD --bad=ff4dcdc ./01_vars/01_basic/009.cf + +EOHIPPUS + + exit; +} diff --git a/tests/acceptance/dcs.cf.sub b/tests/acceptance/dcs.cf.sub new file mode 100644 index 0000000000..15bba3cd3d --- /dev/null +++ b/tests/acceptance/dcs.cf.sub @@ -0,0 +1,978 @@ +######################################################################## +# Acceptance test framework. +# +# See README for details about writing test cases. +######################################################################## + +bundle common G +{ + vars: + windows:: + "cwd" string => execresult("C:\windows\system32\cmd.exe /C cd", "noshell"), + meta => { "simulate_safe" }; + # Use for redirection, not as a real file. + "dev_null" string => "nul"; + "exeext" slist => { ".exe", ".bat" }; + !windows:: + "cwd" string => execresult("/bin/pwd 2>/dev/null || /usr/bin/pwd", "useshell"), + meta => { "simulate_safe" }; + # Use for redirection, not as a real file. + "dev_null" string => "/dev/null"; + "exeext" string => ""; + + any:: + # General commands. + "cmds" slist => { "date", + "dd", + "dos2unix", + "diff", + "cat", + "colordiff", + "cp", + "echo", + "egrep", + "env", + "false", + "grep", + "gsed", # will override sed on solaris hosts + "gzip", + "hexdump", + "ln", + "ls", + "mkdir", + "mkfifo", + "mkgroup", # AIX equivalent of groupadd + "mock_package_manager", + "mv", + "no_fds", + "od", + "perl", + "printf", + "ps", + "psexec", + "pwd", + "rm", + "sed", + "seq", + "sh", + "sleep", + "sort", + "stat", + "sudo", + "tee", + "touch", + "true", + "yes", + "wc", + }; + # Special cases. + "make_cmds" slist => { "gmake", "make" }; + # rmgroup is AIX equivalent of groupdel + "sbin_cmds" slist => { "rmgroup", "groupadd", "groupdel", "useradd", "userdel", "usermod" }; + + any:: + "paths[tests]" string => "$(this.promise_dirname)"; + solaris:: + "paths[usr_xpg4_bin]" string => "/usr/xpg4/bin"; + "paths[usr_ucb_sparcv7]" string => "/usr/ucb/sparcv7"; + "paths[opt_csw_bin]" string => "/opt/csw/bin"; + "paths[usr_sfw_bin]" string => "/usr/sfw/bin"; + !windows:: + "paths[bin]" string => "/bin"; + "paths[usr_bin]" string => "/usr/bin"; + "paths[usr_sbin]" string => "/usr/sbin"; + "paths[usr_local_bin]" string => "/usr/local/bin"; + "paths[usr_contrib_bin]" string => "/usr/contrib/bin"; + windows:: + "paths[msys64_usr_bin]" string => "c:\\msys64\\usr\\bin"; + "paths[temp_msys64_usr_bin]" string => "d:\\a\\_temp\\msys64\\usr\\bin"; + "paths[tool_wrappers]" string => "$(this.promise_dirname)\\tool_wrappers"; + + any:: + "paths_indices" slist => getindices("paths"); + + # dereferenced path of the executable + "deref_paths[$(paths[$(paths_indices)])][$(cmds)$(exeext)]" string => + filestat("$(paths[$(paths_indices)])$(const.dirsep)$(cmds)$(exeext)", + "linktarget"); + + classes: + # General commands. + "$(paths_indices)_$(cmds)$(exeext)" expression => + fileexists("$(deref_paths[$(paths[$(paths_indices)])][$(cmds)$(exeext)])"); + "has_$(cmds)" expression => + fileexists("$(deref_paths[$(paths[$(paths_indices)])][$(cmds)$(exeext)])"), + scope => "namespace"; + + # Special cases. + "$(paths_indices)_$(sbin_cmds)$(exeext)" expression => fileexists("$(paths[$(paths_indices)])$(const.dirsep)$(sbin_cmds)$(exeext)"); + "$(paths_indices)_$(make_cmds)$(exeext)" expression => fileexists("$(paths[$(paths_indices)])$(const.dirsep)$(make_cmds)$(exeext)"); + "has_make" expression => fileexists("$(paths[$(paths_indices)])$(const.dirsep)$(make_cmds)$(exeext)"), + scope => "namespace"; + + "want_color" expression => strcmp(getenv("CFENGINE_COLOR", 4k), "1"); + "have_colordiff" expression => fileexists($(colordiff)); + + vars: + # General commands. + "$(cmds)" string => "$(paths[$(paths_indices)])$(const.dirsep)$(cmds)$(exeext)", + ifvarclass => canonify("$(paths_indices)_$(cmds)$(exeext)"); + + # Special cases. + "$(sbin_cmds)" string => "$(paths[$(paths_indices)])$(const.dirsep)$(sbin_cmds)$(exeext)", + ifvarclass => canonify("$(paths_indices)_$(sbin_cmds)$(exeext)"); + # gmake has higher priority, so should come last. + "make" string => "$(paths[$(paths_indices)])$(const.dirsep)make$(exeext)", + ifvarclass => canonify("$(paths_indices)_make$(exeext)"); + "make" string => "$(paths[$(paths_indices)])$(const.dirsep)gmake$(exeext)", + ifvarclass => canonify("$(paths_indices)_gmake$(exeext)"); + + # on solaris, gsed works more like GNU sed, such as having `-i` option + solaris:: + "sed" string => "$(G.gsed)"; + + # on AIX groupdel is rmgroup and groupadd is mkgroup + aix:: + "groupdel" string => "$(G.rmgroup)"; + "groupadd" string => "$(G.mkgroup)"; + + want_color.have_colordiff:: + "diff_maybecolor" string => $(colordiff); + !want_color|!have_colordiff:: + "diff_maybecolor" string => $(diff); + + classes: + "temp_declared" not => strcmp(getenv("TEMP", "65536"), ""); + + vars: + "etc" string => "$(this.promise_dirname)$(const.dirsep)dummy_etc"; + "etc_motd" string => "$(this.promise_dirname)$(const.dirsep)dummy_etc$(const.dirsep)motd"; + "etc_passwd" string => "$(this.promise_dirname)$(const.dirsep)dummy_etc$(const.dirsep)passwd"; + "etc_shadow" string => "$(this.promise_dirname)$(const.dirsep)dummy_etc$(const.dirsep)shadow"; + "etc_group" string => "$(this.promise_dirname)$(const.dirsep)dummy_etc$(const.dirsep)group"; + # Use instead of /dev/null when you need a real empty file to open. + "etc_null" string => "$(this.promise_dirname)$(const.dirsep)dummy_etc$(const.dirsep)null"; + "write_args_sh" string => "$(this.promise_dirname)/write_args.sh"; + + temp_declared:: + "testroot" string => getenv("TEMP", "65535"); + "testdir" string => concat(getenv("TEMP", "65535"), "$(const.dirsep)TESTDIR.cfengine"); + "testfile" string => concat(getenv("TEMP", "65535"), "$(const.dirsep)TEST.cfengine"); + + !temp_declared:: + "testroot" string => "$(const.dirsep)tmp"; + "testdir" string => "$(const.dirsep)tmp$(const.dirsep)TESTDIR.cfengine"; + "testfile" string => "$(const.dirsep)tmp$(const.dirsep)TEST.cfengine"; + + classes: + "using_fakeroot" + expression => regcmp(".+", getenv("FAKEROOTKEY", "10")); +} + +bundle common dcs_strings +{ + vars: + "multiline_potatoes" string => + "BEGIN + One potato + Two potato + Three potatoes + Four +END"; + + "multiline_potatoes_lesser" string => + "BEGIN + One potato + Three potatoes + Four +END"; +} + +bundle agent paths_init(filename) +{ + classes: + "filename_absolute" expression => regcmp("/.*", "$(filename)"); +} + +bundle agent paths_absolute(filename) +{ + vars: + filename_absolute:: + "input_file" + string => "$(filename)"; + !filename_absolute:: + "input_file" + string => "$(G.cwd)/$(filename)"; +} + +bundle agent default(filename) +{ + classes: + "have_test_run_$(bundles)" expression => "any"; + + vars: + + "tests" slist => { "default:init", + "default:test", + "default:check", + "default:destroy" + }; + + "bundles" slist => bundlesmatching("default:(init|test|check|destroy)"); + + files: + "$(G.testdir)/." create => "true"; + + methods: + "any" usebundle => collect_stories_metadata; + "any" usebundle => test_precheck; + + proceed_with_test:: + "any" usebundle => test_run("$(filename)"), inherit => "true"; + + reports: + EXTRA.verbose_mode:: + "$(this.bundle): found runnable bundle $(bundles)"; +} + +bundle agent test_run(filename) +{ + vars: + + methods: + "any" + usebundle => paths_init("$(filename)"); + "any" + usebundle => paths_absolute("$(filename)"); + + AUTO:: + "any" usebundle => "$(default.tests)", + ifvarclass => canonify("have_test_run_$(default.tests)"); + + reports: + !AUTO:: + "# You must either specify '-D AUTO' or run the following commands:"; + "cf-agent -f .$(const.dirsep)$(filename) -b $(tests)"; +} + +bundle agent collect_stories_metadata +{ + vars: + "test_meta_vars" slist => { "description", "story_id", "covers" }; + classes: + "have_$(test_meta_vars)" expression => isvariable("test_meta.$(test_meta_vars)"); + + reports: + have_description:: + "test description: $(test_meta.description)"; + have_story_id:: + "test story_id: $(test_meta.story_id)"; + have_covers:: + "test covers: $(test_meta.covers)"; +} + +bundle agent test_precheck +{ + vars: + "fail_types" slist => { "suppress", "soft", "flakey" }; + + "test_skip_unsupported" slist => variablesmatching(".*_meta\.test_skip_unsupported"); + "test_skip_needs_work" slist => variablesmatching(".*_meta\.test_skip_needs_work"); + "test_$(fail_types)_fail" slist => variablesmatching(".*_meta\.test_$(fail_types)_fail"); + "test_$(fail_types)_fail_metatags" slist => getvariablemetatags("$(test_$(fail_types)_fail)"); + "test_$(fail_types)_fail_ticket" slist => filter("(redmine|CFE-|ENT-|ARCHIVE-|QA-).*", "test_$(fail_types)_fail_metatags", "true", "false", 1); + + classes: + "test_skip_unsupported_set" expression => some(".*", "test_skip_unsupported"); + "test_skip_needs_work_set" expression => some(".*", "test_skip_needs_work"); + "test_$(fail_types)_fail_set" expression => some(".*", "test_$(fail_types)_fail"); + "test_$(fail_types)_fail_ticket_set" expression => some(".*", "test_$(fail_types)_fail_ticket"); + + "test_skip_unsupported_match" and => { "test_skip_unsupported_set", "$($(test_skip_unsupported))" }; + "test_skip_needs_work_match" and => { "test_skip_needs_work_set", "$($(test_skip_needs_work))" }; + "test_$(fail_types)_fail_match" and => { "test_$(fail_types)_fail_set", "$($(test_$(fail_types)_fail))" }; + + "proceed_with_test" + and => { "!test_skip_unsupported_match", "!test_skip_needs_work_match" }, + scope => "namespace"; + + reports: + test_skip_unsupported_match:: + "$(this.promise_filename) Skip/unsupported"; + test_skip_needs_work_match:: + "$(this.promise_filename) Skip/needs_work"; + test_suppress_fail_match.test_suppress_fail_ticket_set:: + "$(this.promise_filename) XFAIL/$(test_suppress_fail_ticket)"; + test_suppress_fail_match.!test_suppress_fail_ticket_set:: + "$(this.promise_filename) FAIL/no_ticket_number"; + test_soft_fail_match.test_soft_fail_ticket_set:: + "$(this.promise_filename) SFAIL/$(test_soft_fail_ticket)"; + test_soft_fail_match.!test_soft_fail_ticket_set:: + "$(this.promise_filename) FAIL/no_ticket_number"; + test_flakey_fail_match.test_flakey_fail_ticket_set:: + "$(this.promise_filename) FLAKEY/$(test_flakey_fail_ticket)"; + test_flakey_fail_match.!test_flakey_fail_ticket_set:: + "$(this.promise_filename) FAIL/no_ticket_number"; +} + +####################################################### + +bundle agent dcs_sort(infile, outfile) +{ + commands: + # Enforce byte sort order with LC_ALL. + "$(G.env) LC_ALL=C $(G.sort) $(infile) > $(outfile)" + contain => in_shell; +} + +bundle agent dcs_check_state(bundlename, expected_file, test) +{ + vars: + "state" data => bundlestate($(bundlename)); + + methods: + "" usebundle => file_make("$(G.testdir)/template", "{{%state}}"); + "" usebundle => file_make("$(G.testdir)/actual", ""); + "any" usebundle => dcs_check_state_prep(@(state)); + "any" usebundle => dcs_check_diff_expected("$(G.testdir)/actual", + $(expected_file), + $(test), + "no"); +} + +bundle agent dcs_check_state_prep(state_container) +{ + files: + "$(G.testdir)/actual" + create => "true", + edit_template => "$(G.testdir)/template", + template_data => mergedata('{ "state": state_container }'), + template_method => "mustache"; +} + +bundle agent dcs_check_diff(file1, file2, test) +# @brief Pass if two files do not differ. +{ + methods: + "any" usebundle => dcs_check_diff_expected($(file1), + $(file2), + $(test), + "no"); +} + +bundle agent dcs_if_diff(file1, file2, pass_class, fail_class) +{ + methods: + "any" usebundle => dcs_if_diff_expected($(file1), + $(file2), + "no", + $(pass_class), + $(fail_class)); +} + +bundle agent sorted_check_diff(file1, file2, test) +{ + methods: + "any" usebundle => dcs_sort("$(file1)", "$(file1).sort"); + "any" usebundle => dcs_sort("$(file2)", "$(file2).sort"); + "any" usebundle => dcs_check_diff_expected("$(file1).sort", + "$(file2).sort", + $(test), + "no"); +} + +bundle agent xml_check_diff(file1, file2, test, expected_difference) +{ + vars: + DEBUG.check_ready.!no_difference:: + "file1r" string => execresult("$(G.cwd)/xml-c14nize $(file1)", "noshell"), + meta => { "simulate_safe" }; + "file2r" string => execresult("$(G.cwd)/xml-c14nize $(file2)", "noshell"), + meta => { "simulate_safe" }; + + DEBUG.check_ready.!no_difference.has_hexdump:: + "file1h" string => execresult("$(G.hexdump) -C $(file1)", "useshell"), + meta => { "simulate_safe" }; + "file2h" string => execresult("$(G.hexdump) -C $(file2)", "useshell"), + meta => { "simulate_safe" }; + + DEBUG.check_ready.!no_difference.!has_hexdump:: + "file1h" string => execresult("$(G.od) -c $(file1)", "useshell"), + meta => { "simulate_safe" }; + "file2h" string => execresult("$(G.od) -c $(file2)", "useshell"), + meta => { "simulate_safe" }; + + DEBUG.check_ready.!no_difference.has_unified_diff:: + "diffu" string => execresult("$(G.diff_maybecolor) -u $(file2) $(file1) 2>$(G.dev_null)", "useshell"), + meta => { "simulate_safe" }; + DEBUG.check_ready.!no_difference.!has_unified_diff:: + "diffu" string => execresult("$(G.diff_maybecolor) -c $(file2) $(file1) 2>$(G.dev_null)", "useshell"), + meta => { "simulate_safe" }; + + classes: + "has_unified_diff" expression => returnszero( + "$(G.diff_maybecolor) -u $(G.etc_null) $(G.etc_null) >$(G.dev_null) 2>&1", "useshell"), + meta => { "simulate_safe" }; + + c14n_files_created:: + "no_difference" expression => returnszero( + "$(G.diff_maybecolor) $(G.testfile).default-xml-check-diff-1 $(G.testfile).default-xml-check-diff-2 >$(G.dev_null) 2>&1", + "useshell"), + meta => { "simulate_safe" }; + + "expected_difference" expression => strcmp("$(expected_difference)", "yes"); + "check_ready" expression => "any"; + + commands: + "$(G.cwd)/xml-c14nize $(file1) > $(G.testfile).default-xml-check-diff-1" + contain => in_shell; + "$(G.cwd)/xml-c14nize $(file2) > $(G.testfile).default-xml-check-diff-2" + contain => in_shell, + classes => if_ok("c14n_files_created"); + + reports: + check_ready.no_difference.!expected_difference:: + "$(test) Pass"; + check_ready.!no_difference.expected_difference:: + "$(test) Pass"; + check_ready.!no_difference.!expected_difference:: + "$(test) FAIL"; + check_ready.no_difference.expected_difference:: + "$(test) FAIL"; + + DEBUG.check_ready.!no_difference.!expected_difference:: + "$(file1) and $(file2) differ:"; + "$(file1): <$(file1r)>"; + "$(file2): <$(file2r)>"; + "dump $(file1): +$(file1h)"; + "dump $(file2): +$(file2h)"; + "$(diffu)"; + DEBUG.check_ready.no_difference.expected_difference:: + "Contents of $(file1) and $(file) is the same."; +} + +bundle agent dcs_report_generic_classes(x) +{ + vars: + "choices" slist => { "repaired", "failed", "denied", "timeout", "kept" }; + + reports: + DEBUG:: + "class '$(x)_$(choices)' was defined" ifvarclass => '$(x)_$(choices)'; + "class '$(x)_$(choices)' was NOT defined" ifvarclass => '!$(x)_$(choices)'; +} + +bundle agent dcs_generic_classes_to_string(x) +{ + vars: + "choices" slist => { "repaired", "failed", "denied", "timeout", "kept" }; + "s[$(choices)]" string => "$(x)_$(choices)", ifvarclass => '$(x)_$(choices)'; + "found_unsorted" slist => getvalues(s); + "found" slist => sort(found_unsorted, "lex"); + "found_str" string => format("%S", found); + + reports: + "$(found_str)" bundle_return_value_index => "str"; +} + +# These set/clear global classes, so they can be tested in another bundle +body classes dcs_all_classes(prefix) +{ + promise_kept => { "$(prefix)_promise_kept" }; + promise_repaired => { "$(prefix)_promise_repaired" }; + repair_failed => { "$(prefix)_repair_failed" }; + repair_denied => { "$(prefix)_repair_denied" }; + repair_timeout => { "$(prefix)_repair_timeout" }; + cancel_kept => { "$(prefix)_cancel_kept" }; + cancel_repaired => { "$(prefix)_cancel_repaired" }; + cancel_notkept => { "$(prefix)_cancel_notkept" }; +} + +bundle agent dcs_all_negative_classes(prefix) +{ + # These must be global classes so they can be tested in another bundle + # We set them here, so they can be (possibly) cleared later + classes: + "$(prefix)_cancel_kept" expression => "any", scope => "namespace"; + "$(prefix)_cancel_repaired" expression => "any", scope => "namespace"; + "$(prefix)_cancel_notkept" expression => "any", scope => "namespace"; +} + +bundle agent dcs_all_classes_to_string(prefix) +{ + vars: + "classes_all" slist => classesmatching("$(prefix)_.*(kept|notkept|failed|denied|repaired|timeout)"); + "classes" slist => sort(classes_all, "lex"); + "found_str" string => format("%S", classes); + + reports: + "$(found_str)" bundle_return_value_index => "str"; +} + +bundle agent dcs_check_diff_expected(file1, file2, test, expected_difference) +{ + methods: + "any" usebundle => dcs_if_diff_expected($(file1), $(file2), $(expected_difference), + "dcs_check_diff_expected_pass_class", + "dcs_check_diff_expected_fail_class"); + + reports: + dcs_check_diff_expected_pass_class:: + "$(test) Pass"; + dcs_check_diff_expected_fail_class:: + "$(test) FAIL"; +} + +bundle agent dcs_if_diff_expected(file1, file2, expected_difference, pass_class, fail_class) +{ + vars: + DEBUG.check_ready.!no_difference:: + "file1r" string => readfile("$(file1)", "4096"); + "file2r" string => readfile("$(file2)", "4096"); + + (EXTRA|DEBUG_HEXDUMP).check_ready.!no_difference.has_hexdump:: + "file1h" string => execresult("$(G.hexdump) -C $(file1)", "useshell"), + meta => { "simulate_safe" }; + "file2h" string => execresult("$(G.hexdump) -C $(file2)", "useshell"), + meta => { "simulate_safe" }; + + (EXTRA|DEBUG_HEXDUMP).check_ready.!no_difference.!has_hexdump:: + "file1h" string => execresult("$(G.od) -c $(file1)", "useshell"), + meta => { "simulate_safe" }; + "file2h" string => execresult("$(G.od) -c $(file2)", "useshell"), + meta => { "simulate_safe" }; + + DEBUG.check_ready.!no_difference.has_unified_diff:: + "diffu" string => execresult("$(G.diff_maybecolor) -u $(file2) $(file1) 2>$(G.dev_null)", "useshell"), + meta => { "simulate_safe" }; + DEBUG.check_ready.!no_difference.!has_unified_diff:: + "diffu" string => execresult("$(G.diff_maybecolor) -c $(file2) $(file1) 2>$(G.dev_null)", "useshell"), + meta => { "simulate_safe" }; + + classes: + "has_unified_diff" expression => returnszero( + "$(G.diff_maybecolor) -u $(G.etc_null) $(G.etc_null) >$(G.dev_null) 2>&1", "useshell"), + meta => { "simulate_safe" }; + + "no_difference" expression => returnszero( + "$(G.diff_maybecolor) $(file1) $(file2) >$(G.dev_null) 2>&1", + "useshell"), + meta => { "simulate_safe" }; + + "expected_difference" expression => strcmp("$(expected_difference)", "yes"); + "check_ready" expression => "any"; + + check_ready.no_difference.!expected_difference:: + "$(pass_class)" expression => "any", + scope => "namespace"; + check_ready.!no_difference.expected_difference:: + "$(pass_class)" expression => "any", + scope => "namespace"; + check_ready.!no_difference.!expected_difference:: + "$(fail_class)" expression => "any", + scope => "namespace"; + check_ready.no_difference.expected_difference:: + "$(fail_class)" expression => "any", + scope => "namespace"; + + reports: + DEBUG.check_ready.!no_difference.!expected_difference:: + "FILES DIFFER BUT SHOULD BE THE SAME"; + "CONTENTS OF $(file1): +$(file1r)"; + "CONTENTS OF $(file2): +$(file2r)"; + "$(diffu)"; + (DEBUG_HEXDUMP|EXTRA).check_ready.!no_difference.!expected_difference:: + "hexdump $(file1): +$(file1h)"; + "hexdump $(file2): +$(file2h)"; + DEBUG.check_ready.no_difference.expected_difference:: + "Contents of $(file1) and $(file) are the same but should differ."; + EXTRA:: + "Diff command: $(G.diff_maybecolor) -u $(file2) $(file1) 2>$(G.dev_null)"; +} + +bundle agent dcs_sleep(t) +{ + commands: + "$(G.perl) -e 'sleep $(t)'"; + reports: + EXTRA:: + "Sleeping $(t) seconds"; +} + +####################################################### + +# Uses rm -rf instead of selecting and deleting files to avoid side-effects in +# tests due to problems in file deleletion promises. + +bundle agent dcs_fini(file) +{ + commands: + "$(G.rm) -rf $(file)*" + contain => dcs_useshell; + "$(G.rm) -rf $(sys.statedir)/cf_state.*" + contain => dcs_useshell; + + reports: + EXTRA:: + "$(this.bundle): deleting $(file) with $(G.rm) -rf"; +} + +bundle agent dcs_copyfile(from, to) +{ + files: + "$(to)" + create => "true", + copy_from => dcs_sync($(from)); + + reports: + EXTRA:: + "$(this.bundle): copying file $(from) to $(to)"; +} + +body copy_from dcs_sync(f) +{ + source => "$(f)"; + purge => "true"; + preserve => "false"; + type_check => "false"; + compare => "digest"; +} + +body contain dcs_useshell +{ + useshell => "true"; + chdir => "/"; +} + +bundle agent dcs_runagent(args) +{ + commands: + debug_mode:: + "$(sys.cf_agent) -Kdf $(args)"; + verbose_mode:: + "$(sys.cf_agent) -Kvf $(args)"; + inform_mode:: + "$(sys.cf_agent) -KIf $(args)"; + !debug_mode.!verbose_mode.!inform_mode:: + "$(sys.cf_agent) -Kf $(args)"; +} + +####################################################### +# Test based on whether two strings are the same + +bundle agent dcs_check_strcmp(strA, strB, test, expected_difference) +{ + classes: + "equal" expression => strcmp($(strA), $(strB)); + "expected_difference" or => { strcmp($(expected_difference), "yes"), + strcmp($(expected_difference), "true") }; + + "__passif" xor => { "equal", "expected_difference" }; + reports: + EXTRA|!__passif:: + "$(this.bundle): STRING A: '$(strA)'"; + "$(this.bundle): STRING B: '$(strB)'"; + __passif:: + "$(test) Pass"; + !__passif:: + "$(test) FAIL"; +} + +bundle agent dcs_check_diff_elements( setA, setB, test, expected_difference) +# @brief Test that there is or is not a difference between two lists or data containers elements (order elements does not matter) +# +# **Example:** +# ``` +# vars: +# "l1" slist => { "A", "three" }; +# "l2" slist => { "three", "A" }; +# "d1" data => '[ "three", "three" ]'; +# +# methods: +# "Pass/FAIL" +# usebundle => dcs_check_diff_elements( @(l1), # First set of elements +# @(l2), # Second set of elements +# $(this.promise_filename), # Full path to test policy file +# "no"); # Expected difference in elements +# "Pass/FAIL" +# usebundle => dcs_check_diff_elements( @(l1), # First set of elements +# @(d1), # Second set of elements +# $(this.promise_filename), # Full path to test policy file +# "yes"); # Expected difference in elements +# ``` +{ + vars: + "length_A" int => length( setA ); + "length_B" int => length( setB ); + "difference_A_B" slist => difference( setA, setB ); + "difference_B_A" slist => difference( setB, setA ); + + classes: + "_pass_same_length" + expression => eval( "$(length_A) == $(length_B)", class, infix ); + "_pass_no_elements_in_setA_that_are_not_in_setB" + expression => islessthan( length( difference_A_B ), 1 ); + "_pass_no_elements_in_setB_that_are_not_in_setA" + expression => islessthan( length( difference_B_A ), 1 ); + + "__passif" and => { "_pass_same_length", + "_pass_no_elements_in_setA_that_are_not_in_setB", + "_pass_no_elements_in_setB_that_are_not_in_setA"}; + + reports: + DEBUG|EXTRA:: + + # We print each element of the list out individually on versions that didn't + # have the with attribute. No great way to specify a version less than a + # cfengine version. + "setA: $(setA)" if => eval( "$(sys.cf_version_minor) < 11", class, infix ); + "setB: $(setB)" if => eval( "$(sys.cf_version_minor) < 11", class, infix ); +@if minimum_version(3.11) + "setA: $(with)" with => join( ", ", setA ); + "setB: $(with)" with => join( ", ", setB ); +@endif + "setA and setB have the same number of elements" + if => "_pass_same_length"; + "setA and setB do not have the same number of elements" + if => not("_pass_same_length"); + "setA has $(length_A) elements and setB has $(length_B) ements" + if => not("_pass_same_length"); + + "There are no elements in setA that are not in setB" + if => "_pass_no_elements_in_setA_that_are_not_in_setB"; + "Element in setA that is not in setB: $(difference_A_B)" + if => not( "_pass_no_elements_in_setA_that_are_not_in_setB" ); + + "There are no elements in setB that are not in setA" + if => "_pass_no_elements_in_setB_that_are_not_in_setA"; + "Element in setB that is not in setA: $(difference_B_A)" + if => not( "_pass_no_elements_in_setB_that_are_not_in_setA" ); + + __passif:: + "$(test) Pass"; + !__passif:: + "$(test) FAIL"; +} + +####################################################### +# Test based on whether a string matches a regular expression + +bundle agent dcs_check_regcmp(regex, thestring, test, expected_mismatch) +# @brief Check if `regex` matches `thestring` Pass if they match, unless `expected_mismatch` is `true|yes`, in which case, FAIL +# @param regex The regular expression to check `thestring` against +# @param thestring The string to test with `regex` +# @param test The path to the test being run +# @param expected_mismatch When `yes|true` the test will Pass if `regex` does not match `thestring` +{ + classes: + "matched" expression => regcmp($(regex), $(thestring)); + "expected_mismatch" or => { strcmp($(expected_mismatch), "yes"), + strcmp($(expected_mismatch), "true") }; + + "__passif" xor => { "matched", "expected_mismatch" }; +reports: + EXTRA|!__passif:: + "$(this.bundle): REGEX: '$(regex)'"; + "$(this.bundle): STRING TO MATCH: '$(thestring)'"; + __passif:: + "$(test) Pass"; + !__passif:: + "$(test) FAIL"; +} + +####################################################### +# Pass the test if class + +bundle agent dcs_passif(classname, test) +{ + reports: + "$(test) Pass" ifvarclass => $(classname); + "$(test) FAIL" ifvarclass => not($(classname)); + + EXTRA:: + "$(this.bundle): passing based on class '$(classname)'" ifvarclass => $(classname); + DEBUG:: + "$(this.bundle): failing based on class '$(classname)'" ifvarclass => not($(classname)); +} + +bundle agent dcs_pass(test) +{ + reports: + "$(test) Pass"; + EXTRA:: + "$(this.bundle): Explicitly passing..."; +} + +bundle agent dcs_fail(test) +{ + reports: + "$(test) FAIL"; + EXTRA:: + "$(this.bundle): Explicitly failing..."; +} + +bundle agent dcs_wait(test, seconds) +{ + reports: + "$(test) Wait/$(seconds)"; + EXTRA:: + "$(this.bundle): Explicitly waiting $(seconds) seconds..."; +} + +bundle agent dcs_passif_fileexists(file, test) +{ + classes: + "__passif" expression => fileexists($(file)); + + methods: + "" usebundle => dcs_passif("__passif", $(test)), inherit => "true"; + + reports: + DEBUG.!__passif:: + "$(this.bundle): $(file) did not exist!"; + EXTRA.__passif:: + "$(this.bundle): $(file) exists."; +} + +bundle agent dcs_passif_file_absent(file, test) +{ + classes: + "__passif" not => fileexists($(file)); + + methods: + "" usebundle => dcs_passif("__passif", $(test)), inherit => "true"; + + reports: + DEBUG.__passif:: + "$(this.bundle): $(file) did not exist."; + EXTRA.!__passif:: + "$(this.bundle): $(file) exists!"; +} + +bundle agent dcs_passif_expected(expected, not_expected, test) +{ + vars: + "ex" slist => splitstring($(expected), ",", 1000); + "notex" slist => splitstring($(not_expected), ",", 1000); + "e[$(ex)]" string => $(ex); + "e[$(notex)]" string => "!$(notex)"; + "classes" slist => getvalues(e); + + classes: + "__passif" and => { @(classes) }; + methods: + "" usebundle => dcs_passif("__passif", $(test)), inherit => "true"; + + reports: + DEBUG:: + "$(this.bundle): bad: $(ex) was NOT defined" + ifvarclass => "!$(ex)"; + + "$(this.bundle): bad: $(notex) WAS defined" + ifvarclass => "$(notex)"; + + EXTRA:: + "$(this.bundle): good: $(ex) WAS defined" + ifvarclass => "$(ex)"; + + "$(this.bundle): good: $(notex) was NOT defined" + ifvarclass => "!$(notex)"; +} + +####################################################### + +bundle agent dcs_passif_output(wanted, unwanted, command, test) +{ + vars: + "subout" string => execresult($(command), "useshell"), + meta => { "simulate_safe" }; + + classes: + "__passif_output_unwanted" expression => regcmp($(unwanted), $(subout)); + "__passif_output_wanted" expression => regcmp($(wanted), $(subout)); + + methods: + "" usebundle => dcs_passif_expected("__passif_output_wanted", # expected + "__passif_output_unwanted", # not expected + $(test)), + inherit => "true"; + + reports: + DEBUG:: + "$(this.bundle): bad: '$(wanted)' was NOT found in the output of '$(command)'" + ifvarclass => "!__passif_output_wanted"; + + "$(this.bundle): bad: '$(unwanted)' WAS found in the output of '$(command)'" + ifvarclass => "__passif_output_unwanted"; + + EXTRA:: + "$(this.bundle): good: '$(wanted)' WAS found in the output of '$(command)'" + ifvarclass => "__passif_output_wanted"; + + "$(this.bundle): good: '$(unwanted)' was NOT found in the output of '$(command)'" + ifvarclass => "!__passif_output_unwanted"; + + DEBUG:: + "$(this.bundle): the output of command '$(command)' was: '$(subout)'"; +} + +bundle agent dcs_passif_output1(wanted, command, test) +{ + methods: + "" usebundle => dcs_passif_output($(wanted), # expected + "^(unlikely data){400}$", # not expected + $(command), + $(test)), + inherit => "true"; +} + +bundle agent generate_key +{ + classes: + "key_exists" expression => fileexists("$(sys.workdir)/ppkeys/localhost.pub"); + files: + !key_exists:: + "$(sys.workdir)/ppkeys/localhost.priv" + create => "true", + copy_from => dcs_sync("$(this.promise_dirname)/root-MD5=617eb383deffef843ea856b129d0a423.priv"); + "$(sys.workdir)/ppkeys/localhost.pub" + create => "true", + copy_from => dcs_sync("$(this.promise_dirname)/root-MD5=617eb383deffef843ea856b129d0a423.pub"); +} + +bundle agent trust_key +{ + classes: + nova:: + "key_exists" expression => fileexists("$(sys.workdir)/ppkeys/root-SHA=ba71659fc294990d4f61ec1734515f89ce24524fbaeebad5b3f76da532da7390.pub"); + !nova:: + "key_exists" expression => fileexists("$(sys.workdir)/ppkeys/root-MD5=617eb383deffef843ea856b129d0a423.pub"); + files: + !key_exists.nova:: + "$(sys.workdir)/ppkeys/root-SHA=ba71659fc294990d4f61ec1734515f89ce24524fbaeebad5b3f76da532da7390.pub" + create => "true", + copy_from => dcs_sync("$(this.promise_dirname)/root-MD5=617eb383deffef843ea856b129d0a423.pub"); + !key_exists.!nova:: + "$(sys.workdir)/ppkeys/root-MD5=617eb383deffef843ea856b129d0a423.pub" + create => "true", + copy_from => dcs_sync("$(this.promise_dirname)/root-MD5=617eb383deffef843ea856b129d0a423.pub"); +} + +body copy_from dcs_remote_cp(from,server, port) +# @brief Download a file from a remote server. They server is always trusted. +# +# @param from The location of the file on the remote server +# @param server The hostname or IP of the server from which to download +{ + servers => { "$(server)" }; + portnumber => "$(port)"; + source => "$(from)"; + compare => "mtime"; + trustkey => "true"; +} diff --git a/tests/acceptance/default.cf.sub b/tests/acceptance/default.cf.sub new file mode 100644 index 0000000000..1c81340350 --- /dev/null +++ b/tests/acceptance/default.cf.sub @@ -0,0 +1,40 @@ +######################################################################## +# Acceptance test framework. +# +# See README for details about writing test cases. +######################################################################## +bundle common D +{ + vars: + any:: + + "owndir" string => "$(this.promise_dirname)"; + + + # If not testing the masterfiles policy framework we want to include the + # plucked bodies and bundles so that we have conveniant access to commonly + # used bundles and bodies. + !testing_masterfiles_policy_framework:: + "inputs" + slist => { + "$(D.owndir)$(const.dirsep)dcs.cf.sub", + "$(D.owndir)$(const.dirsep)plucked.cf.sub", + }; + + # If testing the masterfiles policy framework then load the stdlib by + # default so it can be leveraged as expected. + testing_masterfiles_policy_framework:: + "inputs" + slist => { + "$(D.owndir)$(const.dirsep)dcs.cf.sub", + "$(D.owndir)$(const.dirsep)..$(const.dirsep)..$(const.dirsep)lib$(const.dirsep)stdlib.cf" + }; + + +} + +body file control +{ + # plucked.cf.sub comes from the stdlib with `make pluck` + inputs => { @(D.inputs) }; +} diff --git a/tests/acceptance/dnswrapper.c b/tests/acceptance/dnswrapper.c new file mode 100644 index 0000000000..3794b4e252 --- /dev/null +++ b/tests/acceptance/dnswrapper.c @@ -0,0 +1,8 @@ +#include +#include + +struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type) +{ + h_errno = HOST_NOT_FOUND; + return NULL; +} diff --git a/tests/acceptance/dummy_etc/group b/tests/acceptance/dummy_etc/group new file mode 100644 index 0000000000..e172c311d6 --- /dev/null +++ b/tests/acceptance/dummy_etc/group @@ -0,0 +1,63 @@ +root:x:0: +daemon:x:1: +bin:x:2: +sys:x:3: +adm:x:4: +tty:x:5: +disk:x:6: +lp:x:7: +mail:x:8: +news:x:9: +uucp:x:10: +man:x:12: +proxy:x:13: +kmem:x:15: +dialout:x:20: +fax:x:21: +voice:x:22: +cdrom:x:24: +floppy:x:25: +tape:x:26: +sudo:x:27: +audio:x:29:pulse +dip:x:30: +www-data:x:33: +backup:x:34: +operator:x:37: +list:x:38: +irc:x:39: +src:x:40: +gnats:x:41: +shadow:x:42: +utmp:x:43: +video:x:44: +sasl:x:45: +plugdev:x:46: +staff:x:50: +games:x:60: +users:x:100: +nogroup:x:65534: +libuuid:x:101: +crontab:x:102: +syslog:x:103: +fuse:x:104: +messagebus:x:105: +bluetooth:x:106: +scanner:x:107: +colord:x:108: +lpadmin:x:109: +ssl-cert:x:110: +lightdm:x:111: +nopasswdlogin:x:112: +netdev:x:113: +whoopsie:x:114: +mlocate:x:115: +ssh:x:116: +avahi-autoipd:x:117: +avahi:x:118: +pulse:x:119: +pulse-access:x:120: +utempter:x:121: +rtkit:x:122: +saned:x:123: +sambashare:x:124: diff --git a/tests/acceptance/dummy_etc/motd b/tests/acceptance/dummy_etc/motd new file mode 100644 index 0000000000..b750e864e9 --- /dev/null +++ b/tests/acceptance/dummy_etc/motd @@ -0,0 +1 @@ +Welcome to the CFEngine Test Suite! diff --git a/tests/acceptance/dummy_etc/null b/tests/acceptance/dummy_etc/null new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/acceptance/dummy_etc/passwd b/tests/acceptance/dummy_etc/passwd new file mode 100644 index 0000000000..db4e99a440 --- /dev/null +++ b/tests/acceptance/dummy_etc/passwd @@ -0,0 +1,33 @@ +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/bin/sh +bin:x:2:2:bin:/bin:/bin/sh +sys:x:3:3:sys:/dev:/bin/sh +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/bin/sh +man:x:6:12:man:/var/cache/man:/bin/sh +lp:x:7:7:lp:/var/spool/lpd:/bin/sh +mail:x:8:8:mail:/var/mail:/bin/sh +news:x:9:9:news:/var/spool/news:/bin/sh +uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh +proxy:x:13:13:proxy:/bin:/bin/sh +www-data:x:33:33:www-data:/var/www:/bin/sh +backup:x:34:34:backup:/var/backups:/bin/sh +list:x:38:38:Mailing List Manager:/var/list:/bin/sh +irc:x:39:39:ircd:/var/run/ircd:/bin/sh +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh +nobody:x:65534:65534:nobody:/nonexistent:/bin/sh +libuuid:x:100:101::/var/lib/libuuid:/bin/sh +syslog:x:101:103::/home/syslog:/bin/false +messagebus:x:102:105::/var/run/dbus:/bin/false +colord:x:103:108:colord colour management daemon,,,:/var/lib/colord:/bin/false +lightdm:x:104:111:Light Display Manager:/var/lib/lightdm:/bin/false +whoopsie:x:105:114::/nonexistent:/bin/false +avahi-autoipd:x:106:117:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false +avahi:x:107:118:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false +usbmux:x:108:46:usbmux daemon,,,:/home/usbmux:/bin/false +kernoops:x:109:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false +pulse:x:110:119:PulseAudio daemon,,,:/var/run/pulse:/bin/false +rtkit:x:111:122:RealtimeKit,,,:/proc:/bin/false +saned:x:112:123::/home/saned:/bin/false +speech-dispatcher:x:113:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/sh +hplip:x:114:7:HPLIP system user,,,:/var/run/hplip:/bin/false diff --git a/tests/acceptance/dummy_etc/shadow b/tests/acceptance/dummy_etc/shadow new file mode 100644 index 0000000000..f4f9483b27 --- /dev/null +++ b/tests/acceptance/dummy_etc/shadow @@ -0,0 +1,33 @@ +root:!:15797:0:99999:7::: +daemon:*:15797:0:99999:7::: +bin:*:15797:0:99999:7::: +sys:*:15797:0:99999:7::: +sync:*:15797:0:99999:7::: +games:*:15797:0:99999:7::: +man:*:15797:0:99999:7::: +lp:*:15797:0:99999:7::: +mail:*:15797:0:99999:7::: +news:*:15797:0:99999:7::: +uucp:*:15797:0:99999:7::: +proxy:*:15797:0:99999:7::: +www-data:*:15797:0:99999:7::: +backup:*:15797:0:99999:7::: +list:*:15797:0:99999:7::: +irc:*:15797:0:99999:7::: +gnats:*:15797:0:99999:7::: +nobody:*:15797:0:99999:7::: +libuuid:!:15797:0:99999:7::: +syslog:*:15797:0:99999:7::: +messagebus:*:15797:0:99999:7::: +colord:*:15797:0:99999:7::: +lightdm:*:15797:0:99999:7::: +whoopsie:*:15797:0:99999:7::: +avahi-autoipd:*:15797:0:99999:7::: +avahi:*:15797:0:99999:7::: +usbmux:*:15797:0:99999:7::: +kernoops:*:15797:0:99999:7::: +pulse:*:15797:0:99999:7::: +rtkit:*:15797:0:99999:7::: +saned:*:15797:0:99999:7::: +speech-dispatcher:!:15797:0:99999:7::: +hplip:*:15797:0:99999:7::: diff --git a/tests/acceptance/fakesyslog.c b/tests/acceptance/fakesyslog.c new file mode 100644 index 0000000000..16c3658759 --- /dev/null +++ b/tests/acceptance/fakesyslog.c @@ -0,0 +1,202 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. + +*/ + +#include +#include +#include +#include +#include + +static void msgheader(void) +{ + fprintf(stderr, "[fakesyslog][%d] ", (int)getuid()); +} + +typedef struct +{ + int mask; + const char *name; +} masks; + +static const masks logopts[] = { + { LOG_PID, "LOG_PID" }, + { LOG_CONS, "LOG_CONS" }, + { LOG_NDELAY, "LOG_NDELAY" }, + { LOG_ODELAY, "LOG_ODELAY" }, + { LOG_NOWAIT, "LOG_NOWAIT" } +}; + +static void print_mask(int value, const masks *masks, int masks_size) +{ + bool got_value = false; + + for (int i = 0; i < masks_size; ++i) + { + if (value & masks[i].mask) + { + fprintf(stderr, "%s%s", got_value ? " | " : "", masks[i].name); + got_value = true; + value &= ~masks[i].mask; + } + } + + if (value) + { + fprintf(stderr, "%s0x%02X", got_value ? " | " : "", value); + got_value = true; + } + + if (!got_value) + { + fprintf(stderr, "0"); + } +} + +static const char *get_level_str(int level) +{ + switch (level) + { + case LOG_EMERG: return "LOG_EMERG"; + case LOG_ALERT: return "LOG_ALERT"; + case LOG_CRIT: return "LOG_CRIT"; + case LOG_ERR: return "LOG_ERR"; + case LOG_WARNING: return "LOG_WARNING"; + case LOG_NOTICE: return "LOG_NOTICE"; + case LOG_INFO: return "LOG_INFO"; + case LOG_DEBUG: return "LOG_DEBUG"; + default: return NULL; + } +} + +static const char *get_facility_str(int facility) +{ + switch (facility) + { + case LOG_KERN: return "LOG_KERN"; + case LOG_USER: return "LOG_USER"; + case LOG_MAIL: return "LOG_MAIL"; + case LOG_NEWS: return "LOG_NEWS"; + case LOG_UUCP: return "LOG_UUCP"; + case LOG_DAEMON: return "LOG_DAEMON"; + case LOG_AUTH: return "LOG_AUTH"; + case LOG_CRON: return "LOG_CRON"; + case LOG_LPR: return "LOG_LPR"; + case LOG_LOCAL0: return "LOG_LOCAL0"; + case LOG_LOCAL1: return "LOG_LOCAL1"; + case LOG_LOCAL2: return "LOG_LOCAL2"; + case LOG_LOCAL3: return "LOG_LOCAL3"; + case LOG_LOCAL4: return "LOG_LOCAL4"; + case LOG_LOCAL5: return "LOG_LOCAL5"; + case LOG_LOCAL6: return "LOG_LOCAL6"; + case LOG_LOCAL7: return "LOG_LOCAL7"; + default: return NULL; + } +} + +static void print_priority(int arg) +{ + int level = LOG_PRI(arg); + int facility = arg & ~level; + + const char *level_str = get_level_str(level); + const char *facility_str = get_facility_str(facility); + + if (facility_str) + { + fprintf(stderr, "%s | %s", level_str, facility_str); + } + else + { + fprintf(stderr, "%s | 0x%02X", level_str, facility); + } +} + +static int logmask = LOG_UPTO(LOG_DEBUG); + +static const masks priority_masks[] = { + { LOG_MASK(LOG_EMERG), "LOG_EMERG" }, + { LOG_MASK(LOG_ALERT), "LOG_ALERT" }, + { LOG_MASK(LOG_CRIT), "LOG_CRIT" }, + { LOG_MASK(LOG_ERR), "LOG_ERR" }, + { LOG_MASK(LOG_WARNING), "LOG_WARNING" }, + { LOG_MASK(LOG_NOTICE), "LOG_NOTICE" }, + { LOG_MASK(LOG_INFO), "LOG_INFO" }, + { LOG_MASK(LOG_DEBUG), "LOG_DEBUG" } +}; + + + + +void openlog(const char *ident, int logopt, int facility) +{ + msgheader(); + + fprintf(stderr, "openlog(%s, ", ident ? ident : ""); + print_mask(logopt, logopts, sizeof(logopts)/sizeof(logopts[0])); + const char *facility_str = get_facility_str(facility); + if (facility_str) + { + fprintf(stderr, ", %s)\n", facility_str); + } + else + { + fprintf(stderr, ", 0x%02X)\n", facility); + } +} + +void vsyslog(int priority, const char *message, va_list ap) +{ + msgheader(); + fputs("syslog(", stderr); + print_priority(priority); + fputs(", ", stderr); + vfprintf(stderr, message, ap); + fputs(")\n", stderr); +} + +void syslog(int priority, const char *message, ...) +{ + va_list ap; + va_start(ap, message); + vsyslog(priority, message, ap); + va_end(ap); +} + +int setlogmask(int maskpri) +{ + int oldlogmask = logmask; + logmask = maskpri; + msgheader(); + fputs("setlogmask(", stderr); + print_mask(maskpri, priority_masks, sizeof(priority_masks)/sizeof(priority_masks[0])); + fputs(")\n", stderr); + return oldlogmask; +} + +void closelog(void) +{ + msgheader(); + fputs("closelog()\n", stderr); +} diff --git a/tests/acceptance/mock_package_manager.c b/tests/acceptance/mock_package_manager.c new file mode 100644 index 0000000000..70ac0d2cd8 --- /dev/null +++ b/tests/acceptance/mock_package_manager.c @@ -0,0 +1,464 @@ +#include +#include + +#include +#include // Syntax() +#include + +static char AVAILABLE_PACKAGES_FILE_NAME[PATH_MAX]; +static char INSTALLED_PACKAGES_FILE_NAME[PATH_MAX]; + +static const int MAX_PACKAGE_ENTRY_LENGTH = 256; + +#define DEFAULT_ARCHITECTURE "x666" + +#define Error(msg) fprintf(stderr, "%s:%d: %s", __FILE__, __LINE__, msg) + +static const Component COMPONENT = +{ + .name = "mock-package-manager - pretend that you are managing packages!", + .website = CF_WEBSITE, + .copyright = CF_COPYRIGHT +}; + +static const struct option OPTIONS[] = +{ + {"clear-installed", no_argument, 0, 'c'}, + {"clear-available", no_argument, 0, 'C'}, + {"list-installed", no_argument, 0, 'l'}, + {"list-available", no_argument, 0, 'L'}, + {"populate-available", required_argument, 0, 'P'}, + {"add", required_argument, 0, 'a'}, + {"delete", required_argument, 0, 'd'}, + {"reinstall", required_argument, 0, 'r'}, + {"update", required_argument, 0, 'u'}, + {"addupdate", required_argument, 0, 'U'}, + {"verify", required_argument, 0, 'v'}, + {NULL, 0, 0, '\0'} +}; + +static const char *HINTS[] = +{ + "Clear all installed imaginary packages", + "Clear all available imaginary packages", + "List installed imarginary packages", + "List imaginary packages available to be installed", + "Add an imaginary package to list of available", + "Add an imaginary package", + "Delete a previously imagined package", + "Reinstall an imaginary package", + "Update a previously imagined package", + "Add or update an imaginary package", + "Verify a previously imagined package", + NULL +}; + +typedef struct +{ + char *name; + char *version; + char *arch; +} Package; + +typedef struct +{ + char *name; + char *version; + char *arch; +} PackagePattern; + +/******************************************************************************/ + +static char *SerializePackage(Package *package) +{ + char *s; + + xasprintf(&s, "%s:%s:%s", package->name, package->version, package->arch); + return s; +} + +/******************************************************************************/ + +static char *SerializePackagePattern(PackagePattern *pattern) +{ + char *s; + + xasprintf(&s, "%s:%s:%s", pattern->name ? pattern->name : "*", + pattern->version ? pattern->version : "*", pattern->arch ? pattern->arch : "*"); + return s; +} + +/******************************************************************************/ + +static Package *DeserializePackage(const char *entry) +{ + Package *package = xcalloc(1, sizeof(Package)); + + char *entry_copy = xstrdup(entry); + + package->name = strtok(entry_copy, ":"); + package->version = strtok(NULL, ":"); + package->arch = strtok(NULL, ":"); + + if (package->name == NULL || strcmp(package->name, "*") == 0 + || package->version == NULL || strcmp(package->version, "*") == 0 + || package->arch == NULL || strcmp(package->arch, "*") == 0) + { + fprintf(stderr, "Incomplete package specification: %s:%s:%s\n", package->name, package->version, package->arch); + exit(255); + } + + return package; +} + +/******************************************************************************/ + +static bool IsWildcard(const char *str) +{ + return str && (strcmp(str, "*") == 0 || strcmp(str, "") == 0); +} + +/******************************************************************************/ + +static void CheckWellformedness(const char *str) +{ + if (str && strchr(str, '*') != NULL) + { + fprintf(stderr, "* is encountered in pattern not as wildcard: '%s'\n", str); + exit(255); + } +} + +/******************************************************************************/ + +static PackagePattern *NewPackagePattern(const char *name, const char *version, const char *arch) +{ + PackagePattern *pattern = xcalloc(1, sizeof(PackagePattern)); + + pattern->name = name ? xstrdup(name) : NULL; + pattern->version = version ? xstrdup(version) : NULL; + pattern->arch = arch ? xstrdup(arch) : NULL; + return pattern; +} + +/******************************************************************************/ + +static PackagePattern *DeserializePackagePattern(const char *entry) +{ +//PackagePattern *pattern = xcalloc(1, sizeof(PackagePattern)); + + char *entry_copy = xstrdup(entry); + + char *name = strtok(entry_copy, ":"); + + if (IsWildcard(name)) + { + name = NULL; + } + CheckWellformedness(name); + + char *version = strtok(NULL, ":"); + + if (IsWildcard(version)) + { + version = NULL; + } + CheckWellformedness(version); + + char *arch = strtok(NULL, ":"); + + if (arch == NULL) + { + arch = DEFAULT_ARCHITECTURE; + } + else if (IsWildcard(arch)) + { + arch = NULL; + } + CheckWellformedness(arch); + + if (strtok(NULL, ":") != NULL) + { + fprintf(stderr, "Too many delimiters are encountered in pattern: %s\n", entry); + exit(255); + } + + return NewPackagePattern(name, version, arch); +} + +/******************************************************************************/ + +static Seq *ReadPackageEntries(const char *database_filename) +{ + FILE *packages_file = fopen(database_filename, "r"); + Seq *packages = SeqNew(1000, NULL); + + if (packages_file != NULL) + { + char serialized_package[MAX_PACKAGE_ENTRY_LENGTH]; + + while (fscanf(packages_file, "%s\n", serialized_package) != EOF) + { + Package *package = DeserializePackage(serialized_package); + + SeqAppend(packages, package); + } + + fclose(packages_file); + } + + return packages; +} + +/******************************************************************************/ + +static void SavePackages(const char *database_filename, Seq *package_entries) +{ + FILE *packages_file = fopen(database_filename, "w"); + + for (size_t i = 0; i < SeqLength(package_entries); i++) + { + fprintf(packages_file, "%s\n", SerializePackage(SeqAt(package_entries, i))); + } + + fclose(packages_file); +} + +/******************************************************************************/ + +static PackagePattern *MatchAllVersions(const Package *p) +{ + return NewPackagePattern(p->name, NULL, p->arch); +} + +/******************************************************************************/ + +static PackagePattern *MatchSame(const Package *p) +{ + return NewPackagePattern(p->name, p->version, p->arch); +} + +/******************************************************************************/ + +static bool MatchPackage(PackagePattern *a, Package *b) +{ + return (a->name == NULL || strcmp(a->name, b->name) == 0) && + (a->version == NULL || strcmp(a->version, b->version) == 0) && + (a->arch == NULL || strcmp(a->arch, b->arch) == 0); +} + +/******************************************************************************/ + +static Seq *FindPackages(const char *database_filename, PackagePattern *pattern) +{ + Seq *db = ReadPackageEntries(database_filename); + Seq *matching = SeqNew(1000, NULL); + + for (size_t i = 0; i < SeqLength(db); i++) + { + Package *package = SeqAt(db, i); + + if (MatchPackage(pattern, package)) + { + SeqAppend(matching, package); + } + } + + return matching; +} + +/******************************************************************************/ + +static void ShowPackages(FILE *out, Seq *package_entries) +{ + for (size_t i = 0; i < SeqLength(package_entries); i++) + { + fprintf(out, "%s\n", SerializePackage(SeqAt(package_entries, i))); + } +} + +/******************************************************************************/ + +static void ClearPackageList(const char *db_file_name) +{ + FILE *packages_file = fopen(db_file_name, "w"); + + if (packages_file == NULL) + { + fprintf(stderr, "fopen(%s): %s", INSTALLED_PACKAGES_FILE_NAME, strerror(errno)); + exit(255); + } + fclose(packages_file); +} + +/******************************************************************************/ + +static void ClearInstalledPackages(void) +{ + ClearPackageList(INSTALLED_PACKAGES_FILE_NAME); +} + +/******************************************************************************/ + +static void ClearAvailablePackages(void) +{ + ClearPackageList(AVAILABLE_PACKAGES_FILE_NAME); +} + +/******************************************************************************/ + +static void AddPackage(PackagePattern *pattern) +{ + fprintf(stderr, "Trying to install all packages matching pattern %s\n", SerializePackagePattern(pattern)); + + Seq *matching_available = FindPackages(AVAILABLE_PACKAGES_FILE_NAME, pattern); + + if (SeqLength(matching_available) == 0) + { + fprintf(stderr, "Unable to find any package matching %s\n", SerializePackagePattern(pattern)); + exit(EXIT_FAILURE); + } + + for (size_t i = 0; i < SeqLength(matching_available); i++) + { + Package *p = SeqAt(matching_available, i); + + PackagePattern *pat = MatchAllVersions(p); + + if (SeqLength(FindPackages(INSTALLED_PACKAGES_FILE_NAME, pat)) > 0) + { + fprintf(stderr, "Package %s is already installed.\n", SerializePackage(p)); + exit(EXIT_FAILURE); + } + } + + Seq *installed_packages = ReadPackageEntries(INSTALLED_PACKAGES_FILE_NAME); + + for (size_t i = 0; i < SeqLength(matching_available); i++) + { + Package *p = SeqAt(matching_available, i); + + SeqAppend(installed_packages, p); + fprintf(stderr, "Successfully installed package %s\n", SerializePackage(p)); + } + + SavePackages(INSTALLED_PACKAGES_FILE_NAME, installed_packages); + exit(EXIT_SUCCESS); +} + +/******************************************************************************/ + +static void PopulateAvailable(const char *arg) +{ + Package *p = DeserializePackage(arg); + PackagePattern *pattern = MatchSame(p); + + if (SeqLength(FindPackages(AVAILABLE_PACKAGES_FILE_NAME, pattern)) > 0) + { + fprintf(stderr, "Skipping already available package %s\n", SerializePackage(p)); + return; + } + + Seq *available_packages = ReadPackageEntries(AVAILABLE_PACKAGES_FILE_NAME); + + SeqAppend(available_packages, p); + SavePackages(AVAILABLE_PACKAGES_FILE_NAME, available_packages); +} + +/******************************************************************************/ + +int main(int argc, char *argv[]) +{ + extern char *optarg; + int option_index = 0; + int c; + +#ifdef __MINGW32__ + InitializeWindows(); +#endif + + char *workdir = getenv("CFENGINE_TEST_OVERRIDE_WORKDIR"); + char *tempdir = getenv("TEMP"); + + if (!workdir && !tempdir) + { + fprintf(stderr, "Please set either CFENGINE_TEST_OVERRIDE_WORKDIR or TEMP environment variables\n" + "to a valid directory.\n"); + return 2; + } + + xsnprintf(AVAILABLE_PACKAGES_FILE_NAME, 256, + "%s/cfengine-mock-package-manager-available", workdir ? workdir : tempdir); + xsnprintf(INSTALLED_PACKAGES_FILE_NAME, 256, + "%s/cfengine-mock-package-manager-installed", workdir ? workdir : tempdir); + + while ((c = getopt_long(argc, argv, "", OPTIONS, &option_index)) != EOF) + { + PackagePattern *pattern = NULL; + + switch (c) + { + case 'c': + ClearInstalledPackages(); + break; + + case 'C': + ClearAvailablePackages(); + break; + + case 'l': + { + Seq *installed_packages = ReadPackageEntries(INSTALLED_PACKAGES_FILE_NAME); + ShowPackages(stdout, installed_packages); + } + break; + + case 'L': + { + Seq *available_packages = ReadPackageEntries(AVAILABLE_PACKAGES_FILE_NAME); + ShowPackages(stdout, available_packages); + } + break; + + case 'a': + pattern = DeserializePackagePattern(optarg); + AddPackage(pattern); + break; + + case 'P': + PopulateAvailable(optarg); + break; + + /* case 'd': */ + /* DeletePackage(pattern); */ + /* break; */ + + /* case 'r': */ + /* ReinstallPackage(pattern); */ + /* break; */ + + /* case 'u': */ + /* UpdatePackage(pattern); */ + /* break; */ + + /* case 'U': */ + /* AddUpdatePackage(pattern); */ + /* break; */ + + /* case 'v': */ + /* VerifyPackage(pattern); */ + /* break; */ + + default: + { + Writer *w = FileWriter(stdout); + WriterWriteHelp(w, &COMPONENT, OPTIONS, HINTS, NULL, false, false); + FileWriterDetach(w); + } + exit(EXIT_FAILURE); + } + + } + + return 0; +} diff --git a/tests/acceptance/no_fds.c b/tests/acceptance/no_fds.c new file mode 100644 index 0000000000..bd8fe12bf3 --- /dev/null +++ b/tests/acceptance/no_fds.c @@ -0,0 +1,101 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include + +static void usage(void) +{ + printf("Runs the given command without inheriting any file descriptors.\n" + "Usage: no_fds [options]\n" + "Options:\n" + " --no-std\n" + " Even stdin/stdout/stderr will not be inherited.\n" + " -h, --help\n" + " This help screen.\n"); +} + +static void close_on_exec(int fd) +{ +#ifdef _WIN32 + SetHandleInformation((HANDLE)_get_osfhandle(fd), HANDLE_FLAG_INHERIT, 0); +#else + long flags = fcntl(fd, F_GETFD); + flags |= FD_CLOEXEC; + fcntl(fd, F_SETFD, flags); +#endif +} + +int main(int argc, char **argv) +{ + bool std = true; + int startarg = 1; + + for (int arg = 1; arg < argc; arg++) + { + if (strcmp(argv[arg], "--no-std") == 0) + { + std = false; + startarg++; + } + else if (strcmp(argv[arg], "-h") == 0 || strcmp(argv[arg], "--help") == 0) + { + usage(); + return 0; + } + else if (argv[arg][0] == '-') + { + fprintf(stderr, "Unknown option: %s\n", argv[arg]); + usage(); + return 1; + } + else + { + break; + } + } + + closefrom(3); + + if (!std) + { + for (int fd = 0; fd < 3; fd++) + { + close_on_exec(fd); + } + } + + char *new_args[argc - startarg + 1]; + for (int i = startarg; i < argc; i++) + { + new_args[i - startarg] = argv[i]; + } + new_args[argc - startarg] = NULL; + + execvp(new_args[0], new_args); + + fprintf(stderr, "Could not execute command '%s': %s\n", new_args[0], GetErrorStr()); + + return 1; +} diff --git a/tests/acceptance/plucked.cf.sub b/tests/acceptance/plucked.cf.sub new file mode 100644 index 0000000000..297b6715a3 --- /dev/null +++ b/tests/acceptance/plucked.cf.sub @@ -0,0 +1,1968 @@ +### This is an auto-generated file, see Makefile.am and `make pluck` ### + +bundle agent run_ifdefined(namespace, mybundle) +# @brief bundle to maybe run another bundle dynamically +# @param namespace the namespace, usually `$(this.namespace)` +# @param mybundle the bundle to maybe run +# +# This bundle simply is a way to run another bundle only if it's defined. +# +# **Example:** +# +# ```cf3 +# bundle agent run +# { +# methods: +# # does nothing if bundle "runthis" is not defined +# "go" usebundle => run_ifdefined($(this.namespace), runthis); +# } +# ``` +{ + vars: + "bundlesfound" slist => bundlesmatching("^$(namespace):$(mybundle)$"); + "count" int => length(bundlesfound); + + methods: + "any" + usebundle => $(bundlesfound), + ifvarclass => strcmp(1, $(count)); + + reports: + verbose_mode:: + "$(this.bundle): found matching bundles $(bundlesfound) for namespace '$(namespace)' and bundle '$(mybundle)'"; +} + +body contain in_dir_shell(dir) +# @brief run command after switching to directory "dir" with full shell +# @param dir directory to change into +# +# **Example:** +# +# ```cf3 +# commands: +# "/bin/pwd | /bin/cat" +# contain => in_dir_shell("/tmp"); +# ``` +{ + chdir => "$(dir)"; + useshell => "true"; # canonical "useshell" but this is backwards-compatible +} + +body contain in_shell +# @brief run command in shell +# +# **Example:** +# +# ```cf3 +# commands: +# "/bin/pwd | /bin/cat" +# contain => in_shell; +# ``` +{ + useshell => "true"; # canonical "useshell" but this is backwards-compatible +} + +body contain in_shell_bg +# @brief deprecated +# This bundle previously had an invalid background attribute that was caught by +# parser strictness enhancements. Backgrounding is handeled by the body action +# background attribute. +{ + useshell => "true"; # canonical "useshell" but this is backwards-compatible +} + +body contain in_shell_and_silent +# @brief run command in shell and suppress output +# +# **Example:** +# +# ```cf3 +# commands: +# "/bin/pwd | /bin/cat" +# contain => in_shell_and_silent, +# comment => "Silently run command in shell"; +# ``` +{ + useshell => "true"; # canonical "useshell" but this is backwards-compatible + no_output => "true"; +} + +body contain in_dir_shell_and_silent(dir) +# @brief run command in shell after switching to 'dir' and suppress output +# @param dir directory to change into +# +# **Example:** +# +# ```cf3 +# commands: +# "/bin/pwd | /bin/cat" +# contain => in_dir_shell_and_silent("/tmp"), +# comment => "Silently run command in shell"; +# ``` + +{ + useshell => "true"; # canonical "useshell" but this is backwards-compatible + no_output => "true"; + chdir => "$(dir)"; +} + +body action if_elapsed(x) +# @brief Evaluate the promise every `x` minutes +# @param x The time in minutes between promise evaluations +{ + ifelapsed => "$(x)"; + expireafter => "$(x)"; +} + +body action if_elapsed_day +# @brief Evalute the promise once every 24 hours +{ + ifelapsed => "1440"; # 60 x 24 + expireafter => "1400"; +} + +body action warn_only +# @brief Warn once an hour if the promise needs to be repaired +# +# The promise does not get repaired. +{ + action_policy => "warn"; + ifelapsed => "60"; +} + +body action immediate +# @brief Evaluate the promise at every `cf-agent` execution. +{ + ifelapsed => "0"; +} + +body classes if_repaired(x) +# @brief Define class `x` if the promise has been repaired +# @param x The name of the class +{ + promise_repaired => { "$(x)" }; +} + +body classes if_else(yes,no) +# @brief Define the classes `yes` or `no` depending on promise outcome +# @param yes The name of the class that should be defined if the promise is kept or repaired +# @param no The name of the class that should be defined if the promise could not be repaired +{ + promise_kept => { "$(yes)" }; + promise_repaired => { "$(yes)" }; + repair_failed => { "$(no)" }; + repair_denied => { "$(no)" }; + repair_timeout => { "$(no)" }; +} + +body classes if_notkept(x) +# @brief Define the class `x` if the promise is not kept and cannot be repaired. +# @param x The name of the class that should be defined +{ + repair_failed => { "$(x)" }; + repair_denied => { "$(x)" }; + repair_timeout => { "$(x)" }; +} + +body classes if_ok(x) +# @brief Define the class `x` if the promise is kept or could be repaired +# @param x The name of the class that should be defined +{ + promise_repaired => { "$(x)" }; + promise_kept => { "$(x)" }; +} + +body classes if_ok_cancel(x) +# @brief Cancel the class `x` if the promise ks kept or repaired +# @param x The name of the class that should be cancelled +{ + cancel_repaired => { "$(x)" }; + cancel_kept => { "$(x)" }; +} + +body classes classes_generic(x) +# @brief Define `x` prefixed/suffixed with promise outcome +# @param x The unique part of the classes to be defined +{ + promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" }; + repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" }; + repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" }; + repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" }; + promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_reached" }; +} + +body classes results(scope, class_prefix) +# @brief Define classes prefixed with `class_prefix` and suffixed with +# appropriate outcomes: _kept, _repaired, _not_kept, _error, _failed, +# _denied, _timeout, _reached +# +# @param scope The scope in which the class should be defined (`bundle` or `namespace`) +# @param class_prefix The prefix for the classes defined +# +# This body can be applied to any promise and sets global +# (`namespace`) or local (`bundle`) classes based on its outcome. For +# instance, with `class_prefix` set to `abc`: +# +# * if the promise is to change a file's owner to `nick` and the file +# was already owned by `nick`, the classes `abc_reached` and +# `abc_kept` will be set. +# +# * if the promise is to change a file's owner to `nick` and the file +# was owned by `adam` and the change succeeded, the classes +# `abc_reached` and `abc_repaired` will be set. +# +# This body is a simpler, more consistent version of the body +# `scoped_classes_generic`, which see. The key difference is that +# fewer classes are defined, and only for outcomes that we can know. +# For example this body does not define "OK/not OK" outcome classes, +# since a promise can be both kept and failed at the same time. +# +# It's important to understand that promises may do multiple things, +# so a promise is not simply "OK" or "not OK." The best way to +# understand what will happen when your specific promises get this +# body is to test it in all the possible combinations. +# +# **Suffix Notes:** +# +# * `_reached` indicates the promise was tried. Any outcome will result +# in a class with this suffix being defined. +# +# * `_kept` indicates some aspect of the promise was kept +# +# * `_repaired` indicates some aspect of the promise was repaired +# +# * `_not_kept` indicates some aspect of the promise was not kept. +# error, failed, denied and timeout outcomes will result in a class +# with this suffix being defined +# +# * `_error` indicates the promise repair encountered an error +# +# * `_failed` indicates the promise failed +# +# * `_denied` indicates the promise repair was denied +# +# * `_timeout` indicates the promise timed out +# +# **Example:** +# +# ```cf3 +# bundle agent example +# { +# commands: +# "/bin/true" +# classes => results("bundle", "my_class_prefix"); +# +# reports: +# my_class_prefix_kept:: +# "My promise was kept"; +# +# my_class_prefix_repaired:: +# "My promise was repaired"; +# } +# ``` +# +# **See also:** `scope`, `scoped_classes_generic`, `classes_generic` +{ + scope => "$(scope)"; + + promise_kept => { "$(class_prefix)_reached", + "$(class_prefix)_kept" }; + + promise_repaired => { "$(class_prefix)_reached", + "$(class_prefix)_repaired" }; + + repair_failed => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_failed" }; + + repair_denied => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_denied" }; + + repair_timeout => { "$(class_prefix)_reached", + "$(class_prefix)_error", + "$(class_prefix)_not_kept", + "$(class_prefix)_timeout" }; +} + +body classes scoped_classes_generic(scope, x) +# @brief Define `x` prefixed/suffixed with promise outcome +# **See also:** `scope` +# +# @param scope The scope in which the class should be defined +# @param x The unique part of the classes to be defined +{ + scope => "$(scope)"; + promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" }; + repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" }; + repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" }; + repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" }; + promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_reached" }; +} + +body classes always(x) +# @brief Define class `x` no matter what the outcome of the promise is +# @param x The name of the class to be defined +{ + promise_repaired => { "$(x)" }; + promise_kept => { "$(x)" }; + repair_failed => { "$(x)" }; + repair_denied => { "$(x)" }; + repair_timeout => { "$(x)" }; +} + +body classes kept_successful_command +# @brief Set command to "kept" instead of "repaired" if it returns 0 +{ + kept_returncodes => { "0" }; +} + +bundle edit_line insert_before_if_no_line(before, string) +# @brief Insert `string` before `before` if `string` is not found in the file +# @param before The regular expression matching the line which `string` will be +# inserted before +# @param string The string to be prepended +# +{ + insert_lines: + "$(string)" + location => before($(before)), + comment => "Prepend a line to the file if it doesn't already exist"; +} + +bundle edit_line insert_lines(lines) +# @brief Append `lines` if they don't exist in the file +# @param lines The lines to be appended +# +# **See also:** [`insert_lines`][insert_lines] in +# [`edit_line`][bundle edit_line] +{ + insert_lines: + + "$(lines)" + comment => "Append lines if they don't exist"; +} + +bundle edit_line insert_file(templatefile) +# @brief Reads the lines from `templatefile` and inserts those into the +# file being edited. +# @param templatefile The name of the file from which to import lines. +{ + insert_lines: + + "$(templatefile)" + comment => "Insert the template file into the file being edited", + insert_type => "file"; +} + +bundle edit_line comment_lines_matching(regex,comment) +# @brief Comment lines in the file that matching an [anchored] regex +# @param regex Anchored regex that the entire line needs to match +# @param comment A string that is prepended to matching lines +{ + replace_patterns: + + "^($(regex))$" + + replace_with => comment("$(comment)"), + comment => "Search and replace string"; +} + +bundle edit_line uncomment_lines_matching(regex,comment) +# @brief Uncomment lines of the file where the regex matches +# the entire text after the comment string +# @param regex The regex that lines need to match after `comment` +# @param comment The prefix of the line that is removed +{ + replace_patterns: + + "^$(comment)\s?($(regex))$" + + replace_with => uncomment, + comment => "Uncomment lines matching a regular expression"; +} + +bundle edit_line comment_lines_containing(regex,comment) +# @brief Comment lines of the file matching a regex +# @param regex A regex that a part of the line needs to match +# @param comment A string that is prepended to matching lines +{ + replace_patterns: + + "^((?!$(comment)).*$(regex).*)$" + + replace_with => comment("$(comment)"), + comment => "Comment out lines in a file"; +} + +bundle edit_line uncomment_lines_containing(regex,comment) +# @brief Uncomment lines of the file where the regex matches +# parts of the text after the comment string +# @param regex The regex that lines need to match after `comment` +# @param comment The prefix of the line that is removed +{ + replace_patterns: + + "^$(comment)\s?(.*$(regex).*)$" + + replace_with => uncomment, + comment => "Uncomment a line containing a fragment"; +} + +bundle edit_line delete_lines_matching(regex) +# @brief Delete lines matching a regular expression +# @param regex The regular expression that the lines need to match +{ + delete_lines: + + "$(regex)" + + comment => "Delete lines matching regular expressions"; +} + +bundle edit_line warn_lines_matching(regex) +# @brief Warn about lines matching a regular expression +# @param regex The regular expression that the lines need to match +{ + delete_lines: + + "$(regex)" + + comment => "Warn about lines in a file", + action => warn_only; +} + +bundle edit_line prepend_if_no_line(string) +# @brief Prepend `string` if it doesn't exist in the file +# @param string The string to be prepended +# +# **See also:** [`insert_lines`][insert_lines] in +# [`edit_line`][bundle edit_line] +{ + insert_lines: + "$(string)" + location => start, + comment => "Prepend a line to the file if it doesn't already exist"; +} + +bundle edit_line append_if_no_line(str) +# @ignore +# This duplicates the insert_lines bundle +{ + insert_lines: + + "$(str)" + + comment => "Append a line to the file if it doesn't already exist"; +} + +bundle edit_line append_if_no_lines(list) +# @ignore +# This duplicates the insert_lines bundle +{ + insert_lines: + + "$(list)" + + comment => "Append lines to the file if they don't already exist"; +} + +bundle edit_line replace_line_end(start,end) +# @brief Give lines starting with `start` the ending given in `end` +# +# Whitespaces will be left unmodified. For example, +# `replace_line_end("ftp", "2121/tcp")` would replace +# +# `"ftp 21/tcp"` +# +# with +# +# `"ftp 2121/tcp"` +# +# @param start The string lines have to start with +# @param end The string lines should end with +{ + field_edits: + + "\s*$(start)\s.*" + comment => "Replace lines with $(this.start) and $(this.end)", + edit_field => line("(^|\s)$(start)\s*", "2", "$(end)","set"); +} + +bundle edit_line append_to_line_end(start,end) +# @brief Append `end` to any lines beginning with `start` +# +# `end` will be appended to all lines starting with `start` and not +# already ending with `end`. Whitespaces will be left unmodified. +# +# For example, `append_to_line_end("kernel", "vga=791")` would replace +# `kernel /boot/vmlinuz root=/dev/sda7` +# +# with +# +# `kernel /boot/vmlinuz root=/dev/sda7 vga=791` +# +# **WARNING**: Be careful not to have multiple promises matching the same line, which would result in the line growing indefinitely. +# +# @param start pattern to match lines of interest +# @param end string to append to matched lines +# +# **Example:** +# +# ```cf3 +# files: +# "/tmp/boot-options" edit_line => append_to_line_end("kernel", "vga=791"); +# ``` +# +{ + field_edits: + + "\s*$(start)\s.*" + comment => "Append lines with $(this.start) and $(this.end)", + edit_field => line("(^|\s)$(start)\s*", "2", "$(end)","append"); +} + +bundle edit_line regex_replace(find,replace) +# @brief Find exactly a regular expression and replace exactly the match with a string. +# You can think of this like a PCRE powered sed. +# @param find The regular expression +# @param replace The replacement string +{ + replace_patterns: + + "$(find)" + replace_with => value("$(replace)"), + comment => "Search and replace string"; +} + +bundle edit_line resolvconf(search,list) +# @brief Adds search domains and name servers to the system +# resolver configuration. +# +# Use this bundle to modify `resolv.conf`. Existing entries for +# `search` and `nameserver` are replaced. +# +# @param search The search domains with space +# @param list An slist of nameserver addresses +{ + delete_lines: + + "search.*" comment => "Reset search lines from resolver"; + "nameserver.*" comment => "Reset nameservers in resolver"; + + insert_lines: + + "search $(search)" comment => "Add search domains to resolver"; + "nameserver $(list)" comment => "Add name servers to resolver"; +} + +bundle edit_line resolvconf_o(search,list,options) +# @brief Adds search domains, name servers and options to the system +# resolver configuration. +# +# Use this bundle to modify `resolv.conf`. Existing entries for +# `search`, `nameserver` and `options` are replaced. +# +# @param search The search domains with space +# @param list An slist of nameserver addresses +# @param options is an slist of variables to modify the resolver + +{ + delete_lines: + + "search.*" comment => "Reset search lines from resolver"; + "nameserver.*" comment => "Reset nameservers in resolver"; + "options.*" comment => "Reset options in resolver"; + + insert_lines: + + "search $(search)" comment => "Add search domains to resolver"; + "nameserver $(list)" comment => "Add name servers to resolver"; + "options $(options)" comment => "Add options to resolver"; +} + +bundle edit_line manage_variable_values_ini(tab, sectionName) +# @brief Sets the RHS of configuration items in the file of the form +# `LHS=RHS` +# +# If the line is commented out with `#`, it gets uncommented first. +# Adds a new line if none exists. +# Removes any variable value pairs not defined for the ini section. +# +# @param tab An associative array containing `tab[sectionName][LHS]="RHS"`. +# The value is not changed when the `RHS` is "dontchange" +# @param sectionName The section in the file within which values should be +# modified +# +# **See also:** `set_variable_values_ini()` +{ + vars: + "index" slist => getindices("$(tab)[$(sectionName)]"); + + # Be careful if the index string contains funny chars + "cindex[$(index)]" string => canonify("$(index)"); + + classes: + "edit_$(cindex[$(index)])" not => strcmp("$($(tab)[$(sectionName)][$(index)])","dontchange"), + comment => "Create conditions to make changes"; + + field_edits: + + # If the line is there, but commented out, first uncomment it + "#+\s*$(index)\s*=.*" + select_region => INI_section(escape("$(sectionName)")), + edit_field => col("=","1","$(index)","set"), + ifvarclass => "edit_$(cindex[$(index)])"; + + # match a line starting like the key something + "$(index)\s*=.*" + edit_field => col("=","2","$($(tab)[$(sectionName)][$(index)])","set"), + select_region => INI_section(escape("$(sectionName)")), + classes => results("bundle", "manage_variable_values_ini_not_$(cindex[$(index)])"), + ifvarclass => "edit_$(cindex[$(index)])"; + + delete_lines: + ".*" + select_region => INI_section(escape("$(sectionName)")), + comment => "Remove all entries in the region so there are no extra entries"; + + insert_lines: + "[$(sectionName)]" + location => start, + comment => "Insert lines"; + + "$(index)=$($(tab)[$(sectionName)][$(index)])" + select_region => INI_section(escape("$(sectionName)")), + ifvarclass => "!(manage_variable_values_ini_not_$(cindex[$(index)])_kept|manage_variable_values_ini_not_$(cindex[$(index)])_repaired).edit_$(cindex[$(index)])"; + +} + +bundle edit_line set_variable_values_ini(tab, sectionName) +# @brief Sets the RHS of configuration items in the file of the form +# `LHS=RHS` +# +# If the line is commented out with `#`, it gets uncommented first. +# Adds a new line if none exists. +# +# @param tab An associative array containing `tab[sectionName][LHS]="RHS"`. +# The value is not changed when the `RHS` is "dontchange" +# @param sectionName The section in the file within which values should be +# modified +# +# **See also:** `manage_variable_values_ini()` +{ + vars: + "index" slist => getindices("$(tab)[$(sectionName)]"); + + # Be careful if the index string contains funny chars + "cindex[$(index)]" string => canonify("$(index)"); + + classes: + "edit_$(cindex[$(index)])" not => strcmp("$($(tab)[$(sectionName)][$(index)])","dontchange"), + comment => "Create conditions to make changes"; + + field_edits: + + # If the line is there, but commented out, first uncomment it + "#+\s*$(index)\s*=.*" + select_region => INI_section("$(sectionName)"), + edit_field => col("=","1","$(index)","set"), + ifvarclass => "edit_$(cindex[$(index)])"; + + # match a line starting like the key something + "$(index)\s*=.*" + edit_field => col("=","2","$($(tab)[$(sectionName)][$(index)])","set"), + select_region => INI_section("$(sectionName)"), + classes => results("bundle", "set_variable_values_ini_not_$(cindex[$(index)])"), + ifvarclass => "edit_$(cindex[$(index)])"; + + insert_lines: + "[$(sectionName)]" + location => start, + comment => "Insert lines"; + + "$(index)=$($(tab)[$(sectionName)][$(index)])" + select_region => INI_section("$(sectionName)"), + ifvarclass => "!(set_variable_values_ini_not_$(cindex[$(index)])_kept|set_variable_values_ini_not_$(cindex[$(index)])_repaired).edit_$(cindex[$(index)])"; + +} + +bundle edit_line insert_ini_section(name, config) +# @brief Inserts a INI section with content +# +# ``` +# # given an array "barray" +# files: +# "myfile.ini" edit_line => insert_innit_section("foo", "barray"); +# ``` +# +# Inserts a section in an INI file with the given configuration +# key-values from the array `config`. +# +# @param name the name of the INI section +# @param config The fully-qualified name of an associative array containing `v[LHS]="rhs"` +{ + vars: + "k" slist => getindices($(config)); + + insert_lines: + "[$(name)]" + location => start, + comment => "Prepend a line to the file if it doesn't already exist"; + + "$(k)=$($(config)[$(k)])"; +} + +bundle edit_line set_quoted_values(v) +# @brief Sets the RHS of variables in shell-like files of the form: +# +# ``` +# LHS="RHS" +# ``` +# +# Adds a new line if no LHS exists, and replaces RHS values if one does exist. +# If the line is commented out with #, it gets uncommented first. +# +# @param v The fully-qualified name of an associative array containing `v[LHS]="rhs"` +# +# **Example:** +# +# ```cf3 +# vars: +# "stuff[lhs-1]" string => "rhs1"; +# "stuff[lhs-2]" string => "rhs2"; +# +# files: +# "myfile" +# edit_line => set_quoted_values(stuff) +# ``` +# +# **See also:** `set_variable_values()` +{ + meta: + "tags" + slist => + { + "deprecated=3.6.0", + "deprecation-reason=Generic reimplementation", + "replaced-by=set_line_based" + }; + + vars: + "index" slist => getindices("$(v)"); + # Be careful if the index string contains funny chars + + "cindex[$(index)]" string => canonify("$(index)"); + + field_edits: + # If the line is there, but commented out, first uncomment it + "#+\s*$(index)\s*=.*" + edit_field => col("=","1","$(index)","set"); + + # match a line starting like the key = something + "\s*$(index)\s*=.*" + edit_field => col("=","2",'"$($(v)[$(index)])"',"set"), + classes => results("bundle", "$(cindex[$(index)])_in_file"), + comment => "Match a line starting like key = something"; + + insert_lines: + '$(index)="$($(v)[$(index)])"' + comment => "Insert a variable definition", + ifvarclass => "!($(cindex[$(index)])_in_file_kept|$(cindex[$(index)])_in_file_repaired)"; +} + +bundle edit_line set_variable_values(v) +# @brief Sets the RHS of variables in files of the form: +# +# ``` +# LHS=RHS +# ``` +# +# Adds a new line if no LHS exists, and replaces RHS values if one does exist. +# If the line is commented out with #, it gets uncommented first. +# +# @param v The fully-qualified name of an associative array containing `v[LHS]="rhs"` +# +# **Example:** +# +# ```cf3 +# vars: +# "stuff[lhs-1]" string => "rhs1"; +# "stuff[lhs-2]" string => "rhs2"; +# +# files: +# "myfile" +# edit_line => set_variable_values(stuff) +# ``` +# +# **See also:** `set_quoted_values()` +{ + meta: + "tags" + slist => + { + "deprecated=3.6.0", + "deprecation-reason=Generic reimplementation", + "replaced-by=set_line_based" + }; + + vars: + + "index" slist => getindices("$(v)"); + + # Be careful if the index string contains funny chars + + "cindex[$(index)]" string => canonify("$(index)"); + "cv" string => canonify("$(v)"); + + field_edits: + + # match a line starting like the key = something + + "\s*$(index)\s*=.*" + + edit_field => col("\s*$(index)\s*=","2","$($(v)[$(index)])","set"), + classes => results("bundle", "$(cv)_$(cindex[$(index)])_in_file"), + comment => "Match a line starting like key = something"; + + insert_lines: + + "$(index)=$($(v)[$(index)])" + + comment => "Insert a variable definition", + ifvarclass => "!($(cv)_$(cindex[$(index)])_in_file_kept|$(cv)_$(cindex[$(index)])_in_file_repaired)"; +} + +bundle edit_line set_config_values(v) +# @brief Sets the RHS of configuration items in the file of the form: +# +# ``` +# LHS RHS +# ``` +# +# If the line is commented out with `#`, it gets uncommented first. +# +# Adds a new line if none exists. +# +# @param v The fully-qualified name of an associative array containing `v[LHS]="rhs"` +{ + meta: + "tags" + slist => + { + "deprecated=3.6.0", + "deprecation-reason=Generic reimplementation", + "replaced-by=set_line_based" + }; + + vars: + "index" slist => getindices("$(v)"); + + # Be careful if the index string contains funny chars + "cindex[$(index)]" string => canonify("$(index)"); + + # Escape the value (had a problem with special characters and regex's) + "ev[$(index)]" string => escape("$($(v)[$(index)])"); + + # Do we have more than one line commented out? + "index_comment_matches_$(cindex[$(index)])" + int => countlinesmatching("^\s*#\s*($(index)\s+.*|$(index))$","$(edit.filename)"); + + + classes: + # Check to see if this line exists + "line_exists_$(cindex[$(index)])" + expression => regline("^\s*($(index)\s.*|$(index))$","$(edit.filename)"), + scope => "bundle"; + + # if there's more than one comment, just add new (don't know who to use) + "multiple_comments_$(cindex[$(index)])" + expression => isgreaterthan("$(index_comment_matches_$(cindex[$(index)]))","1"), + scope => "bundle"; + + replace_patterns: + # If the line is commented out, uncomment and replace with + # the correct value + "^\s*#\s*($(index)\s+.*|$(index))$" + comment => "If we find a single commented entry we can uncomment it to + keep the settings near any inline documentation. If there + are multiple comments, then we don't try to replace them and + instead will later append the new value after the first + commented occurrence of $(index).", + handle => "set_config_values_replace_commented_line", + replace_with => value("$(index) $($(v)[$(index)])"), + ifvarclass => "!line_exists_$(cindex[$(index)]).!replace_attempted_$(cindex[$(index)])_reached.!multiple_comments_$(cindex[$(index)])", + classes => results("bundle", "uncommented_$(cindex[$(index)])"); + + # If the line is there with the wrong value, replace with + # the correct value + "^\s*($(index)\s+(?!$(ev[$(index)])$).*|$(index))$" + comment => "Correct the value $(index)", + replace_with => value("$(index) $($(v)[$(index)])"), + classes => results("bundle", "replace_attempted_$(cindex[$(index)])"); + + insert_lines: + # If the line doesn't exist, or there is more than one occurrence + # of the LHS commented out, insert a new line and try to place it + # after the commented LHS (keep new line with old comments) + "$(index) $($(v)[$(index)])" + comment => "Insert the value, marker exists $(index)", + location => after("^\s*#\s*($(index)\s+.*|$(index))$"), + ifvarclass => "replace_attempted_$(cindex[$(index)])_reached.multiple_comments_$(cindex[$(index)])"; + + # If the line doesn't exist and there are no occurrences + # of the LHS commented out, insert a new line at the eof + "$(index) $($(v)[$(index)])" + comment => "Insert the value, marker doesn't exist $(index)", + ifvarclass => "replace_attempted_$(cindex[$(index)])_reached.!multiple_comments_$(cindex[$(index)])"; + +} + +bundle edit_line set_line_based(v, sep, bp, kp, cp) +# @brief Sets the RHS of configuration items in the file of the form: +# +# ``` +# LHS$(sep)RHS +# ``` +# +# Example usage for `x=y` lines (e.g. rsyncd.conf): +# +# ```cf3 +# "myfile" +# edit_line => set_line_based("test.config", "=", "\s*=\s*", ".*", "\s*#\s*"); +# ``` +# +# Example usage for `x y` lines (e.g. sshd_config): +# +# ```cf3 +# "myfile" +# edit_line => set_line_based("test.config", " ", "\s+", ".*", "\s*#\s*"); +# ``` +# +# If the line is commented out with `$(cp)`, it gets uncommented first. +# +# Adds a new line if none exists or if more than one commented-out +# possible matches exist. +# +# Originally `set_config_values` by Ed King. +# +# @param v The fully-qualified name of an associative array containing `v[LHS]="rhs"` +# @param sep The separator to insert, e.g. ` ` for space-separated +# @param bp The key-value separation regex, e.g. `\s+` for space-separated +# @param kp The keys to select from v, use `.*` for all +# @param cp The comment pattern from line-start, e.g. `\s*#\s*` +{ + meta: + "tags" + slist => + { + "replaces=set_config_values", + "replaces=set_config_values_matching", + "replaces=set_variable_values", + "replaces=set_quoted_values", + "replaces=maintain_key_values", + }; + + vars: + "vkeys" slist => getindices("$(v)"); + "i" slist => grep($(kp), vkeys); + + # Be careful if the index string contains funny chars + "ci[$(i)]" string => canonify("$(i)"); + + # Escape the value (had a problem with special characters and regex's) + "ev[$(i)]" string => escape("$($(v)[$(i)])"); + + # Do we have more than one line commented out? + "comment_matches_$(ci[$(i)])" + int => countlinesmatching("^$(cp)($(i)$(bp).*|$(i))$", + $(edit.filename)); + + + classes: + # Check to see if this line exists + "exists_$(ci[$(i)])" + expression => regline("^\s*($(i)$(bp).*|$(i))$", + $(edit.filename)); + + # if there's more than one comment, just add new (don't know who to use) + "multiple_comments_$(ci[$(i)])" + expression => isgreaterthan("$(comment_matches_$(ci[$(i)]))", + "1"); + + + replace_patterns: + # If the line is commented out, uncomment and replace with + # the correct value + "^$(cp)($(i)$(bp).*|$(i))$" + comment => "Uncommented the value '$(i)'", + replace_with => value("$(i)$(sep)$($(v)[$(i)])"), + ifvarclass => "!exists_$(ci[$(i)]).!replace_attempted_$(ci[$(i)])_reached.!multiple_comments_$(ci[$(i)])", + classes => results("bundle", "uncommented_$(ci[$(i)])"); + + # If the line is there with the wrong value, replace with + # the correct value + "^\s*($(i)$(bp)(?!$(ev[$(i)])$).*|$(i))$" + comment => "Correct the value '$(i)'", + replace_with => value("$(i)$(sep)$($(v)[$(i)])"), + classes => results("bundle", "replace_attempted_$(ci[$(i)])"); + + insert_lines: + # If the line doesn't exist, or there is more than one occurrence + # of the LHS commented out, insert a new line and try to place it + # after the commented LHS (keep new line with old comments) + "$(i)$(sep)$($(v)[$(i)])" + comment => "Insert the value, marker '$(i)' exists", + location => after("^$(cp)($(i)$(bp).*|$(i))$"), + ifvarclass => "replace_attempted_$(ci[$(i)])_reached.multiple_comments_$(ci[$(i)])"; + + # If the line doesn't exist and there are no occurrences + # of the LHS commented out, insert a new line at the eof + "$(i)$(sep)$($(v)[$(i)])" + comment => "Insert the value, marker '$(i)' doesn't exist", + ifvarclass => "replace_attempted_$(ci[$(i)])_reached.!multiple_comments_$(ci[$(i)]).!exists_$(ci[$(i)])"; + + reports: + verbose_mode|EXTRA:: + "$(this.bundle): Line for '$(i)' exists" ifvarclass => "exists_$(ci[$(i)])"; + "$(this.bundle): Line for '$(i)' does not exist" ifvarclass => "!exists_$(ci[$(i)])"; +} + +bundle edit_line set_config_values_matching(v,pat) +# @brief Sets the RHS of configuration items in the file of the form +# +# ``` +# LHS RHS +# ``` +# +# If the line is commented out with `#`, it gets uncommented first. +# Adds a new line if none exists. +# +# @param v the fully-qualified name of an associative array containing v[LHS]="rhs" +# @param pat Only elements of `v` that match the regex `pat` are use +{ + meta: + "tags" + slist => + { + "deprecated=3.6.0", + "deprecation-reason=Generic reimplementation", + "replaced-by=set_line_based" + }; + + vars: + "allparams" slist => getindices("$(v)"); + "index" slist => grep("$(pat)", "allparams"); + + # Be careful if the index string contains funny chars + "cindex[$(index)]" string => canonify("$(index)"); + + replace_patterns: + # If the line is there, maybe commented out, uncomment and replace with + # the correct value + "^\s*($(index)\s+(?!$($(v)[$(index)])).*|# ?$(index)\s+.*)$" + comment => "Correct the value", + replace_with => value("$(index) $($(v)[$(index)])"), + classes => results("bundle", "replace_attempted_$(cindex[$(index)])"); + + insert_lines: + "$(index) $($(v)[$(index)])" + ifvarclass => "replace_attempted_$(cindex[$(index)])_reached"; + +} + +bundle edit_line maintain_key_values(v,sep) +# @ignore +# @brief Sets the RHS of configuration items with an giving separator +# +# Contributed by David Lee +{ + meta: + "tags" + slist => + { + "deprecated=3.6.0", + "deprecation-reason=Generic reimplementation", + "replaced-by=set_line_based" + }; + + vars: + "index" slist => getindices("$(v)"); + # Be careful if the index string contains funny chars + "cindex[$(index)]" string => canonify("$(index)"); + # Matching pattern for line (basically key-and-separator) + "keypat[$(index)]" string => "\s*$(index)\s*$(sep)\s*"; + + # Values may contain regexps. Escape them for replace_pattern matching. + "ve[$(index)]" string => escape("$($(v)[$(index)])"); + + classes: + "$(cindex[$(index)])_key_in_file" + comment => "Dynamic Class created if patterns matching", + expression => regline("^$(keypat[$(index)]).*", "$(edit.filename)"); + + replace_patterns: + # For convergence need to use negative lookahead on value: + # "key sep (?!value).*" + "^($(keypat[$(index)]))(?!$(ve[$(index)])$).*" + comment => "Replace definition of $(index)", + replace_with => value("$(match.1)$($(v)[$(index)])"); + + insert_lines: + "$(index)$(sep)$($(v)[$(index)])" + comment => "Insert definition of $(index)", + ifvarclass => "!$(cindex[$(index)])_key_in_file"; +} + +bundle edit_line append_users_starting(v) +# @brief For adding to `/etc/passwd` or `etc/shadow` +# @param v An array `v[username] string => "line..."` +# +# **Note:** To manage local users with CFEngine 3.6 and later, +# consider making `users` promises instead of modifying system files. +{ + vars: + + "index" slist => getindices("$(v)"); + + classes: + + "add_$(index)" not => userexists("$(index)"), + comment => "Class created if user does not exist"; + + insert_lines: + + "$($(v)[$(index)])" + + comment => "Append users into a password file format", + ifvarclass => "add_$(index)"; +} + +bundle edit_line append_groups_starting(v) +# @brief For adding groups to `/etc/group` +# @param v An array `v[groupname] string => "line..."` +# +# **Note:** To manage local users with CFEngine 3.6 and later, +# consider making `users` promises instead of modifying system files. +{ + vars: + + "index" slist => getindices("$(v)"); + + classes: + + "add_$(index)" not => groupexists("$(index)"), + comment => "Class created if group does not exist"; + + insert_lines: + + "$($(v)[$(index)])" + + comment => "Append users into a group file format", + ifvarclass => "add_$(index)"; + +} + +bundle edit_line set_colon_field(key,field,val) +# @brief Set the value of field number `field` of the line whose +# first field is `key` to the value `val`, in a colon-separated file. +# @param key The value the first field has to match +# @param field The field to be modified +# @param val The new value of `field` +{ + field_edits: + + "$(key):.*" + + comment => "Edit a colon-separated file, using the first field as a key", + edit_field => col(":","$(field)","$(val)","set"); +} + +bundle edit_line set_user_field(user,field,val) +# @brief Set the value of field number "field" in a `:-field` +# formatted file like `/etc/passwd` +# @param user The user to be modified +# @param field The field that should be modified +# @param val The value for `field` +# +# **Note:** To manage local users with CFEngine 3.6 and later, +# consider making `users` promises instead of modifying system files. +{ + field_edits: + + "$(user):.*" + + comment => "Edit a user attribute in the password file", + edit_field => col(":","$(field)","$(val)","set"); +} + +bundle edit_line append_user_field(group,field,allusers) +# @brief For adding users to to a file like `/etc/group` +# at field position `field`, comma separated subfields +# @param group The group to be modified +# @param field The field where users should be added +# @param allusers The list of users to add to `field` +# +# **Note:** To manage local users with CFEngine 3.6 and later, +# consider making `users` promises instead of modifying system files. +{ + vars: + + "val" slist => { @(allusers) }; + + field_edits: + + "$(group):.*" + + comment => "Append users into a password file format", + edit_field => col(":","$(field)","$(val)","alphanum"); +} + +bundle edit_line expand_template(templatefile) +# @brief Read in the named text file and expand `$(var)` inside the file +# @param templatefile The name of the file +{ + insert_lines: + + "$(templatefile)" + + insert_type => "file", + comment => "Expand variables in the template file", + expand_scalars => "true"; +} + +bundle edit_line replace_or_add(pattern,line) +# @brief Replace a pattern in a file with a single line. +# +# If the pattern is not found, add the line to the file. +# +# @param pattern The pattern that should be replaced +# The pattern must match the whole line (it is automatically +# anchored to the start and end of the line) to avoid +# ambiguity. +# @param line The line with which to replace matches of `pattern` +{ + vars: + "cline" string => canonify("$(line)"); + "eline" string => escape("$(line)"); + + replace_patterns: + "^(?!$(eline)$)$(pattern)$" + comment => "Replace a pattern here", + replace_with => value("$(line)"), + classes => results("bundle", "replace_$(cline)"); + + insert_lines: + "$(line)" + ifvarclass => "replace_$(cline)_reached"; +} + +bundle edit_line converge(marker, lines) +# @brief Converge `lines` marked with `marker` +# +# Any content marked with `marker` is removed, then `lines` are +# inserted. Every `line` should contain `marker`. +# +# @param marker The marker (not a regular expression; will be escaped) +# @param lines The lines to insert; all must contain `marker` +{ + vars: + "regex" string => escape($(marker)); + + delete_lines: + "$(regex)" comment => "Delete lines matching the marker"; + insert_lines: + "$(lines)" comment => "Insert the given lines"; +} + +bundle edit_line fstab_option_editor(method, mount, option) +# @brief Add or remove `/etc/fstab` options for a mount +# +# This bundle edits the options field of a mount. The `method` is a +# `field_operation` which can be `append`, `prepend`, `set`, `delete`, +# or `alphanum`. The option is OS-specific. +# +# @param method `field_operation` to apply +# @param mount the mount point +# @param option the option to add or remove +# +# **Example:** +# +# ```cf3 +# files: +# "/etc/fstab" edit_line => fstab_option_editor("delete", "/", "acl"); +# "/etc/fstab" edit_line => fstab_option_editor("append", "/", "acl"); +# ``` +{ + field_edits: + "(?!#)\S+\s+$(mount)\s.+" + edit_field => fstab_options($(option), $(method)); +} + +body edit_field fstab_options(newval, method) +# @brief Edit the options field in a fstab format +# @param newval the new option +# @param method `field_operation` to apply +# +# This body edits the options field in the fstab file format. The +# `method` is a `field_operation` which can be `append`, `prepend`, +# `set`, `delete`, or `alphanum`. The `newval` option is OS-specific. +# +# **Example:** +# +# ```cf3 +# # from the `fstab_options_editor` +# field_edits: +# "(?!#)\S+\s+$(mount)\s.+" +# edit_field => fstab_options($(option), $(method)); +# ``` +{ + field_separator => "\s+"; + select_field => "4"; + value_separator => ","; + field_value => "$(newval)"; + field_operation => "$(method)"; +} + +body edit_field quoted_var(newval,method) +# @brief Edit the quoted value of the matching line +# @param newval The new value +# @param method The method by which to edit the field +{ + field_separator => "\""; + select_field => "2"; + value_separator => " "; + field_value => "$(newval)"; + field_operation => "$(method)"; + extend_fields => "false"; + allow_blank_fields => "true"; +} + +body edit_field col(split,col,newval,method) +# @brief Edit tabluar data with comma-separated sub-values +# @param split The separator that defines columns +# @param col The (1-based) index of the value to change +# @param newval The new value +# @param method The method by which to edit the field +{ + field_separator => "$(split)"; + select_field => "$(col)"; + value_separator => ","; + field_value => "$(newval)"; + field_operation => "$(method)"; + extend_fields => "true"; + allow_blank_fields => "true"; +} + +body edit_field line(split,col,newval,method) +# @brief Edit tabular data with space-separated sub-values +# @param split The separator that defines columns +# @param col The (1-based) index of the value to change +# @param newval The new value +# @param method The method by which to edit the field +{ + field_separator => "$(split)"; + select_field => "$(col)"; + value_separator => " "; + field_value => "$(newval)"; + field_operation => "$(method)"; + extend_fields => "true"; + allow_blank_fields => "true"; +} + +body replace_with value(x) +# @brief Replace matching lines +# @param x The replacement string +{ + replace_value => "$(x)"; + occurrences => "all"; +} + +body select_region INI_section(x) +# @brief Restrict the `edit_line` promise to the lines in section `[x]` +# @param x The name of the section in an INI-like configuration file +{ + select_start => "\[$(x)\]\s*"; + select_end => "\[.*\]\s*"; + +@if minimum_version(3.10) + select_end_match_eof => "true"; +@endif +} + +body edit_defaults empty +# @brief Empty the file before editing +# +# No backup is made +{ + empty_file_before_editing => "true"; + edit_backup => "false"; + #max_file_size => "300000"; +} + +body location start +# @brief Editing occurs before the matched line +{ + before_after => "before"; +} + +body location after(str) +# @brief Editing occurs after the line matching `str` +# @param str Regular expression matching the file line location +{ + before_after => "after"; + select_line_matching => "$(str)"; +} + +body location before(str) +# @brief Editing occurs before the line matching `str` +# @param str Regular expression matching the file line location +{ + before_after => "before"; + select_line_matching => "$(str)"; +} + +body replace_with comment(c) +# @brief Comment all lines matching the pattern by preprending `c` +# @param c The prefix that comments out lines +{ + replace_value => "$(c) $(match.1)"; + occurrences => "all"; +} + +body replace_with uncomment +# @brief Uncomment all lines matching the pattern by removing +# anything outside the matching string +{ + replace_value => "$(match.1)"; + occurrences => "all"; +} + +body copy_from secure_cp(from,server) +# @brief Download a file from a remote server over an encrypted channel +# +# Only copy the file if it is different from the local copy, and verify +# that the copy is correct. +# +# @param from The location of the file on the remote server +# @param server The hostname or IP of the server from which to download +{ + source => "$(from)"; + servers => { "$(server)" }; + compare => "digest"; + encrypt => "true"; + verify => "true"; +} + +body copy_from remote_cp(from,server) +# @brief Download a file from a remote server. +# +# @param from The location of the file on the remote server +# @param server The hostname or IP of the server from which to download +{ + servers => { "$(server)" }; + source => "$(from)"; + compare => "mtime"; +} + +body copy_from remote_dcp(from,server) +# @brief Download a file from a remote server if it is different from the local copy. +# +# @param from The location of the file on the remote server +# @param server The hostname or IP of the server from which to download +{ + servers => { "$(server)" }; + source => "$(from)"; + compare => "digest"; +} + +body copy_from local_cp(from) +# @brief Copy a local file. +# +# @param from The path to the source file. +{ + source => "$(from)"; +} + +body copy_from local_dcp(from) +# @brief Copy a local file if it is different from the existing copy. +# +# @param from The path to the source file. +{ + source => "$(from)"; + compare => "digest"; +} + +body copy_from perms_cp(from) +# @brief Copy a local file and preserve file permissions on the local copy. +# +# @param from The path to the source file. +{ + source => "$(from)"; + preserve => "true"; +} + +body copy_from perms_dcp(from) +# @brief Copy a local file if it is different from the existing copy and +# preserve file permissions on the local copy. +# +# @param from The path to the source file. +{ + source => "$(from)"; + preserve => "true"; + compare => "digest"; +} + +body copy_from backup_local_cp(from) +# @brief Copy a local file and keep a backup of old versions. +# +# @param from The path to the source file. +{ + source => "$(from)"; + copy_backup => "timestamp"; +} + +body copy_from seed_cp(from) +# @brief Copy a local file if the file does not already exist, i.e. seed the placement +# +# @param from The path to the source file. +{ + source => "$(from)"; + compare => "exists"; +} + +body copy_from sync_cp(from,server) +# @brief Download a file if the local copy does not already exist, i.e. seed the placement +# +# @param from The location of the file on the remote server +# @param server The hostname or IP of the server from which to download +{ + servers => { "$(server)" }; + source => "$(from)"; + purge => "true"; + preserve => "true"; + type_check => "false"; +} + +body copy_from no_backup_cp(from) +# @brief Copy a local file and don't make any backup of the previous version +# +# @param from The path to the source file. +{ + source => "$(from)"; + copy_backup => "false"; +} + +body copy_from no_backup_dcp(from) +# @brief Copy a local file if contents have changed, and don't make any backup +# of the previous version +# +# @param from The path to the source file. +{ + source => "$(from)"; + copy_backup => "false"; + compare => "digest"; +} + +body link_from linkfrom(source, type) +# @brief Make any kind of link to a file +# @param source link to this +# @param type the link's type (`symlink` or `hardlink`) +{ + source => $(source); + link_type => $(type); +} + +body perms m(mode) +# @brief Set the file mode +# @param mode The new mode +{ + mode => "$(mode)"; + rxdirs => "true"; +} + +body perms mo(mode,user) +# @brief Set the file's mode and owners +# @param mode The new mode +# @param user The username of the new owner +{ + owners => { "$(user)" }; + mode => "$(mode)"; + rxdirs => "true"; +} + +body perms mog(mode,user,group) +# @brief Set the file's mode, owner and group +# @param mode The new mode +# @param user The username of the new owner +# @param group The group name +{ + owners => { "$(user)" }; + groups => { "$(group)" }; + mode => "$(mode)"; + rxdirs => "true"; +} + +body perms og(u,g) +# @brief Set the file's owner and group +# @param u The username of the new owner +# @param g The group name +{ + owners => { "$(u)" }; + groups => { "$(g)" }; +} + +body perms owner(user) +# @brief Set the file's owner +# @param user The username of the new owner +{ + owners => { "$(user)" }; +} + +body perms system_owned(mode) +# @brief Set the file owner and group to the system default +# @param mode the access permission in octal format +# +# **Example:** +# +# ```cf3 +# files: +# "/etc/passwd" perms => system_owned("0644"); +# ``` +{ + mode => "$(mode)"; + owners => { "root" }; + groups => { "$(sys.user_data[gid])" }; + rxdirs => "true"; +} + + +body depth_search recurse(d) +# @brief Search files and direcories recursively, up to the specified depth +# Directories on different devices are included. +# +# @param d The maximum search depth +{ + depth => "$(d)"; + xdev => "true"; +} + +body depth_search recurse_ignore(d,list) +# @brief Search files and directories recursively, +# but don't recurse into the specified directories +# +# @param d The maximum search depth +# @param list The list of directories to be excluded +{ + depth => "$(d)"; + exclude_dirs => { @(list) }; +} + +body depth_search recurse_with_base(d) +# @brief Search files and directories recursively up to the specified +# depth, starting from the base directory and including directories on +# other devices. +# +# @param d The maximum search depth +{ + depth => "$(d)"; + xdev => "true"; + include_basedir => "true"; +} + +body delete tidy +# @brief Delete the file and remove empty directories +# and links to directories +{ + dirlinks => "delete"; + rmdirs => "true"; +} + +body file_select all +# @brief Select all file system entries +{ + leaf_name => { ".*" }; + file_result => "leaf_name"; +} + +body changes all_changes +# @brief Detect all file changes using sha256 +# and report the diff to CFEngine Enterprise +{ + hash => "sha256"; + report_changes => "all"; + report_diffs => "true"; + update_hashes => "yes"; +} + +bundle agent file_mustache(mustache_file, json_file, target_file) +# @brief Make a file from a Mustache template and a JSON file +# @param mustache_file the file with the Mustache template +# @param json_file a file with JSON data +# @param target_file the target file to write +# +# **Example:** +# +# ```cf3 +# methods: +# "m" usebundle => file_mustache("x.mustache", "y.json", "z.txt"); +# ``` +{ + files: + "$(target_file)" + create => "true", + edit_template => $(mustache_file), + template_data => readjson($(json_file), "100k"), + template_method => "mustache"; +} + +bundle agent file_mustache_jsonstring(mustache_file, json_string, target_file) +# @brief Make a file from a Mustache template and a JSON string +# @param mustache_file the file with the Mustache template +# @param json_string a string with JSON data +# @param target_file the target file to write +# +# **Example:** +# +# ```cf3 +# methods: +# "m" usebundle => file_mustache_jsonstring("x.mustache", '{ "x": "y" }', "z.txt"); +# ``` +{ + files: + "$(target_file)" + create => "true", + edit_template => $(mustache_file), + template_data => parsejson($(json_string)), + template_method => "mustache"; +} + +bundle agent file_tidy(file) +# @brief Remove a file +# @param file to remove +# +# **Example:** +# +# ```cf3 +# methods: +# "" usebundle => file_tidy("/tmp/z.txt"); +# ``` +{ + files: + "$(file)" delete => tidy; + + reports: + "DEBUG|DEBUG_$(this.bundle)":: + "DEBUG $(this.bundle): deleting $(file) with delete => tidy"; +} + +bundle agent dir_sync(from, to) +# @brief Synchronize a directory entire, deleting unknown files +# @param from source directory +# @param to destination directory +# +# **Example:** +# +# ```cf3 +# methods: +# "" usebundle => dir_sync("/tmp", "/var/tmp"); +# ``` +{ + files: + "$(to)/." + create => "true", + depth_search => recurse("inf"), + copy_from => copyfrom_sync($(from)); + + reports: + "DEBUG|DEBUG_$(this.bundle)":: + "DEBUG $(this.bundle): copying directory $(from) to $(to)"; +} + +bundle agent file_copy(from, to) +# @brief Copy a file +# @param from source file +# @param to destination file +# +# **Example:** +# +# ```cf3 +# methods: +# "" usebundle => file_copy("/tmp/z.txt", "/var/tmp/y.txt"); +# ``` +{ + files: + "$(to)" + copy_from => copyfrom_sync($(from)); + + reports: + "DEBUG|DEBUG_$(this.bundle)":: + "DEBUG $(this.bundle): copying file $(from) to $(to)"; +} + +body copy_from copyfrom_sync(f) +# @brief Copy a directory or file with digest checksums, preserving attributes and purging leftovers +# @param f the file or directory +{ + source => "$(f)"; + purge => "true"; + preserve => "true"; + type_check => "false"; + compare => "digest"; +} + +bundle agent file_make(file, str) +# @brief Make a file from a string +# @param file target +# @param str the string data +# +# **Example:** +# +# ```cf3 +# methods: +# "" usebundle => file_make("/tmp/z.txt", "Some text +# and some more text here"); +# ``` +{ + vars: + "len" int => string_length($(str)); + summarize:: + "summary" string => format("%s...%s", + string_head($(str), 18), + string_tail($(str), 18)); + classes: + "summarize" expression => isgreaterthan($(len), 40); + + files: + "$(file)" + create => "true", + edit_line => insert_lines($(str)), + edit_defaults => empty; + + reports: + "DEBUG|DEBUG_$(this.bundle)":: + "DEBUG $(this.bundle): creating $(file) with contents '$(str)'" + ifvarclass => "!summarize"; + + "DEBUG $(this.bundle): creating $(file) with contents '$(summary)'" + ifvarclass => "summarize"; +} + +bundle agent file_make_mog(file, str, mode, owner, group) +# @brief Make a file from a string with mode, owner, group +# @param file target +# @param str the string data +# @param mode the file permissions in octal +# @param owner the file owner as a name or UID +# @param group the file group as a name or GID +# +# **Example:** +# +# ```cf3 +# methods: +# "" usebundle => file_make_mog("/tmp/z.txt", "Some text +# and some more text here", "0644", "root", "root"); +# ``` +{ + vars: + "len" int => string_length($(str)); + summarize:: + "summary" string => format("%s...%s", + string_head($(str), 18), + string_tail($(str), 18)); + classes: + "summarize" expression => isgreaterthan($(len), 40); + + files: + "$(file)" + create => "true", + edit_line => insert_lines($(str)), + perms => mog($(mode), $(owner), $(group)), + edit_defaults => empty; + + reports: + "DEBUG|DEBUG_$(this.bundle)":: + "DEBUG $(this.bundle): creating $(file) with contents '$(str)', mode '$(mode)', owner '$(owner)' and group '$(group)'" + ifvarclass => "!summarize"; + + "DEBUG $(this.bundle): creating $(file) with contents '$(summary)', mode '$(mode)', owner '$(owner)' and group '$(group)'" + ifvarclass => "summarize"; +} + +bundle agent file_empty(file) +# @brief Make an empty file +# @param file target +# +# **Example:** +# +# ```cf3 +# methods: +# "" usebundle => file_empty("/tmp/z.txt"); +# ``` +{ + files: + "$(file)" + create => "true", + edit_defaults => empty; + + reports: + "DEBUG|DEBUG_$(this.bundle)":: + "DEBUG $(this.bundle): creating empty $(file) with 0 size"; +} + +bundle agent file_hardlink(target, link) +# @brief Make a hard link to a file +# @param target of link +# @param link the hard link's location +# +# **Example:** +# +# ```cf3 +# methods: +# "" usebundle => file_hardlink("/tmp/z.txt", "/tmp/z.link"); +# ``` +{ + files: + "$(link)" + move_obstructions => "true", + link_from => linkfrom($(target), "hardlink"); + + reports: + "DEBUG|DEBUG_$(this.bundle)":: + "DEBUG $(this.bundle): $(link) will be a hard link to $(target)"; +} + +bundle agent file_link(target, link) +# @brief Make a symlink to a file +# @param target of symlink +# @param link the symlink's location +# +# **Example:** +# +# ```cf3 +# methods: +# "" usebundle => file_link("/tmp/z.txt", "/tmp/z.link"); +# ``` +{ + files: + "$(link)" + move_obstructions => "true", + link_from => linkfrom($(target), "symlink"); + + reports: + "DEBUG|DEBUG_$(this.bundle)":: + "DEBUG $(this.bundle): $(link) will be a symlink to $(target)"; +} + +bundle edit_line create_solaris_admin_file +# @brief The following bundle is part of a package setup for solaris +# +# See unit examples. +{ + insert_lines: + + "$(solaris_knowledge.admin_nocheck)" + comment => "Insert contents of Solaris admin file (automatically install packages)"; +} diff --git a/tests/acceptance/root-MD5=617eb383deffef843ea856b129d0a423.priv b/tests/acceptance/root-MD5=617eb383deffef843ea856b129d0a423.priv new file mode 100644 index 0000000000..e801cd2794 --- /dev/null +++ b/tests/acceptance/root-MD5=617eb383deffef843ea856b129d0a423.priv @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,03888484F391EBEF + +Md6pmS3kdvXhvjxI07s7w42xLZsEboZBAP8xbLjaj2MTjbnBnBymeYK6TNk1G1WS +ZAvwujdfgimXLi3uDmfWuhWE9yZtG4KMZuQAGm7ZQTmmxhj4KLFS0htoU1zTe56g +xnQvIB/wH0YETukkBdKnApb7gGS34cPbq8rNHb9qK5qgNwJAnDPN7x7QYAXIslXM +jlXAtzbavVa83KGsivibf4UxIQiEZ4LYNxh+qfAzthsCHDxsM8WSvIbyv0kE2xpF +tWyIsHyCrd1jrbkwbd6ADpUvyuPoKEhXFe51Sc2ntgjYeTcVRt7B3ldW67IgvaOT +VdjPhse8aevQwOeLAjYz9aohv3ndNrXydS4wUub4+zd5NlV4Nt7DrmiT/T0fZrtd +cBlfwF7NHdTk2GU41Rkd4702YyqLomfQlCHtyk3X62qIFhXiYScqZLF/3++3kqnA +cfsBVigDmcnr64hbxWty/AoeObvNKY/ACjU6NOMerxzrSvLCe+kZAZl4exh/SvK0 +uO0hP+dOj6wJ0IAq2MbvSRlxqQB4nGeN3BrlRbFyAEul0r5ZWfTEUysMJFLde9ye +2dYeXSX1DRrPF5TRh+66Osh4eElc7N5NaFZmyNvT3OT1CxnVbncgA6nWaBD7RI+j +pW1tQ35o/y/9JiC6Gfbe5DQdbKEWKItDL5eoz/l7k+92J+Cx7VP/6jH8cMSdoIl5 +KntdwQtEE8XG8XIuid7o7bmySXNHW32mt8uqh6yX8J9QPmufALbth0fnVIOJYNs6 +GYeaJWcaXe36hMfVdkkGkPkP5qa2iBBPI+DILEooa+0bLent0ki36wFwZ0Ynu2x5 +WOzlpWUGcezM3hXssffEwIvgonrnrdRiLO9wcdkOhClZe1M65gMrbCARPvNpK5II +9qtC/JWM7rOHV5xAwUYq0PlQ7X/FZGM6tILM/2B03lhZUPsPfMWWUGQYtkVjtmJE +ktEsKDmzg3E3HutnhhrbX18NAD7PlheZ5TCBSllZTESG6QaEYXVwRMzgkSAhH+4N +4twwYUpi4ONKc/rqw5oWmOigysOanCWOIbAGDsf5sdxV2N/Y1tfERD/4ze2c3KOr +s7EXCzYvrpRX8sifnl9ahriEskzl3Vl9pk8XZefEvt2pCCei2HfCpzv6XLnyPNd3 +8bUV3BhTGxQtzhLT8s5crO8o2dOcUkj+zd2rFeHF5k2YycqTtU8jqKsRhtbBkMdU +8pRBPD1DIcBWs4q8ljuzPwFY5vrpRzJZpsdlosvxa3SVkc6ND3EQDm58BVBMqqSm +pfk8ht34bv6Hst3EqSCsh8JUNDlivE/eUW2AyPEEoqexgpTQjMjYXZy0BDlE9yZn +nc7JyLVQax6GAF3o7nb8KANo6gs2nSBjwraTnFREdeqPk45kVZn9Z/i2y0JFNWxJ +vis+ntFCTe1Z2WlA8aKG6pCQ9IqX9j6tGmy9YqqHJ7N0zDxgjRGnDEg1sAKxmTxp +HES65jsAvuAmmEj2q99B2QHnODl4pUZ+Tc1UexLTQQvmla5yJzLsPqN57jaZSXqW +cxB3DXv5JUKF6i7ZJog+/KVCX10cF/zNt4hu+3XKH2INCkBxOq2bVhCQkyztwOFQ +-----END RSA PRIVATE KEY----- diff --git a/tests/acceptance/root-MD5=617eb383deffef843ea856b129d0a423.pub b/tests/acceptance/root-MD5=617eb383deffef843ea856b129d0a423.pub new file mode 100644 index 0000000000..3050657612 --- /dev/null +++ b/tests/acceptance/root-MD5=617eb383deffef843ea856b129d0a423.pub @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEA56WwX+9IW9Hbw83iDf8uAdSciUgTgFQQG0ror5rxUXoTyetChNKW +y2JlOqHO61dIbEkSAg9C1bp3N6/ukkjXwqEOMf4W0BLfkZaFTrZvy8ivbE/HconJ +ZWzvJ/jNKI7z8SbEMCfAPuH+z+bwcLCi+Ut1/Byy4kZupIGM4DiJwjI7ldsHdEZG +D6lplmPre9k/07pZl04o0NqiTheL3EWaGzFPOBBXSe9PaeuX/WSBYmc3Jc6SqlZO +MmDm5mywHnD2uhCnZDeSOfaWhj4XhA41LL00h+g006JSLtWzrrkQHJkRvVEHWGu9 +qAvAddXihFMwquFsOPNGrhaOK76q2kVf/QIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/tests/acceptance/run_with_server.cf.sub b/tests/acceptance/run_with_server.cf.sub new file mode 100644 index 0000000000..32cf3a3dc5 --- /dev/null +++ b/tests/acceptance/run_with_server.cf.sub @@ -0,0 +1,110 @@ +body classes if_failed(x) +{ + repair_failed => { "$(x)" }; +} + +body contain run_under_shell +{ + useshell => "useshell"; +} + +body action in_background +{ + background => "true"; +} + + +bundle agent start_server(server_config) +{ + classes: + "debug_server" expression => "debug_mode"; + + vars: + "config_basename" string => filestat("$(server_config)", basename); + "dlog" string => "$(sys.workdir)/server-debug.$(config_basename).log"; + + commands: + !debug_server.!windows:: + "$(sys.cf_serverd) -Kf $(server_config)" + classes => if_failed("server_failed"); + !debug_server.windows:: + # Windows cannot daemonize and needs to start in background. + "$(sys.cf_serverd) -Kf $(server_config)" + action => in_background, + classes => if_failed("server_failed"); + + debug_server:: + "$(sys.cf_serverd) -Kldf $(server_config) > $(dlog) 2>&1" + contain => run_under_shell, + action => in_background, + classes => if_failed("server_failed"); + + debug_server|windows:: + # Sleep 3 seconds since cf-serverd takes some time to start in background + # mode. We need to add a handle with $(server_config) in it so that the + # promise is not skipped if this bundle is used for multiple configs. + "$(G.sleep) 3" + contain => run_under_shell, + handle => "sleep_for_$(server_config)"; + + reports: + debug_server:: + "$(sys.cf_serverd) was run in debug mode, the logs will be in $(dlog)"; +} + + +bundle agent run_test(test_name) +{ + classes: + "debug_client" expression => "debug_mode"; + + vars: + "dlog" string => "$(sys.workdir)/agent-debug.log"; + + commands: + !debug_client:: + "$(sys.cf_agent) -KIf $(test_name) -D DEBUG,AUTO 2>&1 | $(G.tee) $(dlog)" + contain => run_under_shell; + debug_client:: + "$(sys.cf_agent) -Kldf $(test_name) -D DEBUG,EXTRA,AUTO 2>&1 | $(G.tee) $(dlog)" + contain => run_under_shell; + + reports: + debug_client:: + "$(sys.cf_agent) was run in debug mode, the logs will be in $(dlog)"; +} + + +bundle agent run_runagent(runagent_params) +{ + classes: + "debug_client" expression => "debug_mode"; + + vars: + "dlog" string => "$(sys.workdir)/runagent-debug.log"; + + commands: + !debug_client:: + "$(sys.cf_runagent) -I $(runagent_params) 2>&1 | $(G.tee) $(dlog)" + contain => run_under_shell; + debug_client:: + "$(sys.cf_runagent) -d $(runagent_params) 2>&1 | $(G.tee) $(dlog)" + contain => run_under_shell; + + reports: + debug_client:: + "$(sys.cf_runagent) was run in debug mode, the logs will be in $(dlog)"; +} + + +bundle agent stop_server(server_config) +{ + # On some old platforms, "ps" truncates its output, which CFEngine depends on. This can lead to + # the test servers not being killed. + # On HP-UX you can set the DEFAULT_CMD_LINE_WIDTH inside /etc/default/ps to a higher value, which + # controls the maximum line length of "ps". Unfortunately it is not overridable from the + # environment. + processes: + "$(server_config)" + signals => { "term", "kill" }; +} diff --git a/tests/acceptance/selftest.sh b/tests/acceptance/selftest.sh new file mode 100755 index 0000000000..eb250a3f2e --- /dev/null +++ b/tests/acceptance/selftest.sh @@ -0,0 +1,100 @@ +#!/bin/sh + + +# because results in test.xml should not be parsed by CI or otherwise +# failures there may be expected and will be checked by grep below +clean () +{ + rm -f test.xml +} +check () +{ + expect=$1 + log=$2 + if ! grep "$expect" "$log" >/dev/null + then + echo "error: failed to match regex \"$expect\" in log file $log" + return 1 + fi +} + +mkdir -p workdir + +./testall selftest > workdir/selftest_normal.log +ret=$? +if [ "$ret" -ne "1" ] +then + echo "error: exit code for selftest should be 1 but was $ret" + clean + exit 1 +fi + +log="workdir/selftest_normal.log" + +errors=0 +_pwd=$(pwd) + +for regex in \ +"./selftest/fail.cf FAIL (Suppressed, R: $_pwd/./selftest/fail.cf XFAIL)" \ +"./selftest/flakey_meta_fail.cf Flakey fail (ENT-6947)" \ +"./selftest/flakey_meta_no_ticket.cf FAIL (Tried to suppress failure, but no issue number is provided) (UNEXPECTED FAILURE)" \ +"./selftest/flaky_fail.cf Flakey fail (R: $_pwd/./selftest/flaky_fail.cf FLAKEY)" \ +"./selftest/flaky_pass.cf Pass" \ +"./selftest/pass.cf Pass" \ +"./selftest/skipped.cf Skipped (Test needs work)" \ +"./selftest/soft.cf Soft fail (R: $_pwd/./selftest/soft.cf SFAIL)" \ +"./selftest/soft_no_ticket.cf FAIL (Tried to suppress failure, but no issue number is provided) (UNEXPECTED FAILURE)" \ +"Passed tests: 2" \ +"Failed tests: 3 (1 are known and suppressed)" \ +"Skipped tests: 1" \ +"Soft failures: 1" \ +"Flakey failures: 2" \ +"Total tests: 9" +do + check "$regex" "$log" || errors=$((errors + 1)) +done +if [ "$errors" -ne "0" ] +then + echo "error: $errors error(s) occurred for selftest (flakey is not fail)" + echo "=== BEGIN $log ===" + cat $log + echo "=== END $log ===" + clean + exit 1 +fi + + +FLAKEY_IS_FAIL=1 ./testall selftest/flakey_meta_fail.cf selftest/flaky_fail.cf selftest/flaky_pass.cf > workdir/selftest_flakey_is_fail.log +ret=$? +if [ "$ret" -ne "4" ] +then + echo "error: exit code for testall with FLAKEY_IS_FAIL=1 should be 4 but was $ret" + clean + exit 1 +fi + +errors=0 +log="workdir/selftest_flakey_is_fail.log" + +for regex in \ +"./selftest/flakey_meta_fail.cf Flakey fail (ENT-6947)" \ +"./selftest/flaky_fail.cf Flakey fail (R: $_pwd/./selftest/flaky_fail.cf FLAKEY)" \ +"./selftest/flaky_pass.cf Pass" \ +"Passed tests: 1" \ +"Failed tests: 0" \ +"Skipped tests: 0" \ +"Soft failures: 0" \ +"Flakey failures: 2" \ +"Total tests: 3" +do + check "$regex" "$log" || errors=$((errors + 1)) +done +if [ "$errors" -ne "0" ] +then + echo "error: $errors error(s) occurred for selftest (flakey is fail)" + echo "=== BEGIN $log ===" + cat $log + echo "=== END $log ===" + clean + exit 1 +fi diff --git a/tests/acceptance/selftest/fail.cf b/tests/acceptance/selftest/fail.cf new file mode 100644 index 0000000000..86ff9e9e84 --- /dev/null +++ b/tests/acceptance/selftest/fail.cf @@ -0,0 +1,5 @@ +bundle agent main +{ + reports: + "$(this.promise_filename) XFAIL"; +} diff --git a/tests/acceptance/selftest/flakey_meta_fail.cf b/tests/acceptance/selftest/flakey_meta_fail.cf new file mode 100644 index 0000000000..b9c813387d --- /dev/null +++ b/tests/acceptance/selftest/flakey_meta_fail.cf @@ -0,0 +1,19 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_flakey_fail" string => "any", + meta => { "ENT-6947" }; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/selftest/flakey_meta_no_ticket.cf b/tests/acceptance/selftest/flakey_meta_no_ticket.cf new file mode 100644 index 0000000000..20d6598e3e --- /dev/null +++ b/tests/acceptance/selftest/flakey_meta_no_ticket.cf @@ -0,0 +1,18 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_flakey_fail" string => "any"; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/selftest/flaky_fail.cf b/tests/acceptance/selftest/flaky_fail.cf new file mode 100644 index 0000000000..ab5a3f874f --- /dev/null +++ b/tests/acceptance/selftest/flaky_fail.cf @@ -0,0 +1,5 @@ +bundle agent main +{ + reports: + "$(this.promise_filename) FLAKEY"; +} diff --git a/tests/acceptance/selftest/flaky_pass.cf b/tests/acceptance/selftest/flaky_pass.cf new file mode 100644 index 0000000000..e5cca28800 --- /dev/null +++ b/tests/acceptance/selftest/flaky_pass.cf @@ -0,0 +1,5 @@ +bundle agent main +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/selftest/pass.cf b/tests/acceptance/selftest/pass.cf new file mode 100644 index 0000000000..e5cca28800 --- /dev/null +++ b/tests/acceptance/selftest/pass.cf @@ -0,0 +1,5 @@ +bundle agent main +{ + reports: + "$(this.promise_filename) Pass"; +} diff --git a/tests/acceptance/selftest/skipped.cf b/tests/acceptance/selftest/skipped.cf new file mode 100644 index 0000000000..3f33de1a24 --- /dev/null +++ b/tests/acceptance/selftest/skipped.cf @@ -0,0 +1,18 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_skip_needs_work" string => "any"; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/selftest/soft.cf b/tests/acceptance/selftest/soft.cf new file mode 100644 index 0000000000..f9379abde0 --- /dev/null +++ b/tests/acceptance/selftest/soft.cf @@ -0,0 +1,5 @@ +bundle agent main +{ + reports: + "$(this.promise_filename) SFAIL"; +} diff --git a/tests/acceptance/selftest/soft_no_ticket.cf b/tests/acceptance/selftest/soft_no_ticket.cf new file mode 100644 index 0000000000..3cad6df23f --- /dev/null +++ b/tests/acceptance/selftest/soft_no_ticket.cf @@ -0,0 +1,18 @@ +body common control +{ + inputs => { "../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "any"; +} + +bundle agent check +{ + reports: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/serverd-multi-versions-blacklist.txt b/tests/acceptance/serverd-multi-versions-blacklist.txt new file mode 100644 index 0000000000..76e1cb43c5 --- /dev/null +++ b/tests/acceptance/serverd-multi-versions-blacklist.txt @@ -0,0 +1,23 @@ +# This file contains cf-serverd tests to be excluded from multi-version testing +# Lines starting from # are ignored, so are embty lines + +# Default behaviour in absence of "allowlegacyconnects" parameter changed +# since 3.9.0, ref https://northerntech.atlassian.net/browse/CFE-2339 +copy_from_classic_protocol_fail.cf +copy_from_classic_protocol_success.cf + +# This is a test for a feature which appeared first in 3.10.0 +# ref https://github.com/cfengine/core/commit/1ac4e0c8a177c32f8c47614ac7ea082e38092a52 +copied_files_can_be_sparse.cf + +# "missing_ok" attribute in body copy_from, introduced in 3.12 +copy_missing_ok.cf + +# These tests run two cf-serverd instances, and it is currently unsupported +allow_path1_then_deny_path2_a.cf +copy_from_ciphers_fail.cf +copy_from_ciphers_success.cf +copy_from_digest_different.cf +copy_from_digest_different_expand_ip_and_shortcut.cf +simple_copy_from_admit_localhost.cf +tcp_port_copy_from_within_range.cf diff --git a/tests/acceptance/serverd-multi-versions.sh b/tests/acceptance/serverd-multi-versions.sh new file mode 100755 index 0000000000..906db31cf4 --- /dev/null +++ b/tests/acceptance/serverd-multi-versions.sh @@ -0,0 +1,158 @@ +#!/bin/bash + +logdir=serverd-multi-versions-logs +statsdir=serverd-multi-versions-stats + +mkdir -p hub +mkdir -p client +mkdir -p $logdir +mkdir -p $statsdir +cat >server.sh <agent.sh </dev/null 2>&1 + make >/dev/null 2>&1 + make install >/dev/null 2>&1 + else + test ! -z "$3" -a ! -f "$2" && wget "$3" -O "$2" + dpkg -x "$2" "$1" + fi +} + +function testit(){ + echo "===== $1" + ./server.sh -V + ./agent.sh -V + ls tests/acceptance/16_cf-serverd/serial/*.cf \ + | grep -v -f tests/acceptance/serverd-multi-versions-blacklist.txt \ + | sed 's%^tests/%%' \ + | while read file + do + logfilename="$1-$(basename $file)" + logfile="$logdir/$logfilename" + ./server.sh -V >> $logfile.log + ./agent.sh -V >> $logfile.log + + # delete dirs left here from previous testing round, if any. + # We keep deleting them until they disappear - they can be kept back by + # still-running process from previous testing round. + for d in /tmp/TESTDIR.cfengine work_here + do + while test -d "$d" + do + rm -rf "$d" || ( + # Try to debug why dir failed to delete + ps auxww | grep '[c]f-' + ls -lR "$d" + ) + done + done + + cp -a work_stub work_here + # actually run the test + ./agent.sh -Kf $file -D AUTO,debug_server >> $logfile.log + retval=$? + result=unknown + if expr "$file" : '.*\.x\.cf$' >/dev/null + then + # test should exit with non-zero status + # if it exits with status 128 or higher - then it's a CRASH + if [ $retval -gt 0 ] && [ $retval -lt 128 ] + then + result=passed + else + result=failed + fi + else + # test is expected to pass + if [ $retval != 0 ] + then + result=failed + else + if grep "R: $PWD/work_here/inputs/$file Pass" $logfile.log > /dev/null + then + result=passed + else + result=failed + fi + fi + fi + echo $logfile > $statsdir/$result-$logfilename + + if [ "$result" != "passed" ] + then + echo "$file FAIL" + mv work_here $logfile.workdir + mv /tmp/TESTDIR.cfengine $logfile.testdir + else + echo "$file Pass" + fi + done +} + +install hub +install agent 3.10.deb https://cfengine-package-repos.s3.amazonaws.com/community_binaries/Community-3.10.2/agent_debian7_x86_64/cfengine-community_3.10.2-1_amd64-debian7.deb +testit master-hub-with-3.10.2-agent + +install agent 3.7.deb https://cfengine-package-repos.s3.amazonaws.com/community_binaries/Community-3.7.6/agent_deb_x86_64/cfengine-community_3.7.6-1_amd64-debian4.deb +testit master-hub-with-3.7.6-agent + +install agent +install hub 3.10.deb +testit master-agent-with-3.10.2-hub + +install hub 3.7.deb +testit master-agent-with-3.7.6-hub + +echo '===== Printing results =====' +echo "Passed tests: $(ls $statsdir/pass* 2>/dev/null | wc -l)" +echo "FAILED tests: $(ls $statsdir/fail* 2>/dev/null | wc -l)" +echo "Unknown result: $(ls $statsdir/unknown* 2>/dev/null | wc -l)" +echo '===== Failed tests =====' +cat $statsdir/fail* 2>/dev/null || NO_FAILED=true +echo '===== Unknown results =====' +cat $statsdir/unknown* 2>/dev/null || NO_UNKNOWNS=true + +# Return code +test -z "$NO_FAILED" && exit 1 +test -z "$NO_UNKNOWNS" && exit 2 +exit 0 diff --git a/tests/acceptance/tag.sh b/tests/acceptance/tag.sh new file mode 100755 index 0000000000..4b7483ab94 --- /dev/null +++ b/tests/acceptance/tag.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +folder=$1 +project_id=$2 +category_id=$3 + +echo "Tagging files on $folder with $project_id, $category_id"; + +for i in `find $folder -name "*.cf" -print`; +do + echo "### PROJECT_ID: $project_id" >> $i + echo "### CATEGORY_ID: $category_id" >> $i +done +exit 0 diff --git a/tests/acceptance/testall b/tests/acceptance/testall new file mode 100755 index 0000000000..aac5305646 --- /dev/null +++ b/tests/acceptance/testall @@ -0,0 +1,1409 @@ +#!/bin/sh +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +# +# Detect and replace non-POSIX shell +# +try_exec() { + type "$1" > /dev/null 2>&1 && exec "$@" +} + +broken_posix_shell() +{ + unset foo + local foo=1 + test "$foo" != "1" +} + +if broken_posix_shell >/dev/null 2>&1 +then + try_exec /usr/xpg4/bin/sh "$0" "$@" + echo "No compatible shell script interpreter found." + echo "Please find a POSIX shell for your system." + exit 42 +fi + +# +# Explicitly use POSIX tools if needed +# +if [ -f /usr/xpg4/bin/grep ] +then + PATH=/usr/xpg4/bin:$PATH + export PATH +fi + +# +# Unset environment variables which might break runinng acceptance tests +# +GREP_OPTIONS= +export GREP_OPTIONS + +# +# Defaults (overridden by command-line arguments) +# +LOG=test.log +INOTIFYWATCH_LOG=inotifywatch.log +INOTIFYWAIT_LOG=inotifywait.log +INOTIFYWATCH_OPTS="-r -e modify -e attrib -e moved_to -e create -e delete -e delete_self" +INOTIFYWAIT_OPTS="-r -m -e modify -e attrib -e moved_to -e create -e delete -e delete_self" +SUMMARY=summary.log +XML=test.xml +WHOAMI=/c/Windows/System32/whoami.exe +BASE_WORKDIR=${BASE_WORKDIR:-$(pwd)/workdir} +QUIET= + +# Default test types to run if --tests= is not passed + +# DEFAULT ON +COMMON_TESTS=${COMMON_TESTS:-1} ; export COMMON_TESTS +TIMED_TESTS=${TIMED_TESTS:-1} ; export TIMED_TESTS +SLOW_TESTS=${SLOW_TESTS:-1} ; export SLOW_TESTS +ERROREXIT_TESTS=${ERROREXIT_TESTS:-1} ; export ERROREXIT_TESTS +ERRORLOG_TESTS=${ERRORLOG_TESTS:-1} ; export ERRORLOG_TESTS +SERIAL_TESTS=${SERIAL_TESTS:-1} ; export SERIAL_TESTS +NETWORK_TESTS=${NETWORK_TESTS:-1} ; export NETWORK_TESTS +LIBXML2_TESTS=${LIBXML2_TESTS:-1} ; export LIBXML2_TESTS +LIBCURL_TESTS=${LIBCURL_TESTS:-1} ; export LIBCURL_TESTS +# DEFAULT OFF +UNSAFE_TESTS=${UNSAFE_TESTS:-0} ; export UNSAFE_TESTS +STAGING_TESTS=${STAGING_TESTS:-0} ; export STAGING_TESTS + + +BINDIR=${BINDIR:-} ; export BINDIR +NO_CLEAN=${NO_CLEAN:-0} ; export NO_CLEAN +BASECLASSES=${BASECLASSES:-AUTO} ; export BASECLASSES +EXTRACLASSES=${EXTRACLASSES:-DEBUG} ; export EXTRACLASSES +AGENT=${AGENT:-} ; export AGENT +CF_PROMISES=${CF_PROMISES:-} ; export CF_PROMISES +CF_SERVERD=${CF_SERVERD:-} ; export CF_SERVERD +CF_EXECD=${CF_EXECD:-} ; export CF_EXECD +CF_KEY=${CF_KEY:-} ; export CF_KEY +CF_SECRET=${CF_SECRET:-} ; export CF_SECRET +CF_NET=${CF_NET:-} ; export CF_NET +CF_CHECK=${CF_CHECK:-} ; export CF_CHECK +CF_RUNAGENT=${CF_RUNAGENT:-} ; export CF_RUNAGENT +RPMVERCMP=${RPMVERCMP:-} ; export RPMVERCMP +DIFF=${DIFF:-} ; export DIFF +LIBTOOL=${LIBTOOL:-} ; export LIBTOOL +INCLUDE_IN_WORKDIR=${INCLUDE_IN_WORKDIR:-} ; export INCLUDE_IN_WORKDIR + +VALGRIND_OPTS="${VALGRIND_OPTS:---leak-check=full --show-reachable=yes --suppressions=valgrind-suppressions}" +export VALGRIND_OPTS + +export MAKEFLAGS +export GAINROOT + +# Use TEST_INDEX for indexing a poor man's array. Basically the subscript is +# just appended to the variable name. +TEST_INDEX=0 +TEST_TIMED_INDEX=0 +#TESTS_TIMED_=0 +#TESTS_TIMEOUT_=0 +#TESTS_PASSES_=0 +TESTS_COUNT=0 +TESTS_NORMAL_COUNT=0 +TESTS_TIMED_COUNT=0 +TESTS_TIMED_REMAINING=0 + +case "$OSTYPE" in + msys) + if "$WHOAMI" -priv | grep SeTakeOwnershipPrivilege > /dev/null + then + # Don't use elevate if we already have the privileges. It is slower and + # pops up a flashing window for every single test. + DEFAULT_GAINROOT= + else + DEFAULT_GAINROOT="`dirname $0`/tool_wrappers/elevate.sh" + fi + # Use hardlinks on Windows. Using symbolic links will work, but Msys creates a + # real copy, which eats disk space very quickly when you multiply with the number + # of tests. + LN_CMD="ln -f" + ;; + *) + DEFAULT_GAINROOT=fakeroot + LN_CMD="ln -sf" + ;; +esac + +GAINROOT=${GAINROOT:-$DEFAULT_GAINROOT} + +PASSED_TESTS=0 +FAILED_TESTS=0 +SUPPRESSED_FAILURES=0 +SOFT_FAILURES=0 +SKIPPED_TESTS=0 +FLAKEY_FAILURES=0 + +# +# Many older platforms don't support date +%s, so check for compatibility +# and find Perl for the unix_seconds() routine below. (Mantis #1254) +# +HAVE_DATE_PCT_S= +date +%s | grep %s >/dev/null 2>&1 +if [ $? -ne 0 ] +then + HAVE_DATE_PCT_S=1 +fi +PERL=`which perl 2>/dev/null` + +# color! +if [ "${CFENGINE_COLOR}" = "1" ] +then + COLOR_SUCCESS="\\033[1;32m" + COLOR_FAILURE="\\033[1;31m" + COLOR_WARNING="\\033[1;33m" + COLOR_NORMAL="\\033[0;39m" +else + COLOR_SUCCESS= + COLOR_FAILURE= + COLOR_WARNING= + COLOR_NORMAL= +fi + +# +# Obtain UNIX time(), using date +%s, Perl, or POSIX-compatible approach. +# +unix_seconds() { + if [ "$HAVE_DATE_PCT_S" ] + then + date +%s + return 0 + fi + + if [ "$PERL" ] + then + $PERL -e 'print time() . "\n"' 2>/dev/null + if [ $? -eq 0 ] + then + return 0 + fi + fi + + # Last resort if Perl fails - the extended cpio interchange format has + # the file modification timestamp in columns 48-59, in octal. + : > $BASE_WORKDIR/x + echo "ibase=8;$(pax -wx cpio $BASE_WORKDIR/$$.seconds | cut -c 48-59)" | bc 2>/dev/null + rm $BASE_WORKDIR/x +} + +usage() { + echo "testall [-h|--help] [-q|--quiet] [--gainroot=] [--agent=] [--cfpromises=] [--cfserverd=] [--cfexecd=] [--cfkey=] [--cfkeycrypt=] [--cfnet=] [--cfcheck=] [--bindir=] [--tests=...] [--gdb] [--printlog] [ ...]" + echo + echo "If no test is given, all standard tests are run:" + echo " Tests with names of form .cf are expected to run successfully" + echo " Tests with names of form .x.cf are expected to exit with error" + echo "Set ${COLOR_SUCCESS}CFENGINE_COLOR=1${COLOR_NORMAL} to get ANSI color markers where appropriate." + echo + echo "If arguments are given, those are executed as tests" + echo + echo " -h" + echo " --help prints usage" + echo " -q" + echo " --quiet makes script much quieter" + echo " --gainroot= forces use of command to gain root privileges," + echo " otherwise fakeroot is used. Use --gainroot=env to make this" + echo " option a no-op (e.g. you're running inside fakeroot already)" + echo " --agent provides a way to specify non-default cf-agent location," + echo " and defaults to $DEFAGENT." + echo " --baseclasses provides a way to override the default cf-agent classes," + echo " and defaults to ${BASECLASSES}. Also can use --bc" + echo " --extraclasses provides a way to append to the default cf-agent classes," + echo " and defaults to ${EXTRACLASSES}. Also can use --ec" + echo " --cfpromises provides a way to specify non-default cf-promises location," + echo " and defaults to $DEFCF_PROMISES." + echo " --cfserverd provides a way to specify non-default cf-serverd location," + echo " and defaults to $DEFCF_SERVERD." + echo " --cfexecd provides a way to specify non-default cf-execd location," + echo " and defaults to $DEFCF_EXECD." + echo " --cfkey provides a way to specify non-default cf-key location," + echo " and defaults to $DEFCF_KEY." + echo " --cfkeycrypt provides a way to specify non-default cf-secret location," + echo " and defaults to $DEFCF_SECRET." + echo " --cfnet provides a way to specify non-default cf-net location," + echo " and defaults to $DEFCF_NET." + echo " --cfcheck provides a way to specify non-default cf-check location," + echo " and defaults to $DEFCF_CHECK." + echo " --cfrunagent provides a way to specify non-default cf-runagent location," + echo " and defaults to $DEFCF_RUNAGENT." + echo " --rpmvercmp provides a way to specify non-default rpmvercmp location," + echo " and defaults to $DEFRPMVERCMP." + echo " --bindir specifies the directory containing all the binaries." + echo " Mutually exclusive with --agent and --cf* arguments." + echo " --libtool specify non-default libtool location (only needed for --gdb)." + echo " defaults to $DEFLIBTOOL." + echo " --include Include the file or directory given in the workdir before the test" + echo " starts. This option may be given several times." + echo " --tests=common,network,serial,timed,slow,errorexit,errorlog,libxml2,libcurl" + echo " This is the default value, you can also add from the following:" + echo " unsafe,staging" + echo " NOTE: 'staging' tests are not expected to pass" + echo " WARN: 'unsafe' tests modify the system they are running on and can cause DAMAGE!" + echo " If you use this option you should also use --gainroot=sudo." + echo " --printlog print the full test.log output immediately. Override with $PRINTLOG" + echo " --gdb Run test under GDB" + echo " --valgrind Run test under Valgrind" + echo " --callgrind Run test under valgrind --tool=callgrind" + echo " --inotifywatch Run tests and log filesystem statistics" + echo " --inotifywait Run tests and log filesystem events" + echo " --no-clean does not clean workdir after test finishes" + echo " (by default it gets cleaned only if test passed)." + echo " --base-workdir Specify a local directory for all working files" + echo " -j[n]" + echo " -jobs=[n] Run tests in parallel, works like make -j option. Note that some" + echo " tests will always run one by one." + echo " --verbose Run tests with verbose logging" + echo " --debug Run tests with debug logging" +} + +workdir() { + echo "$BASE_WORKDIR/$(echo "$1" | sed 's,[./],_,g')" +} + +# Example: fgrepvar 'word' VARNAME +# Silent fgrep for variables: search for "word" in $VARNAME +fgrepvar() { + eval echo \$$2 | fgrep "$1" > /dev/null +} + +# Same but instead of word search for a regex +grepvar() { + eval echo \$$2 | grep "$1" > /dev/null +} + +# Takes the following arguments: +# 1. Agent - The agent to execute. +# 2. Test - The test to execute. +# 3. Pass number - [Optional] The current pass number. Used by timed tests. +# 4. Timeout variable - [Optional] The name of the variable to put the next +# timeout into. Used by timed tests. +runtest() { + # Clear/Reset local variables + unset AGENT TEST TEST_TYPE EXTRATEXT SKIP SKIPREASON RESULT RESULT_MSG TEST_START_TIME FLATNAME WORKDIR OUTFILE TEST_DESCRIPTION TEST_STORY TEST_COVERS + + AGENT="$1" + TEST="$2" + PASS_NUM="$3" + NEXT_TIMEOUT_VAR="$4" + # With STAY_IN_WORKDIR we may be running alongside others, and need to print everything at once + # on one line, so that we don't risk mixing lines together. + if [ -z "$QUIET" -a -z "$STAY_IN_WORKDIR" ] + then + printf "$TEST " + fi + + case "$TEST" in + *.x.cf) TEST_TYPE=errorexit && + EXTRATEXT='(should exit with non-zero, but not crash)' ;; + *.error.cf) TEST_TYPE=errorlog && + EXTRATEXT='(should log error(s), but exit with 0)' ;; + */staging/*) TEST_TYPE=staging ;; + */unsafe/*) TEST_TYPE=unsafe ;; + */network/*) TEST_TYPE=network ;; + */timed/*) TEST_TYPE=timed ;; + */slow/*) TEST_TYPE=slow ;; + *[/_]serial[/_.]*) TEST_TYPE=serial ;; + */11_xml_edits/*) TEST_TYPE=libxml2 ;; + */01_vars/02_functions/network/url_get.cf) TEST_TYPE=libcurl ;; + *) TEST_TYPE=common ;; + esac + + SKIP=0 # by default the test is not skipped + SKIPREASON= + if [ $ERROREXIT_TESTS = 0 -a $TEST_TYPE = errorexit ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}'errorexit' tests are disabled${COLOR_NORMAL}" + elif [ $ERRORLOG_TESTS = 0 -a $TEST_TYPE = errorlog ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}'errorlog' tests are disabled${COLOR_NORMAL}" + elif [ $STAGING_TESTS = 0 -a $TEST_TYPE = staging ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}Staging tests are disabled${COLOR_NORMAL}" + elif [ $UNSAFE_TESTS = 0 -a $TEST_TYPE = unsafe ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}Unsafe tests are disabled${COLOR_NORMAL}" + elif [ $NETWORK_TESTS = 0 -a $TEST_TYPE = network ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}Network-dependent tests are disabled${COLOR_NORMAL}" + elif [ $TIMED_TESTS = 0 -a $TEST_TYPE = timed ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}Timed tests are disabled${COLOR_NORMAL}" + elif [ $SLOW_TESTS = 0 -a $TEST_TYPE = slow ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}Slow tests are disabled${COLOR_NORMAL}" + elif [ $SERIAL_TESTS = 0 -a $TEST_TYPE = serial ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}Serial tests are disabled${COLOR_NORMAL}" + elif [ $LIBXML2_TESTS = 0 -a $TEST_TYPE = libxml2 ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}XML file editing tests are disabled${COLOR_NORMAL}" + elif [ $LIBCURL_TESTS = 0 -a $TEST_TYPE = libcurl ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}libcurl tests are disabled${COLOR_NORMAL}" + elif [ $COMMON_TESTS = 0 -a $TEST_TYPE = common ] + then + SKIP=1 + SKIPREASON="${COLOR_WARNING}Common tests are disabled${COLOR_NORMAL}" + fi + + TEST_START_TIME=$(unix_seconds) + + # Create workdir + WORKDIR="$(workdir "$TEST")" + OUTFILE="$WORKDIR/output.log" + + if [ -z "$PASS_NUM" ] || [ "$PASS_NUM" -eq 1 ] + then + # Don't reset workdir if this is a subsequent pass. + $GAINROOT rm -rf "$WORKDIR" + mkdir -p "$WORKDIR/bin" "$WORKDIR/tmp" + chmod ugo+rwxt "$WORKDIR/tmp" + fi + + # For unknown reason on the second pass, this file is root-owned, so we need GAINROOT here + $GAINROOT rm -f "$OUTFILE" + + # Make sure these files are owned by the script, for some reason + # each agent execution or each pass, messes this up. + $GAINROOT touch "$OUTFILE" "$WORKDIR/$LOG" "$WORKDIR/$SUMMARY" "$WORKDIR/$XML" + $GAINROOT chown $USER "$OUTFILE" "$WORKDIR/$LOG" "$WORKDIR/$SUMMARY" "$WORKDIR/$XML" + + if [ -n "$NEXT_TIMEOUT_VAR" ] + then + eval $NEXT_TIMEOUT_VAR= + fi + + if [ "$SKIP" = 1 ] # Skip + then + TEST_END_TIME=$TEST_START_TIME + RESULT=Skip + RESULT_MSG="${COLOR_WARNING}Skipped ($SKIPREASON)${COLOR_NORMAL}" + + else # Do not skip + # Prepare workdir + + # Don't copy into workdir if this is a subsequent pass. + if [ -z "$PASS_NUM" ] || [ "$PASS_NUM" -eq 1 ] + then + if [ -n "$BINDIR" ] + then + # Copy everything, because Windows depends on DLLs. + $LN_CMD "$BINDIR"/* "$WORKDIR/bin" + else + $LN_CMD "$AGENT" "$WORKDIR/bin" + $LN_CMD "$CF_PROMISES" "$WORKDIR/bin" + $LN_CMD "$CF_SERVERD" "$WORKDIR/bin" + $LN_CMD "$CF_EXECD" "$WORKDIR/bin" + $LN_CMD "$CF_KEY" "$WORKDIR/bin" + $LN_CMD "$CF_SECRET" "$WORKDIR/bin" + $LN_CMD "$CF_NET" "$WORKDIR/bin" + $LN_CMD "$CF_CHECK" "$WORKDIR/bin" + $LN_CMD "$CF_RUNAGENT" "$WORKDIR/bin" + if [ "$NEED_RPMVERCMP" = "yes" ] + then + $LN_CMD "$RPMVERCMP" "$WORKDIR/bin" + fi + $LN_CMD "$DIFF" "$WORKDIR/bin" + fi + for inc in $INCLUDE_IN_WORKDIR + do ( + # Copy directory structure, but make links inside. This allows tests to + # add additional files to the directory. + base=$(basename $inc) + mkdir -p "$WORKDIR/$base" + cd $inc || exit 2 + for dir in $(find . -type d) + do + mkdir -p "$WORKDIR/$base/$dir" + done + for file in $(find . \! -type d) + do + $LN_CMD "$inc/$file" "$WORKDIR/$base/$file" + done + ) + done + fi + if uname | grep MINGW > /dev/null + then + PLATFORM_WORKDIR="$(echo $WORKDIR | sed -e 's%^/\([a-zA-Z]\)/%\1:/%' | sed -e 's%/%\\%g')" + DS="\\" + else + PLATFORM_WORKDIR="$WORKDIR" + DS="/" + fi + + ( echo ---------------------------------------------------------------------- + echo "$TEST" "$EXTRATEXT" + echo ---------------------------------------------------------------------- + ) >> "$WORKDIR/$LOG" + + echo "#!/bin/sh +CFENGINE_TEST_OVERRIDE_WORKDIR=\"$PLATFORM_WORKDIR\" +TEMP=\"$PLATFORM_WORKDIR${DS}tmp\" +CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR=\"$CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR\" +export CFENGINE_TEST_OVERRIDE_WORKDIR TEMP CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR + +" > "$WORKDIR/runtest" + + if [ "$GDB" = 1 ] + then + if grep libtool < "$AGENT" > /dev/null + then + printf "\"$LIBTOOL\" --mode=execute " >> "$WORKDIR/runtest" + fi + printf "gdb --args " >> "$WORKDIR/runtest" + fi + + if [ -n "$USE_VALGRIND" -a $TEST_TYPE = errorexit ] + then + if grep libtool < "$AGENT" > /dev/null + then + printf "\"$LIBTOOL\" --mode=execute " >> "$WORKDIR/runtest" + fi + printf "valgrind ${VALGRIND_OPTS} \"$AGENT\" $VERBOSE $DEBUG -Klf \"$TEST\" -D ${PASS_NUM:+test_pass_$PASS_NUM,}${BASECLASSES},${EXTRACLASSES} 2>&1\n" >> "$WORKDIR/runtest" + elif [ x"$PRELOAD_ASAN" != x ] + then + printf "LD_PRELOAD=$PRELOAD_ASAN \"$AGENT\" $VERBOSE $DEBUG -Klf \"$TEST\" -D ${PASS_NUM:+test_pass_$PASS_NUM,}${BASECLASSES},${EXTRACLASSES}\n" >> "$WORKDIR/runtest" + else + printf "\"$AGENT\" $VERBOSE $DEBUG -Klf \"$TEST\" -D ${PASS_NUM:+test_pass_$PASS_NUM,}${BASECLASSES},${EXTRACLASSES}\n" >> "$WORKDIR/runtest" + fi + + chmod +x "$WORKDIR/runtest" + + if [ "$GDB" = 1 ] + then + $GAINROOT "$WORKDIR/runtest" + else + eval $GAINROOT "$WORKDIR/runtest" >>$OUTFILE 2>&1 + fi + + RETVAL=$? + TEST_END_TIME=$(unix_seconds) + + cat $OUTFILE >> "$WORKDIR/$LOG" + echo >> "$WORKDIR/$LOG" + echo "Return code is $RETVAL." >> "$WORKDIR/$LOG" + + # Try to collect test metadata if any + if egrep "R: test description: " $OUTFILE > /dev/null + then + TEST_DESCRIPTION="$(egrep "R: test description" $OUTFILE | sed -e "s,.*test description: \([A-Za-z0-9_]*\),\1,")" + fi + if egrep -e "R: test story_id: " $OUTFILE > /dev/null + then + TEST_STORY="$(egrep "R: test story_id" $OUTFILE | sed -e "s,.*test story_id: \([0-9][0-9]*\),\1,")" + fi + if egrep -e "R: test covers: " $OUTFILE > /dev/null + then + TEST_COVERS="$(egrep "R: test covers" $OUTFILE | sed -e "s,.*test covers: \([A-Za-z0-9_]*\),\1,")" + fi + + if [ $TEST_TYPE = errorexit ] + then + if [ $RETVAL -gt 0 ] && [ $RETVAL -lt 128 ] && egrep "error:|aborted on defined class" $OUTFILE > /dev/null + then + RESULT=Pass + RESULT_MSG="${COLOR_SUCCESS}Pass${COLOR_NORMAL}" + elif [ $RETVAL -ge 128 ] + then + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL (CRASH!!! THIS IS A BUG!)${COLOR_NORMAL}" + else + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL (Failed to exit with error!)${COLOR_NORMAL}" + fi + elif [ $TEST_TYPE = errorlog ] + then + if [ $RETVAL != 0 ] + then + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL (NON-ZERO EXIT CODE!)${COLOR_NORMAL}" + elif fgrep 'error: ' "$OUTFILE" > /dev/null + then + RESULT=Pass + RESULT_MSG="${COLOR_SUCCESS}Pass${COLOR_NORMAL}" + else + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL (No errors printed)${COLOR_NORMAL}" + fi + else # TEST_TYPE is not errorlog or errorexit + # We need to be careful when matching test outcomes. Because of convergence + # passes, a test may output FAIL before it outputs Pass; in this case the + # latter trumps the former. Also be careful when matching an [XS]FAIL; the + # test should not be allowed to output any other outcome in the same run, + # except for FAIL. + + # Some states are output by dcs.cf.sub, therefore check for both TEST + # prefix and dcs.cf.sub prefix. + ESCAPED_TEST="$(echo "($TEST|dcs.cf.sub)" | sed -e 's/\./\\./g')" + if egrep "R: .*$ESCAPED_TEST [XS]FAIL" $OUTFILE > /dev/null && ! egrep "R: .*$ESCAPED_TEST Wait/" $OUTFILE > /dev/null + then + # Check for passed outcome, which should not happen. + if egrep "R: .*$ESCAPED_TEST " $OUTFILE | egrep "R: .*$ESCAPED_TEST Pass" > /dev/null + then + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL (The test Passed, but failure was expected)${COLOR_NORMAL}" + elif egrep "R: .*$ESCAPED_TEST " $OUTFILE | egrep -v "R: .*$ESCAPED_TEST [XS]?FAIL" > /dev/null + then + # Other test case outcomes than fail. Should not happen. + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL (Failure was expected, but the test had an unexpected test outcome, check test output, Pass/FAIL need to match exactly)${COLOR_NORMAL}" + else + TICKET="$(egrep "R: .*$ESCAPED_TEST [XS]FAIL" $OUTFILE | sed -e "s,.*[XS]FAIL/\(.*\),\1,")" + RESULT="$(egrep "R: .*$ESCAPED_TEST [XS]FAIL" $OUTFILE | sed -e "s,.*\([XS]FAIL\).*,\1,")" + if [ "$RESULT" = "XFAIL" ] + then + RESULT_MSG="${COLOR_WARNING}FAIL (Suppressed, $TICKET)${COLOR_NORMAL}" + else + RESULT_MSG="${COLOR_WARNING}Soft fail ($TICKET)${COLOR_NORMAL}" + fi + fi + elif [ $RETVAL -ne 0 ] + then + RESULT=FAIL + elif egrep "R: .*$ESCAPED_TEST FAIL/no_ticket_number" $OUTFILE > /dev/null + then + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL (Tried to suppress failure, but no issue number is provided)${COLOR_NORMAL}" + elif egrep "R: .*$ESCAPED_TEST Wait/[0-9]+" $OUTFILE > /dev/null + then + if [ -z "$NEXT_TIMEOUT_VAR" ] + then + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL (Test tried to wait but is not in \"timed\" directory)${COLOR_NORMAL}" + else + WAIT_TIME=$(egrep "R: .*$ESCAPED_TEST Wait/[0-9]+" $OUTFILE | sed -e 's,.*Wait/\([0-9][0-9]*\).*,\1,') + eval $NEXT_TIMEOUT_VAR=$(($TEST_END_TIME+$WAIT_TIME)) + RESULT=Wait + RESULT_MSG="Awaiting ($WAIT_TIME seconds)..." + fi + elif egrep "R: .*$ESCAPED_TEST Pass" $OUTFILE > /dev/null + then + RESULT=Pass + RESULT_MSG="${COLOR_SUCCESS}Pass${COLOR_NORMAL}" + elif egrep "R: .*$ESCAPED_TEST Skip/unsupported" $OUTFILE > /dev/null + then + RESULT=Skip + RESULT_MSG="${COLOR_WARNING}Skipped (No platform support)${COLOR_NORMAL}" + elif egrep "R: .*$ESCAPED_TEST Skip/needs_work" $OUTFILE > /dev/null + then + RESULT=Skip + RESULT_MSG="${COLOR_WARNING}Skipped (Test needs work)${COLOR_NORMAL}" + elif egrep "R: .*$ESCAPED_TEST FLAKEY" $OUTFILE > /dev/null + then + RESULT=FLAKEY + TICKET="$(egrep "R: .*$ESCAPED_TEST FLAKEY" $OUTFILE | sed -e "s,.*FLAKEY/\(.*\),\1,")" + RESULT_MSG="${COLOR_WARNING}Flakey fail ($TICKET)${COLOR_NORMAL}" + + else + RESULT=FAIL + RESULT_MSG="${COLOR_FAILURE}FAIL${COLOR_NORMAL}" + fi + fi + + if [ "x$RESULT_MSG" = "x" ] + then + RESULT_MSG=$RESULT + fi + + if [ "$RESULT" = "FAIL" ] && [ -e .succeeded/"$FLATNAME" ] + then + RESULT_MSG="${COLOR_FAILURE}$RESULT_MSG (UNEXPECTED FAILURE)${COLOR_NORMAL}" + fi + fi + + if [ "$RESULT" = "XFAIL" -o "$RESULT" = "SFAIL" ] + then + echo " " + elif [ "$RESULT" != Wait ] + then + echo " " + fi >> "$WORKDIR/$XML" + + + # Fill test metadata if any + if [ ! -z "$TEST_DESCRIPTION" ] || [ ! -z "$TEST_STORY" ] || [ ! -z "$TEST_COVERS" ] + then + if [ ! -z "$TEST_DESCRIPTION" ] + then + TEST_DESCRIPTION_METATAG="name=\"$TEST_DESCRIPTION\"" + fi + if [ ! -z "$TEST_STORY" ] + then + TEST_STORY_METATAG="story_id=\"$TEST_STORY\"" + fi + if [ ! -z "$TEST_COVERS" ] + then + TEST_COVERS_METATAG="covers=\"$TEST_COVERS\"" + fi + echo " $TEST_DESCRIPTION_METATAG $TEST_STORY_METATAG $TEST_COVERS_METATAG" + fi >> "$WORKDIR/$XML" + + + echo $RESULT $TEST >> "$WORKDIR/$SUMMARY" + case "$RESULT" in + Pass) + PASSED_TESTS=$(($PASSED_TESTS + 1)) + + mkdir -p '.succeeded' + touch .succeeded/"$FLATNAME" + ;; + + FAIL|XFAIL) + ( echo " " + cat $OUTFILE | sed -e "s/&/\&/g; s//\>/g; s/'/\"/g" + echo " " + ) >> "$WORKDIR/$XML" + FAILED_TESTS=$(($FAILED_TESTS + 1)) + if [ "$RESULT" = "XFAIL" ] + then + SUPPRESSED_FAILURES=$(($SUPPRESSED_FAILURES + 1)) + fi + ;; + + SFAIL) + ( echo " " + echo " message=\"$RESULT_MSG $TEST\">" + cat $OUTFILE | sed -e "s/&/\&/g; s//\>/g; s/'/\"/g" + echo " " + ) >> "$WORKDIR/$XML" + SOFT_FAILURES=$(($SOFT_FAILURES + 1)) + ;; + + Skip) + ( echo " " + echo " " + ) >> "$WORKDIR/$XML" + SKIPPED_TESTS=$(($SKIPPED_TESTS + 1)) + ;; + esac + + if [ "$RESULT" != Wait ] + then + echo " " >> "$WORKDIR/$XML" + fi + + if [ -e "$WORKDIR" -a "$RESULT" != "FAIL" -a "$RESULT" != "Wait" -a "$NO_CLEAN" = "0" ] + then + # Delete everything except the logs from the workdir. + ls -1 "$WORKDIR" | while read s + do + case "$s" in + "$LOG"|"$SUMMARY"|"$XML") + ;; + *) + $GAINROOT rm -fr "$WORKDIR/$s" + ;; + esac + done + if [ "$GITHUB_ACTIONS" = "true" ] && [ "$RUNNER_OS" = "Windows" ] + then + # on Github Actions Windows runners, `rm -rf` moves files into /d/d/ directory. + # To clean this "Recycle bin", we delete this directory. + # Otherwise, runner promptly run out of disk space. + rm -rf /d/d/ + fi + fi + + if [ -z "$QUIET" ] + then + if [ -n "$STAY_IN_WORKDIR" ] + then + # See comment about STAY_IN_WORKDIR near start of runtest. + echo "$TEST $RESULT_MSG" + else + echo $RESULT_MSG + fi + else + if [ "$RESULT" = Pass ] + then + printf '.' + elif [ "$RESULT" = Skip ] + then + printf '-' + elif [ "$RESULT" = FAIL ] + then + if [ $TEST_TYPE = errorexit ] + then + printf '!' + else + printf 'x' + fi + fi + fi + + ( + echo + echo ' ==>' "$RESULT_MSG" + echo + ) >> "$WORKDIR/$LOG" +} + + +ORIG_ARGS= +while true +do + case "$1" in + -h|--help) + usage + exit;; + -q|--quiet) + QUIET=1;; + --gainroot=*) + GAINROOT=${1#--gainroot=};; + --valgrind) + USE_VALGRIND=1;; + --callgrind) + USE_VALGRIND=1 + VALGRIND_OPTS="--suppressions=valgrind-suppressions --tool=callgrind";; + --inotifywatch) + USE_INOTIFYWATCH=1;; + --inotifywait) + USE_INOTIFYWAIT=1;; + --tests=*) + + # Deselect all test types, in order to have only the ones + # given as arguments + COMMON_TESTS=0 + TIMED_TESTS=0 + SLOW_TESTS=0 + ERROREXIT_TESTS=0 + ERRORLOG_TESTS=0 + SERIAL_TESTS=0 + NETWORK_TESTS=0 + LIBXML2_TESTS=0 + LIBCURL_TESTS=0 + UNSAFE_TESTS=0 + STAGING_TESTS=0 + + TESTS_SELECTED=${1#--tests=} + for testtype in `echo $TESTS_SELECTED | tr , ' '` + do + case "$testtype" in + common) COMMON_TESTS=1;; + timed) TIMED_TESTS=1;; + slow) SLOW_TESTS=1;; + errorexit) ERROREXIT_TESTS=1;; + errorlog) ERRORLOG_TESTS=1;; + serial) SERIAL_TESTS=1;; + network) NETWORK_TESTS=1;; + libxml2) LIBXML2_TESTS=1;; + libcurl) LIBCURL_TESTS=1;; + unsafe) UNSAFE_TESTS=1;; + staging) STAGING_TESTS=1;; + esac + done + ;; + + --agent=*) + AGENT=${1#--agent=};; + --baseclasses=*) + BASECLASSES=${1#--baseclasses=};; + --bc=*) + BASECLASSES=${1#--bc=};; + --extraclasses=*) + EXTRACLASSES=${1#--extraclasses=};; + --ec=*) + EXTRACLASSES=${1#--ec=};; + --cfpromises=*) + CF_PROMISES=${1#--cfpromises=};; + --cfserverd=*) + CF_SERVERD=${1#--cfserverd=};; + --cfexecd=*) + CF_EXECD=${1#--cfexecd=};; + --cfkey=*) + CF_KEY=${1#--cfkey=};; + --cfkeycrypt=*) + CF_SECRET=${1#--cfkeycrypt=};; + --cfnet=*) + CF_NET=${1#--cfnet=};; + --cfcheck=*) + CF_CHECK=${1#--cfcheck=};; + --cfrunagent=*) + CF_RUNAGENT=${1#--cfrunagent=};; + --rpmvercmp=*) + RPMVERCMP=${1#--rpmvercmp=};; + --bindir=*) + BINDIR=${1#--bindir=};; + --libtool=*) + LIBTOOL=${1#--libtool=};; + --include=*) + INCLUDE_IN_WORKDIR="$INCLUDE_IN_WORKDIR${INCLUDE_IN_WORKDIR:+ }${1#--include=}";; + -j*|--jobs*) + MAKEFLAGS="$MAKEFLAGS $1";; + --printlog) + PRINTLOG=1;; + --gdb) + GDB=1;; + --no-clean) + NO_CLEAN=1;; + --verbose) + VERBOSE="-v";; + --debug) + DEBUG="--debug";; + --stay-in-workdir) + # Internal option. Meant to keep sub invocations from interfering by + # writing files only into the workdir. + STAY_IN_WORKDIR=1;; + --base-workdir=*) + BASE_WORKDIR="${1#--base-workdir=}";; + -*) + echo "Unknown option: $1" + exit 1;; + *) + break;; + esac + # Make sure spaces are preserved by escaping them. + ORIG_ARGS="$ORIG_ARGS $(echo "$1" | sed -e 's/ /\\ /g')" + shift +done + +# +# Close stdin file descriptor to avoid tty_interactive check in cf-agent being success. +# Do this iff GDB AND Valgrind are not in use. +# +if [ "$GDB" != 1 ] && [ "$USE_VALGRIND" != 1 ] +then + exec /dev/null +then + # On Windows we run the entire test run under GAINROOT, because doing it for + # each test is horribly slow. + echo "export GAINROOT=" > runtests.sh + echo "export TESTALL_DO_NOT_RECURSE=1" >> runtests.sh + echo "export DEBUG='$DEBUG'" >> runtests.sh + echo "export VERBOSE='$VERBOSE'" >> runtests.sh + echo "$0 $ORIG_ARGS $@ &" >> runtests.sh + # Note quote change. We want to keep below variables unexpanded. + echo 'trap "kill $!" INT' >> runtests.sh + echo 'trap "kill $!" TERM' >> runtests.sh + # Traps do not fire during commands, but *do* fire during wait. + echo 'wait $!' >> runtests.sh + $GAINROOT "./runtests.sh" + exit $? +fi + +# Check last -j flag, and check if it is -j1. +for arg in $MAKEFLAGS +do + case "$arg" in + -j|--jobs) PARALLEL=1 ;; + -j*) if [ 1 -eq "${arg#-j}" ] + then PARALLEL= + else PARALLEL=1 + fi ;; + --jobs=*) if [ 1 -eq "${arg#--jobs=}" ] + then PARALLEL= + else PARALLEL=1 + fi ;; + esac +done + +# check if rpm is used on this host, sometimes it can be installed but not used say on Ubuntu +if [ -n "$(command -v rpm)" ] && [ "$(rpm -qa | wc -l)" != "0" ] +then + NEED_RPMVERCMP="yes" +else + NEED_RPMVERCMP="no" +fi + +if [ -n "$AGENT" -o -n "$CF_PROMISES" -o -n "$CF_SERVERD" -o -n "$CF_EXECD" -o -n "$CF_KEY" -o -n "$CF_SECRET" -o -n "$CF_NET" -o -n "$CF_CHECK" -o -n "$CF_RUNAGENT" -o \( "$NEED_RPMVERCMP" = "yes" -a -n "$RPMVERCMP" \) -o -n "$DIFF" ] +then + if [ -n "$BINDIR" ] + then + echo "--bindir is mutually exclusive with specifying individual binaries." + exit 2 + fi +fi + +# We assume we're running this script from one of the following +# $objdir +# $objdir/tests/acceptance +# /var/cfengine/tests/acceptance or from +# $core_objdir/../{enterprise,masterfiles}/tests/acceptance +find_default_binary() +{ + [ -x "`pwd`/../../../core/$2/$2" ] && eval $1=\""`pwd`/../../../core/$2/$2"\" + [ -x "`pwd`/../../../core/ext/$2" ] && eval $1=\""`pwd`/../../../core/ext/$2"\" + [ -x "`pwd`/../../ext/$2" ] && eval $1=\""`pwd`/../../ext/$2"\" + [ -x "`pwd`/../../bin/$2" ] && eval $1=\""`pwd`/../../bin/$2"\" + [ -x "`pwd`/../../$2/$2" ] && eval $1=\""`pwd`/../../$2/$2"\" + [ -x "`pwd`/$2/$2" ] && eval $1=\""`pwd`/$2/$2"\" + [ -n "$BINDIR" -a -x "$BINDIR/$2" ] && eval $1=\""$BINDIR/$2"\" + [ $2 = "diff" ] && eval $1=\""`command -v diff`"\" +} +find_default_binary DEFAGENT cf-agent +find_default_binary DEFCF_PROMISES cf-promises +find_default_binary DEFCF_SERVERD cf-serverd +find_default_binary DEFCF_EXECD cf-execd +find_default_binary DEFCF_KEY cf-key +find_default_binary DEFCF_SECRET cf-secret +find_default_binary DEFCF_NET cf-net +find_default_binary DEFCF_CHECK cf-check +find_default_binary DEFCF_RUNAGENT cf-runagent +if [ "$NEED_RPMVERCMP" = "yes" ] +then + find_default_binary DEFRPMVERCMP rpmvercmp +fi +find_default_binary DIFF diff + +[ -x "`pwd`/libtool" ] && DEFLIBTOOL="`pwd`/libtool" +[ -x "`pwd`/../../libtool" ] && DEFLIBTOOL="`pwd`/../../libtool" + +AGENT=${AGENT:-${DEFAGENT}} +CF_PROMISES=${CF_PROMISES:-${DEFCF_PROMISES}} +CF_SERVERD=${CF_SERVERD:-${DEFCF_SERVERD}} +CF_EXECD=${CF_EXECD:-${DEFCF_EXECD}} +CF_KEY=${CF_KEY:-${DEFCF_KEY}} +CF_SECRET=${CF_SECRET:-${DEFCF_SECRET}} +CF_NET=${CF_NET:-${DEFCF_NET}} +CF_CHECK=${CF_CHECK:-${DEFCF_CHECK}} +CF_RUNAGENT=${CF_RUNAGENT:-${DEFCF_RUNAGENT}} +RPMVERCMP=${RPMVERCMP:-${DEFRPMVERCMP}} +LIBTOOL=${LIBTOOL:-${DEFLIBTOOL}} + +if [ ! -x "$AGENT" -o ! -x "$CF_PROMISES" -o ! -x "$CF_SERVERD" -o ! -x "$CF_EXECD" -o ! -x "$CF_KEY" -o ! -x "$CF_SECRET" -o ! -x "$CF_NET" -o ! -x "$CF_CHECK" -o ! -x "$CF_RUNAGENT" -o \( "$NEED_RPMVERCMP" = "yes" -a ! -x "$RPMVERCMP" \) ] +then + echo "ERROR: can't find cf-agent or other binary;" \ + " Are you sure you're running this from OBJDIR" \ + " or {core,masterfiles,enterprise}_OBJDIR/tests/acceptance ?" + echo '$AGENT =' "$AGENT" + echo '$CF_PROMISES =' "$CF_PROMISES" + echo '$CF_SERVERD =' "$CF_SERVERD" + echo '$CF_EXECD =' "$CF_EXECD" + echo '$CF_KEY =' "$CF_KEY" + echo '$CF_SECRET =' "$CF_SECRET" + echo '$CF_RUNAGENT =' "$CF_RUNAGENT" + echo '$CF_NET =' "$CF_NET" + echo '$CF_CHECK =' "$CF_CHECK" + echo '$RPMVERCMP =' "$RPMVERCMP" + exit 1 +fi + +if [ "$UNSAFE_TESTS" = "1" ] +then + if [ "$GAINROOT" = "fakeroot" ] + then + echo "Unsafe tests do not play well together with fakeroot. Please use a different" + echo "--gainroot (like \"sudo\"), or you will get incorrect results." + exit 1 + fi + + # Make sure test dir is accessible to everyone, because unsafe tests may + # switch user during the test (users promises do this). + DIR="$(cd "$(dirname "$0")"; pwd)" + while [ "$DIR" != "/" -a "$DIR" != "" ] + do + $GAINROOT chmod go+rx "$DIR" + DIR="$(dirname "$DIR")" + done +fi + +if [ $# -gt 0 ] +then + # We run all specified tests according to the defaults (no unsafe ones). + for test in "$@" + do + if ! expr "$test" : '[/.]' >/dev/null + then + test="./$test" + fi + + if [ -f $test ] + then + ALL_TESTS="$ALL_TESTS${ALL_TESTS:+ }$test" + elif [ -d $test ] + then + ALL_TESTS="$ALL_TESTS${ALL_TESTS:+ }$(find "$test" -name workdir -prune -o -name '*.cf' -print | sort)" + else + echo "Unable to open test file/directory: $test" + fi + done +else + ALL_TESTS="$ALL_TESTS${ALL_TESTS:+ }$(find . \( -name selftest -o -name workdir \) -prune -o -name '*.cf' -print | sort)" +fi + +for addtest in $ALL_TESTS +do + if echo "$addtest" | fgrep "/timed/" > /dev/null + then + if [ "$TIMED_TESTS" = 1 ] + then + eval TESTS_TIMED_$TEST_TIMED_INDEX="$addtest" + eval TESTS_TIMEOUT_$TEST_TIMED_INDEX=0 + eval TESTS_PASSES_$TEST_TIMED_INDEX=0 + TEST_TIMED_INDEX=$(($TEST_TIMED_INDEX+1)) + fi + else + eval TESTS_$TEST_INDEX="$addtest" + TEST_INDEX=$(($TEST_INDEX+1)) + fi +done + +TESTS_NORMAL_COUNT=$TEST_INDEX +TESTS_TIMED_COUNT=$TEST_TIMED_INDEX +TESTS_COUNT=$(($TESTS_NORMAL_COUNT + $TESTS_TIMED_COUNT)) +TESTS_TIMED_REMAINING=$TEST_TIMED_INDEX + +# +# fd 7 is a /dev/null for quiet execution and stdout for default one +# +if [ -z "$QUIET" ] +then + exec 7>&1 +else + exec 7>/dev/null +fi + +print_header() { + ( echo ====================================================================== + echo Testsuite started at $(date "+%Y-%m-%d %T") + echo ---------------------------------------------------------------------- + echo Total tests: $TESTS_COUNT + echo + for feature in COMMON_TESTS TIMED_TESTS SLOW_TESTS ERROREXIT_TESTS SERIAL_TESTS NETWORK_TESTS LIBXML2_TESTS LIBCURL_TESTS UNSAFE_TESTS STAGING_TESTS + do + if eval "[ \${$feature} != 0 ]" + then + printf '%20s: %s\n' $feature enabled + else + printf '%20s: %s\n' $feature disabled + fi + done + echo + if [ x$PARALLEL = x1 ] + then + echo "Test run is PARALLEL with MAKEFLAGS=$MAKEFLAGS" + else + echo "Test run is not parallel ${MAKEFLAGS:+(MAKEFLAGS=$MAKEFLAGS)}" + fi + echo + ) | tee "$LOG" | tee "$SUMMARY" >&7 +} + +print_footer() { + # Recalculate results since sub invocations may not have been recorded. + PASSED_TESTS=`egrep '^Pass ' $SUMMARY | wc -l | tr -d ' '` + FAILED_TESTS=`egrep '^(FAIL|XFAIL) ' $SUMMARY | wc -l | tr -d ' '` + SUPPRESSED_FAILURES=`egrep '^XFAIL ' $SUMMARY | wc -l | tr -d ' '` + SOFT_FAILURES=`egrep '^SFAIL ' $SUMMARY | wc -l | tr -d ' '` + FLAKEY_FAILURES=`egrep '^FLAKEY ' $SUMMARY | wc -l | tr -d ' '` + SKIPPED_TESTS=`egrep '^Skip ' $SUMMARY | wc -l | tr -d ' '` + + ( echo + echo ====================================================================== + echo "Testsuite finished at $(date "+%F %T") ($(($END_TIME - $START_TIME)) seconds)" + ) | tee -a "$LOG" | tee -a "$SUMMARY" >&7 + + ( echo + echo "Passed tests: $PASSED_TESTS" + printf "Failed tests: $FAILED_TESTS" + if [ $SUPPRESSED_FAILURES -gt 0 ] + then + echo " ($SUPPRESSED_FAILURES are known and suppressed)" + else + echo + fi + echo "Skipped tests: $SKIPPED_TESTS" + echo "Soft failures: $SOFT_FAILURES" + echo "Flakey failures: $FLAKEY_FAILURES" + echo "Total tests: $TESTS_COUNT" + ) | tee -a "$LOG" | tee -a "$SUMMARY" + + if [ -n "$PRINTLOG" ] + then + cat "$LOG" + fi +} + +finish_xml() { + mv "$XML" xml.tmp + ( + cat < + +EOF + cat xml.tmp + cat < +EOF + ) > "$XML" + rm -f xml.tmp +} + +collect_results() +{ + if [ -n "$STAY_IN_WORKDIR" ] + then + return 0 + fi + + WORKDIR="$(workdir "$1")" + for file in "$LOG" "$SUMMARY" "$XML" + do + if [ -e "$WORKDIR/$file" ] + then + cat "$WORKDIR/$file" >> "$file" + if [ "$NO_CLEAN" = "0" ] + then + # For unknown reason after the second pass, these file are root-owned, so we need GAINROOT here + $GAINROOT rm -f "$WORKDIR/$file" + fi + fi + done + rmdir "$WORKDIR" >/dev/null 2>&1 || true +} + +check_and_run_timed_tests() { + TEST_TIMED_INDEX=0 + time=$(unix_seconds) + # Run timed tests if any deadlines have expired. + while [ $TEST_TIMED_INDEX -lt $TESTS_TIMED_COUNT ] + do + eval test=\$TESTS_TIMED_$TEST_TIMED_INDEX + eval timeout=\$TESTS_TIMEOUT_$TEST_TIMED_INDEX + if [ -n "$timeout" ] && [ "$time" -ge "$timeout" ] + then + eval TESTS_PASSES_$TEST_TIMED_INDEX="\$((\$TESTS_PASSES_$TEST_TIMED_INDEX+1))" + eval pass=\$TESTS_PASSES_$TEST_TIMED_INDEX + runtest "$AGENT" "$test" "$pass" "TESTS_TIMEOUT_$TEST_TIMED_INDEX" + collect_results "$test" + eval timeout=\$TESTS_TIMEOUT_$TEST_TIMED_INDEX + if [ -z "$timeout" ] + then + TESTS_TIMED_REMAINING=$(($TESTS_TIMED_REMAINING - 1)) + fi + fi + TEST_TIMED_INDEX=$(($TEST_TIMED_INDEX+1)) + done +} + +run_all_tests() { + TEST_INDEX=0 + while [ $TEST_INDEX -lt $TESTS_NORMAL_COUNT -o $TESTS_TIMED_REMAINING -gt 0 ] + do + check_and_run_timed_tests + + # Run normal test. + if [ $TEST_INDEX -lt $TESTS_NORMAL_COUNT ] + then + eval test=\$TESTS_$TEST_INDEX + runtest "$AGENT" "$test" + collect_results "$test" + TEST_INDEX=$(($TEST_INDEX+1)) + elif [ $TESTS_TIMED_REMAINING -gt 0 ] + then + sleep 1 + fi + done +} + + +# TODO this function is currently creating a Makefile with all possible +# tests, either they will run or be skipped. The proper way is to +# do all the checks that now are in runtest(), and add the test +# here only if it will run. This should be done higher up, where +# ALL_TESTS is populated. +# +# When this is done, there won't be a need to pass --tests=... down +# the makefile subexecutions. + +run_all_tests_using_make() { + MAKEFILE=Makefile.testall + + TEST_BLOCKS=1 + LAST_WAS_SERIAL=0 + + for curr_test in $ALL_TESTS + do + # Keep serial tests after preceding tests and before following tests. + case "$curr_test" in + */serial_*|*_serial_*|*_serial.*|*/serial/*|*/unsafe/*) + if [ $LAST_WAS_SERIAL = 0 ] + then + TEST_BLOCKS=$(($TEST_BLOCKS + 1)) + LAST_WAS_SERIAL=1 + fi + ;; + *) + if [ $LAST_WAS_SERIAL = 1 ] + then + TEST_BLOCKS=$(($TEST_BLOCKS + 1)) + LAST_WAS_SERIAL=0 + fi + ;; + esac + case "$curr_test" in + */serial_*|*_serial_*|*_serial.*|*/serial/*|*/unsafe/*|*/timed/*|*/slow/*) + eval MAKE_RECIPE_LIST$TEST_BLOCKS='$MAKE_RECIPE_LIST'$TEST_BLOCKS'${MAKE_RECIPE_LIST'$TEST_BLOCKS':+ }$curr_test' + ;; + *) + # Separate make target list (notice the "_rule"), because we do not want the target to be a + # file name, since make will skip it if it exists (which of course it does). + eval MAKE_TARGET_LIST$TEST_BLOCKS='$MAKE_TARGET_LIST'$TEST_BLOCKS'${MAKE_TARGET_LIST'$TEST_BLOCKS':+ }${curr_test}_rule' + ;; + esac + done + + # Redirect to makefile all at once. + ( + printf "tests:\n\n" + printf "parallel_block0:\n\n" + printf "serial_block0:\n\n" + printf "tests: serial_block0 parallel_block0\n\n" + + i=1 + while [ $i -le $TEST_BLOCKS ] + do + if eval test -n '"$MAKE_RECIPE_LIST'$i'"' + then + eval printf '"serial_block$i:\n\t@-$0 --stay-in-workdir -j1 ${TESTS_SELECTED:+--tests=$TESTS_SELECTED} $MAKE_RECIPE_LIST'$i'\n\n"' + fi + printf "serial_block$i: serial_block$(($i-1)) parallel_block$(($i-1))\n\n" + eval printf '"parallel_block$i: $MAKE_TARGET_LIST'$i'\n\n"' + if eval test -n '"$MAKE_TARGET_LIST'$i'"' + then + eval printf '"$MAKE_TARGET_LIST'$i': serial_block$(($i-1)) parallel_block$(($i-1))\n\n"' + fi + printf "tests: serial_block$i parallel_block$i\n\n" + + i=$(($i + 1)) + done + + printf "%%_rule: %%\n\t@-$0 --stay-in-workdir -j1 ${TESTS_SELECTED:+--tests=$TESTS_SELECTED} \$<\n\n" + ) > $MAKEFILE + + ${MAKE:-make} -k -f $MAKEFILE + + for curr_test in $ALL_TESTS + do + collect_results "$curr_test" + done +} + + + +# +# ========== RUN THE TESTS ========== +# + +# isEnabled? USE_INOTIFYWATCH +if [ -n "$USE_INOTIFYWATCH" ] +then + if which inotifywatch 2>&1 > /dev/null + then + mkdir -p "$BASE_WORKDIR" 2> /dev/null + inotifywatch ${INOTIFYWATCH_OPTS} $BASE_WORKDIR < /dev/null 2>&1 > ${INOTIFYWATCH_LOG} & + INOTIFYWATCH_PID=$! + else + echo "info: inotifywatch not detected." + exit 0 + fi +fi + +# isEnabled? USE_INOTIFYWAIT +if [ -n "$USE_INOTIFYWAIT" ] +then + if which inotifywait 2>&1 > /dev/null + then + mkdir -p "$BASE_WORKDIR" 2> /dev/null + inotifywait ${INOTIFYWAIT_OPTS} $BASE_WORKDIR < /dev/null 2>&1 > ${INOTIFYWAIT_LOG} & + INOTIFYWAIT_PID=$! + else + echo "info: inotifywait not detected." + exit 0 + fi +fi + +trap_handler() { + [ -n "$INOTIFYWATCH_PID" ] && kill -9 ${INOTIFYWATCH_PID} 2>&1 > /dev/null + [ -n "$INOTIFYWAIT_PID" ] && kill -9 ${INOTIFYWAIT_PID} 2>&1 > /dev/null + exit 0 +} +if [ -n "$USE_INOTIFYWATCH" -o -n "$USE_INOTIFYWAIT" ] +then + trap trap_handler EXIT INT TERM +fi + +START_TIME=$(unix_seconds) +if [ -z "$STAY_IN_WORKDIR" ] +then + # This is top level invocation. + print_header + rm -f "$XML" +fi +if [ -z "$PARALLEL" -o "$TESTS_COUNT" -eq 1 ] +then + run_all_tests +else + run_all_tests_using_make +fi +END_TIME=$(unix_seconds) +if [ -z "$STAY_IN_WORKDIR" ] +then + # This is top level invocation. + print_footer + finish_xml +fi + +if [ $(($PASSED_TESTS+$FAILED_TESTS+$SOFT_FAILURES+$SKIPPED_TESTS+$FLAKEY_FAILURES)) -ne $TESTS_COUNT ] +then + echo "WARNING: Number of test results does not match number of tests!" + echo "Did something go wrong during execution?" + exit 2 +fi +if [ "$FAILED_TESTS" -gt "$SUPPRESSED_FAILURES" ] +then + exit 1 +elif [ "$FLAKEY_FAILURES" -gt "0" ] && [ -n "$FLAKEY_IS_FAIL" ] +then + exit 4 +else + exit 0 +fi diff --git a/tests/acceptance/tool_wrappers/echo.bat b/tests/acceptance/tool_wrappers/echo.bat new file mode 100644 index 0000000000..4e1ca6a245 --- /dev/null +++ b/tests/acceptance/tool_wrappers/echo.bat @@ -0,0 +1 @@ +@%~p0\template.bat %~n0 "%*" diff --git a/tests/acceptance/tool_wrappers/egrep.bat b/tests/acceptance/tool_wrappers/egrep.bat new file mode 100644 index 0000000000..4e1ca6a245 --- /dev/null +++ b/tests/acceptance/tool_wrappers/egrep.bat @@ -0,0 +1 @@ +@%~p0\template.bat %~n0 "%*" diff --git a/tests/acceptance/tool_wrappers/elevate.sh b/tests/acceptance/tool_wrappers/elevate.sh new file mode 100644 index 0000000000..ba0b8f3f4b --- /dev/null +++ b/tests/acceptance/tool_wrappers/elevate.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +# Launch a command with elevated privileges, but use sh to do it. +# Requires "elevate.exe" from enterprise. +# Be careful what you pass into this script. It cannot handle any redirection +# or spaces in pathnames. + +kill_all_subprocesses() { + SUB_PID="$(cat pid.$$)" + # Extract sub process group ID. + SUB_PGID="$(ps -W | egrep "^ *$SUB_PID " | sed -e 's/^ *[0-9]\+ \+[0-9]\+ \+\([0-9]\+\).*/\1/')" + # Extract list of all processes with that PGID. + # Note: We extract WINPID, not PID, for use in native Windows command. + WINPID_LIST="$(ps -W | egrep "^ *[0-9]+ +[0-9]+ +$SUB_PGID " | sed -e 's/^ *[0-9]\+ \+[0-9]\+ \+[0-9]\+ \+\([0-9]\+\).*/\1/')" + # Kill them all. + for i in $WINPID_LIST; do + $0 taskkill -f -t -pid $i + done +} + +touch output.$$ + +$(dirname $0)/../elevate.exe -wait "$(dirname $0)\template.bat" "cd '`pwd`'; export PATH='$PATH'; $(dirname $0)/preserve-output-and-status.sh $$ $@" & + +trap kill_all_subprocesses INT +trap kill_all_subprocesses TERM +# Traps do not fire during commands, but *do* fire during wait. +tail -F output.$$ --pid=$! 2>/dev/null & +wait $! + +# All these are written by "preserve-output-and-status.sh", because none of +# them can be preserved across elevate.exe. +rm -f pid.$$ +rm -f output.$$ +return_code=`cat return-code.$$` +rm -f return-code.$$ +exit $return_code diff --git a/tests/acceptance/tool_wrappers/preserve-output-and-status.sh b/tests/acceptance/tool_wrappers/preserve-output-and-status.sh new file mode 100644 index 0000000000..aa0e478231 --- /dev/null +++ b/tests/acceptance/tool_wrappers/preserve-output-and-status.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Run a command and store its output and return code. + +PID=$1 +shift +CWD=`pwd` + +$@ >& output.$PID & +echo $! > pid.$PID +wait $! + +RETURN_CODE=$? +cd $CWD +echo $RETURN_CODE > return-code.$PID diff --git a/tests/acceptance/tool_wrappers/printf.bat b/tests/acceptance/tool_wrappers/printf.bat new file mode 100644 index 0000000000..4e1ca6a245 --- /dev/null +++ b/tests/acceptance/tool_wrappers/printf.bat @@ -0,0 +1 @@ +@%~p0\template.bat %~n0 "%*" diff --git a/tests/acceptance/tool_wrappers/pwd.bat b/tests/acceptance/tool_wrappers/pwd.bat new file mode 100644 index 0000000000..4e1ca6a245 --- /dev/null +++ b/tests/acceptance/tool_wrappers/pwd.bat @@ -0,0 +1 @@ +@%~p0\template.bat %~n0 "%*" diff --git a/tests/acceptance/tool_wrappers/template.bat b/tests/acceptance/tool_wrappers/template.bat new file mode 100644 index 0000000000..5beff82862 --- /dev/null +++ b/tests/acceptance/tool_wrappers/template.bat @@ -0,0 +1,5 @@ +@echo off +if exist c:\msys64\usr\bin\sh.exe c:\msys64\usr\bin\sh.exe -c "%*" & goto end +if exist d:\a\_temp\msys64\usr\bin\sh.exe d:\a\_temp\msys64\usr\bin\sh.exe -c "%*" & goto end + +:end diff --git a/tests/acceptance/valgrind-suppressions b/tests/acceptance/valgrind-suppressions new file mode 100644 index 0000000000..49a645b2c0 --- /dev/null +++ b/tests/acceptance/valgrind-suppressions @@ -0,0 +1,195 @@ +################################################## +# +# Valgrind suppression file for acceptance tests +# +################################################## + + +################################################## +# libcrypto weirdness +################################################## +{ + libcrypto-leak + Memcheck:Leak + ... + obj:*/libcrypto.so.* +} +{ + libcrypto-cond + Memcheck:Cond + ... + obj:*/libcrypto.so.* +} +{ + libcrypto-value8 + Memcheck:Value8 + ... + obj:*/libcrypto.so.* +} + +################################################ +# Lex weirdness +################################################ +{ + lex-internal + Memcheck:Leak + ... + fun:yylex +} + +################################################ +# libc weirdness +################################################ + +{ + # This can probably be fixed by giving it a static buffer + gethostbyaddr-cannot-decide-on-const + Memcheck:Leak + ... + fun:gethostbyaddr + ... +} + +{ + msgsnd_nocancel-param + Memcheck:Param + msgsnd(msgp->mtext) + fun:__msgsnd_nocancel + fun:send_fakem + ... +} + +################################################ +# libxml2 weirdness +################################################ + +{ + # appears to do things right on our side + libxml2-xmlDocDumpMemory + Memcheck:Leak + fun:malloc + ... + fun:xmlDocDumpFormatMemoryEnc + fun:XmlDocsEqualMem + ... +} + +{ + # appears to do things right on our side + libxml2-xmlSAXParseFileWithData + Memcheck:Leak + fun:malloc + ... + fun:xmlSAXParseFileWithData + fun:LoadFileAsXmlDoc + ... +} + +{ + libxml2-dlopen-lzma + Memcheck:Leak + ... + fun:_dl_open + ... + fun:__libxml2_xzread +} + + +################################################ +# CFEngine specific suppressions +################################################ + +{ + cfe-enterprise-extensions-remain-open + Memcheck:Leak + ... + fun:shlib_open + fun:extension_library_open + ... +} + +{ + # Not sure why this is not cleaned, separate threads maybe + cfe-private-logging-context + Memcheck:Leak + ... + fun:xcalloc + fun:GetCurrentThreadContext + fun:LoggingPrivGetContext + fun:EvalContextNew + ... +} + +{ + cfe-global-server-list + Memcheck:Leak + ... + fun:SeqNew + fun:GetGlobalServerList + ... +} + +{ + cfe-global-dbm-handles + Memcheck:Leak + ... + fun:MapNameCopy + fun:DBIdToPath + fun:DBHandleGet + ... +} + +{ + cfe-global-children-fds + Memcheck:Leak + ... + fun:xcalloc + fun:InitChildrenFD + fun:CreatePipeAndFork + ... +} + +{ + cfe-global-process-table + Memcheck:Leak + ... + fun:AppendItem + fun:LoadProcessTable + ... +} + +{ + cfe-global-mounted-fs + Memcheck:Leak + ... + fun:SeqNew + fun:GetGlobalMountedFSList + ... +} + +{ + cfe-global-edit-anchors + Memcheck:Leak + ... + fun:PrependItem + fun:PromiseRecheckAllConstraints + ... +} + +{ + cfe-global-repository + Memcheck:Leak + ... + fun:xstrdup + fun:SetRepositoryLocation + ... +} + +{ + cfe-global-varname-regex + Memcheck:Leak + ... + fun:CompileRegex + fun:IsValidVariableName + ... +} diff --git a/tests/acceptance/write_args.sh b/tests/acceptance/write_args.sh new file mode 100755 index 0000000000..c126624d3f --- /dev/null +++ b/tests/acceptance/write_args.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# WARNING keep this /bin/sh compatible! + +# Outputs to file $1 all the rest of the arguments + + +if [ $# = 0 ] +then + echo "ERROR: At least one argument OUTPUT_FILE expected!" + exit 1 +fi + +OUTFILE="$1" +if [ -f "$OUTFILE" ] +then + echo "ERROR: already exists, I refuse to overwrite file: $OUTFILE" + exit 1 +fi + + +shift +echo "$*" > "$OUTFILE" diff --git a/tests/acceptance/xml-c14nize.c b/tests/acceptance/xml-c14nize.c new file mode 100644 index 0000000000..c37cf2f90b --- /dev/null +++ b/tests/acceptance/xml-c14nize.c @@ -0,0 +1,50 @@ +#include + +#include +#include +#include + +static bool xmlC14nizeFile(const char *filename) +{ + xmlDocPtr doc = xmlParseFile(filename); + + if (doc == NULL) + { + fprintf(stderr, "Unable to open %s for canonicalization\n", filename); + return false; + } + + xmlOutputBufferPtr out = xmlOutputBufferCreateFile(stdout, NULL); + + if (out == NULL) + { + fprintf(stderr, "Unable to set up writer for stdout\n"); + return false; + } + + if (xmlC14NDocSaveTo(doc, NULL, XML_C14N_1_0, 0, true, out) < 0) + { + fprintf(stderr, "Unable to c14nize XML document\n"); + return false; + } + + if (xmlOutputBufferClose(out) < 0) + { + fprintf(stderr, "Unable to close writer for stdout\n"); + return false; + } + + xmlFreeDoc(doc); + return true; +} + +int main(int argc, char **argv) +{ + if (argc != 2) + { + fprintf(stderr, "Usage: xml-c14nize \n"); + return 2; + } + + return xmlC14nizeFile(argv[1]) ? 0 : 1; +} diff --git a/tests/cf_remote_windows.sh b/tests/cf_remote_windows.sh new file mode 100644 index 0000000000..e3907f7fd1 --- /dev/null +++ b/tests/cf_remote_windows.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# This test / example shows how to spawn (windows) instances using cf-remote +# can be useful for manual testing, or be converted to an automated test. + +set -e +set -x + +# Note, cf-remote uses private AMIs, you may have to create your own and add +# them to cloud_data.py + +cf-remote --version master download msi + +cf-remote destroy --all +cf-remote spawn --count 1 --platform ubuntu-18-04-x64 --role hub --name hub +cf-remote spawn --count 1 --platform windows-2012-x64 --role client --name twelve +cf-remote spawn --count 1 --platform windows-2016-x64 --role client --name sixteen +sleep 60 +cf-remote --version master install --bootstrap hub --hub hub --clients sixteen,twelve +sleep 60 +cf-remote sudo --raw -H hub "/var/cfengine/bin/psql -d cfdb -c 'SELECT * FROM __hosts;'" +cf-remote destroy --all diff --git a/tests/load/Makefile.am b/tests/load/Makefile.am new file mode 100644 index 0000000000..89c5762d81 --- /dev/null +++ b/tests/load/Makefile.am @@ -0,0 +1,63 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +# automake does not support "maude_LIBS" variables. We can only alter +# the generic LIBS one. In case the functions are mocked in the test +# implementation, then we are pretty sure that they will be overriden by +# our local implementation. So we include *everything*... +LIBS = $(CORE_LIBS) +AM_LDFLAGS = $(CORE_LDFLAGS) + +# Those tests use stub functions interposition which does not work (yet) +# under OS X. Another way of stubbing functions from libpromises is needed. +if !XNU +AM_CPPFLAGS = $(CORE_CPPFLAGS) \ + $(ENTERPRISE_CPPFLAGS) \ + -I../../libpromises \ + -I../../libntech/libutils \ + -I../../libcfnet \ + -I../../libpromises + +EXTRA_DIST = \ + run_db_load.sh \ + run_lastseen_threaded_load.sh + +TESTS = \ + run_db_load.sh \ + run_lastseen_threaded_load.sh + +check_PROGRAMS = db_load lastseen_load lastseen_threaded_load + + +db_load_SOURCES = db_load.c +db_load_LDADD = ../unit/libdb.la + + +lastseen_load_SOURCES = lastseen_load.c \ + $(srcdir)/../../libpromises/lastseen.c \ + $(srcdir)/../../libntech/libutils/statistics.c +lastseen_load_LDADD = ../unit/libdb.la ../../libpromises/libpromises.la +endif + +lastseen_threaded_load_LDADD = \ + ../../libpromises/libpromises.la diff --git a/tests/load/db_load.c b/tests/load/db_load.c new file mode 100644 index 0000000000..58365351ba --- /dev/null +++ b/tests/load/db_load.c @@ -0,0 +1,292 @@ +#include +#include +#include +#include + +#include + + +#define MAX_THREADS 10000 +#define DB_ID dbid_classes + +#define STATUS_SUCCESS 0 +#define STATUS_FAILED_OPEN 1 +#define STATUS_FAILED_CLOSE 2 +#define STATUS_ERROR 3 + + +#define READWRITEKEY 123123123 +#define READWRITEDATA1 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +#define READWRITEDATA2 "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + +#define RECORD_COUNT_JUNK 7000 +#define RECORD_COUNT_READWRITE 1 // only one read/write key above +#define RECORD_COUNT_TOTAL (RECORD_COUNT_JUNK + RECORD_COUNT_READWRITE) +#define VALUE_OFFSET1 10000 +#define VALUE_OFFSET2 100000 + +char CFWORKDIR[CF_BUFSIZE]; + +static void WriteReadWriteData(CF_DB *db); +static bool ReadWriteDataIsValid(char *data); +static void DBWriteTestData(CF_DB *db); +static void TestReadWriteData(CF_DB *db); +static void TestCursorIteration(CF_DB *db); + +static void tests_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/db_load.XXXXXX"; + + char *workdir = strchr(env, '=') + 1; /* start of the path */ + assert(workdir - 1 && workdir[0] == '/'); + + mkdtemp(workdir); + strlcpy(CFWORKDIR, workdir, CF_BUFSIZE); + putenv(env); + mkdir(GetStateDir(), (S_IRWXU | S_IRWXG | S_IRWXO)); +} + +static void *contend(ARG_UNUSED void *param) +{ + CF_DB *db; + + if (!OpenDB(&db, DB_ID)) + { + return (void *)STATUS_FAILED_OPEN; + } + + DBWriteTestData(db); + + TestReadWriteData(db); + TestCursorIteration(db); + + CloseDB(db); + + return (void *)STATUS_SUCCESS; +} + +static void TestReadWriteData(CF_DB *db) +{ + WriteReadWriteData(db); + + int iterations = rand() % 1000000; + + for(int i = 0; i < iterations; i++) + { + // sleep gets complicated in threads... + } + + static const int key = READWRITEKEY; + + char readData[sizeof(READWRITEDATA1)]; + + if(!ReadComplexKeyDB(db, (const char *)&key, sizeof(key), readData, sizeof(readData))) + { + printf("Error read\n"); + } + + if(!ReadWriteDataIsValid(readData)) + { + printf("corrupt data: \"%s\"\n", readData); + } +} + +static bool CoinFlip(void) +{ + return rand() % 2 == 0; +} + +static void WriteReadWriteData(CF_DB *db) +{ + const char *const data = CoinFlip() ? READWRITEDATA1 : READWRITEDATA2; + static const int key = READWRITEKEY; + + if(!WriteComplexKeyDB(db, (const char *)&key, sizeof(key), data, sizeof(READWRITEDATA1))) + { + printf("Error write!\n"); + pthread_exit((void*)STATUS_ERROR); + } +} + +static bool ReadWriteDataIsValid(char *data) +{ + return (strcmp(data, READWRITEDATA1) == 0 || + strcmp(data, READWRITEDATA2) == 0); +} + +static void TestCursorIteration(CF_DB *db) +{ + CF_DBC *dbc; + + if(!NewDBCursor(db, &dbc)) + { + fprintf(stderr, "Test: could not create cursor"); + pthread_exit((void*)STATUS_ERROR); + exit(EXIT_FAILURE); + } + + char *key; + void *value; + int key_sz, value_sz; + + int count = 0; + while(NextDB(dbc, &key, &key_sz, &value, &value_sz)) + { + int key_num = *(int *)key; + int value_num = *(int *)value; + + if(key_num >= 0 && key_num < RECORD_COUNT_JUNK) + { + if((key_num + VALUE_OFFSET1 != value_num) && (key_num + VALUE_OFFSET2 != value_num)) + { + printf("Error: key,value %d,%d are inconsistent\n", key_num, value_num); + } + } + else if(key_num == READWRITEKEY) + { + if(!ReadWriteDataIsValid(value)) + { + printf("Error: ReadWrite data is invalid\n"); + } + } + else + { + printf("Error: invalid key \"%s\"", key); + } + + count++; + } + + if(count != RECORD_COUNT_TOTAL) + { + printf("Error: During iteration count was %d (expected %d)\n", count, RECORD_COUNT_TOTAL); + } + + if(!DeleteDBCursor(dbc)) + { + fprintf(stderr, "Test: could not delete cursor"); + exit(EXIT_FAILURE); + } + +} + + +int WriteReturnValues(int retvals[MAX_THREADS], pthread_t tids[MAX_THREADS], int numthreads) +{ + int failures = 0; + + for(int i = 0; i < numthreads; i++) + { + uintptr_t status; + pthread_join(tids[i], (void **)&status); + retvals[i] = status; + + if(status != STATUS_SUCCESS) + { + failures++; + } + } + + return failures; +} + +static void Cleanup(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +int main(int argc, char **argv) +{ + if (argc != 2) + { + fprintf(stderr, "Usage: db_load \n"); + exit(EXIT_FAILURE); + } + + /* To clean up after databases are closed */ + atexit(&Cleanup); + + tests_setup(); + + int numthreads = atoi(argv[1]); + + assert(numthreads < MAX_THREADS); + + srand(time(NULL)); + + pthread_t tids[MAX_THREADS]; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 65536); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + for (int i = 0; i < numthreads; ++i) + { + int ret = pthread_create(&(tids[i]), &attr, &contend, NULL); + + if (ret != 0) + { + fprintf(stderr, "Unable to create thread: %s\n", strerror(ret)); + } + } + + pthread_attr_destroy(&attr); + + int retvals[MAX_THREADS]; + + int failures = WriteReturnValues(retvals, tids, numthreads); + + exit(failures); +} + + +static void DBWriteTestData(CF_DB *db) +{ + for(int i = 0; i < RECORD_COUNT_JUNK; i++) + { + bool flip = CoinFlip(); + int value_num = i + (flip ? VALUE_OFFSET1 : VALUE_OFFSET2); + + if (!WriteComplexKeyDB(db, (const char *)&i, sizeof(i), &value_num, sizeof(value_num))) + { + Log(LOG_LEVEL_ERR, "Unable to write data to database"); + pthread_exit((void*)STATUS_ERROR); + } + } + + WriteReadWriteData(db); +} + +/* Stub out */ + +void FatalError(ARG_UNUSED const EvalContext *ctx, char *fmt, ...) +{ + if (fmt) + { + va_list ap; + char buf[CF_BUFSIZE] = ""; + + va_start(ap, fmt); + vsnprintf(buf, CF_BUFSIZE - 1, fmt, ap); + va_end(ap); + Log(LOG_LEVEL_ERR, "Fatal CFEngine error: %s", buf); + } + else + { + Log(LOG_LEVEL_ERR, "Fatal CFEngine error (no description)"); + } + + exit(EXIT_FAILURE); +} + + +pthread_mutex_t test_lock = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; + +pthread_mutex_t *cft_dbhandle; + +const char *const DAY_TEXT[] = {}; +const char *const MONTH_TEXT[] = {}; diff --git a/tests/load/lastseen_load.c b/tests/load/lastseen_load.c new file mode 100644 index 0000000000..9d76edabd9 --- /dev/null +++ b/tests/load/lastseen_load.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include + +char CFWORKDIR[CF_BUFSIZE]; + +static void tests_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/lastseen_migration_test.XXXXXX"; + + char *workdir = strchr(env, '=') + 1; /* start of the path */ + assert(workdir - 1 && workdir[0] == '/'); + + mkdtemp(workdir); + strlcpy(CFWORKDIR, workdir, CF_BUFSIZE); + putenv(env); + mkdir(GetStateDir(), (S_IRWXU | S_IRWXG | S_IRWXO)); +} + +void UpdateLastSawHost(const char *hostkey, const char *address, + bool incoming, time_t timestamp); + +int main() +{ + tests_setup(); + + for (int i = 0; i < 1000000; ++i) + { + if ((i % 10000) == 0) + { + printf("."); + fflush(stdout); + } + + char hostkey[50]; + xsnprintf(hostkey, 50, "SHA-%040d", i); + char ip[50]; + xsnprintf(ip, 50, "250.%03d.%03d.%03d", i / (256*256), (i / 256) % 256, i % 256); + + UpdateLastSawHost(hostkey, ip, false, i); + UpdateLastSawHost(hostkey, ip, true, 2000000 - i); + } + + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); + + return 0; +} + +/* STUBS */ + +void FatalError(char *s, ...) +{ + exit(42); +} + +HashMethod CF_DEFAULT_DIGEST; +const char *const DAY_TEXT[] = {}; +const char *const MONTH_TEXT[] = {}; +const char *const SHIFT_TEXT[] = {}; +pthread_mutex_t *cft_output; +char VIPADDRESS[CF_MAX_IP_LEN]; +RSA *PUBKEY; + +Item *IdempPrependItem(Item **liststart, const char *itemstring, const char *classes) +{ + exit(42); +} + +bool IsItemIn(Item *list, const char *item) +{ + exit(42); +} + +void DeleteItemList(Item *item) +{ + exit(42); +} + +bool MINUSF; + +char *MapAddress(char *addr) +{ + exit(42); +} + +char *HashPrintSafe(char *dst, size_t dst_size, const unsigned char *digest, + HashMethod type, bool use_prefix) +{ + exit(42); +} + +void HashPubKey(const RSA *key, unsigned char digest[EVP_MAX_MD_SIZE + 1], HashMethod type) +{ + exit(42); +} + +void *ConstraintGetRvalValue(char *lval, Promise *promise, char type) +{ + exit(42); +} diff --git a/tests/load/lastseen_threaded_load.c b/tests/load/lastseen_threaded_load.c new file mode 100644 index 0000000000..631c66c8bb --- /dev/null +++ b/tests/load/lastseen_threaded_load.c @@ -0,0 +1,710 @@ +#include +#include +#include +#include /* GracefulTerminate */ +#include /* ThreadLock */ +#include /* xclock_gettime */ +#include /* GetStateDir */ + +#include /* basename */ + + +unsigned int ROUND_DURATION = 10; /* how long to run each loop */ +#define NHOSTS 5000 /* how many hosts to store in db */ +#define MAX_NUM_THREADS 10000 +#define MAX_NUM_FORKS 10000 + + +time_t START_TIME; +pid_t PPID; +char CFWORKDIR[CF_BUFSIZE]; + +int CHILDREN_OUTPUTS[MAX_NUM_FORKS]; +/* TODO For correctness, the following variables should be guarded by mutexes + * since they are written from one thread and read from another. It's only a + * test though, and all tested platforms are behaving properly. */ +unsigned long lastsaw_COUNTER[MAX_NUM_THREADS]; +unsigned long keycount_COUNTER[MAX_NUM_THREADS]; +unsigned long scanlastseen_COUNTER[MAX_NUM_THREADS]; +volatile bool DONE; + +/* Counter and wait condition to see if test properly finished. */ +unsigned long FINISHED_THREADS = 0; +unsigned long TOTAL_NUM_THREADS; +pthread_mutex_t end_mtx = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; +pthread_cond_t end_cond = PTHREAD_COND_INITIALIZER; + + +void UpdateLastSawHost(const char *hostkey, const char *address, + bool incoming, time_t timestamp); + + +static bool PurgeCurrentLastSeen() +{ + CF_DB *db_conn = NULL; + CF_DBC *db_cursor = NULL; + char *key = NULL; + void *value = NULL; + int ksize = 0, vsize = 0; + + if (!OpenDB(&db_conn, dbid_lastseen)) + { + Log(LOG_LEVEL_ERR, "Unable to open lastseen db"); + return false; + } + + if (!NewDBCursor(db_conn, &db_cursor)) + { + Log(LOG_LEVEL_ERR, "Unable to scan lastseen db"); + CloseDB(db_conn); + return false; + } + + while (NextDB(db_cursor, &key, &ksize, &value, &vsize)) + { + /* Only read the 'quality of connection' entries */ + if (key[0] != 'q') + { + continue; + } + + time_t then = 0; + + if (value != NULL) + { + if (sizeof(KeyHostSeen) < vsize) + { + Log(LOG_LEVEL_ERR, "Invalid entry in lastseen database."); + continue; + } + + KeyHostSeen entry = { 0 }; + memcpy(&entry, value, vsize); + + then = entry.lastseen; + } + + if (then - START_TIME > NHOSTS) + { + DBCursorDeleteEntry(db_cursor); + Log(LOG_LEVEL_DEBUG, "Deleting expired entry for %s", key); + continue; + } + } + DeleteDBCursor(db_cursor); + CloseDB(db_conn); + + return true; +} + +static bool callback(const char *hostkey ARG_UNUSED, + const char *address ARG_UNUSED, + bool incoming ARG_UNUSED, + const KeyHostSeen *quality ARG_UNUSED, + void *ctx ARG_UNUSED) +{ + return true; +} + + +/* ============== WORKER THREADS ================ */ + +static void thread_exit_clean() +{ + /* Signal that we finished. */ + ThreadLock(&end_mtx); + FINISHED_THREADS++; + if (FINISHED_THREADS >= TOTAL_NUM_THREADS) + { + pthread_cond_signal(&end_cond); + } + ThreadUnlock(&end_mtx); +} + +void *lastsaw_worker_thread(void *arg) +{ + int thread_id = (intptr_t) arg; + + size_t i = 0; + while (!DONE) + { + char hostkey[50]; + xsnprintf(hostkey, sizeof(hostkey), "SHA-%040zx", i); + char ip[50]; + xsnprintf(ip, sizeof(ip), "250.%03zu.%03zu.%03zu", + i / (256*256), (i / 256) % 256, i % 256); + + UpdateLastSawHost(hostkey, ip, + ((i % 2 == 0) ? LAST_SEEN_ROLE_ACCEPT : + LAST_SEEN_ROLE_CONNECT), + START_TIME + i); + + i = (i + 1) % NHOSTS; + lastsaw_COUNTER[thread_id]++; + } + + thread_exit_clean(); + return NULL; +} + +void *keycount_worker_thread(void *arg) +{ + int id = (intptr_t) arg; + + while (!DONE) + { + LastSeenHostKeyCount(); + keycount_COUNTER[id]++; + } + + thread_exit_clean(); + return NULL; +} + +void *scanlastseen_worker_thread(void *arg) +{ + int id = (intptr_t) arg; + + while (!DONE) + { + ScanLastSeenQuality(callback, NULL); + scanlastseen_COUNTER[id]++; + } + + thread_exit_clean(); + return NULL; +} + +/* ============== END OF WORKER THREADS ================ */ + + +/* ============== CHILD PROCESS ======================== */ + +unsigned long child_COUNTER; +int PIPE_FD[2]; +FILE *PARENT_INPUT; +bool child_START = false; + +void print_progress_sighandler(int signum ARG_UNUSED) +{ + fprintf(PARENT_INPUT, "%6lu", child_COUNTER); + putc('\0', PARENT_INPUT); + int ret = fflush(PARENT_INPUT); + if (ret != 0) + { + perror("fflush"); + fprintf(stderr, "Child couldn't write to parent, " + "it probably died, exiting!\n"); + exit(EXIT_FAILURE); + } + + child_COUNTER = 0; +} + +/* First time the signal is received, it interrupts the sleep() syscall and + * sets child_START to true, which causes db crunching to start. Second time + * child_START is set to false and we exit the child process. */ +void startstop_handler(int signum ARG_UNUSED) +{ + child_START = !child_START; +} + +void worker_process() +{ + struct sigaction new_handler; + int ret; + + /* 1a. Register SIGUSR1 handler so that we start/finish the test */ + new_handler = (struct sigaction) { .sa_handler = startstop_handler }; + sigemptyset(&new_handler.sa_mask); + ret = sigaction(SIGUSR1, &new_handler, NULL); + if (ret != 0) + { + perror("sigaction"); + exit(EXIT_FAILURE); + } + + /* 1b. Register SIGUSR2 handler so that we report progress when pinged */ + new_handler = (struct sigaction) { .sa_handler = print_progress_sighandler }; + sigemptyset(&new_handler.sa_mask); + ret = sigaction(SIGUSR2, &new_handler, NULL); + if (ret != 0) + { + perror("sigaction"); + exit(EXIT_FAILURE); + } + + /* 2. Wait for signal */ + unsigned long wait_seconds = 0; + while (!child_START) + { + sleep(1); + wait_seconds++; + + pid_t ppid = getppid(); + if (ppid != PPID) + { + fprintf(stderr, + "PPID changed (%ld != %ld), maybe parent died, exiting!\n", + (long) PPID, (long) ppid); + exit(EXIT_FAILURE); + } + + if (wait_seconds >= 100) + { + fprintf(stderr, + "Child was not signaled to start after %lu seconds, " + "exiting!\n", wait_seconds); + exit(EXIT_FAILURE); + } + } + + /* 3. DO THE WORK until SIGUSR1 comes */ + while (child_START) + { + pid_t ppid = getppid(); + if (ppid != PPID) + { + fprintf(stderr, + "PPID changed (%ld != %ld), maybe parent died, exiting!\n", + (long) PPID, (long) ppid); + exit(EXIT_FAILURE); + } + + if (child_COUNTER % 10 == 0) + { + LastSeenHostKeyCount(); + } + else if (child_COUNTER % 10 == 1) + { + ScanLastSeenQuality(callback, NULL); + } + else if (child_COUNTER % 10 == 2) + { + PurgeCurrentLastSeen(); + } + else + { + char hostkey[50]; + xsnprintf(hostkey, sizeof(hostkey), "SHA-%040lx", child_COUNTER); + char ip[50]; + xsnprintf(ip, sizeof(ip), "250.%03lu.%03lu.%03lu", + child_COUNTER / (256*256), + (child_COUNTER / 256) % 256, + child_COUNTER % 256); + + UpdateLastSawHost(hostkey, ip, + ((child_COUNTER % 2 == 0) ? + LAST_SEEN_ROLE_ACCEPT : + LAST_SEEN_ROLE_CONNECT), + START_TIME + child_COUNTER); + } + + child_COUNTER++; + } +} + +/* ============== END OF CHILD PROCESS ================= */ + + +void spawn_worker_threads(void *(*worker_routine) (void *), + int num_threads, const char *description) +{ + pthread_t tid[num_threads]; + + printf("Spawning %s worker threads: ", description); + for (int i = 0; i < num_threads; i++) + { + int ret = pthread_create(&tid[i], NULL, + worker_routine, (void *)(intptr_t) i); + if (ret != 0) + { + fprintf(stderr, "pthread_create(%d): %s", + i, GetErrorStrFromCode(ret)); + exit(EXIT_FAILURE); + } + + printf("%i ", i+1); + } + printf("done!\n"); +} + +void print_progress(int lastsaw_num_threads, int keycount_num_threads, + int scanlastseen_num_threads, int num_children) +{ + for (int j = 0; j < lastsaw_num_threads; j++) + { + printf("%6lu", lastsaw_COUNTER[j]); + lastsaw_COUNTER[j] = 0; + } + if (keycount_num_threads > 0) + { + fputs(" | ", stdout); + } + for (int j = 0; j < keycount_num_threads; j++) + { + printf("%6lu", keycount_COUNTER[j]); + keycount_COUNTER[j] = 0; + } + if (scanlastseen_num_threads > 0) + { + fputs(" | ", stdout); + } + for (int j = 0; j < scanlastseen_num_threads; j++) + { + printf("%6lu", scanlastseen_COUNTER[j]); + scanlastseen_COUNTER[j] = 0; + } + if (num_children > 0) + { + fputs(" | Children:", stdout); + } + for (int j = 0; j < num_children; j++) + { + char child_report[32] = {0}; + int ret = read(CHILDREN_OUTPUTS[j], + child_report, sizeof(child_report) - 1); + if (ret <= 0) + { + perror("read"); + fprintf(stderr, + "Couldn't read from child %d, it probably died, " + "exiting!\n", j); + exit(EXIT_FAILURE); + } + printf("%6s", child_report); + } +} + +void print_usage(const char *argv0) +{ + printf("\ +\n\ +Usage:\n\ + %s [options] LASTSAW_NUM_THREADS [KEYCOUNT_NUM_THREADS [SCAN_NUM_THREADS]]\n\ +\n\ +This program creates many threads and optionally many processes stressing the\n\ +lastseen database.\n\ +\n\ +Options:\n\ + -d N: Duration of each round of testing in seconds (default is 10s)\n\ + -c N: After finishing all rounds with threads, N spawned child\n\ + processes shall apply a mixed workload to the database each one\n\ + for another round (default is 0, i.e. don't fork children)\n\ +\n", + argv0); +} + +void parse_args(int argc, char *argv[], + int *lastsaw_num_threads, int *keycount_num_threads, + int *scanlastseen_num_threads, int *num_forked_children) +{ + *lastsaw_num_threads = 0; + *keycount_num_threads = 0; + *scanlastseen_num_threads = 0; + *num_forked_children = 0; + + int i = 1; + while (i < argc && argv[i][0] == '-') + { + switch (argv[i][1]) + { + case 'd': + { + i++; + int N = 0; + int ret = sscanf((argv[i] != NULL) ? argv[i] : "", + "%d", &N); + if (ret != 1 || N <= 0) + { + print_usage(basename(argv[0])); + exit(EXIT_FAILURE); + } + + ROUND_DURATION = N; + break; + } + case 'c': + { + i++; + int N = -1; + int ret = sscanf((argv[i] != NULL) ? argv[i] : "", + "%d", &N); + if (ret != 1 || N < 0) + { + print_usage(basename(argv[0])); + exit(EXIT_FAILURE); + } + + *num_forked_children = N; + break; + } + default: + print_usage(basename(argv[0])); + exit(EXIT_FAILURE); + } + + i++; + } + + /* Last 3 arguments */ + + if (i < argc) + { + sscanf(argv[i], "%d", lastsaw_num_threads); + } + if (i + 1 < argc) + { + sscanf(argv[i + 1], "%d", keycount_num_threads); + } + if (i + 2 < argc) + { + sscanf(argv[i + 2], "%d", scanlastseen_num_threads); + } + + /* lastsaw_num_threads is the only /mandatory/ argument. */ + if (*lastsaw_num_threads <= 0 || *lastsaw_num_threads > MAX_NUM_THREADS) + { + print_usage(basename(argv[0])); + exit(EXIT_FAILURE); + } + + if (*num_forked_children > 1) /* TODO FIX! */ + { + printf("WARNING: Currently only one forked child is supported TODO FIX!\n"); + *num_forked_children = 1; + } +} + + +void tests_setup(void) +{ + LogSetGlobalLevel(LOG_LEVEL_DEBUG); + + xsnprintf(CFWORKDIR, sizeof(CFWORKDIR), + "/tmp/lastseen_threaded_load.XXXXXX"); + char *retp = mkdtemp(CFWORKDIR); + if (retp == NULL) + { + perror("mkdtemp"); + exit(EXIT_FAILURE); + } + printf("Created directory: %s\n", CFWORKDIR); + + char *envvar; + xasprintf(&envvar, "%s=%s", + "CFENGINE_TEST_OVERRIDE_WORKDIR", CFWORKDIR); + putenv(envvar); + + const char *state_dir = GetStateDir(); + printf("StateDir: %s\n", state_dir); + int ret = mkdir(state_dir, (S_IRWXU | S_IRWXG | S_IRWXO)); + if (ret != 0) + { + perror("mkdir"); + exit(EXIT_FAILURE); + } + + PPID = getpid(); /* for children to determine if parent lives */ + START_TIME = time(NULL); +} + + +int main(int argc, char *argv[]) +{ + int ret; + int lastsaw_num_threads, keycount_num_threads, scanlastseen_num_threads; + int num_forked_children; + + parse_args(argc, argv, + &lastsaw_num_threads, &keycount_num_threads, + &scanlastseen_num_threads, &num_forked_children); + TOTAL_NUM_THREADS = + lastsaw_num_threads + keycount_num_threads + scanlastseen_num_threads; + + tests_setup(); + + /* === SPAWN A CHILD PROCESS FOR LATER === */ + + pid_t child; + if (num_forked_children > 0) + { + ret = pipe(PIPE_FD); + if (ret != 0) + { + perror("pipe"); + exit(EXIT_FAILURE); + } + + child = fork(); + if (child == -1) + { + perror("fork"); + exit(EXIT_FAILURE); + } + else if (child == 0) /* child */ + { + /* We only write to the pipe. */ + close(PIPE_FD[0]); + /* Connect pipe to a FILE to talk to parent via fprintf(). */ + PARENT_INPUT = fdopen(PIPE_FD[1], "w"); + if (PARENT_INPUT == NULL) + { + perror("child fdopen"); + exit(EXIT_FAILURE); + } + + worker_process(); + exit(EXIT_SUCCESS); + } + + /* Parent: We only read from the pipe. */ + close(PIPE_FD[1]); + CHILDREN_OUTPUTS[0] = PIPE_FD[0]; + + printf("Forked child process for later, PID %ju\n", + (uintmax_t) child); + } + + + printf("Showing number of operations per second:\n\n"); + + /* === CREATE lastsaw() WORKER THREADS === */ + + spawn_worker_threads(lastsaw_worker_thread, lastsaw_num_threads, + "UpdateLastSawHost()"); + + /* === PRINT PROGRESS FOR ROUND_DURATION SECONDS === */ + + for (int i = 0; i < ROUND_DURATION; i++) + { + sleep(1); + print_progress(lastsaw_num_threads, 0, 0, 0); + putc('\n', stdout); + } + + /* === CREATE CURSOR COUNTING WORKERS === */ + + if (keycount_num_threads > 0) + { + spawn_worker_threads(keycount_worker_thread, keycount_num_threads, + "LastSeenHostKeyCount()"); + + /* === PRINT PROGRESS FOR ROUND_DURATION SECONDS === */ + for (int i = 0; i < ROUND_DURATION; i++) + { + sleep(1); + print_progress(lastsaw_num_threads, keycount_num_threads, 0, 0); + putc('\n', stdout); + } + } + + /* === CREATE CURSOR READING WORKERS === */ + + if (scanlastseen_num_threads > 0) + { + spawn_worker_threads(scanlastseen_worker_thread, scanlastseen_num_threads, + "ScanLastSeenQuality()"); + + /* === PRINT PROGRESS FOR ROUND_DURATION SECONDS === */ + for (int i = 0; i < ROUND_DURATION; i++) + { + sleep(1); + print_progress(lastsaw_num_threads, keycount_num_threads, + scanlastseen_num_threads, 0); + putc('\n', stdout); + } + } + + /* === START CHILD PROCESS WORK === */ + + if (num_forked_children > 0) + { + printf("Doing mix of operations in forked children\n"); + kill(child, SIGUSR1); + + /* === PRINT PROGRESS FOR ROUND_DURATION SECONDS === */ + + for (int i = 0; i < ROUND_DURATION; i++) + { + sleep(1); + kill(child, SIGUSR2); /* receive progress from child */ + print_progress(lastsaw_num_threads, keycount_num_threads, + scanlastseen_num_threads, 1); + putc('\n', stdout); + } + + kill(child, SIGUSR1); /* signal child to finish */ + } + + /* === TEST FINISHED, signal threads to exit === */ + + DONE = true; + + /* === WAIT AT MOST 30 SECONDS FOR EVERYBODY TO FINISH === */ + + printf("Waiting at most 30s for all threads to finish...\n"); + + unsigned long finished_children = 0; + time_t wait_starttime = time(NULL); + time_t seconds_waited = 0; + ThreadLock(&end_mtx); + while (!( FINISHED_THREADS == TOTAL_NUM_THREADS + && finished_children == num_forked_children) + && seconds_waited < 30) + { + struct timespec ts; + xclock_gettime(CLOCK_REALTIME, &ts); + + /* Wait at most 1s for the thread to signal us before looping over. */ + ts.tv_sec++; + if (FINISHED_THREADS < TOTAL_NUM_THREADS) + { + pthread_cond_timedwait(&end_cond, &end_mtx, &ts); + } + else + { + sleep(1); + } + + /* Has any child process died? */ + while (waitpid(-1, NULL, WNOHANG) > 0) + { + finished_children++; + } + + seconds_waited = time(NULL) - wait_starttime; + } + ThreadUnlock(&end_mtx); + + /* === CLEAN UP TODO register these with atexit() === */ + + int retval = EXIT_SUCCESS; + if (finished_children != num_forked_children) + { + fprintf(stderr, + "Forked child seems to be still alive, killing it!\n"); + GracefulTerminate(child, PROCESS_START_TIME_UNKNOWN); + wait(NULL); + retval = EXIT_FAILURE; + } + + if (FINISHED_THREADS != TOTAL_NUM_THREADS) + { + fprintf(stderr, "Only %lu of %lu threads actually finished!\n", + FINISHED_THREADS, TOTAL_NUM_THREADS); + retval = EXIT_FAILURE; + } + + if (retval == EXIT_SUCCESS) + { + printf("DONE!\n\n"); + } + + char *cmd; + xasprintf(&cmd, "rm -rf '%s'", CFWORKDIR); + system(cmd); + free(cmd); + + return retval; +} diff --git a/tests/load/run_db_load.sh b/tests/load/run_db_load.sh new file mode 100755 index 0000000000..cd7cb5be46 --- /dev/null +++ b/tests/load/run_db_load.sh @@ -0,0 +1,7 @@ +#!/bin/sh -e +echo "Starting run_db_load.sh test" +#for threads in 1 5 10 50; do +while false; do + echo db_load $threads + ./db_load $threads +done diff --git a/tests/load/run_lastseen_threaded_load.sh b/tests/load/run_lastseen_threaded_load.sh new file mode 100755 index 0000000000..aebcf21a91 --- /dev/null +++ b/tests/load/run_lastseen_threaded_load.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +if [ "x$label" = "xPACKAGES_x86_64_solaris_10" ] ; +then + echo "Skipping lastseen_threaded_load on $label" + exit 0; +fi + +echo "Starting run_lastseen_threaded_load.sh test" + +./lastseen_threaded_load -c 1 4 1 1 diff --git a/tests/static-check/Makefile.am b/tests/static-check/Makefile.am new file mode 100644 index 0000000000..c0da54a561 --- /dev/null +++ b/tests/static-check/Makefile.am @@ -0,0 +1,25 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +DISTFILES = cppcheck_suppressions.txt run_checks.sh run.sh Makefile.in Makefile.am diff --git a/tests/static-check/cppcheck_suppressions.txt b/tests/static-check/cppcheck_suppressions.txt new file mode 100644 index 0000000000..6094575fec --- /dev/null +++ b/tests/static-check/cppcheck_suppressions.txt @@ -0,0 +1,25 @@ +// suppress warnings for access to individual bytes of a uint32 in platform.h +objectIndex:libntech/libutils/platform.h + +// cppcheck is not clever enough to see that if (i >= PLATFORM_CONTEXT_MAX) then 'found' is false +arrayIndexOutOfBounds:libenv/sysinfo.c:587 + +// 'psin' is assigned to 'ai->ai_addr' and 'ai' is returned to the caller +memleak:libntech/libcompat/getaddrinfo.c:153 + +// cppcheck doesn't understand va_copy() properly +va_list_usedBeforeStarted:libntech/libcompat/snprintf.c:1505 +va_list_usedBeforeStarted:libntech/libcompat/snprintf.c:1506 + +// too cryptic code for cppcheck to see that the 'tmp' variable is initialized +// through a pointer to the same address space +uninitvar:libntech/libcompat/inet_pton.c:115 + +// cppcheck completely confused by our macro-based enterprise stubs +returnDanglingLifetime:libpromises/enterprise_stubs.c:60 +returnDanglingLifetime:libpromises/enterprise_stubs.c:121 +returnDanglingLifetime:libpromises/enterprise_stubs.c:128 +returnDanglingLifetime:libpromises/enterprise_stubs.c:153 +returnDanglingLifetime:libpromises/enterprise_stubs.c:159 +returnDanglingLifetime:libpromises/enterprise_stubs.c:165 +returnDanglingLifetime:libpromises/enterprise_stubs.c:172 diff --git a/tests/static-check/run.sh b/tests/static-check/run.sh new file mode 100755 index 0000000000..a7159048fb --- /dev/null +++ b/tests/static-check/run.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Note that this container build requires about 700MB minimum RAM for dnf to operate +# use debian-12+ or rhel-8+, debian-11 buildah seems to fail setting up networking/dns for the container so dnf doesn't work (CFE-4295) + +set -eE # include E so that create_image() failures bubble up to the surface +trap "echo FAILURE" ERR + +if [ -z "$STATIC_CHECKS_FEDORA_VERSION" ]; then + default_f_ver="40" + echo "No Fedora version for static checks specified, using the default (Fedora $default_f_ver)" + BASE_IMG="fedora:$default_f_ver" + STATIC_CHECKS_FEDORA_VERSION="$default_f_ver" +else + BASE_IMG="fedora:$STATIC_CHECKS_FEDORA_VERSION" +fi + +function create_image() { + local c=$(buildah from -q $BASE_IMG) + buildah run $c -- dnf -q -y install "@C Development Tools and Libraries" clang cppcheck which >/dev/null 2>&1 + buildah run $c -- dnf -q -y install pcre-devel pcre2-devel openssl-devel libxml2-devel pam-devel lmdb-devel libacl-devel libyaml-devel curl-devel libvirt-devel >/dev/null 2>&1 + buildah run $c -- dnf clean all >/dev/null 2>&1 + buildah commit $c cfengine-static-checker-f$STATIC_CHECKS_FEDORA_VERSION >/dev/null 2>&1 + echo $c +} + +set -x + +# TODO: check how old the image is and recreate if it's too old +if buildah inspect cfengine-static-checker-f$STATIC_CHECKS_FEDORA_VERSION >/dev/null 2>&1; then + c=$(buildah from cfengine-static-checker-f$STATIC_CHECKS_FEDORA_VERSION) +else + c=$(create_image) +fi +trap "buildah rm $c >/dev/null" EXIT + +buildah copy $c "$(dirname $0)/../../" /tmp/core/ >/dev/null 2>&1 +buildah run $c /tmp/core/tests/static-check/run_checks.sh diff --git a/tests/static-check/run_checks.sh b/tests/static-check/run_checks.sh new file mode 100755 index 0000000000..cae449f930 --- /dev/null +++ b/tests/static-check/run_checks.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -x + +n_procs="$(getconf _NPROCESSORS_ONLN)" + +function check_with_gcc() { + rm -f config.cache + make clean + ./configure -C --enable-debug CC=gcc + local gcc_exceptions="-Wno-sign-compare -Wno-enum-int-mismatch" + make -j -l${n_procs} --keep-going CFLAGS="-Werror -Wall -Wextra $gcc_exceptions" +} + +function check_with_clang() { + rm -f config.cache + make clean + ./configure -C --enable-debug CC=clang + make -j -l${n_procs} --keep-going CFLAGS="-Werror -Wall -Wextra -Wno-sign-compare" +} + +function check_with_cppcheck() { + rm -f config.cache + ./configure -C --enable-debug + + # cppcheck options: + # -I -- include paths + # -i -- ignored files/folders + # --include= -- force including a file, e.g. config.h + # Identified issues are printed to stderr + cppcheck --quiet -j${n_procs} --error-exitcode=1 ./ \ + --suppressions-list=tests/static-check/cppcheck_suppressions.txt \ + --include=config.h \ + -I cf-serverd/ -I libpromises/ -I libcfnet/ -I libntech/libutils/ \ + -i 3rdparty -i .github/codeql -i libntech/.lgtm -i tests -i libpromises/cf3lex.c \ + 2>&1 1>/dev/null +} + +cd "$(dirname $0)"/../../ + +failure=0 +failures="" +check_with_gcc || { failures="${failures}FAIL: GCC check failed\n"; failure=1; } +check_with_clang || { failures="${failures}FAIL: Clang check failed\n"; failure=1; } +check_with_cppcheck || { failures="${failures}FAIL: cppcheck failed\n"; failure=1; } + +echo -en "$failures" +exit $failure diff --git a/tests/stress/american_psycho.cf b/tests/stress/american_psycho.cf new file mode 100644 index 0000000000..7f83abc518 --- /dev/null +++ b/tests/stress/american_psycho.cf @@ -0,0 +1,227 @@ +# It might be interesting to run some of these with action => background to test parralism +# might be good to have a test to create large files (over 100M) and very large files (over 1G) and copy_from on those, especially when using compare digest, encrypt and verify + +bundle agent stress +{ + + vars: + "large_number" + string => "1834", + comment => "values larger than this generate a segfault in execresult for seq"; + + methods: + + STRESS_DEFINE_CLASSES:: + "define_classes" + usebundle => define_classes; + + STRESS_COMMANDS_ECHO:: + "commands_echo" + usebundle => commands_echo; + + STRESS_DEFINE_VARIABLES:: + "define_variables" + usebundle => define_variables; + + STRESS_EXECRESULT_ECHO_NOSHELL:: + "execresult_echo_noshell" + usebundle => execresult_echo_noshell; + + STRESS_EXECRESULT_ECHO_USESHELL:: + "execresult_echo_useshell" + usebundle => execresult_echo_useshell; + + STRESS_DEFINE_PERSISTENT_CLASSES:: + "define_persistent_classes" + usebundle => define_persistent_classes; + + STRESS_NESTED_USEBUNDLE:: + "nested_usebundle" + usebundle => nested_usebundle; + + STRESS_COPY_FROM_INIT:: + "copy_from_init" + usebundle => copy_from_init; + + STRESS_COPY_MANY_FILES_TO_MANY_FILES:: + "copy_many_files_to_many_files" + usebundle => copy_many_files_to_many_files; +} + +bundle common knowledge +{ + vars: + "seq" string => "/usr/bin/seq"; + "echo" string => "/bin/echo"; + "bc" string => "/usr/bin/bc -l"; + + "range" string => execresult("$(knowledge.seq) $(stress.large_number)", "noshell"); + "range_list" slist => splitstring("$(range)", $(const.n), "$(stress.large_number)"); +} + +bundle agent define_classes +{ + classes: + "$(this.bundle)_class_$(knowledge.range_list)" expression => "any"; + + reports: + DEBUG:: + "$(this.bundle): Defined $(this.bundle)_class_$(knowledge.range_list)"; +} + +bundle agent commands_echo +{ + commands: + "$(knowledge.echo)" + args => "$(this.bundle): $(knowledge.range_list)"; + + reports: + DEBUG:: + "$(this.bundle) Called $(knowledge.echo) $(this.bundle): $(knowledge.range_list)"; + +} + +bundle agent define_variables +{ + vars: + "var_$(knowledge.range_list)" string => "var_$(knowledge.range_list)"; + + reports: + DEBUG:: + "$(this.bundle) Defined var_$(knowledge.range_list)"; +} + +bundle agent execresult_echo_noshell +{ + vars: + "var_$(knowledge.range_list)" + string => execresult("$(knowledge.echo) $(this.bundle): $(knowledge.range_list)", "noshell"); + + reports: + DEBUG:: + "$(this.bundle) Defined var_$(knowledge.echo) with value $(var_$(knowledge.range_list))"; +} + +bundle agent execresult_echo_useshell +{ + vars: + "var_$(knowledge.range_list)" + string => execresult("$(knowledge.echo) $(this.bundle): $(knowledge.range_list)", "useshell"); + + reports: + DEBUG:: + "$(this.bundle) Defined var_$(knowledge.echo) with value $(var_$(knowledge.range_list))"; +} + +bundle agent define_persistent_classes +{ + classes: + "$(this.bundle)_class_$(knowledge.range_list)" + expression => "any", + classes => persist("persistent_$(this.promiser)", "5"); + + reports: + DEBUG:: + "$(this.bundle): Defined persistent_$(this.bundle)_class_$(knowledge.range_list)"; + +} + +bundle agent nested_usebundle +{ + methods: + "call myself" + usebundle => nested_usebundle_param("$(knowledge.range_list)"); +} + +bundle agent nested_usebundle_param(number) +# If the divisor is changed to 1 (effectively not reducing the initial number +# called with this bundle call will generate a segfault relatively quickly +{ + vars: + "half" string => execresult("$(knowledge.echo) $(number)/1.2 | $(knowledge.bc)", "useshell"); + + methods: + "call myself inderectly" + usebundle => nested_usebundle_param($(half)); + + reports: + "$(knowledge.echo) $(number)/1.2 | $(knowledge.bc)"; + "$(this.bundle): '$(knowledge.echo) $(number)/1.2 | $(knowledge.bc)' = $(half)" + action => immediate; +} + +bundle agent copy_from_init +{ + vars: + "copy_from_source_path" string => "/var/cfengine/masterfiles/"; + "copy_from_single_file" string => "$(copy_from_source_path)/promises.cf"; + + + files: + "$(copy_from_source_path)/$(knowledge.range_list).txt" + copy_from => local_cp("$(copy_from_single_file)"); + + "/tmp/$(knowledge.range_list)" + delete => tidy; + +} + +bundle agent copy_many_files_to_many_files +{ + files: + "/tmp/$(knowledge.range_list)" + copy_from => secure_cp("$(copy_from_init.copy_from_source_path)/$(knowledge.range_list).txt", $(sys.uqhost)), + classes => classes_generic("$(this.bundle)_tmp_$(knowledge.range_list)"); + # if you use 'generic' as the classes body here the agent aborts, need acceptance test. + + reports: + DEBUG:: + "$(this.bundle): copied $(copy_from_init.copy_from_source_path)/$(knowledge.range_list).txt to /tmp/$(knowledge.range_list)" + if => "$(this.bundle)_tmp_$(knowledge.range_list)_repaired"; +} + +body classes classes_generic(x) +# Define x prefixed/suffixed with promise outcome +{ + promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" }; + repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" }; + repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" }; + repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_not_kept", "$(x)_not_repaired", "$(x)_reached" }; + promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_not_repaired", "$(x)_reached" }; +} + +body copy_from local_cp(from) +{ +source => "$(from)"; +} + +body delete tidy +{ +dirlinks => "delete"; +rmdirs => "true"; +} + +body copy_from secure_cp(from,server) +{ +source => "$(from)"; +servers => { "$(server)" }; +compare => "digest"; +encrypt => "true"; +verify => "true"; +} + + + +body action immediate +{ +ifelapsed => "0"; +} + + +body classes persist(class, minutes) +{ + promise_kept => { @(class) }; + promise_repaired => { @(class) }; + persist_time => "$(minutes)"; +} + diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am new file mode 100644 index 0000000000..26c3b4c28c --- /dev/null +++ b/tests/unit/Makefile.am @@ -0,0 +1,428 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +# Just recursively include in dist tarball all data we need for unit tests +EXTRA_DIST = data + +AM_CPPFLAGS = $(CORE_CPPFLAGS) \ + $(ENTERPRISE_CPPFLAGS) \ + -I$(srcdir)/../../libcfecompat \ + -I$(srcdir)/../../libcfnet \ + -I$(srcdir)/../../libenv \ + -I$(srcdir)/../../libpromises \ + -I$(srcdir)/../../libntech/libutils \ + -I$(srcdir)/../../cf-monitord \ + -I$(srcdir)/../../cf-serverd \ + -I$(srcdir)/../../cf-agent \ + -I$(srcdir)/../../cf-execd \ + -I$(srcdir)/../../cf-key \ + -I$(srcdir)/../../cf-check \ + -DTESTDATADIR='"$(srcdir)/data"' + +LDADD = ../../libpromises/libpromises.la libtest.la + +# automake does not support "maude_LIBS" variables. We can only alter +# the generic LIBS one. In case the functions are mocked in the test +# implementation, then we are pretty sure that they will be overriden by +# our local implementation. So we include *everything*... +LIBS = $(CORE_LIBS) +AM_LDFLAGS = $(CORE_LDFLAGS) + +AM_CFLAGS = $(PTHREAD_CFLAGS) + + +check_LTLIBRARIES = libtest.la +libtest_la_SOURCES = cmockery.c cmockery.h schema.h test.c test.h \ + ../../libntech/libutils/alloc.c \ + ../../libpromises/patches.c \ + ../../libpromises/constants.c \ + ../../libntech/libutils/known_dirs.c \ + ../../libntech/libutils/map.c \ + ../../libntech/libutils/array_map.c \ + ../../libntech/libutils/hash.c \ + ../../libntech/libutils/hash_map.c + +libtest_la_LIBADD = ../../libntech/libcompat/libcompat.la + +check_LTLIBRARIES += libstr.la +libstr_la_SOURCES = \ + ../../libntech/libutils/buffer.c \ + ../../libntech/libutils/encode.c \ + ../../libntech/libutils/logging.c \ + ../../libntech/libutils/misc_lib.c \ + ../../libntech/libutils/regex.c \ + ../../libntech/libutils/sequence.c \ + ../../libntech/libutils/string_lib.c \ + ../../libntech/libutils/writer.c +libstr_la_LIBADD = libtest.la + + +check_LTLIBRARIES += libdb.la +libdb_la_SOURCES = db_stubs.c \ + ../../libpromises/dbm_api.c \ + ../../libpromises/dbm_quick.c \ + ../../libpromises/dbm_tokyocab.c \ + ../../libpromises/dbm_lmdb.c \ + ../../libpromises/dbm_migration.c \ + ../../libpromises/dbm_migration_lastseen.c \ + ../../libpromises/global_mutex.c \ + ../../cf-check/backup.c \ + ../../cf-check/diagnose.c \ + ../../cf-check/lmdump.c \ + ../../cf-check/repair.c \ + ../../cf-check/replicate_lmdb.c \ + ../../cf-check/utilities.c \ + ../../cf-check/validate.c \ + ../../libntech/libutils/logging.c \ + ../../libntech/libutils/mutex.c \ + ../../libntech/libutils/cleanup.c +if HPUX +libdb_la_SOURCES += ../../libpromises/cf3globals.c +endif +libdb_la_LIBADD = libstr.la ../../libntech/libutils/libutils.la +#libdb_la_CPPFLAGS = $(LMDB_CPPFLAGS) $(TOKYOCABINET_CPPFLAGS) $(QDBM_CPPFLAGS) +# Make sure that source files are compiled to separate object files +# libdb_la-file.o +libdb_la_CFLAGS = $(AM_CFLAGS) + +check_PROGRAMS = \ + arg_split_test \ + assoc_test \ + getopt_test \ + item_test \ + rlist_test \ + domainname_test \ + set_domainname_test \ + evalfunction_test \ + eval_context_test \ + regex_test \ + lastseen_test \ + lastseen_migration_test \ + changes_migration_test \ + db_test \ + db_concurrent_test \ + item_lib_test \ + crypto_symmetric_test \ + persistent_lock_test \ + package_versions_compare_test \ + files_lib_test \ + files_copy_test \ + parsemode_test \ + parser_test \ + passopenfile_test \ + policy_test \ + sort_test \ + file_name_test \ + logging_test \ + granules_test \ + scope_test \ + conversion_test \ + files_interfaces_test \ + connection_management_test \ + expand_test \ + string_expressions_test \ + var_expressions_test \ + process_terminate_unix_test \ + process_test \ + exec-config-test \ + generic_agent_test \ + syntax_test \ + sysinfo_test \ + variable_test \ + verify_databases_test \ + protocol_test \ + mon_cpu_test \ + mon_load_test \ + mon_processes_test \ + mustache_test \ + class_test \ + key_test \ + cf_upgrade_test \ + matching_test \ + strlist_test \ + addr_lib_test \ + policy_server_test \ + split_process_line_test \ + new_packages_promise_test \ + iteration_test + +if HAVE_AVAHI_CLIENT +if HAVE_AVAHI_COMMON +check_PROGRAMS += \ + findhub_test \ + avahi_config_test +endif +endif + +if !NT +check_PROGRAMS += redirection_test +noinst_PROGRAMS = redirection_test_stub + +redirection_test_stub_SOURCES = redirection_test_stub.c +endif + +check_SCRIPTS = dynamic_dependency_test.sh \ + tar_portability_test.sh + +if WITH_INIT_D_SCRIPT +check_SCRIPTS += init_script_test.sh +endif + +EXTRA_DIST += $(check_SCRIPTS) + +TESTS = $(check_PROGRAMS) $(check_SCRIPTS) + +if MACOSX +XFAIL_TESTS = set_domainname_test +XFAIL_TESTS += process_test +XFAIL_TESTS += mon_processes_test +endif + +if HPUX +XFAIL_TESTS = mon_load_test # Redmine #3569 +endif + +# +# OS X uses real system calls instead of our stubs unless this option is used +# +TESTS_ENVIRONMENT = DYLD_FORCE_FLAT_NAMESPACE=yes + +conversion_test_SOURCES = conversion_test.c ../../libpromises/conversion.c + +if !BUILTIN_EXTENSIONS +check_PROGRAMS += enterprise_extension_test + +enterprise_extension_test_SOURCES = enterprise_extension_test.c + +check_LTLIBRARIES += cfengine-enterprise.la +cfengine_enterprise_la_SOURCES = enterprise_extension_test_lib.c +cfengine_enterprise_la_LDFLAGS = $(AM_LDFLAGS) \ + -avoid-version -module -shared -export-dynamic -rpath / +EXTRA_enterprise_extension_test_DEPENDENCIES = cfengine-enterprise.la +endif + +set_domainname_test_SOURCES = set_domainname_test.c +set_domainname_test_LDADD = libstr.la ../../libpromises/libpromises.la + +str_test_SOURCES = str_test.c +str_test_LDADD = libstr.la + +regex_test_SOURCES = regex_test.c ../../libpromises/match_scope.c + +protocol_test_SOURCES = protocol_test.c \ + ../../cf-serverd/server_common.c \ + ../../cf-serverd/server_tls.c \ + ../../cf-serverd/server.c \ + ../../cf-serverd/cf-serverd-enterprise-stubs.c \ + ../../cf-serverd/server_transform.c \ + ../../cf-serverd/cf-serverd-functions.c \ + ../../cf-serverd/server_access.c \ + ../../cf-serverd/strlist.c +protocol_test_LDADD = ../../libpromises/libpromises.la libtest.la + +if HAVE_AVAHI_CLIENT +if HAVE_AVAHI_COMMON + +findhub_test_SOURCES = findhub_test.c \ + ../../cf-agent/findhub.c \ + ../../cf-agent/load_avahi.c + +avahi_config_test_SOURCES = avahi_config_test.c \ + ../../cf-serverd/server_common.c \ + ../../cf-serverd/server_tls.c \ + ../../cf-serverd/server.c \ + ../../cf-serverd/server_transform.c \ + ../../cf-serverd/cf-serverd-enterprise-stubs.c \ + ../../cf-serverd/server_access.c \ + ../../cf-serverd/server_classic.c \ + ../../cf-serverd/strlist.c +avahi_config_test_LDADD = ../../libpromises/libpromises.la libtest.la + +endif +endif + +db_test_SOURCES = db_test.c +db_test_LDADD = libtest.la ../../libpromises/libpromises.la + +db_concurrent_test_SOURCES = db_concurrent_test.c +#db_concurrent_test_CPPFLAGS = $(libdb_la_CPPFLAGS) +db_concurrent_test_LDADD = libdb.la + +lastseen_test_SOURCES = lastseen_test.c \ + ../../libpromises/item_lib.c \ + ../../libpromises/lastseen.c \ + ../../libntech/libutils/statistics.c +#lastseen_test_CPPFLAGS = $(libdb_la_CPPFLAGS) +lastseen_test_LDADD = libdb.la ../../libpromises/libpromises.la + +lastseen_migration_test_SOURCES = lastseen_migration_test.c \ + ../../libpromises/lastseen.c \ + ../../libntech/libutils/statistics.c \ + ../../libpromises/item_lib.c +#lastseen_migration_test_CPPFLAGS = $(libdb_la_CPPFLAGS) +lastseen_migration_test_LDADD = libdb.la ../../libpromises/libpromises.la + +CLEANFILES = *.gcno *.gcda cfengine-enterprise.so + +package_versions_compare_test_SOURCES = package_versions_compare_test.c \ + ../../cf-agent/package_module.c \ + ../../cf-agent/verify_packages.c \ + ../../cf-agent/verify_new_packages.c \ + ../../cf-agent/vercmp.c \ + ../../cf-agent/vercmp_internal.c \ + ../../cf-agent/retcode.c \ + ../../libpromises/match_scope.c + +#package_versions_compare_test_CPPFLAGS = $(AM_CPPFLAGS) +package_versions_compare_test_LDADD = ../../libpromises/libpromises.la \ + libtest.la + +files_copy_test_SOURCES = files_copy_test.c +files_copy_test_LDADD = libtest.la ../../libpromises/libpromises.la + +sort_test_SOURCES = sort_test.c +sort_test_LDADD = libtest.la ../../libpromises/libpromises.la + +logging_test_SOURCES = logging_test.c ../../libpromises/syslog_client.c +logging_test_LDADD = libtest.la ../../libntech/libutils/libutils.la + +connection_management_test_SOURCES = connection_management_test.c \ + ../../cf-serverd/server_common.c \ + ../../cf-serverd/server_tls.c +connection_management_test_LDADD = ../../libpromises/libpromises.la \ + libtest.la \ + ../../cf-serverd/libcf-serverd.la + +rlist_test_SOURCES = rlist_test.c \ + ../../libpromises/rlist.c +rlist_test_LDADD = libtest.la ../../libpromises/libpromises.la +# Workaround for object file basename conflicts, search the web for +# "automake created with both libtool and without" +rlist_test_CPPFLAGS = $(AM_CPPFLAGS) + +process_test_LDADD = libtest.la ../../libpromises/libpromises.la + +if LINUX + +check_PROGRAMS += linux_process_test + +linux_process_test_SOURCES = linux_process_test.c \ + ../../libpromises/process_unix.c \ + ../../libpromises/process_linux.c \ + ../../libntech/libutils/file_lib.c +linux_process_test_LDADD = libtest.la ../../libntech/libutils/libutils.la + +endif + +if AIX + +check_PROGRAMS += aix_process_test +# We need to use -Wl,-bexpall when linking tests binaries on AIX +# because they provide dummy versions of some standard functions. +set_domainname_test_LDFLAGS = -Wl,-bexpall +evalfunction_test_LDFLAGS = -Wl,-bexpall +aix_process_test_SOURCES = aix_process_test.c \ + ../../libpromises/process_unix.c \ + ../../libpromises/process_aix.c \ + ../../libntech/libutils/file_lib.c +aix_process_test_LDADD = libtest.la ../../libntech/libutils/libutils.la + +endif + +if SOLARIS + +check_PROGRAMS += solaris_process_test + +solaris_process_test_SOURCES = solaris_process_test.c \ + ../../libpromises/process_unix.c \ + ../../libpromises/process_solaris.c \ + ../../libntech/libutils/file_lib.c +solaris_process_test_LDADD = libtest.la ../../libntech/libutils/libutils.la + +endif + +process_terminate_unix_test_SOURCES = process_terminate_unix_test.c \ + ../../libpromises/process_unix.c +process_terminate_unix_test_LDADD = libtest.la ../../libntech/libutils/libutils.la + +exec_config_test_SOURCES = exec-config-test.c \ + ../../cf-execd/exec-config.c ../../cf-execd/execd-config.c +exec_config_test_LDADD = libtest.la ../../libpromises/libpromises.la + +sysinfo_test_LDADD = libtest.la \ + ../../libenv/libenv.la \ + ../../libpromises/libpromises.la + +mon_cpu_test_SOURCES = mon_cpu_test.c \ + ../../cf-monitord/mon.h \ + ../../cf-monitord/mon_cpu.c +mon_cpu_test_LDADD = ../../libpromises/libpromises.la libtest.la + +mon_load_test_SOURCES = mon_load_test.c \ + ../../cf-monitord/mon.h \ + ../../cf-monitord/mon_load.c +mon_load_test_LDADD = ../../libpromises/libpromises.la libtest.la + +mon_processes_test_SOURCES = mon_processes_test.c \ + ../../cf-monitord/mon.h \ + ../../cf-monitord/mon_processes.c +mon_processes_test_LDADD = ../../libpromises/libpromises.la libtest.la + +key_test_SOURCES = key_test.c +key_test_LDADD = ../../libpromises/libpromises.la \ + ../../libntech/libutils/libutils.la \ + libtest.la + +strlist_test_SOURCES = strlist_test.c \ + ../../cf-serverd/strlist.c \ + ../../cf-serverd/strlist.h + +verify_databases_test_LDADD = ../../cf-agent/libcf-agent.la libtest.la + +iteration_test_SOURCES = iteration_test.c + +cf_upgrade_test_SOURCES = cf_upgrade_test.c \ + $(top_srcdir)/cf-upgrade/alloc-mini.c \ + $(top_srcdir)/cf-upgrade/alloc-mini.h \ + $(top_srcdir)/cf-upgrade/command_line.c \ + $(top_srcdir)/cf-upgrade/command_line.h \ + $(top_srcdir)/cf-upgrade/configuration.c \ + $(top_srcdir)/cf-upgrade/configuration.h \ + $(top_srcdir)/cf-upgrade/log.c $(top_srcdir)/cf-upgrade/log.h \ + $(top_srcdir)/cf-upgrade/process.c $(top_srcdir)/cf-upgrade/process.h \ + $(top_srcdir)/cf-upgrade/update.c \ + $(top_srcdir)/cf-upgrade/update.h +cf_upgrade_test_CPPFLAGS = -I$(top_srcdir)/cf-upgrade -I$(top_srcdir)/libntech/libutils -I$(top_srcdir) +cf_upgrade_test_LDADD = libtest.la ../../libntech/libcompat/libcompat.la + +if !NT +check_PROGRAMS += nfs_test +nfs_test_SOURCES = nfs_test.c +nfs_test_LDADD = ../../libpromises/libpromises.la libtest.la + +init_script_test_helper_SOURCES = init_script_test_helper.c +init_script_test.sh: init_script_test_helper +CLEANFILES += init_script_test_helper +endif +EXTRA_DIST += init_script_test_helper.c +EXTRA_DIST += init_script_test.sh diff --git a/tests/unit/README b/tests/unit/README new file mode 100644 index 0000000000..3a5c2cad4d --- /dev/null +++ b/tests/unit/README @@ -0,0 +1,95 @@ +How to add unit tests +===================== + +1. Make sure what you are trying to write is actually a unit test. A unit test + should execute in milliseconds. If you are testing functionality that e.g. requires + a generic agent to be set up, consider writing an acceptance test. + A unit test should test a small piece of code (a function) in isolation. + +2. You typically want to test some function of a datastructure, e.g. CfAssoc. + Check to see if a test suite (e.g. assoc_test.c), exists already. If not, create + one. + + 2.1 (Optional) Creating a new test suite + + Create a new file, e.g. mystruct_test.c, preferably by copying some existing + file so you get the boilerplate. + + In Makefile.am, append mystruct_test to check_PROGRAMS, and add an automake + entry such as + + mystruct_test_SOURCES = $(MOCKERY_SOURCES) mystruct_test.c + mystruct_test_LDADD = ../../libpromises/libpromises.la + + +3. We are using cmockery as our testing framework. Google for it and read/skim + their basic intro page. + +4. Suppose you want to test your new ToString function for CfAssoc. In assoc_test.c, + you could do the following. + + 4.1 Write the test + + static test_to_string(void **state) + { + /* assert some condition here */ + } + + 4.2 In the main function of assoc_test.c, add the test to the suite + + int main() + { + const UnitTest tests[] = + { + unit_test(test_create_destroy), + unit_test(test_copy), + unit_test(test_to_string) + }; + + return run_tests(tests); + } + + +5. Mocking. Suppose you want to test some function that calls a database, but you + don't want to deal with setting up and managing the state of the database. + Furthermore, you don't actually want to test the database in this test. + + What you need to do is to stub out the function. For example, suppose your tested + function calls some DB_Insert("host123", 42); + + A mock function is a dummy replacement function with NOOP functionality, so in this + case, in your test suite, you could add a + + static int written_measure = -1; + + static void DB_Insert(const char* key, int measure) + { + written_measure = measure; + } + + Then, if later your function tries to retrieve it back, you could do + + static int DB_Query(const char*key) + { + return written_measure; + } + + Now, the key is to not have the test link towards the actual definition of + the function, so in Makefile.am, rather than linking against all of libpromises, + you probably want to be more specific, for example + + str_test_SOURCES = $(MOCKERY_SOURCES) str_test.c ../../libpromises/string_lib.c + str_test_LDADD = ../../libcompat/libcompat.la + + Finally, if you made some mocking functions and you think it will be useful to + other tests later, consider extracting them in a separate file, e.g. db_mock.c. + +6. Memory checking. We don't really care about the quality of code in the unit tests, + but we do care that the code tested is not leaking memory. So it's important + to free everything you allocate in the test code. Then, to check to see if your + code leaks, you can run + + valgrind --leak-check=yes .libs/lt-mystruct_test + + Valgrind can do stuff beyond simple leak checking, so learning about it could + be a worthwhile investment. diff --git a/tests/unit/addr_lib_test.c b/tests/unit/addr_lib_test.c new file mode 100644 index 0000000000..f6785c4014 --- /dev/null +++ b/tests/unit/addr_lib_test.c @@ -0,0 +1,156 @@ +#include + +#include +#include + + +static void test_ParseHostPort() +{ + char *hostname, *port; + + char test_string[64]; + + // Domain name: + ParseHostPort(strcpy(test_string, "www.cfengine.com"), &hostname, &port); + assert_string_equal(hostname, "www.cfengine.com"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "www.cfengine.com:80"), &hostname, &port); + assert_string_equal(hostname, "www.cfengine.com"); + assert_string_equal(port, "80"); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "www.cfengine.com:"), &hostname, &port); + assert_string_equal(hostname, "www.cfengine.com"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "localhost"), &hostname, &port); + assert_string_equal(hostname, "localhost"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "localhost:"), &hostname, &port); + assert_string_equal(hostname, "localhost"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "localhost:80"), &hostname, &port); + assert_string_equal(hostname, "localhost"); + assert_string_equal(port, "80"); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[localhost]"), &hostname, &port); + assert_string_equal(hostname, "localhost"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[localhost]:"), &hostname, &port); + assert_string_equal(hostname, "localhost"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[localhost]:80"), &hostname, &port); + assert_string_equal(hostname, "localhost"); + assert_string_equal(port, "80"); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[www.cfengine.com]"), &hostname, &port); + assert_string_equal(hostname, "www.cfengine.com"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[www.cfengine.com]:80"), &hostname, &port); + assert_string_equal(hostname, "www.cfengine.com"); + assert_string_equal(port, "80"); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[www.cfengine.com]:"), &hostname, &port); + assert_string_equal(hostname, "www.cfengine.com"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + // IPv4: + ParseHostPort(strcpy(test_string, "1.2.3.4"), &hostname, &port); + assert_string_equal(hostname, "1.2.3.4"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "1.2.3.4:80"), &hostname, &port); + assert_string_equal(hostname, "1.2.3.4"); + assert_string_equal(port, "80"); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "1.2.3.4:"), &hostname, &port); + assert_string_equal(hostname, "1.2.3.4"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + // IPv6 with square brackets: + ParseHostPort(strcpy(test_string, "[ffff::dd:12:34]"), &hostname, &port); + assert_string_equal(hostname, "ffff::dd:12:34"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[ffff::dd:12:34]:80"), &hostname, &port); + assert_string_equal(hostname, "ffff::dd:12:34"); + assert_string_equal(port, "80"); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[ffff::dd:12:34]:"), &hostname, &port); + assert_string_equal(hostname, "ffff::dd:12:34"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + // IPv6 without square brackets: + ParseHostPort(strcpy(test_string, "ffff::dd:12:34"), &hostname, &port); + assert_string_equal(hostname, "ffff::dd:12:34"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + // IPv4 mapped IPv6 addresses: + ParseHostPort(strcpy(test_string, "::ffff:192.0.2.128"), &hostname, &port); + assert_string_equal(hostname, "::ffff:192.0.2.128"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[::ffff:192.0.2.128]"), &hostname, &port); + assert_string_equal(hostname, "::ffff:192.0.2.128"); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + /***** CORNER CASES *****/ + + ParseHostPort(strcpy(test_string, ""), &hostname, &port); + assert_int_equal(hostname, NULL); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[]"), &hostname, &port); + assert_int_equal(hostname, NULL); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, "[]:"), &hostname, &port); + assert_int_equal(hostname, NULL); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; + + ParseHostPort(strcpy(test_string, ":"), &hostname, &port); + assert_int_equal(hostname, NULL); + assert_int_equal(port, NULL); + hostname = NULL; port = NULL; +} + + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_ParseHostPort) + }; + + return run_tests(tests); +} diff --git a/tests/unit/aix_process_test.c b/tests/unit/aix_process_test.c new file mode 100644 index 0000000000..cf489d3398 --- /dev/null +++ b/tests/unit/aix_process_test.c @@ -0,0 +1,144 @@ +#include + +#include +#include +#include + +#include + +/* + * AIX 5.3 is missing this declaration + */ +#ifndef HAVE_GETPROCS64 +int getprocs64(void *procsinfo, int sizproc, void *fdsinfo, int sizfd, pid_t *index, int count); +#endif + +int getprocs64(void *procsinfo, int process_size, void *fdsinfo, int sizfd, pid_t *index, int count) +{ + assert_int_equal(count, 1); + assert_true(fdsinfo == NULL); + + struct procentry64* pe = procsinfo; + + switch (*index) + { + /* Normal process, running, started 1 jan 2000 00:00:00 */ + case 1: + memset(pe, 0, sizeof(struct procentry64)); + pe->pi_pid = 1; + pe->pi_start = 946681200; + pe->pi_state = SACTIVE; + *index = 2; + return 1; + + /* Normal process, stopped, started 31 dec 1980 23:59:59 */ + case 2: + memset(pe, 0, sizeof(struct procentry64)); + pe->pi_pid = 2; + pe->pi_start = 347151599; + pe->pi_state = SSTOP; + *index = 3; + return 1; + + /* Permission denied, getprocs64 returns EINVAL */ + case 666: + errno = EINVAL; + return -1; + + /* Non-existing process, getprocs64 returns another process' info */ + case 1000: + memset(pe, 0, sizeof(struct procentry64)); + pe->pi_pid = 1001; + pe->pi_start = 312312313; + pe->pi_state = SACTIVE; + *index = 1002; + return 1; + + /* Non-existing process, table sentinel. getprocs64 return 0 */ + case 1000000: + return 0; + } +} + +static void test_get_start_time_process1(void) +{ + time_t t = GetProcessStartTime(1); + assert_int_equal(t, 946681200); +} + + +static void test_get_start_time_process2(void) +{ + time_t t2 = GetProcessStartTime(2); + assert_int_equal(t2, 347151599); +} + +static void test_get_start_time_process666(void) +{ + time_t t = GetProcessStartTime(666); + assert_int_equal(t, PROCESS_START_TIME_UNKNOWN); +} + +static void test_get_start_time_process1000(void) +{ + time_t t = GetProcessStartTime(1000); + assert_int_equal(t, PROCESS_START_TIME_UNKNOWN); +} + +static void test_get_start_time_process1000000(void) +{ + time_t t = GetProcessStartTime(1000000); + assert_int_equal(t, PROCESS_START_TIME_UNKNOWN); +} + +static void test_get_state_process1(void) +{ + ProcessState s = GetProcessState(1); + assert_int_equal(s, PROCESS_STATE_RUNNING); +} + +static void test_get_state_process2(void) +{ + ProcessState s = GetProcessState(2); + assert_int_equal(s, PROCESS_STATE_STOPPED); +} + +static void test_get_state_process666(void) +{ + ProcessState s = GetProcessState(666); + assert_int_equal(s, PROCESS_STATE_DOES_NOT_EXIST); +} + +static void test_get_state_process1000(void) +{ + ProcessState s = GetProcessState(1000); + assert_int_equal(s, PROCESS_STATE_DOES_NOT_EXIST); +} + +static void test_get_state_process1000000(void) +{ + ProcessState s = GetProcessState(1000000); + assert_int_equal(s, PROCESS_STATE_DOES_NOT_EXIST); +} + + +int main() +{ + PRINT_TEST_BANNER(); + + const UnitTest tests[] = + { + unit_test(test_get_start_time_process1), + unit_test(test_get_start_time_process2), + unit_test(test_get_start_time_process666), + unit_test(test_get_start_time_process1000), + unit_test(test_get_start_time_process1000000), + unit_test(test_get_state_process1), + unit_test(test_get_state_process2), + unit_test(test_get_state_process666), + unit_test(test_get_state_process1000), + unit_test(test_get_state_process1000000), + }; + + return run_tests(tests); +} diff --git a/tests/unit/arg_split_test.c b/tests/unit/arg_split_test.c new file mode 100644 index 0000000000..ef7e10f421 --- /dev/null +++ b/tests/unit/arg_split_test.c @@ -0,0 +1,256 @@ +#include + +#include +#include + +#include +#include + +static void test_split_empty(void) +{ + char **s = ArgSplitCommand("", NULL); + + assert_true(s); + assert_false(*s); + ArgFree(s); + + char *exec, *args; + ArgGetExecutableAndArgs("", &exec, &args); + assert_false(exec); + assert_false(args); +} + +static void test_split_easy(void) +{ + char **s = ArgSplitCommand("zero one two", NULL); + + assert_string_equal(s[0], "zero"); + assert_string_equal(s[1], "one"); + assert_string_equal(s[2], "two"); + assert_false(s[3]); + + ArgFree(s); + + char *exec, *args; + ArgGetExecutableAndArgs("zero one two", &exec, &args); + assert_string_equal(exec, "zero"); + assert_string_equal(args, "one two"); + + free(exec); + free(args); +} + +static void test_split_whitespace_prefix(void) +{ + char **s = ArgSplitCommand(" zero one two", NULL); + + assert_string_equal(s[0], "zero"); + assert_string_equal(s[1], "one"); + assert_string_equal(s[2], "two"); + assert_false(s[3]); + + ArgFree(s); + + char *exec, *args; + ArgGetExecutableAndArgs("zero one two", &exec, &args); + assert_string_equal(exec, "zero"); + assert_string_equal(args, "one two"); + + free(exec); + free(args); +} + +static void test_split_quoted_beginning(void) +{ + char **s = ArgSplitCommand("\"quoted string\" atbeginning", NULL); + + assert_string_equal(s[0], "quoted string"); + assert_string_equal(s[1], "atbeginning"); + assert_false(s[2]); + ArgFree(s); + + char *exec, *args; + ArgGetExecutableAndArgs("\"quoted string\" atbeginning", &exec, &args); + assert_string_equal(exec, "quoted string"); + assert_string_equal(args, "atbeginning"); + + free(exec); + free(args); +} + +static void test_split_quoted_end(void) +{ + char **s = ArgSplitCommand("atend 'quoted string'", NULL); + + assert_string_equal(s[0], "atend"); + assert_string_equal(s[1], "quoted string"); + assert_false(s[2]); + ArgFree(s); + + char *exec, *args; + ArgGetExecutableAndArgs("atend 'quoted string'", &exec, &args); + assert_string_equal(exec, "atend"); + assert_string_equal(args, "'quoted string'"); + + free(exec); + free(args); +} + +static void test_split_quoted_middle(void) +{ + char **s = ArgSplitCommand("at `quoted string` middle", NULL); + + assert_string_equal(s[0], "at"); + assert_string_equal(s[1], "quoted string"); + assert_string_equal(s[2], "middle"); + assert_false(s[3]); + ArgFree(s); + + char *exec, *args; + ArgGetExecutableAndArgs("at `quoted string` middle", &exec, &args); + assert_string_equal(exec, "at"); + assert_string_equal(args, "`quoted string` middle"); + + free(exec); + free(args); +} + +static void test_complex_quoting(void) +{ + char **s = ArgSplitCommand("\"foo`'bar\"", NULL); + + assert_string_equal(s[0], "foo`'bar"); + assert_false(s[1]); + ArgFree(s); + + char *exec, *args; + ArgGetExecutableAndArgs("\"foo`'bar\"", &exec, &args); + assert_string_equal(exec, "foo`'bar"); + assert_false(args); + + free(exec); +} + +static void test_arguments_resize_for_null(void) +{ +/* This test checks that extending returned argument list for NULL terminator + * works correctly */ + char **s = ArgSplitCommand("0 1 2 3 4 5 6 7", NULL); + + assert_string_equal(s[7], "7"); + assert_false(s[8]); + ArgFree(s); +} + +static void test_arguments_resize(void) +{ + char **s = ArgSplitCommand("0 1 2 3 4 5 6 7 8", NULL); + + assert_string_equal(s[7], "7"); + assert_string_equal(s[8], "8"); + assert_false(s[9]); + ArgFree(s); +} + +static void test_arguments_with_arglist(void) +{ + char *command = "lorem ipsum dolor sit amet"; + + Seq *arglist = SeqNew(10, NULL); + char extra[][32] = { "consectetur adipiscing", "elit", "sed do", "eiusmod" }; + for (size_t i = 0; i < sizeof(extra) / sizeof(*extra); i++) { + SeqAppend(arglist, extra[i]); + } + + char **argv = ArgSplitCommand(command, arglist); + SeqDestroy(arglist); + + assert_string_equal(argv[0], "lorem"); + assert_string_equal(argv[2], "dolor"); + assert_string_equal(argv[5], "consectetur adipiscing"); + assert_string_equal(argv[8], "eiusmod"); + assert_false(argv[9]); + ArgFree(argv); +} + +static void test_command_promiser(void) +{ + char *t1 = "/bin/echo"; + assert_string_equal(CommandArg0(t1), "/bin/echo"); + + char *t2 = "/bin/rpm -qa --queryformat \"i | repos | %{name} | %{version}-%{release} | %{arch}\n\""; + assert_string_equal(CommandArg0(t2), "/bin/rpm"); + + char *t3 = "/bin/mount -va"; + assert_string_equal(CommandArg0(t3), "/bin/mount"); + + char *t4 = "\"/bin/echo\""; + assert_string_equal(CommandArg0(t4), "/bin/echo"); + + char *t5 = "\"/bin/echo\" 123"; + assert_string_equal(CommandArg0(t5), "/bin/echo"); + + char *t6 = "\"/bin/echo with space\" 123"; + assert_string_equal(CommandArg0(t6), "/bin/echo with space"); + + char *t7 = "c:\\Windows\\System32\\cmd.exe"; + assert_string_equal(CommandArg0(t7), "c:\\Windows\\System32\\cmd.exe"); + + char *t8 = "\"c:\\Windows\\System32\\cmd.exe\""; + assert_string_equal(CommandArg0(t8), "c:\\Windows\\System32\\cmd.exe"); + + char *t9 = "\"c:\\Windows\\System32\\cmd.exe\" /some args here"; + assert_string_equal(CommandArg0(t9), "c:\\Windows\\System32\\cmd.exe"); + + char *t10 = "\"c:\\Windows\\System32 with space\\cmd.exe\""; + assert_string_equal(CommandArg0(t10), "c:\\Windows\\System32 with space\\cmd.exe"); + + char *t11 = "\"c:\\Windows\\System32 with space\\cmd.exe\" /some args here"; + assert_string_equal(CommandArg0(t11), "c:\\Windows\\System32 with space\\cmd.exe"); + + char *t12 = "\"c:\\Windows\\System32 with space\\cmd.exe\" /some \"args here\""; + assert_string_equal(CommandArg0(t12), "c:\\Windows\\System32 with space\\cmd.exe"); + + char *t13 = "\\\\mycommand"; + assert_string_equal(CommandArg0(t13), "\\\\mycommand"); + + char *t14 = "\\\\myhost\\share\\command.exe"; + assert_string_equal(CommandArg0(t14), "\\\\myhost\\share\\command.exe"); + + char *t15 = "\"\\\\myhost\\share\\command.exe\""; + assert_string_equal(CommandArg0(t15), "\\\\myhost\\share\\command.exe"); + + + /* bad input */ + + char *b1 = "\"/bin/echo 123"; + assert_string_equal(CommandArg0(b1), "/bin/echo 123"); + + char *b2 = "/bin/echo\" 123"; + assert_string_equal(CommandArg0(b2), "/bin/echo\""); + + char *b3 = ""; + assert_string_equal(CommandArg0(b3), ""); + +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_split_empty), + unit_test(test_split_easy), + unit_test(test_split_whitespace_prefix), + unit_test(test_split_quoted_beginning), + unit_test(test_split_quoted_middle), + unit_test(test_split_quoted_end), + unit_test(test_complex_quoting), + unit_test(test_arguments_resize_for_null), + unit_test(test_arguments_resize), + unit_test(test_arguments_with_arglist), + unit_test(test_command_promiser), + }; + + return run_tests(tests); +} diff --git a/tests/unit/assoc_test.c b/tests/unit/assoc_test.c new file mode 100644 index 0000000000..10d92acafb --- /dev/null +++ b/tests/unit/assoc_test.c @@ -0,0 +1,20 @@ +#include + +#include + +static void test_create_destroy(void) +{ + CfAssoc *ap = NewAssoc("hello", (Rval) { "world", RVAL_TYPE_SCALAR }, CF_DATA_TYPE_STRING); + DeleteAssoc(ap); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_create_destroy), + }; + + return run_tests(tests); +} diff --git a/tests/unit/avahi_config_test.c b/tests/unit/avahi_config_test.c new file mode 100644 index 0000000000..a5a51e8992 --- /dev/null +++ b/tests/unit/avahi_config_test.c @@ -0,0 +1,61 @@ +#include + +#include +#include +#include + + +static void generateTestFile() +{ + FILE *fp = fopen("/tmp/test_file", "w+"); + + assert_int_not_equal(fp, NULL); + + fprintf(fp, "\n"); + fprintf(fp, "\n"); + fprintf(fp, "\n"); + fprintf(fp, "\n"); + FprintAvahiCfengineTag(fp); + fprintf(fp, "\n"); + fprintf(fp, "_cfenginehub._tcp\n"); + DetermineCfenginePort(); + fprintf(fp, "\n"); + fprintf(fp, "5308\n"); + fprintf(fp, "\n"); + fprintf(fp, "\n"); + fclose(fp); +} + +static void test_generateAvahiConfig(void) +{ + generateTestFile(); + assert_int_equal(GenerateAvahiConfig("/tmp/avahi_config"), 0); + FILE *testfile = fopen("/tmp/test_file", "r+"); + assert_int_not_equal(testfile, NULL); + FILE *optfile = fopen("/tmp/avahi_config", "r+"); + assert_int_not_equal(optfile, NULL); + char buffer1[256], buffer2[256]; + + while (!feof(testfile) && !feof(optfile)) + { + memset(buffer1, 0, sizeof(buffer1)); + memset(buffer2, 0, sizeof(buffer2)); + fgets(buffer1, sizeof(buffer1), testfile); + fgets(buffer2, sizeof(buffer2), optfile); + assert_int_equal(strcmp(buffer1, buffer2), 0); + } + + fclose(testfile); + fclose(optfile); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_generateAvahiConfig) + }; + + return run_tests(tests); +} diff --git a/tests/unit/cf_upgrade_test.c b/tests/unit/cf_upgrade_test.c new file mode 100644 index 0000000000..76f3dea504 --- /dev/null +++ b/tests/unit/cf_upgrade_test.c @@ -0,0 +1,102 @@ +#include + +#include +#include +#include +#include + +static void test_parse(void) +{ + Configuration *configuration = NULL; + char *empty_command_line[] = { "this" }; + assert_int_equal (-1, parse(1, empty_command_line, &configuration)); + assert_true (configuration == NULL); + + char *common_command_line[] = { "this", "-b", "backup.sh", "-s", + "backup.tar.gz", "-i", "rpm", "-ivh", + "package.rpm"}; + assert_int_equal(0, parse(9, common_command_line, &configuration)); + assert_true(configuration != NULL); + assert_string_equal("backup.sh", ConfigurationBackupTool(configuration)); + assert_string_equal("backup.tar.gz", ConfigurationBackupPath(configuration)); + assert_string_equal("rpm", ConfigurationCommand(configuration)); + assert_int_equal(3, ConfigurationNumberOfArguments(configuration)); + ConfigurationDestroy(&configuration); + assert_true(configuration == NULL); + + char *full_command_line[] = { "this", "-b", "backup.sh", "-s", + "backup.tar.gz", "-c", "copy", "-f", + "/opt/cfengine", "-i", "dpkg", "--install", + "package.deb" + }; + assert_int_equal(0, parse(13, full_command_line, &configuration)); + assert_true(configuration != NULL); + assert_string_equal("backup.sh", ConfigurationBackupTool(configuration)); + assert_string_equal("backup.tar.gz", ConfigurationBackupPath(configuration)); + assert_string_equal("copy", ConfigurationCopy(configuration)); + assert_string_equal("/opt/cfengine", ConfigurationCFEnginePath(configuration)); + assert_string_equal("this", ConfigurationCFUpgrade(configuration)); + assert_string_equal("dpkg", ConfigurationCommand(configuration)); + assert_int_equal(3, ConfigurationNumberOfArguments(configuration)); + ConfigurationDestroy(&configuration); + assert_true(configuration == NULL); +} + +static void test_configuration(void) +{ + Configuration *configuration = ConfigurationNew(); + assert_true(configuration != NULL); + assert_true(ConfigurationBackupTool(configuration) == NULL); + assert_true(ConfigurationBackupPath(configuration) == NULL); + assert_string_equal("/tmp/cf-upgrade", ConfigurationCopy(configuration)); + assert_string_equal("/var/cfengine/", ConfigurationCFEnginePath(configuration)); + assert_true(ConfigurationCommand(configuration) == NULL); + assert_true(ConfigurationCFUpgrade(configuration) == NULL); + assert_int_equal(0, ConfigurationNumberOfArguments(configuration)); + assert_false(ConfigurationPerformUpdate(configuration)); + ConfigurationDestroy(&configuration); + assert_true(configuration == NULL); + + configuration = ConfigurationNew(); + char backup_tool[] = "backup.sh"; + char backup_path[] = "backup.tar.gz"; + char cfupgrade_copy[] = "/tmp/copy"; + char cfupgrade[] = "cf-upgrade"; + char command[] = "dpkg"; + char argument[] = "-ivh"; + char cfengine[] = "/opt/cfengine"; + ConfigurationSetBackupTool(configuration, backup_tool); + assert_string_equal(ConfigurationBackupTool(configuration), backup_tool); + ConfigurationSetBackupPath(configuration, backup_path); + assert_string_equal(ConfigurationBackupPath(configuration), backup_path); + ConfigurationSetCopy(configuration, cfupgrade_copy); + assert_string_equal(ConfigurationCopy(configuration), cfupgrade_copy); + ConfigurationSetCFUpgrade(configuration, cfupgrade); + assert_string_equal(ConfigurationCFUpgrade(configuration), cfupgrade); + ConfigurationAddArgument(configuration, command); + assert_string_equal(ConfigurationCommand(configuration), command); + assert_string_equal(ConfigurationArgument(configuration, 0), command); + assert_int_equal(1, ConfigurationNumberOfArguments(configuration)); + ConfigurationAddArgument(configuration, argument); + assert_string_equal(ConfigurationArgument(configuration, 0), command); + assert_string_equal(ConfigurationArgument(configuration, 1), argument); + assert_int_equal(2, ConfigurationNumberOfArguments(configuration)); + ConfigurationSetCFEnginePath(configuration, cfengine); + assert_string_equal(ConfigurationCFEnginePath(configuration), cfengine); + assert_int_equal(2, ConfigurationNumberOfArguments(configuration)); + ConfigurationDestroy(&configuration); + assert_true(configuration == NULL); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_parse), + unit_test(test_configuration) + }; + + return run_tests(tests); +} + diff --git a/tests/unit/changes_migration_test.c b/tests/unit/changes_migration_test.c new file mode 100644 index 0000000000..6d1e58c320 --- /dev/null +++ b/tests/unit/changes_migration_test.c @@ -0,0 +1,199 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include +#include /* xsnprintf */ + + +#include + +#define NO_FILES 4 + +char *CHECKSUM_VALUE[NO_FILES] = + { + "0001", + "0002", + "0003", + "0004", + }; +struct stat filestat_value; + +static void test_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/changes_migration_test.XXXXXX"; + + char *workdir = strchr(env, '=') + 1; /* start of the path */ + assert(workdir - 1 && workdir[0] == '/'); + + mkdtemp(workdir); + putenv(env); + mkdir(GetStateDir(), (S_IRWXU | S_IRWXG | S_IRWXO)); + + CF_DB *db; + assert_true(OpenDB(&db, dbid_checksums)); + // Hand crafted from the old version of NewIndexKey(). + char checksum_key[NO_FILES][30] = + { + { 'M','D','5','\0','\0','\0','\0','\0', + '/','e','t','c','/','h','o','s','t','s','\0' }, + { 'M','D','5','\0','\0','\0','\0','\0', + '/','e','t','c','/','p','a','s','s','w','d','\0' }, + { 'M','D','5','\0','\0','\0','\0','\0', + '/','f','i','l','e','1','\0' }, + { 'M','D','5','\0','\0','\0','\0','\0', + '/','f','i','l','e','2','\0' }, + }; + + for (int c = 0; c < NO_FILES; c++) + { + int ksize = CHANGES_HASH_FILE_NAME_OFFSET + strlen(checksum_key[c] + CHANGES_HASH_FILE_NAME_OFFSET) + 1; + int vsize = strlen(CHECKSUM_VALUE[c]) + 1; + assert_true(WriteComplexKeyDB(db, checksum_key[c], ksize, + CHECKSUM_VALUE[c], vsize)); + } + + CloseDB(db); + + assert_true(OpenDB(&db, dbid_filestats)); + + char *filestat_key[NO_FILES] = + { + "/etc/hosts", + "/etc/passwd", + "/file1", + "/file2", + }; + filestat_value.st_uid = 4321; + memset(&filestat_value, 0, sizeof(filestat_value)); + + for (int c = 0; c < NO_FILES; c++) + { + assert_true(WriteDB(db, filestat_key[c], + &filestat_value, sizeof(filestat_value))); + } + + CloseDB(db); +} + +static void test_migration(void) +{ + CF_DB *db; + Seq *list = SeqNew(NO_FILES, free); + // Hand crafted from the new version of NewIndexKey(). + char checksum_key[NO_FILES][30] = + { + { 'H','_','M','D','5','\0','\0','\0','\0','\0', + '/','e','t','c','/','h','o','s','t','s','\0' }, + { 'H','_','M','D','5','\0','\0','\0','\0','\0', + '/','e','t','c','/','p','a','s','s','w','d','\0' }, + { 'H','_','M','D','5','\0','\0','\0','\0','\0', + '/','f','i','l','e','1','\0' }, + { 'H','_','M','D','5','\0','\0','\0','\0','\0', + '/','f','i','l','e','2','\0' }, + }; + char *filestat_key[NO_FILES] = + { + "S_/etc/hosts", + "S_/etc/passwd", + "S_/file1", + "S_/file2", + }; + + // Should cause migration to happen. + assert_true(FileChangesGetDirectoryList("/etc", list)); + assert_int_equal(SeqLength(list), 2); + assert_string_equal(SeqAt(list, 0), "hosts"); + assert_string_equal(SeqAt(list, 1), "passwd"); + + SeqClear(list); + assert_true(FileChangesGetDirectoryList("/", list)); + assert_int_equal(SeqLength(list), 2); + assert_string_equal(SeqAt(list, 0), "file1"); + assert_string_equal(SeqAt(list, 1), "file2"); + SeqDestroy(list); + + assert_true(OpenDB(&db, dbid_changes)); + for (int c = 0; c < NO_FILES; c++) + { + { + int ksize = 2 + CHANGES_HASH_FILE_NAME_OFFSET + + strlen(checksum_key[c] + 2 + CHANGES_HASH_FILE_NAME_OFFSET) + 1; + int vsize = ValueSizeDB(db, checksum_key[c], ksize); + assert_int_equal(vsize, strlen(CHECKSUM_VALUE[c]) + 1); + char value[vsize]; + assert_true(ReadComplexKeyDB(db, checksum_key[c], ksize, value, vsize)); + assert_int_equal(memcmp(value, CHECKSUM_VALUE[c], vsize), 0); + } + + { + int vsize = ValueSizeDB(db, filestat_key[c], strlen(filestat_key[c]) + 1); + assert_int_equal(vsize, sizeof(struct stat)); + char value[vsize]; + assert_true(ReadDB(db, filestat_key[c], value, vsize)); + assert_int_equal(memcmp(value, &filestat_value, vsize), 0); + } + } + + int db_entries = 0; + CF_DBC *db_cursor; + assert_true(NewDBCursor(db, &db_cursor)); + char *key, *value; + int ksize, vsize; + while (NextDB(db_cursor, &key, &ksize, (void **)&value, &vsize)) + { + db_entries++; + } + DeleteDBCursor(db_cursor); + // 2 x Directories ("/" and "/etc") + // 4 x File hashes + // 4 x File stats + assert_int_equal(db_entries, 10); + + CloseDB(db); +} + +static void test_teardown(void) +{ + DeleteDirectoryTree(GetWorkDir()); + rmdir(GetWorkDir()); +} + +int main() +{ + const UnitTest tests[] = + { + unit_test(test_setup), + unit_test(test_migration), + unit_test(test_teardown), + }; + + PRINT_TEST_BANNER(); + int ret = run_tests(tests); + + return ret; +} diff --git a/tests/unit/class_test.c b/tests/unit/class_test.c new file mode 100644 index 0000000000..5fcce06004 --- /dev/null +++ b/tests/unit/class_test.c @@ -0,0 +1,121 @@ +#include + +#include + +static void test_class_ref(void) +{ + { + ClassRef ref = ClassRefParse("class"); + assert_true(ref.ns == NULL); + assert_string_equal("class", ref.name); + char *expr = ClassRefToString(ref.ns, ref.name); + assert_string_equal("class", expr); + free(expr); + ClassRefDestroy(ref); + } + + { + ClassRef ref = ClassRefParse("default:class"); + assert_string_equal("default", ref.ns); + assert_string_equal("class", ref.name); + char *expr = ClassRefToString(ref.ns, ref.name); + assert_string_equal("class", expr); + free(expr); + ClassRefDestroy(ref); + } + + { + ClassRef ref = ClassRefParse("ns:class"); + assert_string_equal("ns", ref.ns); + assert_string_equal("class", ref.name); + char *expr = ClassRefToString(ref.ns, ref.name); + assert_string_equal("ns:class", expr); + free(expr); + ClassRefDestroy(ref); + } +} + +static void test_ns(void) +{ + { + ClassTable *t = ClassTableNew(); + assert_false(ClassTablePut(t, "foo", "127.0.0.1", true, CONTEXT_SCOPE_BUNDLE, NULL, NULL)); + Class *cls = ClassTableGet(t, "foo", "127_0_0_1"); + assert_string_equal("foo", cls->ns); + assert_string_equal("127_0_0_1", cls->name); + assert_true(cls->is_soft); + + cls = ClassTableMatch(t, "foo:127_0_.*"); + assert_true(cls); + cls = ClassTableMatch(t, "foo:127_1_.*"); + assert_false(cls); + cls = ClassTableMatch(t, "127_0_.*"); + assert_false(cls); + + ClassTableDestroy(t); + } +} + +static void test_default_ns(void) +{ + { + ClassTable *t = ClassTableNew(); + assert_false(ClassTablePut(t, NULL, "127.0.0.1", false, CONTEXT_SCOPE_NAMESPACE, NULL, NULL)); + Class *cls = ClassTableGet(t, NULL, "127_0_0_1"); + assert_true(cls != NULL); + assert_true(cls->ns == NULL); + cls = ClassTableGet(t, "default", "127_0_0_1"); + assert_true(cls->ns == NULL); + assert_string_equal("127_0_0_1", cls->name); + assert_false(cls->is_soft); + + cls = ClassTableMatch(t, "127_0_.*"); + assert_true(cls); + cls = ClassTableMatch(t, "127_1_.*"); + assert_false(cls); + + ClassTableDestroy(t); + } + + { + ClassTable *t = ClassTableNew(); + assert_false(ClassTablePut(t, "default", "127.0.0.1", false, CONTEXT_SCOPE_NAMESPACE, NULL, NULL)); + Class *cls = ClassTableGet(t, NULL, "127_0_0_1"); + assert_true(cls->ns == NULL); + cls = ClassTableGet(t, "default", "127_0_0_1"); + assert_true(cls->ns == NULL); + assert_string_equal("127_0_0_1", cls->name); + assert_false(cls->is_soft); + ClassTableDestroy(t); + } +} + +static void test_put_replace(void) +{ + ClassTable *t = ClassTableNew(); + assert_false(ClassTablePut(t, NULL, "test", false, CONTEXT_SCOPE_NAMESPACE, NULL, NULL)); + Class *cls = ClassTableGet(t, NULL, "test"); + assert_true(cls); + assert_int_equal(CONTEXT_SCOPE_NAMESPACE, cls->scope); + + assert_true(ClassTablePut(t, NULL, "test", true, CONTEXT_SCOPE_BUNDLE, NULL, NULL)); + cls = ClassTableGet(t, NULL, "test"); + assert_true(cls); + assert_int_equal(CONTEXT_SCOPE_BUNDLE, cls->scope); + + ClassTableDestroy(t); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_default_ns), + unit_test(test_ns), + unit_test(test_class_ref), + unit_test(test_put_replace), + }; + + return run_tests(tests); +} diff --git a/tests/unit/cmockery.c b/tests/unit/cmockery.c new file mode 100644 index 0000000000..8b9009d49f --- /dev/null +++ b/tests/unit/cmockery.c @@ -0,0 +1,2071 @@ +/* LCOV_EXCL_STOP */ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* LCOV_EXCL_START */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#ifdef HAVE_MALLOC_H +# include +#endif +#include +#ifndef _WIN32 +# include +#endif // !_WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +# include +#endif // _WIN32 +#include +#include +#include + +/* Backwards compatibility with headers shipped with Visual Studio 2005 and + * earlier. */ +#ifdef _WIN32 +WINBASEAPI BOOL WINAPI IsDebuggerPresent(VOID); +#endif // _WIN32 + +// Size of guard bytes around dynamically allocated blocks. +#define MALLOC_GUARD_SIZE 16 +// Pattern used to initialize guard blocks. +#define MALLOC_GUARD_PATTERN 0xEF +// Pattern used to initialize memory allocated with test_malloc(). +#define MALLOC_ALLOC_PATTERN 0xBA +#define MALLOC_FREE_PATTERN 0xCD +// Alignment of allocated blocks. NOTE: This must be base2. +#define MALLOC_ALIGNMENT sizeof(size_t) + +// Printf formatting for source code locations. +#define SOURCE_LOCATION_FORMAT "%s:%d" + +// Calculates the number of elements in an array. +#define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) + +// Declare and initialize the pointer member of ValuePointer variable name +// with ptr. +#define declare_initialize_value_pointer_pointer(name, ptr) \ + ValuePointer name ; \ + name.value = 0; \ + name.pointer = (void*)(ptr) + +// Declare and initialize the value member of ValuePointer variable name +// with val. +#define declare_initialize_value_pointer_value(name, val) \ + ValuePointer name ; \ + name.value = val + +// Cast a LargestIntegralType to pointer_type via a ValuePointer. +#define cast_largest_integral_type_to_pointer( \ + pointer_type, largest_integral_type) \ + ((pointer_type)((ValuePointer*)&(largest_integral_type))->pointer) + +// Used to cast LargestIntegralType to void* and vice versa. +typedef union ValuePointer +{ + LargestIntegralType value; + void *pointer; +} ValuePointer; + +// Doubly linked list node. +typedef struct ListNode +{ + const void *value; + int refcount; + struct ListNode *next; + struct ListNode *prev; +} ListNode; + +// Debug information for malloc(). +typedef struct MallocBlockInfo +{ + void *block; // Address of the block returned by malloc(). + size_t allocated_size; // Total size of the allocated block. + size_t size; // Request block size. + SourceLocation location; // Where the block was allocated. + ListNode node; // Node within list of all allocated blocks. +} MallocBlockInfo; + +// State of each test. +typedef struct TestState +{ + const ListNode *check_point; // Check point of the test if there's a + // setup function. + void *state; // State associated with the test. +} TestState; + +// Determines whether two values are the same. +typedef int (*EqualityFunction) (const void *left, const void *right); + +// Value of a symbol and the place it was declared. +typedef struct SymbolValue +{ + SourceLocation location; + LargestIntegralType value; +} SymbolValue; + +/* Contains a list of values for a symbol. + * NOTE: Each structure referenced by symbol_values_list_head must have a + * SourceLocation as its' first member. + */ +typedef struct SymbolMapValue +{ + const char *symbol_name; + ListNode symbol_values_list_head; +} SymbolMapValue; + +// Used by list_free() to deallocate values referenced by list nodes. +typedef void (*CleanupListValue) (const void *value, void *cleanup_value_data); + +// Structure used to check the range of integer types. +typedef struct CheckIntegerRange +{ + CheckParameterEvent event; + LargestIntegralType minimum; + LargestIntegralType maximum; +} CheckIntegerRange; + +// Structure used to check whether an integer value is in a set. +typedef struct CheckIntegerSet +{ + CheckParameterEvent event; + const LargestIntegralType *set; + size_t size_of_set; +} CheckIntegerSet; + +/* Used to check whether a parameter matches the area of memory referenced by + * this structure. */ +typedef struct CheckMemoryData +{ + CheckParameterEvent event; + const void *memory; + size_t size; +} CheckMemoryData; + +static ListNode *list_initialize(ListNode *const node); +static ListNode *list_add(ListNode *const head, ListNode *new_node); +static ListNode *list_add_value(ListNode *const head, const void *value, const int count); +static ListNode *list_remove(ListNode *const node, const CleanupListValue cleanup_value, + void *const cleanup_value_data); +static void list_remove_free(ListNode *const node, const CleanupListValue cleanup_value, + void *const cleanup_value_data); +static int list_empty(const ListNode *const head); +static int list_find(ListNode *const head, const void *value, const EqualityFunction equal_func, ListNode **output); +static int list_first(ListNode *const head, ListNode **output); +static ListNode *list_free(ListNode *const head, const CleanupListValue cleanup_value, void *const cleanup_value_data); + +static void add_symbol_value(ListNode *const symbol_map_head, const char *const symbol_names[], + const size_t number_of_symbol_names, const void *value, const int count); +static int get_symbol_value(ListNode *const symbol_map_head, const char *const symbol_names[], + const size_t number_of_symbol_names, void **output); +static void free_value(const void *value, void *cleanup_value_data); +static void free_symbol_map_value(const void *value, void *cleanup_value_data); +static void remove_always_return_values(ListNode *const map_head, const size_t number_of_symbol_names); +static int check_for_leftover_values(const ListNode *const map_head, const char *const error_message, + const size_t number_of_symbol_names); +// This must be called at the beginning of a test to initialize some data +// structures. +static void initialize_testing(void); + +// This must be called at the end of a test to free() allocated structures. +static void teardown_testing(void); + +// Keeps track of the calling context returned by setenv() so that the fail() +// method can jump out of a test. +static jmp_buf global_run_test_env; +static int global_running_test = 0; + +// Keeps track of the calling context returned by setenv() so that +// mock_assert() can optionally jump back to expect_assert_failure(). +jmp_buf global_expect_assert_env; +const char *global_expect_assert_expression; +int global_expecting_assert = 0; + +// Keeps a map of the values that functions will have to return to provide +// mocked interfaces. +static ListNode global_function_result_map_head; + +// Location of the last mock value returned was declared. +static SourceLocation global_last_mock_value_location; + +/* Keeps a map of the values that functions expect as parameters to their + * mocked interfaces. */ +static ListNode global_function_parameter_map_head; + +// Location of last parameter value checked was declared. +static SourceLocation global_last_parameter_location; + +// List of all currently allocated blocks. +static ListNode global_allocated_blocks; + +// Data of running tests for XML output. +static int global_errors; +static const char *global_filename; +static const char *global_xmlfile; +static int global_is_file_writer_test; /* bool, but type not defined here */ + +#ifndef _WIN32 +// Signals caught by exception_handler(). +static const int exception_signals[] = +{ + SIGFPE, + SIGILL, + SIGSEGV, + SIGBUS, + SIGSYS, +}; + +// Default signal functions that should be restored after a test is complete. +typedef void (*SignalFunction) (int signal); +static SignalFunction default_signal_functions[ARRAY_LENGTH(exception_signals)]; + +#else // _WIN32 + +// The default exception filter. +static LPTOP_LEVEL_EXCEPTION_FILTER previous_exception_filter; + +// Fatal exceptions. +typedef struct ExceptionCodeInfo +{ + DWORD code; + const char *description; +} ExceptionCodeInfo; + +# define EXCEPTION_CODE_INFO(exception_code) {exception_code, #exception_code} + +static const ExceptionCodeInfo exception_codes[] = +{ + EXCEPTION_CODE_INFO(EXCEPTION_ACCESS_VIOLATION), + EXCEPTION_CODE_INFO(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), + EXCEPTION_CODE_INFO(EXCEPTION_DATATYPE_MISALIGNMENT), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_DENORMAL_OPERAND), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_DIVIDE_BY_ZERO), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_INEXACT_RESULT), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_INVALID_OPERATION), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_OVERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_STACK_CHECK), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_UNDERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_GUARD_PAGE), + EXCEPTION_CODE_INFO(EXCEPTION_ILLEGAL_INSTRUCTION), + EXCEPTION_CODE_INFO(EXCEPTION_INT_DIVIDE_BY_ZERO), + EXCEPTION_CODE_INFO(EXCEPTION_INT_OVERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_INVALID_DISPOSITION), + EXCEPTION_CODE_INFO(EXCEPTION_INVALID_HANDLE), + EXCEPTION_CODE_INFO(EXCEPTION_IN_PAGE_ERROR), + EXCEPTION_CODE_INFO(EXCEPTION_NONCONTINUABLE_EXCEPTION), + EXCEPTION_CODE_INFO(EXCEPTION_PRIV_INSTRUCTION), + EXCEPTION_CODE_INFO(EXCEPTION_STACK_OVERFLOW), +}; +#endif // !_WIN32 + +static void exit_test(void) FUNC_ATTR_NORETURN; + +// Exit the currently executing test. + static void exit_test(void) +{ + if (global_running_test) + { + longjmp(global_run_test_env, 1); + } + else + { + exit(-1); + } +} + +#ifdef _WIN32 +// Exit the currently executing test. +static void exit_test_no_app(void) +{ + if (global_running_test) + { + longjmp(global_run_test_env, 1); + } +} +#endif + +// Initialize a SourceLocation structure. +static void initialize_source_location(SourceLocation *const location) +{ + assert_true(location); + location->file = NULL; + location->line = 0; +} + +// Determine whether a source location is currently set. +static int source_location_is_set(const SourceLocation *const location) +{ + assert_true(location); + return location->file && location->line; +} + +// Set a source location. +static void set_source_location(SourceLocation *const location, const char *const file, const int line) +{ + assert_true(location); + location->file = file; + location->line = line; +} + +// Create function results and expected parameter lists. +void initialize_testing(void) +{ + list_initialize(&global_function_result_map_head); + initialize_source_location(&global_last_mock_value_location); + list_initialize(&global_function_parameter_map_head); + initialize_source_location(&global_last_parameter_location); +} + +static void fail_if_leftover_values(const char *test_name) +{ + int error_occurred = 0; + + remove_always_return_values(&global_function_result_map_head, 1); + if (check_for_leftover_values(&global_function_result_map_head, "%s() has remaining non-returned values.\n", 1)) + { + print_xml(XS_RUN_TEST_ERROR + "\"%s() has remaining non-returned values.\">\n" + XS_RUN_TEST_ERROR_END, + "fail_if_leftover_values", test_name); + error_occurred = 1; + } + + remove_always_return_values(&global_function_parameter_map_head, 2); + if (check_for_leftover_values(&global_function_parameter_map_head, + "%s parameter still has values that haven't been checked.\n", 2)) + { + print_xml(XS_RUN_TEST_ERROR + "\"%s parameter still has values that haven't been checked.\">\n" + XS_RUN_TEST_ERROR_END, + "fail_if_leftover_values", test_name); + error_occurred = 1; + } + if (error_occurred) + { + global_errors++; + exit_test(); + } +} + +void teardown_testing(void) +{ + list_free(&global_function_result_map_head, free_symbol_map_value, (void *) 0); + initialize_source_location(&global_last_mock_value_location); + list_free(&global_function_parameter_map_head, free_symbol_map_value, (void *) 1); + initialize_source_location(&global_last_parameter_location); +} + +// Initialize a list node. +static ListNode *list_initialize(ListNode *const node) +{ + node->value = NULL; + node->next = node; + node->prev = node; + node->refcount = 1; + return node; +} + +/* Adds a value at the tail of a given list. + * The node referencing the value is allocated from the heap. */ +static ListNode *list_add_value(ListNode *const head, const void *value, const int refcount) +{ + ListNode *const new_node = (ListNode *) malloc(sizeof(ListNode)); + + assert_true(head); + assert_true(value); + new_node->value = value; + new_node->refcount = refcount; + return list_add(head, new_node); +} + +// Add new_node to the end of the list. +static ListNode *list_add(ListNode *const head, ListNode *new_node) +{ + assert_true(head); + assert_true(new_node); + new_node->next = head; + new_node->prev = head->prev; + head->prev->next = new_node; + head->prev = new_node; + return new_node; +} + +// Remove a node from a list. +static ListNode *list_remove(ListNode *const node, const CleanupListValue cleanup_value, void *const cleanup_value_data) +{ + assert_true(node); + node->prev->next = node->next; + node->next->prev = node->prev; + if (cleanup_value) + { + cleanup_value(node->value, cleanup_value_data); + } + return node; +} + +/* Remove a list node from a list and free the node. */ +static void list_remove_free(ListNode *const node, const CleanupListValue cleanup_value, void *const cleanup_value_data) +{ + assert_true(node); + free(list_remove(node, cleanup_value, cleanup_value_data)); +} + +/* Frees memory kept by a linked list + * The cleanup_value function is called for every "value" field of nodes in the + * list, except for the head. In addition to each list value, + * cleanup_value_data is passed to each call to cleanup_value. The head + * of the list is not deallocated. + */ +static ListNode *list_free(ListNode *const head, const CleanupListValue cleanup_value, void *const cleanup_value_data) +{ + assert_true(head); + while (!list_empty(head)) + { + list_remove_free(head->next, cleanup_value, cleanup_value_data); + } + return head; +} + +// Determine whether a list is empty. +static int list_empty(const ListNode *const head) +{ + assert_true(head); + return head->next == head; +} + +/* Find a value in the list using the equal_func to compare each node with the + * value. + */ +static int list_find(ListNode *const head, const void *value, const EqualityFunction equal_func, ListNode **output) +{ + ListNode *current; + + assert_true(head); + for (current = head->next; current != head; current = current->next) + { + if (equal_func(current->value, value)) + { + *output = current; + return 1; + } + } + return 0; +} + +// Returns the first node of a list +static int list_first(ListNode *const head, ListNode **output) +{ + ListNode *target_node; + + assert_true(head); + if (list_empty(head)) + { + return 0; + } + target_node = head->next; + *output = target_node; + return 1; +} + +#if defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__ * 10) >= 240) +# define ARG_UNUSED __attribute__((unused)) +#else +# define ARG_UNUSED +#endif + +// Deallocate a value referenced by a list. +static void free_value(const void *value, ARG_UNUSED void *cleanup_value_data) +{ + assert_true(value); + free((void *) value); +} + +// Releases memory associated to a symbol_map_value. +static void free_symbol_map_value(const void *value, void *cleanup_value_data) +{ + SymbolMapValue *const map_value = (SymbolMapValue *) value; + const uintptr_t children = (uintptr_t) cleanup_value_data; + + assert_true(value); + list_free(&map_value->symbol_values_list_head, + children ? free_symbol_map_value : free_value, (void *) (children - 1)); + free(map_value); +} + +/* Determine whether a symbol name referenced by a symbol_map_value + * matches the specified function name. */ +static int symbol_names_match(const void *map_value, const void *symbol) +{ + return !strcmp(((SymbolMapValue *) map_value)->symbol_name, (const char *) symbol); +} + +/* Adds a value to the queue of values associated with the given + * hierarchy of symbols. It's assumed value is allocated from the heap. + */ +static void add_symbol_value(ListNode *const symbol_map_head, + const char *const symbol_names[], + const size_t number_of_symbol_names, const void *value, const int refcount) +{ + const char *symbol_name; + ListNode *target_node; + SymbolMapValue *target_map_value; + + assert_true(symbol_map_head); + assert_true((const char *const *)symbol_names); + assert_true(number_of_symbol_names); + symbol_name = symbol_names[0]; + + if (!list_find(symbol_map_head, symbol_name, symbol_names_match, &target_node)) + { + SymbolMapValue *const new_symbol_map_value = malloc(sizeof(*new_symbol_map_value)); + + new_symbol_map_value->symbol_name = symbol_name; + list_initialize(&new_symbol_map_value->symbol_values_list_head); + target_node = list_add_value(symbol_map_head, new_symbol_map_value, 1); + } + + target_map_value = (SymbolMapValue *) target_node->value; + if (number_of_symbol_names == 1) + { + list_add_value(&target_map_value->symbol_values_list_head, value, refcount); + } + else + { + add_symbol_value(&target_map_value->symbol_values_list_head, + &symbol_names[1], number_of_symbol_names - 1, value, refcount); + } +} + +/* Gets the next value associated with the given hierarchy of symbols. + * The value is returned as an output parameter with the function returning the + * node's old refcount value if a value is found, 0 otherwise. + * This means that a return value of 1 indicates the node was just removed from + * the list. + */ +static int get_symbol_value(ListNode *const head, const char *const symbol_names[], + const size_t number_of_symbol_names, void **output) +{ + const char *symbol_name; + ListNode *target_node; + + assert_true(head); + assert_true((const char *const *)symbol_names); + assert_true(number_of_symbol_names); + assert_true(output); + symbol_name = symbol_names[0]; + + if (list_find(head, symbol_name, symbol_names_match, &target_node)) + { + SymbolMapValue *map_value; + ListNode *child_list; + int return_value = 0; + + assert_true(target_node); + assert_true(target_node->value); + + map_value = (SymbolMapValue *) target_node->value; + child_list = &map_value->symbol_values_list_head; + + if (number_of_symbol_names == 1) + { + ListNode *value_node = NULL; + + return_value = list_first(child_list, &value_node); + assert_true(return_value); + *output = (void *) value_node->value; + return_value = value_node->refcount; + if (--value_node->refcount == 0) + { + list_remove_free(value_node, NULL, NULL); + } + } + else + { + return_value = get_symbol_value(child_list, &symbol_names[1], number_of_symbol_names - 1, output); + } + if (list_empty(child_list)) + { + list_remove_free(target_node, free_symbol_map_value, (void *) 0); + } + return return_value; + } + else + { + print_error("No entries for symbol %s.\n", symbol_name); + } + return 0; +} + +/* Traverse down a tree of symbol values and remove the first symbol value + * in each branch that has a refcount < -1 (i.e should always be returned + * and has been returned at least once). + */ +static void remove_always_return_values(ListNode *const map_head, const size_t number_of_symbol_names) +{ + ListNode *current; + + assert_true(map_head); + assert_true(number_of_symbol_names); + current = map_head->next; + while (current != map_head) + { + SymbolMapValue *const value = (SymbolMapValue *) current->value; + ListNode *const next = current->next; + ListNode *child_list; + + assert_true(value); + child_list = &value->symbol_values_list_head; + + if (!list_empty(child_list)) + { + if (number_of_symbol_names == 1) + { + ListNode *const child_node = child_list->next; + + // If this item has been returned more than once, free it. + if (child_node->refcount < -1) + { + list_remove_free(child_node, free_value, NULL); + } + } + else + { + remove_always_return_values(child_list, number_of_symbol_names - 1); + } + } + + if (list_empty(child_list)) + { + list_remove_free(current, free_value, NULL); + } + current = next; + } +} + +/* Checks if there are any leftover values set up by the test that were never + * retrieved through execution, and fail the test if that is the case. + */ +static int check_for_leftover_values(const ListNode *const map_head, const char *const error_message, + const size_t number_of_symbol_names) +{ + const ListNode *current; + int symbols_with_leftover_values = 0; + + assert_true(map_head); + assert_true(number_of_symbol_names); + + for (current = map_head->next; current != map_head; current = current->next) + { + const SymbolMapValue *const value = (SymbolMapValue *) current->value; + const ListNode *child_list; + + assert_true(value); + child_list = &value->symbol_values_list_head; + + if (!list_empty(child_list)) + { + if (number_of_symbol_names == 1) + { + const ListNode *child_node; + + print_error(error_message, value->symbol_name); + print_error(" Remaining item(s) declared at...\n"); + + for (child_node = child_list->next; child_node != child_list; child_node = child_node->next) + { + const SourceLocation *const location = child_node->value; + + print_error(" " SOURCE_LOCATION_FORMAT "\n", location->file, location->line); + } + } + else + { + print_error("%s.", value->symbol_name); + check_for_leftover_values(child_list, error_message, number_of_symbol_names - 1); + } + symbols_with_leftover_values++; + } + } + return symbols_with_leftover_values; +} + +LargestIntegralType _cast_to_largest_integral_type(size_t size, ...) +{ + LargestIntegralType ret; + va_list args; + + va_start(args, size); + if (size <= sizeof(unsigned int)) + { + ret = va_arg(args, unsigned int); + } + else if (size <= sizeof(unsigned long)) + { + ret = va_arg(args, unsigned long); + } + else if (size <= sizeof(LargestIntegralType)) + { + ret = va_arg(args, LargestIntegralType); + } + else + { + assert(size <= sizeof(LargestIntegralType)); + exit(255); + } + + return ret; +} + +// Get the next return value for the specified mock function. +LargestIntegralType _mock(const char *const function, const char *const file, const int line) +{ + void *result; + const int rc = get_symbol_value(&global_function_result_map_head, + &function, 1, &result); + if (rc) + { + SymbolValue *const symbol = (SymbolValue *) result; + const LargestIntegralType value = symbol->value; + + global_last_mock_value_location = symbol->location; + if (rc == 1) + { + free(symbol); + } + return value; + } + else + { + print_error("ERROR: " SOURCE_LOCATION_FORMAT " - Could not get value " + "to mock function %s\n", file, line, function); + print_xml(XS_RUN_TEST_ERROR + "\"ERROR: " SOURCE_LOCATION_FORMAT " - Could not get value " + "to mock function %s\"\n", "_mock", file, line, function); + + if (source_location_is_set(&global_last_mock_value_location)) + { + print_error("Previously returned mock value was declared at " + SOURCE_LOCATION_FORMAT "\n", + global_last_mock_value_location.file, global_last_mock_value_location.line); + print_xml(" \"Previously returned mock value was declared at " + SOURCE_LOCATION_FORMAT "\">\n" + XS_RUN_TEST_ERROR_END, + global_last_mock_value_location.file, global_last_mock_value_location.line); + } + else + { + print_error("There were no previously returned mock values for " "this test.\n"); + print_xml(" \"There were no previously returned mock values for this test.\">\n" + XS_RUN_TEST_ERROR_END); + } + global_errors++; + exit_test(); + } + return 0; +} + +// Add a return value for the specified mock function name. +void _will_return(const char *const function_name, const char *const file, + const int line, const LargestIntegralType value, const int count) +{ + SymbolValue *const return_value = malloc(sizeof(*return_value)); + + assert_true(count > 0 || count == -1); + return_value->value = value; + set_source_location(&return_value->location, file, line); + add_symbol_value(&global_function_result_map_head, &function_name, 1, return_value, count); +} + +/* Add a custom parameter checking function. If the event parameter is NULL + * the event structure is allocated internally by this function. If event + * parameter is provided it must be allocated on the heap and doesn't need to + * be deallocated by the caller. + */ +void _expect_check(const char *const function, const char *const parameter, + const char *const file, const int line, + const CheckParameterValue check_function, + const LargestIntegralType check_data, CheckParameterEvent *const event, const int count) +{ + CheckParameterEvent *const check = event ? event : malloc(sizeof(*check)); + const char *symbols[] = { function, parameter }; + check->parameter_name = parameter; + check->check_value = check_function; + check->check_value_data = check_data; + set_source_location(&check->location, file, line); + add_symbol_value(&global_function_parameter_map_head, symbols, 2, check, count); +} + +/* Returns 1 if the specified values are equal. If the values are not equal + * an error is displayed and 0 is returned. */ +static int values_equal_display_error(const LargestIntegralType left, const LargestIntegralType right) +{ + const int equal = left == right; + + if (!equal) + { + print_error(LargestIntegralTypePrintfFormat " != " LargestIntegralTypePrintfFormat "\n", left, right); + } + return equal; +} + +/* Returns 1 if the specified values are not equal. If the values are equal + * an error is displayed and 0 is returned. */ +static int values_not_equal_display_error(const LargestIntegralType left, const LargestIntegralType right) +{ + const int not_equal = left != right; + + if (!not_equal) + { + print_error(LargestIntegralTypePrintfFormat " == " LargestIntegralTypePrintfFormat "\n", left, right); + } + return not_equal; +} + +/* Determine whether value is contained within check_integer_set. + * If invert is 0 and the value is in the set 1 is returned, otherwise 0 is + * returned and an error is displayed. If invert is 1 and the value is not + * in the set 1 is returned, otherwise 0 is returned and an error is + * displayed. */ +static int value_in_set_display_error(const LargestIntegralType value, + const CheckIntegerSet *const check_integer_set, const int invert) +{ + int succeeded = invert; + + assert_true(check_integer_set); + { + const LargestIntegralType *const set = check_integer_set->set; + const size_t size_of_set = check_integer_set->size_of_set; + size_t i; + + for (i = 0; i < size_of_set; i++) + { + if (set[i] == value) + { + // If invert = 0 and item is found, succeeded = 1. + // If invert = 1 and item is found, succeeded = 0. + succeeded = !succeeded; + break; + } + } + if (succeeded) + { + return 1; + } + print_error(LargestIntegralTypePrintfDecimal " is %sin the set (", + value, invert ? "" : "not "); + for (i = 0; i < size_of_set; i++) + { + print_error(LargestIntegralTypePrintfDecimal ", ", set[i]); + } + print_error(")\n"); + } + return 0; +} + +/* Determine whether a value is within the specified range. If the value is + * within the specified range 1 is returned. If the value isn't within the + * specified range an error is displayed and 0 is returned. */ +static int integer_in_range_display_error(const LargestIntegralType value, const LargestIntegralType range_min, + const LargestIntegralType range_max) +{ + if (value >= range_min && value <= range_max) + { + return 1; + } + print_error(LargestIntegralTypePrintfDecimal " is not within the range " + LargestIntegralTypePrintfDecimal "-" LargestIntegralTypePrintfDecimal "\n", + value, range_min, range_max); + return 0; +} + +/* Determine whether a value is within the specified range. If the value + * is not within the range 1 is returned. If the value is within the + * specified range an error is displayed and zero is returned. */ +static int integer_not_in_range_display_error(const LargestIntegralType value, const LargestIntegralType range_min, + const LargestIntegralType range_max) +{ + if (value < range_min || value > range_max) + { + return 1; + } + print_error(LargestIntegralTypePrintfDecimal " is within the range " + LargestIntegralTypePrintfDecimal "-" LargestIntegralTypePrintfDecimal "\n", + value, range_min, range_max); + return 0; +} + +/* Determine whether the specified strings are equal. If the strings are equal + * 1 is returned. If they're not equal an error is displayed and 0 is + * returned. */ +static int string_equal_display_error(const char *const left, const char *const right) +{ + if (strcmp(left, right) == 0) + { + return 1; + } + print_error("\"%s\" != \"%s\"\n", left, right); + return 0; +} + +/* Determine whether the specified strings are equal. If the strings are not + * equal 1 is returned. If they're not equal an error is displayed and 0 is + * returned */ +static int string_not_equal_display_error(const char *const left, const char *const right) +{ + if (strcmp(left, right) != 0) + { + return 1; + } + print_error("\"%s\" == \"%s\"\n", left, right); + return 0; +} + +/* Determine whether the specified areas of memory are equal. If they're equal + * 1 is returned otherwise an error is displayed and 0 is returned. */ +static int memory_equal_display_error(const char *const a, const char *const b, const size_t size) +{ + int differences = 0; + size_t i; + + for (i = 0; i < size; i++) + { + const char l = a[i]; + const char r = b[i]; + + if (l != r) + { + print_error("difference at offset %d 0x%02x 0x%02x\n", i, l, r); + differences++; + } + } + if (differences) + { + print_error("%d bytes of 0x%08x and 0x%08x differ\n", differences, a, b); + return 0; + } + return 1; +} + +/* Determine whether the specified areas of memory are not equal. If they're + * not equal 1 is returned otherwise an error is displayed and 0 is + * returned. */ +static int memory_not_equal_display_error(const char *const a, const char *const b, const size_t size) +{ + int same = 0; + size_t i; + + for (i = 0; i < size; i++) + { + const char l = a[i]; + const char r = b[i]; + + if (l == r) + { + same++; + } + } + if (same == size) + { + print_error("%d bytes of 0x%08x and 0x%08x the same\n", same, a, b); + return 0; + } + return 1; +} + +// CheckParameterValue callback to check whether a value is within a set. +static int check_in_set(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + return value_in_set_display_error(value, + cast_largest_integral_type_to_pointer(CheckIntegerSet *, check_value_data), 0); +} + +// CheckParameterValue callback to check whether a value isn't within a set. +static int check_not_in_set(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + return value_in_set_display_error(value, + cast_largest_integral_type_to_pointer(CheckIntegerSet *, check_value_data), 1); +} + +/* Create the callback data for check_in_set() or check_not_in_set() and + * register a check event. */ +static void expect_set(const char *const function, const char *const parameter, + const char *const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, + const CheckParameterValue check_function, const int count) +{ + CheckIntegerSet *const check_integer_set = + malloc(sizeof(*check_integer_set) + (sizeof(values[0]) * number_of_values)); + LargestIntegralType *const set = (LargestIntegralType *) (check_integer_set + 1); + + declare_initialize_value_pointer_pointer(check_data, check_integer_set); + assert_true((const unsigned long long *)values); + assert_true(number_of_values); + memcpy(set, values, number_of_values * sizeof(values[0])); + check_integer_set->set = set; + _expect_check(function, parameter, file, line, check_function, check_data.value, &check_integer_set->event, count); +} + +// Add an event to check whether a value is in a set. +void _expect_in_set(const char *const function, const char *const parameter, + const char *const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, const int count) +{ + expect_set(function, parameter, file, line, values, number_of_values, check_in_set, count); +} + +// Add an event to check whether a value isn't in a set. +void _expect_not_in_set(const char *const function, const char *const parameter, + const char *const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, const int count) +{ + expect_set(function, parameter, file, line, values, number_of_values, check_not_in_set, count); +} + +// CheckParameterValue callback to check whether a value is within a range. +static int check_in_range(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + CheckIntegerRange *const check_integer_range = cast_largest_integral_type_to_pointer(CheckIntegerRange *, + check_value_data); + + assert_true(check_integer_range); + return integer_in_range_display_error(value, check_integer_range->minimum, check_integer_range->maximum); +} + +// CheckParameterValue callback to check whether a value is not within a range. +static int check_not_in_range(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + CheckIntegerRange *const check_integer_range = cast_largest_integral_type_to_pointer(CheckIntegerRange *, + check_value_data); + + assert_true(check_integer_range); + return integer_not_in_range_display_error(value, check_integer_range->minimum, check_integer_range->maximum); +} + +/* Create the callback data for check_in_range() or check_not_in_range() and + * register a check event. */ +static void expect_range(const char *const function, const char *const parameter, + const char *const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, + const CheckParameterValue check_function, const int count) +{ + CheckIntegerRange *const check_integer_range = malloc(sizeof(*check_integer_range)); + + declare_initialize_value_pointer_pointer(check_data, check_integer_range); + check_integer_range->minimum = minimum; + check_integer_range->maximum = maximum; + _expect_check(function, parameter, file, line, check_function, + check_data.value, &check_integer_range->event, count); +} + +// Add an event to determine whether a parameter is within a range. +void _expect_in_range(const char *const function, const char *const parameter, + const char *const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, const int count) +{ + expect_range(function, parameter, file, line, minimum, maximum, check_in_range, count); +} + +// Add an event to determine whether a parameter is not within a range. +void _expect_not_in_range(const char *const function, const char *const parameter, + const char *const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, const int count) +{ + expect_range(function, parameter, file, line, minimum, maximum, check_not_in_range, count); +} + +/* CheckParameterValue callback to check whether a value is equal to an + * expected value. */ +static int check_value(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + return values_equal_display_error(value, check_value_data); +} + +// Add an event to check a parameter equals an expected value. +void _expect_value(const char *const function, const char *const parameter, + const char *const file, const int line, const LargestIntegralType value, const int count) +{ + _expect_check(function, parameter, file, line, check_value, value, NULL, count); +} + +/* CheckParameterValue callback to check whether a value is not equal to an + * expected value. */ +static int check_not_value(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + return values_not_equal_display_error(value, check_value_data); +} + +// Add an event to check a parameter is not equal to an expected value. +void _expect_not_value(const char *const function, const char *const parameter, + const char *const file, const int line, const LargestIntegralType value, const int count) +{ + _expect_check(function, parameter, file, line, check_not_value, value, NULL, count); +} + +// CheckParameterValue callback to check whether a parameter equals a string. +static int check_string(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + return string_equal_display_error(cast_largest_integral_type_to_pointer(char *, value), + cast_largest_integral_type_to_pointer(char *, check_value_data)); +} + +// Add an event to check whether a parameter is equal to a string. +void _expect_string(const char *const function, const char *const parameter, + const char *const file, const int line, const char *string, const int count) +{ + declare_initialize_value_pointer_pointer(string_pointer, (char *) string); + _expect_check(function, parameter, file, line, check_string, string_pointer.value, NULL, count); +} + +/* CheckParameterValue callback to check whether a parameter is not equals to + * a string. */ +static int check_not_string(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + return string_not_equal_display_error(cast_largest_integral_type_to_pointer(char *, value), + cast_largest_integral_type_to_pointer(char *, check_value_data)); +} + +// Add an event to check whether a parameter is not equal to a string. +void _expect_not_string(const char *const function, const char *const parameter, + const char *const file, const int line, const char *string, const int count) +{ + declare_initialize_value_pointer_pointer(string_pointer, (char *) string); + _expect_check(function, parameter, file, line, check_not_string, string_pointer.value, NULL, count); +} + +/* CheckParameterValue callback to check whether a parameter equals an area of + * memory. */ +static int check_memory(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + CheckMemoryData *const check = cast_largest_integral_type_to_pointer(CheckMemoryData *, check_value_data); + + assert_true(check); + return memory_equal_display_error(cast_largest_integral_type_to_pointer(void *, value), check->memory, check->size); +} + +/* Create the callback data for check_memory() or check_not_memory() and + * register a check event. */ +static void expect_memory_setup(const char *const function, const char *const parameter, + const char *const file, const int line, + const void *const memory, const size_t size, + const CheckParameterValue check_function, const int count) +{ + CheckMemoryData *const check_data = malloc(sizeof(*check_data) + size); + void *const mem = (void *) (check_data + 1); + + declare_initialize_value_pointer_pointer(check_data_pointer, check_data); + assert_true(memory); + assert_true(size); + memcpy(mem, memory, size); + check_data->memory = mem; + check_data->size = size; + _expect_check(function, parameter, file, line, check_function, check_data_pointer.value, &check_data->event, count); +} + +// Add an event to check whether a parameter matches an area of memory. +void _expect_memory(const char *const function, const char *const parameter, + const char *const file, const int line, const void *const memory, + const size_t size, const int count) +{ + expect_memory_setup(function, parameter, file, line, memory, size, check_memory, count); +} + +/* CheckParameterValue callback to check whether a parameter is not equal to + * an area of memory. */ +static int check_not_memory(const LargestIntegralType value, const LargestIntegralType check_value_data) +{ + CheckMemoryData *const check = cast_largest_integral_type_to_pointer(CheckMemoryData *, check_value_data); + + assert_true(check); + return memory_not_equal_display_error(cast_largest_integral_type_to_pointer(void *, value), check->memory, + check->size); +} + +// Add an event to check whether a parameter doesn't match an area of memory. +void _expect_not_memory(const char *const function, const char *const parameter, + const char *const file, const int line, const void *const memory, + const size_t size, const int count) +{ + expect_memory_setup(function, parameter, file, line, memory, size, check_not_memory, count); +} + +// CheckParameterValue callback that always returns 1. +static int check_any(ARG_UNUSED const LargestIntegralType value, ARG_UNUSED const LargestIntegralType check_value_data) +{ + return 1; +} + +// Add an event to allow any value for a parameter. +void _expect_any(const char *const function, const char *const parameter, + const char *const file, const int line, const int count) +{ + _expect_check(function, parameter, file, line, check_any, 0, NULL, count); +} + +void _check_expected(const char *const function_name, const char *const parameter_name, + const char *file, const int line, const LargestIntegralType value) +{ + void *result; + const char *symbols[] = { function_name, parameter_name }; + const int rc = get_symbol_value(&global_function_parameter_map_head, + symbols, 2, &result); + if (rc) + { + CheckParameterEvent *const check = (CheckParameterEvent *) result; + int check_succeeded; + + global_last_parameter_location = check->location; + check_succeeded = check->check_value(value, check->check_value_data); + if (rc == 1) + { + free(check); + } + + if (!check_succeeded) + { + print_error("ERROR: Check of parameter %s, function %s failed\n" + "Expected parameter declared at " + SOURCE_LOCATION_FORMAT "\n", + parameter_name, function_name, + global_last_parameter_location.file, global_last_parameter_location.line); + print_xml(XS_RUN_TEST_ERROR + "\"ERROR: Check of parameter %s, function %s failed\"\n" + " \"Expected parameter declared at " + SOURCE_LOCATION_FORMAT "\">\n" + XS_RUN_TEST_ERROR_END, + "check_expected", parameter_name, function_name, + global_last_parameter_location.file, global_last_parameter_location.line); + global_errors++; + _fail(file, line); + } + } + else + { + print_error("ERROR: " SOURCE_LOCATION_FORMAT " - Could not get value " + "to check parameter %s of function %s\n", file, line, parameter_name, function_name); + print_xml(XS_RUN_TEST_ERROR + "\"ERROR: " SOURCE_LOCATION_FORMAT " - Could not get value " + "to check parameter %s of function %s\"\n", + "check_expected", file, line, parameter_name, function_name); + if (source_location_is_set(&global_last_parameter_location)) + { + print_error("Previously declared parameter value was declared at " + SOURCE_LOCATION_FORMAT "\n", + global_last_parameter_location.file, global_last_parameter_location.line); + print_xml(" \"Previously declared parameter value was declared at " + SOURCE_LOCATION_FORMAT "\">\n" + XS_RUN_TEST_ERROR_END, + global_last_parameter_location.file, global_last_parameter_location.line); + } + else + { + print_error("There were no previously declared parameter values " "for this test.\n"); + print_xml(" \"There were no previously declared parameter values " "for this test.\">\n" + XS_RUN_TEST_ERROR_END); + } + global_errors++; + exit_test (); + } +} + +// Replacement for assert. +void mock_assert(const int result, const char *const expression, const char *const file, const int line) +{ + if (!result) + { + if (global_expecting_assert) + { + global_expect_assert_expression = expression; + longjmp(global_expect_assert_env, 1); + } + else + { + const char *assertname = "mock_assert"; + print_error("ASSERT: %s\n", expression); + print_xml (XS_RUN_TEST_FAILURE_ASSERT, assertname, result, global_filename, line, assertname, result); + _fail(file, line); + } + } +} + +void _assert_true(const LargestIntegralType result, + const char *const expression, const char *const file, const int line) +{ + if (!result) + { + const char *assertname = "assert_true"; + print_error("%s\n", expression); + print_xml (XS_RUN_TEST_FAILURE_ASSERT, assertname, result, global_filename, line, assertname, result); + _fail(file, line); + } +} + +void _assert_int_equal(const LargestIntegralType a, const LargestIntegralType b, const char *const file, const int line) +{ + if (!values_equal_display_error(a, b)) + { + const char *assertname = "assert_int_equal"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_EQUALITY_LLD, assertname, a, b, global_filename, line, assertname, a, b); + _fail(file, line); + } +} + +void _assert_int_not_equal(const LargestIntegralType a, const LargestIntegralType b, + const char *const file, const int line) +{ + if (!values_not_equal_display_error(a, b)) + { + const char *assertname = "assert_int_not_equal"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_EQUALITY_LLD, assertname, a, b, global_filename, line, assertname, a, b); + _fail(file, line); + } +} + +void _assert_string_equal(const char *const a, const char *const b, const char *const file, const int line) +{ + if (!string_equal_display_error(a, b)) + { + const char *assertname = "assert_string_equal"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_EQUALITY_STRING, assertname, a, b, global_filename, line, assertname, a, b); + _fail(file, line); + } +} + +void _assert_string_not_equal(const char *const a, const char *const b, const char *file, const int line) +{ + if (!string_not_equal_display_error(a, b)) + { + const char *assertname = "assert_string_not_equal"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_EQUALITY_STRING, assertname, a, b, global_filename, line, assertname, a, b); + _fail(file, line); + } +} + +void _assert_memory_equal(const void *const a, const void *const b, + const size_t size, const char *const file, const int line) +{ + if (!memory_equal_display_error((const char *) a, (const char *) b, size)) + { + const char *assertname = "assert_memory_equal"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_EQUALITY_LLD, assertname, a, b, global_filename, line, assertname, a, b); + _fail(file, line); + } +} + +void _assert_memory_not_equal(const void *const a, const void *const b, + const size_t size, const char *const file, const int line) +{ + if (!memory_not_equal_display_error((const char *) a, (const char *) b, size)) + { + const char *assertname = "assert_memory_not_equal"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_EQUALITY_LLD, assertname, a, b, global_filename, line, assertname, a, b); + _fail(file, line); + } +} + +void _assert_in_range(const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char *const file, const int line) +{ + if (!integer_in_range_display_error(value, minimum, maximum)) + { + const char *assertname = "assert_in_range"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_RANGE_LLD, assertname, value, minimum, maximum, global_filename, line, assertname, value, minimum, maximum); + _fail(file, line); + } +} + +void _assert_not_in_range(const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char *const file, const int line) +{ + if (!integer_not_in_range_display_error(value, minimum, maximum)) + { + const char *assertname = "assert_not_in_range"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_RANGE_LLD, assertname, value, minimum, maximum, global_filename, line, assertname, value, minimum, maximum); + _fail(file, line); + } +} + +void _assert_in_set(const LargestIntegralType value, + const LargestIntegralType values[], + const size_t number_of_values, const char *const file, const int line) +{ + CheckIntegerSet check_integer_set; + + check_integer_set.set = values; + check_integer_set.size_of_set = number_of_values; + if (!value_in_set_display_error(value, &check_integer_set, 0)) + { + const char *assertname = "assert_in_set"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_SET_LLD, assertname, value, number_of_values, global_filename, line, assertname, value, number_of_values); + _fail(file, line); + } +} + +void _assert_not_in_set(const LargestIntegralType value, + const LargestIntegralType values[], + const size_t number_of_values, const char *const file, const int line) +{ + CheckIntegerSet check_integer_set; + + check_integer_set.set = values; + check_integer_set.size_of_set = number_of_values; + if (!value_in_set_display_error(value, &check_integer_set, 1)) + { + const char *assertname = "assert_not_in_set"; + print_xml (XS_RUN_TEST_FAILURE_ASSERT_SET_LLD, assertname, value, number_of_values, global_filename, line, assertname, value, number_of_values); + _fail(file, line); + } +} + +// Get the list of allocated blocks. +static ListNode *get_allocated_blocks_list() +{ + // If it initialized, initialize the list of allocated blocks. + if (!global_allocated_blocks.value) + { + list_initialize(&global_allocated_blocks); + global_allocated_blocks.value = (void *) 1; + } + return &global_allocated_blocks; +} + +// Use the real malloc in this function. +#undef malloc +void *_test_malloc(const size_t size, const char *file, const int line) +{ + char *ptr; + MallocBlockInfo *block_info; + ListNode *const block_list = get_allocated_blocks_list(); + const size_t allocate_size = size + (MALLOC_GUARD_SIZE * 2) + sizeof(*block_info) + MALLOC_ALIGNMENT; + char *const block = (char *) malloc(allocate_size); + + assert_true(block); + + // Calculate the returned address. + ptr = (char *) (((size_t) block + MALLOC_GUARD_SIZE + sizeof(*block_info) + + MALLOC_ALIGNMENT) & ~(MALLOC_ALIGNMENT - 1)); + + // Initialize the guard blocks. + memset(ptr - MALLOC_GUARD_SIZE, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE); + memset(ptr + size, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE); + memset(ptr, MALLOC_ALLOC_PATTERN, size); + + block_info = (MallocBlockInfo *) (ptr - (MALLOC_GUARD_SIZE + sizeof(*block_info))); + set_source_location(&block_info->location, file, line); + block_info->allocated_size = allocate_size; + block_info->size = size; + block_info->block = block; + block_info->node.value = block_info; + list_add(block_list, &block_info->node); + return ptr; +} + +#define malloc test_malloc + +void *_test_calloc(const size_t number_of_elements, const size_t size, const char *file, const int line) +{ + void *const ptr = _test_malloc(number_of_elements * size, file, line); + + if (ptr) + { + memset(ptr, 0, number_of_elements * size); + } + return ptr; +} + +// Use the real free in this function. +#undef free +void _test_free(void *const ptr, const char *file, const int line) +{ + unsigned int i; + char *block = (char *) ptr; + MallocBlockInfo *block_info; + + _assert_true(ptr != NULL, "ptr", file, line); + block_info = (MallocBlockInfo *) (block - (MALLOC_GUARD_SIZE + sizeof(*block_info))); + // Check the guard blocks. + { + char *guards[2] = { block - MALLOC_GUARD_SIZE, + block + block_info->size + }; + for (i = 0; i < ARRAY_LENGTH(guards); i++) + { + unsigned int j; + char *const guard = guards[i]; + + for (j = 0; j < MALLOC_GUARD_SIZE; j++) + { + const char diff = guard[j] - MALLOC_GUARD_PATTERN; + + if (diff) + { + print_error("Guard block of 0x%08x size=%d allocated by " + SOURCE_LOCATION_FORMAT " at 0x%08x is corrupt\n", + (size_t) ptr, block_info->size, + block_info->location.file, block_info->location.line, (size_t) &guard[j]); + print_xml(XS_RUN_TEST_ERROR + "\"Guard block of 0x%08x size=%d allocated by " + SOURCE_LOCATION_FORMAT " at 0x%08x is corrupt\">\n" + XS_RUN_TEST_ERROR_END, + "test_free", (size_t) ptr, block_info->size, + block_info->location.file, block_info->location.line, (size_t) &guard[j]); + global_errors++; + _fail(file, line); + } + } + } + } + list_remove(&block_info->node, NULL, NULL); + + block = block_info->block; + memset(block, MALLOC_FREE_PATTERN, block_info->allocated_size); + free(block); +} + +#define free test_free + +// Crudely checkpoint the current heap state. +static const ListNode *check_point_allocated_blocks() +{ + return get_allocated_blocks_list()->prev; +} + +/* Display the blocks allocated after the specified check point. This + * function returns the number of blocks displayed. */ +static int display_allocated_blocks(const ListNode *const check_point) +{ + const ListNode *const head = get_allocated_blocks_list(); + const ListNode *node; + int allocated_blocks = 0; + + assert_true(check_point); + assert_true(check_point->next); + + for (node = check_point->next; node != head; node = node->next) + { + const MallocBlockInfo *const block_info = node->value; + + assert_true(block_info); + + if (!allocated_blocks) + { + print_error("Blocks allocated...\n"); + } + print_error(" 0x%08x : " SOURCE_LOCATION_FORMAT "\n", + block_info->block, block_info->location.file, block_info->location.line); + allocated_blocks++; + } + return allocated_blocks; +} + +// Free all blocks allocated after the specified check point. +static void free_allocated_blocks(const ListNode *const check_point) +{ + const ListNode *const head = get_allocated_blocks_list(); + const ListNode *node; + + assert_true(check_point); + + node = check_point->next; + assert_true(node); + + while (node != head) + { + MallocBlockInfo *const block_info = (MallocBlockInfo *) node->value; + + node = node->next; + free((char *) block_info + sizeof(*block_info) + MALLOC_GUARD_SIZE); + } +} + +// Fail if any any blocks are allocated after the specified check point. +static void fail_if_blocks_allocated(const ListNode *const check_point, const char *const test_name) +{ + const int allocated_blocks = display_allocated_blocks(check_point); + + if (allocated_blocks) + { + free_allocated_blocks(check_point); + print_error("ERROR: %s leaked %d block(s)\n", test_name, allocated_blocks); + print_xml(XS_RUN_TEST_ERROR + "\"ERROR: %s leaked %d block(s)\">\n" + XS_RUN_TEST_ERROR_END, "fail_if_blocks_allocated", test_name, allocated_blocks); + global_errors++; + exit_test(); + } +} + +void _fail(const char *const file, const int line) +{ + print_error("ERROR: " SOURCE_LOCATION_FORMAT " Failure!\n", file, line); + exit_test(); +} + +#ifndef _WIN32 +static void exception_handler(int sig) +{ + print_error("%s\n", strsignal(sig)); + print_xml(XS_RUN_TEST_ERROR + "\"%s\">\n" + XS_RUN_TEST_ERROR_END, "exception_handler", strsignal(sig)); + global_errors++; + exit_test(); +} + +#else // _WIN32 + +static LONG WINAPI exception_filter(EXCEPTION_POINTERS *exception_pointers) +{ + EXCEPTION_RECORD *const exception_record = exception_pointers->ExceptionRecord; + const DWORD code = exception_record->ExceptionCode; + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(exception_codes); i++) + { + const ExceptionCodeInfo *const code_info = &exception_codes[i]; + + if (code == code_info->code) + { + static int shown_debug_message = 0; + + fflush(stdout); + print_error("%s occurred at 0x%08x.\n", code_info->description, exception_record->ExceptionAddress); + print_xml(XS_RUN_TEST_ERROR + "\"%s occurred at 0x%08x.\">\n" + XS_RUN_TEST_ERROR_END, "exception_filter", code_info->description, exception_record->ExceptionAddress); + if (!shown_debug_message) + { + print_error("\n" + "To debug in Visual Studio...\n" + "1. Select menu item File->Open Project\n" + "2. Change 'Files of type' to 'Executable Files'\n" + "3. Open this executable.\n" + "4. Select menu item Debug->Start\n" + "\n" + "Alternatively, set the environment variable \n" + "UNIT_TESTING_DEBUG to 1 and rebuild this executable, \n" + "then click 'Debug' in the popup dialog box.\n" "\n"); + shown_debug_message = 1; + } + global_errors++; + exit_test_no_app(); + return EXCEPTION_EXECUTE_HANDLER; + } + } + return EXCEPTION_CONTINUE_SEARCH; +} +#endif // !_WIN32 + +void vinit_xml (const char *const format, va_list args) +{ + FILE* xmlfile = fopen(global_xmlfile, "w"); + vfprintf(xmlfile, format, args); + fclose(xmlfile); +#ifdef _WIN32 + OutputDebugString(buffer); +#endif // _WIN32 +} + +void init_xml (const char *const format, ...) +{ + if (!global_is_file_writer_test) + { + va_list args; + va_start(args, format); + vinit_xml(format, args); + va_end(args); + } +} + +void vprint_xml(const char *const format, va_list args) +{ + FILE* xmlfile = fopen(global_xmlfile, "a"); + vfprintf(xmlfile, format, args); + fclose(xmlfile); +#ifdef _WIN32 + OutputDebugString(buffer); +#endif // _WIN32 +} + +void print_xml(const char *const format, ...) +{ + if (!global_is_file_writer_test) + { + va_list args; + va_start(args, format); + vprint_xml(format, args); + va_end(args); + } +} + +void append_xml(const char *ofile, const char *ifile) +{ + if (!global_is_file_writer_test) + { + char ch; + FILE* xmlfile = fopen(ofile, "ab"); + FILE* xml_tmp = fopen(ifile, "rb"); + while(!feof(xml_tmp)) + { + ch = getc(xml_tmp); + if(!feof(xml_tmp)) + { + putc(ch, xmlfile); + } + } + fclose(xmlfile); + fclose(xml_tmp); + } +} + +// Standard output and error print methods. +void vprint_message(const char *const format, va_list args) +{ + vprintf(format, args); +#ifdef _WIN32 + OutputDebugString(buffer); +#endif // _WIN32 +} + +void vprint_error(const char *const format, va_list args) +{ + vfprintf(stderr, format, args); +#ifdef _WIN32 + OutputDebugString(buffer); +#endif // _WIN32 +} + +void print_message(const char *const format, ...) +{ + va_list args; + + va_start(args, format); + vprint_message(format, args); + va_end(args); +} + +void print_error(const char *const format, ...) +{ + va_list args; + + va_start(args, format); + vprint_error(format, args); + va_end(args); +} + +int _run_test(const char *const function_name, const UnitTestFunction Function, + void **const state, const UnitTestFunctionType function_type, const void *const heap_check_point) +{ + const ListNode *const check_point = heap_check_point ? heap_check_point : check_point_allocated_blocks(); + void *current_state = NULL; + int rc = 1; + int handle_exceptions = 1; + +#ifdef _WIN32 + handle_exceptions = !IsDebuggerPresent(); +#endif // _WIN32 +#if UNIT_TESTING_DEBUG + handle_exceptions = 0; +#endif // UNIT_TESTING_DEBUG + + if (handle_exceptions) + { +#ifndef _WIN32 + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) + { + default_signal_functions[i] = signal(exception_signals[i], exception_handler); + } +#else // _WIN32 + previous_exception_filter = SetUnhandledExceptionFilter(exception_filter); +#endif // !_WIN32 + } + + if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST || function_type == UNIT_TEST_FUNCTION_TYPE_TEST_WITH_STATE) + { + print_message("%s: Starting test\n", function_name); + } + initialize_testing(); + global_running_test = 1; + if (setjmp(global_run_test_env) == 0) + { + if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) + { + Function(); + } + else + { + ((UnitTestFunctionWithState)Function)(state ? state : ¤t_state); + } + + fail_if_leftover_values(function_name); + + /* If this is a setup function then ignore any allocated blocks + * only ensure they're deallocated on tear down. */ + if (function_type != UNIT_TEST_FUNCTION_TYPE_SETUP) + { + fail_if_blocks_allocated(check_point, function_name); + } + + global_running_test = 0; + + if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST || function_type == UNIT_TEST_FUNCTION_TYPE_TEST_WITH_STATE) + { + print_message("%s: Test completed successfully.\n", function_name); + } + rc = 0; + } + else + { + global_running_test = 0; + print_message("%s: Test failed.\n", function_name); + } + teardown_testing(); + + if (handle_exceptions) + { +#ifndef _WIN32 + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) + { + signal(exception_signals[i], default_signal_functions[i]); + } +#else // _WIN32 + if (previous_exception_filter) + { + SetUnhandledExceptionFilter(previous_exception_filter); + previous_exception_filter = NULL; + } +#endif // !_WIN32 + } + + return rc; +} + +int _run_tests(const UnitTest *const tests, const size_t number_of_tests, const char *const file) +{ + // Whether to execute the next test. + int run_next_test = 1; + + // Whether the previous test failed. + int previous_test_failed = 0; + + // Check point of the heap state. + const ListNode *const check_point = check_point_allocated_blocks(); + + + // Time of testsuite execution + time_t time_suite, time_case, time_now; + time_t ttime; + time(&ttime); + char timestamp[1024]; + strcpy(timestamp, ctime(&ttime)); + timestamp[strlen(timestamp)-1] = '\0'; + + // Current test being executed. + size_t current_test = 0; + + // Number of tests executed. + size_t tests_executed = 0; + + // Number of failed tests. + size_t total_failed = 0; + + // Number of setup functions. + size_t setups = 0; + + // Number of teardown functions. + size_t teardowns = 0; + + /* A stack of test states. A state is pushed on the stack + * when a test setup occurs and popped on tear down. */ + TestState *test_states = malloc(number_of_tests * sizeof(*test_states)); + size_t number_of_test_states = 0; + + // Names of the tests that failed. + const char **failed_names = malloc(number_of_tests * sizeof(*failed_names)); + void **current_state = NULL; + + // Make sure LargestIntegralType is at least the size of a pointer. + assert_true(sizeof(LargestIntegralType) >= sizeof(void *)); + + //Initialize an xml file and parameters + char path[1024] = {0}; + char filename[1024] = {0}; + char suitename[1024] = {0}; + char casename[1024] = {0}; + char xmlfile[sizeof(suitename) + sizeof(".xml")] = {0}; + int len; + + strcpy(path, file); + strcpy(filename, basename(path)); + len = strrchr(filename, '.')-filename; + + strcpy(suitename, ""); + strncat(suitename, filename, len); + strcpy(xmlfile, "xml_tmp_suite"); + + global_filename = filename; + global_is_file_writer_test = (0 == strcmp(suitename, "file_writer_test")); + global_xmlfile = xmlfile; + global_errors = 0; + + init_xml(""); + time(&time_suite); + + while (current_test < number_of_tests) + { + const ListNode *test_check_point = NULL; + TestState *current_TestState; + const UnitTest *const test = &tests[current_test++]; + + if (!test->f.function) + { + continue; + } + + switch (test->function_type) + { + case UNIT_TEST_FUNCTION_TYPE_TEST: + case UNIT_TEST_FUNCTION_TYPE_TEST_WITH_STATE: + run_next_test = 1; + break; + case UNIT_TEST_FUNCTION_TYPE_SETUP: + { + // Checkpoint the heap before the setup. + current_TestState = &test_states[number_of_test_states++]; + current_TestState->check_point = check_point_allocated_blocks(); + test_check_point = current_TestState->check_point; + current_state = ¤t_TestState->state; + *current_state = NULL; + run_next_test = 1; + setups++; + break; + } + case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: + // Check the heap based on the last setup checkpoint. + assert_true(number_of_test_states); + current_TestState = &test_states[--number_of_test_states]; + test_check_point = current_TestState->check_point; + current_state = ¤t_TestState->state; + teardowns++; + break; + default: + print_error("Invalid unit test function type %d\n", test->function_type); + print_xml(XS_RUN_TEST_ERROR + "\"Invalid unit test function type %d\">\n" + XS_RUN_TEST_ERROR_END, + "", test->function_type); + global_errors++; + exit_test(); + break; + } + + if (run_next_test) + { + strcpy(casename, test->name); + strcpy(xmlfile, "xml_tmp_case"); + init_xml(""); + time(&time_case); + int failed = _run_test(test->name, test->f.function, current_state, + test->function_type, test_check_point); + strcpy(xmlfile, "xml_tmp_suite"); + time(&time_now); + print_xml(XS_TESTCASE, casename, path, difftime(time_now, time_case)); + if (failed) + { + failed_names[total_failed] = test->name; + append_xml("xml_tmp_suite", "xml_tmp_case"); + } + print_xml(XS_TESTCASE_END); + switch (test->function_type) + { + case UNIT_TEST_FUNCTION_TYPE_TEST: + case UNIT_TEST_FUNCTION_TYPE_TEST_WITH_STATE: + previous_test_failed = failed; + total_failed += failed; + tests_executed++; + break; + + case UNIT_TEST_FUNCTION_TYPE_SETUP: + if (failed) + { + total_failed++; + tests_executed++; + // Skip forward until the next test or setup function. + run_next_test = 0; + } + previous_test_failed = 0; + break; + + case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: + // If this test failed. + if (failed && !previous_test_failed) + { + total_failed++; + } + break; + default: + assert_false("BUG: shouldn't be here!"); + break; + } + } + } + snprintf(xmlfile, sizeof(xmlfile), "%s.xml", suitename); + time(&time_now); + init_xml(XS_INIT_TESTSUITE, suitename, timestamp, "localhost", number_of_tests, total_failed, global_errors, difftime(time_now, time_suite)); + append_xml(xmlfile, "xml_tmp_suite"); + print_xml(XS_TESTSUITE_END); + + if (total_failed) + { + size_t i; + + print_error("%d out of %d tests failed!\n", total_failed, tests_executed); + for (i = 0; i < total_failed; i++) + { + print_error(" %s\n", failed_names[i]); + } + } + else + { + print_message("All %d tests passed\n", tests_executed); + } + + if (number_of_test_states) + { + print_error("Mismatched number of setup %d and teardown %d " "functions\n", setups, teardowns); + total_failed = -1; + } + + free(test_states); + free((void *) failed_names); + + fail_if_blocks_allocated(check_point, "run_tests"); + return (int) total_failed; +} + +/* LCOV_EXCL_STOP */ diff --git a/tests/unit/cmockery.h b/tests/unit/cmockery.h new file mode 100644 index 0000000000..d9b63365db --- /dev/null +++ b/tests/unit/cmockery.h @@ -0,0 +1,476 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CMOCKERY_H_ +# define CMOCKERY_H_ +/* + * These headers or their equivalents should be included prior to including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + */ + +# if defined(__GNUC__) && (__GNUC__ * 100 >= 3) +# define FUNC_ATTR_NORETURN __attribute__((noreturn)) +# else/* not gcc >= 3.0 */ +# define FUNC_ATTR_NORETURN +# endif + +// For those who are used to __func__ from gcc. +# ifndef __func__ +# define __func__ __FUNCTION__ +# endif + +/* Largest integral type. This type should be large enough to hold any + * pointer or integer supported by the compiler. */ +# ifndef LargestIntegralType +# define LargestIntegralType unsigned long long +# endif// LargestIntegralType + +// Printf format used to display LargestIntegralType. +# ifndef LargestIntegralTypePrintfFormat +# ifdef _WIN32 +# define LargestIntegralTypePrintfFormat "%I64x" +# define LargestIntegralTypePrintfDecimal "%I64d" +# else +# define LargestIntegralTypePrintfFormat "%llx" +# define LargestIntegralTypePrintfDecimal "%lld" +# endif + // _WIN32 +# endif// LargestIntegralTypePrintfFormat + +// Perform an unsigned cast to LargestIntegralType. +# define cast_to_largest_integral_type(value) \ + _cast_to_largest_integral_type(sizeof(value), value) + +// Retrieves a return value for the current function. +# define mock() _mock(__func__, __FILE__, __LINE__) + +/* Stores a value to be returned by the specified function later. + * The count parameter returns the number of times the value should be returned + * by mock(). If count is set to -1 the value will always be returned. + */ +# define will_return(function, value) \ + _will_return(#function, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), 1) +# define will_return_count(function, value, count) \ + _will_return(#function, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) + +/* Add a custom parameter checking function. If the event parameter is NULL + * the event structure is allocated internally by this function. If event + * parameter is provided it must be allocated on the heap and doesn't need to + * be deallocated by the caller. + */ +# define expect_check(function, parameter, check_function, check_data) \ + _expect_check(#function, #parameter, __FILE__, __LINE__, check_function, \ + cast_to_largest_integral_type(check_data), NULL, 0) + +/* Add an event to check a parameter, using check_expected(), against a set of + * values. See will_return() for a description of the count parameter. + */ +# define expect_in_set(function, parameter, value_array) \ + expect_in_set_count(function, parameter, value_array, 1) +# define expect_in_set_count(function, parameter, value_array, count) \ + _expect_in_set(#function, #parameter, __FILE__, __LINE__, value_array, \ + sizeof(value_array) / sizeof((value_array)[0]), count) +# define expect_not_in_set(function, parameter, value_array) \ + expect_not_in_set_count(function, parameter, value_array, 1) +# define expect_not_in_set_count(function, parameter, value_array, count) \ + _expect_not_in_set( \ + #function, #parameter, __FILE__, __LINE__, value_array, \ + sizeof(value_array) / sizeof((value_array)[0]), count) + +/* Add an event to check a parameter, using check_expected(), against a + * signed range. Where range is minimum <= value <= maximum. + * See will_return() for a description of the count parameter. + */ +# define expect_in_range(function, parameter, minimum, maximum) \ + expect_in_range_count(function, parameter, minimum, maximum, 1) +# define expect_in_range_count(function, parameter, minimum, maximum, count) \ + _expect_in_range(#function, #parameter, __FILE__, __LINE__, minimum, \ + maximum, count) + +/* Add an event to check a parameter, using check_expected(), against a + * signed range. Where range is value < minimum or value > maximum. + * See will_return() for a description of the count parameter. + */ +# define expect_not_in_range(function, parameter, minimum, maximum) \ + expect_not_in_range_count(function, parameter, minimum, maximum, 1) +# define expect_not_in_range_count(function, parameter, minimum, maximum, \ + count) \ + _expect_not_in_range(#function, #parameter, __FILE__, __LINE__, \ + minimum, maximum, count) + +/* Add an event to check whether a parameter, using check_expected(), is or + * isn't a value. See will_return() for a description of the count parameter. + */ +# define expect_value(function, parameter, value) \ + expect_value_count(function, parameter, value, 1) +# define expect_value_count(function, parameter, value, count) \ + _expect_value(#function, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) +# define expect_not_value(function, parameter, value) \ + expect_not_value_count(function, parameter, value, 1) +# define expect_not_value_count(function, parameter, value, count) \ + _expect_not_value(#function, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) + +/* Add an event to check whether a parameter, using check_expected(), + * is or isn't a string. See will_return() for a description of the count + * parameter. + */ +# define expect_string(function, parameter, string) \ + expect_string_count(function, parameter, string, 1) +# define expect_string_count(function, parameter, string, count) \ + _expect_string(#function, #parameter, __FILE__, __LINE__, \ + (const char*)(string), count) +# define expect_not_string(function, parameter, string) \ + expect_not_string_count(function, parameter, string, 1) +# define expect_not_string_count(function, parameter, string, count) \ + _expect_not_string(#function, #parameter, __FILE__, __LINE__, \ + (const char*)(string), count) + +/* Add an event to check whether a parameter, using check_expected() does or + * doesn't match an area of memory. See will_return() for a description of + * the count parameter. + */ +# define expect_memory(function, parameter, memory, size) \ + expect_memory_count(function, parameter, memory, size, 1) +# define expect_memory_count(function, parameter, memory, size, count) \ + _expect_memory(#function, #parameter, __FILE__, __LINE__, \ + (const void*)(memory), size, count) +# define expect_not_memory(function, parameter, memory, size) \ + expect_not_memory_count(function, parameter, memory, size, 1) +# define expect_not_memory_count(function, parameter, memory, size, count) \ + _expect_not_memory(#function, #parameter, __FILE__, __LINE__, \ + (const void*)(memory), size, count) + +/* Add an event to allow any value for a parameter checked using + * check_expected(). See will_return() for a description of the count + * parameter. + */ +# define expect_any(function, parameter) \ + expect_any_count(function, parameter, 1) +# define expect_any_count(function, parameter, count) \ + _expect_any(#function, #parameter, __FILE__, __LINE__, count) + +/* Determine whether a function parameter is correct. This ensures the next + * value queued by one of the expect_*() macros matches the specified variable. + */ +# define check_expected(parameter) \ + _check_expected(__func__, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(parameter)) + +// Assert that the given expression is true. +# define assert_true(c) _assert_true(cast_to_largest_integral_type(c), #c, \ + __FILE__, __LINE__) +// Assert that the given expression is false. +# define assert_false(c) _assert_true(!(cast_to_largest_integral_type(c)), #c, \ + __FILE__, __LINE__) + +// Assert that the two given integers are equal, otherwise fail. +# define assert_int_equal(a, b) \ + _assert_int_equal(cast_to_largest_integral_type(a), \ + cast_to_largest_integral_type(b), \ + __FILE__, __LINE__) +// Assert that the two given integers are not equal, otherwise fail. +# define assert_int_not_equal(a, b) \ + _assert_int_not_equal(cast_to_largest_integral_type(a), \ + cast_to_largest_integral_type(b), \ + __FILE__, __LINE__) + +// Assert that the two given strings are equal, otherwise fail. +# define assert_string_equal(a, b) \ + _assert_string_equal((const char*)(a), (const char*)(b), __FILE__, \ + __LINE__) +// Assert that the two given strings are not equal, otherwise fail. +# define assert_string_not_equal(a, b) \ + _assert_string_not_equal((const char*)(a), (const char*)(b), __FILE__, \ + __LINE__) + +// Assert that the two given areas of memory are equal, otherwise fail. +# define assert_memory_equal(a, b, size) \ + _assert_memory_equal((const char*)(a), (const char*)(b), size, __FILE__, \ + __LINE__) +// Assert that the two given areas of memory are not equal, otherwise fail. +# define assert_memory_not_equal(a, b, size) \ + _assert_memory_not_equal((const char*)(a), (const char*)(b), size, \ + __FILE__, __LINE__) + +// Assert that the specified value is >= minimum and <= maximum. +# define assert_in_range(value, minimum, maximum) \ + _assert_in_range( \ + cast_to_largest_integral_type(value), \ + cast_to_largest_integral_type(minimum), \ + cast_to_largest_integral_type(maximum), __FILE__, __LINE__) + +// Assert that the specified value is < minimum or > maximum +# define assert_not_in_range(value, minimum, maximum) \ + _assert_not_in_range( \ + cast_to_largest_integral_type(value), \ + cast_to_largest_integral_type(minimum), \ + cast_to_largest_integral_type(maximum), __FILE__, __LINE__) + +// Assert that the specified value is within a set. +# define assert_in_set(value, values, number_of_values) \ + _assert_in_set(value, values, number_of_values, __FILE__, __LINE__) +// Assert that the specified value is not within a set. +# define assert_not_in_set(value, values, number_of_values) \ + _assert_not_in_set(value, values, number_of_values, __FILE__, __LINE__) + +// Forces the test to fail immediately and quit. +# define fail() _fail(__FILE__, __LINE__) + +// Generic method to kick off testing +# define run_test(f) _run_test(#f, f, NULL, UNIT_TEST_FUNCTION_TYPE_TEST, NULL) + +// Initializes a UnitTest structure. +# define unit_test(func) { .name = #func, .f = { .function = func }, .function_type = UNIT_TEST_FUNCTION_TYPE_TEST } +# define unit_test_with_state(func) { .name = #func, .f = { .function_with_state = func }, .function_type = UNIT_TEST_FUNCTION_TYPE_TEST_WITH_STATE } +# define unit_test_setup(test, setup) \ + { .name = #test "_" #setup, .f = { .function_with_state = setup }, .function_type = UNIT_TEST_FUNCTION_TYPE_SETUP } +# define unit_test_teardown(test, teardown) \ + { .name = #test "_" #teardown, .f = { .function_with_state = teardown }, .function_type = UNIT_TEST_FUNCTION_TYPE_TEARDOWN } + +/* Initialize an array of UnitTest structures with a setup function for a test + * and a teardown function. Either setup or teardown can be NULL. + */ +# define unit_test_setup_teardown(test, setup, teardown) \ + unit_test_setup(test, setup), \ + unit_test_with_state(test), \ + unit_test_teardown(test, teardown) + +/* + * Run tests specified by an array of UnitTest structures. The following + * example illustrates this macro's use with the unit_test macro. + * + * void Test0(); + * void Test1(); + * + * int main(int argc, char* argv[]) { + * const UnitTest tests[] = +{ + * unit_test(Test0); + * unit_test(Test1); + * }; + * return run_tests(tests); + * } + */ +# define run_tests(tests) _run_tests(tests, sizeof(tests) / sizeof(tests)[0], __FILE__) + +// Dynamic allocators +# define test_malloc(size) _test_malloc(size, __FILE__, __LINE__) +# define test_calloc(num, size) _test_calloc(num, size, __FILE__, __LINE__) +# define test_free(ptr) _test_free(ptr, __FILE__, __LINE__) + +// Redirect malloc, calloc and free to the unit test allocators. +# if UNIT_TESTING +# define malloc test_malloc +# define calloc test_calloc +# define free test_free +# endif// UNIT_TESTING + +/* + * Ensure mock_assert() is called. If mock_assert() is called the assert + * expression string is returned. + * For example: + * + * #define assert mock_assert + * + * void showmessage(const char *message) { + * assert(message); + * } + * + * int main(int argc, const char* argv[]) { + * expect_assert_failure(show_message(NULL)); + * printf("succeeded\n"); + * return 0; + * } + */ +# define expect_assert_failure(function_call) \ + { \ + const int has_expression = setjmp(global_expect_assert_env); \ + global_expecting_assert = 1; \ + if (has_expression) { \ + print_message("Expected assertion %s occurred\n", \ + global_expect_assert_expression); \ + global_expecting_assert = 0; \ + } else { \ + function_call ; \ + global_expecting_assert = 0; \ + print_error("Expected assert in %s\n", #function_call); \ + _fail(__FILE__, __LINE__); \ + } \ + } + +// Function prototype for setup, test and teardown functions. +typedef void (*UnitTestFunction) (void); + +typedef void (*UnitTestFunctionWithState) (void **state); + +// Function that determines whether a function parameter value is correct. +typedef int (*CheckParameterValue) (const LargestIntegralType value, const LargestIntegralType check_value_data); + +// Type of the unit test function. +typedef enum UnitTestFunctionType +{ + UNIT_TEST_FUNCTION_TYPE_TEST = 0, + UNIT_TEST_FUNCTION_TYPE_TEST_WITH_STATE, + UNIT_TEST_FUNCTION_TYPE_SETUP, + UNIT_TEST_FUNCTION_TYPE_TEARDOWN, +} UnitTestFunctionType; + +/* Stores a unit test function with its name and type. + * NOTE: Every setup function must be paired with a teardown function. It's + * possible to specify NULL function pointers. + */ +typedef struct UnitTest +{ + const char *name; + union + { + UnitTestFunction function; + UnitTestFunctionWithState function_with_state; + } f; + UnitTestFunctionType function_type; +} UnitTest; + +// Location within some source code. +typedef struct SourceLocation +{ + const char *file; + int line; +} SourceLocation; + +// Event that's called to check a parameter value. +typedef struct CheckParameterEvent +{ + SourceLocation location; + const char *parameter_name; + CheckParameterValue check_value; + LargestIntegralType check_value_data; +} CheckParameterEvent; + +// Used by expect_assert_failure() and mock_assert(). +extern int global_expecting_assert; +extern const char *global_expect_assert_expression; +extern jmp_buf global_expect_assert_env; + +LargestIntegralType _cast_to_largest_integral_type(size_t size, ...); + +// Retrieves a value for the given function, as set by "will_return". +LargestIntegralType _mock(const char *const function, const char *const file, const int line); + +void _expect_check(const char *const function, const char *const parameter, + const char *const file, const int line, + const CheckParameterValue check_function, + const LargestIntegralType check_data, CheckParameterEvent *const event, const int count); + +void _expect_in_set(const char *const function, const char *const parameter, + const char *const file, const int line, const LargestIntegralType values[], + const size_t number_of_values, const int count); +void _expect_not_in_set(const char *const function, const char *const parameter, + const char *const file, const int line, const LargestIntegralType values[], + const size_t number_of_values, const int count); + +void _expect_in_range(const char *const function, const char *const parameter, + const char *const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, const int count); +void _expect_not_in_range(const char *const function, const char *const parameter, + const char *const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, const int count); + +void _expect_value(const char *const function, const char *const parameter, + const char *const file, const int line, const LargestIntegralType value, const int count); +void _expect_not_value(const char *const function, const char *const parameter, + const char *const file, const int line, const LargestIntegralType value, const int count); + +void _expect_string(const char *const function, const char *const parameter, + const char *const file, const int line, const char *string, const int count); +void _expect_not_string(const char *const function, const char *const parameter, + const char *const file, const int line, const char *string, const int count); + +void _expect_memory(const char *const function, const char *const parameter, + const char *const file, const int line, const void *const memory, + const size_t size, const int count); +void _expect_not_memory(const char *const function, const char *const parameter, + const char *const file, const int line, const void *const memory, + const size_t size, const int count); + +void _expect_any(const char *const function, const char *const parameter, + const char *const file, const int line, const int count); + +void _check_expected(const char *const function_name, const char *const parameter_name, + const char *file, const int line, const LargestIntegralType value); + +// Can be used to replace assert in tested code so that in conjunction with +// check_assert() it's possible to determine whether an assert condition has +// failed without stopping a test. +void mock_assert(const int result, const char *const expression, const char *const file, const int line); + +void _will_return(const char *const function_name, const char *const file, + const int line, const LargestIntegralType value, const int count); +void _assert_true(const LargestIntegralType result, + const char *const expression, const char *const file, const int line); +void _assert_int_equal(const LargestIntegralType a, const LargestIntegralType b, + const char *const file, const int line); +void _assert_int_not_equal(const LargestIntegralType a, const LargestIntegralType b, + const char *const file, const int line); +void _assert_string_equal(const char *const a, const char *const b, const char *const file, const int line); +void _assert_string_not_equal(const char *const a, const char *const b, const char *file, const int line); +void _assert_memory_equal(const void *const a, const void *const b, + const size_t size, const char *const file, const int line); +void _assert_memory_not_equal(const void *const a, const void *const b, + const size_t size, const char *const file, const int line); +void _assert_in_range(const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char *const file, const int line); +void _assert_not_in_range(const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char *const file, const int line); +void _assert_in_set(const LargestIntegralType value, const LargestIntegralType values[], + const size_t number_of_values, const char *const file, const int line); +void _assert_not_in_set(const LargestIntegralType value, const LargestIntegralType values[], + const size_t number_of_values, const char *const file, const int line); + +void *_test_malloc(const size_t size, const char *file, const int line); +void *_test_calloc(const size_t number_of_elements, const size_t size, const char *file, const int line); +void _test_free(void *const ptr, const char *file, const int line); + +void _fail(const char *const file, const int line) FUNC_ATTR_NORETURN; + int _run_test(const char *const function_name, const UnitTestFunction Function, + void **const state, const UnitTestFunctionType function_type, const void *const heap_check_point); + int _run_tests(const UnitTest *const tests, const size_t number_of_tests, const char *const file); + +// XML init and output + void vinit_xml (const char *const format, va_list args); + void vprint_xml(const char *const format, va_list args); + void init_xml (const char *const format, ...); + void print_xml(const char *const format, ...); + void vinit_cunit_run_files (const char *const file, const char *const format, va_list args); + void init_cunit_run_files (const char *const file, const char *const format, ...); + void append_xml_tmp(const char *ofile, const char *ifile); + +// Standard output and error print methods. + void print_message(const char *const format, ...); + void print_error(const char *const format, ...); + void vprint_message(const char *const format, va_list args); + void vprint_error(const char *const format, va_list args); + +#endif diff --git a/tests/unit/connection_management_test.c b/tests/unit/connection_management_test.c new file mode 100644 index 0000000000..78c073b5cb --- /dev/null +++ b/tests/unit/connection_management_test.c @@ -0,0 +1,151 @@ +#include + +#include +#include /* xsnprintf */ +#include +#include + + +#include /* PurgeOldConnections */ + + +const int CONNECTION_MAX_AGE_SECONDS = SECONDS_PER_HOUR * 2; + +/* NOTE: Invalid memory access has been seen in PurgeOldConnections(). + This does not always result in a segfault, but running this test + in valgrind will detect it. */ + + +static void test_purge_old_connections_nochange(void) +{ + const time_t time_now = 100000; + + Item *connections = NULL; + char time_str[64]; + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS); + PrependItem(&connections, "123.123.123.3", time_str); + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS + 1); + PrependItem(&connections, "123.123.123.2", time_str); + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS + 100); + PrependItem(&connections, "123.123.123.1", time_str); + + assert_int_equal(ListLen(connections), 3); + + PurgeOldConnections(&connections, time_now); + + assert_int_equal(ListLen(connections), 3); + + assert_true(IsItemIn(connections, "123.123.123.1")); + assert_true(IsItemIn(connections, "123.123.123.2")); + assert_true(IsItemIn(connections, "123.123.123.3")); + + DeleteItemList(connections); +} + + +static void test_purge_old_connections_purge_first(void) +{ + const time_t time_now = 100000; + + Item *connections = NULL; + char time_str[64]; + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS + 100); + PrependItem(&connections, "123.123.123.3", time_str); + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS + 2); + PrependItem(&connections, "123.123.123.2", time_str); + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS - 5); + PrependItem(&connections, "123.123.123.1", time_str); + + assert_int_equal(ListLen(connections), 3); + + PurgeOldConnections(&connections, time_now); + + assert_int_equal(ListLen(connections), 2); + + assert_false(IsItemIn(connections, "123.123.123.1")); + assert_true(IsItemIn(connections, "123.123.123.2")); + assert_true(IsItemIn(connections, "123.123.123.3")); + + DeleteItemList(connections); +} + + +static void test_purge_old_connections_purge_middle(void) +{ + const time_t time_now = 100000; + + Item *connections = NULL; + char time_str[64]; + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS); + PrependItem(&connections, "123.123.123.3", time_str); + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS - 1); + PrependItem(&connections, "123.123.123.2", time_str); + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS + 100); + PrependItem(&connections, "123.123.123.1", time_str); + + assert_int_equal(ListLen(connections), 3); + + PurgeOldConnections(&connections, time_now); + + assert_int_equal(ListLen(connections), 2); + + assert_true(IsItemIn(connections, "123.123.123.1")); + assert_false(IsItemIn(connections, "123.123.123.2")); + assert_true(IsItemIn(connections, "123.123.123.3")); + + DeleteItemList(connections); +} + + +static void test_purge_old_connections_purge_last(void) +{ + const time_t time_now = 100000; + + Item *connections = NULL; + char time_str[64]; + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS - 100); + PrependItem(&connections, "123.123.123.3", time_str); + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS + 10); + PrependItem(&connections, "123.123.123.2", time_str); + + xsnprintf(time_str, sizeof(time_str), "%ld", time_now - CONNECTION_MAX_AGE_SECONDS); + PrependItem(&connections, "123.123.123.1", time_str); + + assert_int_equal(ListLen(connections), 3); + + PurgeOldConnections(&connections, time_now); + + assert_int_equal(ListLen(connections), 2); + + assert_true(IsItemIn(connections, "123.123.123.1")); + assert_true(IsItemIn(connections, "123.123.123.2")); + assert_false(IsItemIn(connections, "123.123.123.3")); + + DeleteItemList(connections); +} + + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_purge_old_connections_nochange), + unit_test(test_purge_old_connections_purge_first), + unit_test(test_purge_old_connections_purge_middle), + unit_test(test_purge_old_connections_purge_last) + }; + + return run_tests(tests); +} diff --git a/tests/unit/conversion_test.c b/tests/unit/conversion_test.c new file mode 100644 index 0000000000..6767636cb1 --- /dev/null +++ b/tests/unit/conversion_test.c @@ -0,0 +1,290 @@ +#include + +#include +#include + +static void test_string_is_boolean(void) +{ + // This test should be updated if someone changes CF_BOOL: + assert_string_equal(CF_BOOL, "true,false,yes,no,on,off"); + + // Accepted boolean values: + assert_true(StringIsBoolean("true")); + assert_true(StringIsBoolean("false")); + assert_true(StringIsBoolean("yes")); + assert_true(StringIsBoolean("no")); + assert_true(StringIsBoolean("on")); + assert_true(StringIsBoolean("off")); + + // Anything else is not boolean: + assert_false(StringIsBoolean("boolean")); + assert_false(StringIsBoolean("bool")); + assert_false(StringIsBoolean("")); + assert_false(StringIsBoolean(" ")); + assert_false(StringIsBoolean(",")); + assert_false(StringIsBoolean(",,")); + assert_false(StringIsBoolean("()")); + assert_false(StringIsBoolean("$(foo)")); + assert_false(StringIsBoolean("$(true)")); + assert_false(StringIsBoolean("y")); + assert_false(StringIsBoolean("n")); + assert_false(StringIsBoolean("tru")); + assert_false(StringIsBoolean("fals")); + assert_false(StringIsBoolean("true,")); + assert_false(StringIsBoolean(",false")); + assert_false(StringIsBoolean("o,n")); + assert_false(StringIsBoolean(" on ")); + assert_false(StringIsBoolean("onoff")); + assert_false(StringIsBoolean("onon")); +} + +static void test_boolean_from_string(void) +{ + // This test should be updated if someone changes CF_BOOL: + assert_string_equal(CF_BOOL, "true,false,yes,no,on,off"); + + // Expected true values: + assert_true(BooleanFromString("true")); + assert_true(BooleanFromString("yes")); + assert_true(BooleanFromString("on")); + + // Expected false values: + assert_false(BooleanFromString("false")); + assert_false(BooleanFromString("no")); + assert_false(BooleanFromString("off")); + + // Edge cases, default to true: + assert_true(BooleanFromString("boolean")); + assert_true(BooleanFromString("bool")); + assert_true(BooleanFromString("")); + assert_true(BooleanFromString(" ")); + assert_true(BooleanFromString(",")); + assert_true(BooleanFromString(",,")); + assert_true(BooleanFromString("()")); + assert_true(BooleanFromString("$(foo)")); + assert_true(BooleanFromString("$(true)")); + assert_true(BooleanFromString("y")); + assert_true(BooleanFromString("n")); + assert_true(BooleanFromString("tru")); + assert_true(BooleanFromString("fals")); + assert_true(BooleanFromString("true,")); + assert_true(BooleanFromString(",false")); + assert_true(BooleanFromString("o,n")); + assert_true(BooleanFromString(" on ")); + assert_true(BooleanFromString("onoff")); + assert_true(BooleanFromString("onon")); +} + +static void test_int_from_string(void) +{ + assert_int_equal(IntFromString("0"), 0); + assert_int_equal(IntFromString("1"), 1); + assert_int_equal(IntFromString(" 1"), 1); + assert_int_equal(IntFromString("-1"), (long) -1); + assert_int_equal(IntFromString("-1k"), (long) -1000); + assert_int_equal(IntFromString("-123"), (long) -123); + assert_int_equal(IntFromString("12 "), 12); + assert_int_equal(IntFromString("12k "), 12000); + assert_int_equal(IntFromString("\t1m "), 1000000); + + /* ==== SPECIAL CASES ==== */ + assert_int_equal(IntFromString("inf"), CF_INFINITY); + assert_int_equal(IntFromString("now"), CFSTARTTIME); + assert_int_equal(IntFromString("2k"), 2000); + assert_int_equal(IntFromString("3K"), 3072); + assert_int_equal(IntFromString("4m"), 4000000); + assert_int_equal(IntFromString("1M"), 1024 * 1024); + /* Percentages are stored as negatives TODO fix. */ + assert_int_equal(IntFromString("10%"), (long) -10); + /* Unknown quantifiers are just being ignored. */ + assert_int_equal(IntFromString("13o"), 13); + + /* ==== CONTROLLED FAILURES ==== */ + assert_int_equal(IntFromString(NULL), CF_NOINT); + assert_int_equal(IntFromString(""), CF_NOINT); + assert_int_equal(IntFromString(" "), CF_NOINT); + assert_int_equal(IntFromString("$(blah)"), CF_NOINT); + assert_int_equal(IntFromString("123.45"), CF_NOINT); + assert_int_equal(IntFromString("120%"), CF_NOINT); + assert_int_equal(IntFromString("-1%"), CF_NOINT); + assert_int_equal(IntFromString("14 o"), CF_NOINT); + assert_int_equal(IntFromString("2ko"), CF_NOINT); + assert_int_equal(IntFromString("3K o"), CF_NOINT); + /* The quantifier is not expanded yet. */ + assert_int_equal(IntFromString("99$(blah)"), CF_NOINT); +} + +static void test_double_from_string(void) +{ + double val; + + /* ===== TESTING SUCCESS ===== */ + + assert_true(DoubleFromString("1.2k", &val)); + assert_double_close(1200.0, val); + + assert_true(DoubleFromString("1m", &val)); + assert_double_close(1000000.0, val); + + assert_true(DoubleFromString("1K", &val)); + assert_double_close(1024.0, val); + + /* Previously reserved as NO_DOUBLE define. */ + assert_true(DoubleFromString("-123.45", &val)); + assert_double_close(-123.45, val); + + assert_true(DoubleFromString("0.1", &val)); + assert_double_close(0.1, val); + + /* leading space is OK. */ + assert_true(DoubleFromString(" 0.2", &val)); + assert_double_close(0.2, val); + assert_true(DoubleFromString(" 0.2k", &val)); + assert_double_close(200., val); + + assert_true(DoubleFromString("0.1%", &val)); + /* Currently percentages are stored as negatives; TODO FIX! */ + assert_double_close(-0.1, val); + + /* Space quantifier ignored. */ + assert_true(DoubleFromString("1233 ", &val)); + assert_double_close(1233, val); + + assert_true(DoubleFromString("1112 ", &val)); + assert_double_close(1112, val); + + /* Invalid quantifier, ignored for backwards compatibility. */ + assert_true(DoubleFromString("11.1o", &val)); + assert_double_close(11.1, val); + + /* ===== ERROR RETURN ===== */ + + /* Verify that parameter is not modified. */ + double old_val = val; + + assert_false(DoubleFromString("", &val)); + assert_false(DoubleFromString(" ", &val)); + assert_false(DoubleFromString(" ", &val)); + assert_false(DoubleFromString("abc", &val)); + assert_false(DoubleFromString("G1", &val)); + + /* Anomalous remainders. */ + assert_false(DoubleFromString("123adf", &val)); + assert_false(DoubleFromString("123 adf", &val)); + assert_false(DoubleFromString("123 adf", &val)); + assert_false(DoubleFromString("123 adf", &val)); + + assert_false(DoubleFromString("123$(remainder)", &val)); + + + assert_true(val == old_val); +} + +static void test_CommandArg0_bound(void) +{ + char dst[128]; + size_t zret; + + zret = CommandArg0_bound(dst, "", sizeof(dst)); + assert_string_equal(dst, ""); + assert_int_equal(zret, 0); + zret = CommandArg0_bound(dst, " ", sizeof(dst)); + assert_string_equal(dst, ""); + assert_int_equal(zret, 0); + zret = CommandArg0_bound(dst, " blah", sizeof(dst)); + assert_string_equal(dst, ""); + assert_int_equal(zret, 0); + zret = CommandArg0_bound(dst, "blah", sizeof(dst)); + assert_string_equal(dst, "blah"); + assert_int_equal(zret, 4); + zret = CommandArg0_bound(dst, "blah blue", sizeof(dst)); + assert_string_equal(dst, "blah"); + assert_int_equal(zret, 4); + + zret = CommandArg0_bound(dst, "\"\"", sizeof(dst)); + assert_string_equal(dst, ""); + assert_int_equal(zret, 0); + zret = CommandArg0_bound(dst, "\"blah\"", sizeof(dst)); + assert_string_equal(dst, "blah"); + assert_int_equal(zret, 4); + zret = CommandArg0_bound(dst, "\"blah", sizeof(dst)); + assert_string_equal(dst, "blah"); + assert_int_equal(zret, 4); + zret = CommandArg0_bound(dst, "\"blah blue", sizeof(dst)); + assert_string_equal(dst, "blah blue"); + assert_int_equal(zret, 9); + + zret = CommandArg0_bound(dst, "\"\" blus", sizeof(dst)); + assert_string_equal(dst, ""); + assert_int_equal(zret, 0); + zret = CommandArg0_bound(dst, "\"blah\" blue", sizeof(dst)); + assert_string_equal(dst, "blah"); + assert_int_equal(zret, 4); + zret = CommandArg0_bound(dst, "\"blah\"blue", sizeof(dst)); + assert_string_equal(dst, "blah"); + assert_int_equal(zret, 4); + + zret = CommandArg0_bound(dst, "blah ", sizeof(dst)); + assert_string_equal(dst, "blah"); + assert_int_equal(zret, 4); + + zret = CommandArg0_bound(dst, "\" \"", sizeof(dst)); + assert_string_equal(dst, " "); + assert_int_equal(zret, 1); + zret = CommandArg0_bound(dst, "\" \" ", sizeof(dst)); + assert_string_equal(dst, " "); + assert_int_equal(zret, 1); + + zret = CommandArg0_bound(dst, "blah \"blue\"", sizeof(dst)); + assert_string_equal(dst, "blah"); + assert_int_equal(zret, 4); + + zret = CommandArg0_bound(dst, "blah\"blue\"", sizeof(dst)); + assert_string_equal(dst, "blah\"blue\""); + assert_int_equal(zret, 10); + + zret = CommandArg0_bound(dst, "blah\"blue", sizeof(dst)); + assert_string_equal(dst, "blah\"blue"); + assert_int_equal(zret, 9); + + /* TEST OVERFLOW */ + + zret = CommandArg0_bound(dst, "", 0); + assert_int_equal(zret, (size_t) -1); + zret = CommandArg0_bound(dst, "blah", 0); + assert_int_equal(zret, (size_t) -1); + zret = CommandArg0_bound(dst, " ", 0); + assert_int_equal(zret, (size_t) -1); + zret = CommandArg0_bound(dst, "\"blah\"", 0); + assert_int_equal(zret, (size_t) -1); + + zret = CommandArg0_bound(dst, "blah", 1); + assert_int_equal(zret, (size_t) -1); + zret = CommandArg0_bound(dst, "\"blah\"", 1); + assert_int_equal(zret, (size_t) -1); + + zret = CommandArg0_bound(dst, "b", 1); + assert_int_equal(zret, (size_t) -1); + zret = CommandArg0_bound(dst, "\"b\"", 1); + assert_int_equal(zret, (size_t) -1); + + zret = CommandArg0_bound(dst, "", 1); + assert_int_equal(zret, 0); /* empty string fits */ + zret = CommandArg0_bound(dst, " ", 1); + assert_int_equal(zret, 0); /* empty string fits */ +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_string_is_boolean), + unit_test(test_boolean_from_string), + unit_test(test_int_from_string), + unit_test(test_double_from_string), + unit_test(test_CommandArg0_bound), + }; + + return run_tests(tests); +} diff --git a/tests/unit/crypto_symmetric_test.c b/tests/unit/crypto_symmetric_test.c new file mode 100644 index 0000000000..cd8ad67bec --- /dev/null +++ b/tests/unit/crypto_symmetric_test.c @@ -0,0 +1,114 @@ +#include + +#include +#include + +#define PLAINTEXT "123456789012345678901234567890123" +#define KEY "1234567890123456789012345678901234567890123456789012345678901234" /* at least 512 bits long (to be sure) */ + +// use Blowfish (64-bit block size) for now +#define CIPHER_TYPE_CFENGINE 'c' +#define CIPHER_BLOCK_SIZE_BYTES 8 +static const char CIPHERTEXT_PRECOMPUTED[] = +{ + 0x99, 0xfd, 0x86, 0x9c, 0x17, 0xb9, 0xe4, 0x98, + 0xab, 0x01, 0x17, 0x5a, 0x4a, 0xcf, 0xfc, 0x1f, + 0xd4, 0xc5, 0xa3, 0xab, 0xf0, 0x1c, 0xa7, 0x39, + 0xf1, 0xf4, 0x09, 0xe4, 0xac, 0xb6, 0x44, 0xbb, + 0x47, 0xdd, 0xe6, 0xc4, 0x0e, 0x4a, 0x16, 0xf0 +}; + +static int ComputeCiphertextLen(int plaintext_len, int cipher_block_size_bytes) +{ + int last_block_offset = plaintext_len % cipher_block_size_bytes; + int padding = cipher_block_size_bytes - last_block_offset; + + return (plaintext_len + padding); +} + +static void test_cipher_init(void) +{ + unsigned char key[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; + unsigned char iv[] = {1,2,3,4,5,6,7,8}; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + + EVP_CIPHER_CTX_init(ctx); + EVP_EncryptInit_ex(ctx, EVP_bf_cbc(), NULL, key, iv); + EVP_CIPHER_CTX_free(ctx); +} + +static void test_symmetric_encrypt(void) +{ + char ciphertext[CF_BUFSIZE]; + int plaintext_len = strlen(PLAINTEXT) + 1; + + int ciphertext_len = EncryptString(ciphertext, sizeof(ciphertext), + PLAINTEXT, plaintext_len, + CIPHER_TYPE_CFENGINE, KEY); + + assert_int_equal(ciphertext_len, ComputeCiphertextLen(plaintext_len, CIPHER_BLOCK_SIZE_BYTES)); + + assert_memory_equal(ciphertext, CIPHERTEXT_PRECOMPUTED, ciphertext_len); +} + +static void test_symmetric_decrypt(void) +{ + char *ciphertext = (char *)CIPHERTEXT_PRECOMPUTED; + int ciphertext_len = sizeof(CIPHERTEXT_PRECOMPUTED); + + char plaintext_out[CF_BUFSIZE]; + + int plaintext_len = DecryptString(plaintext_out, sizeof(plaintext_out), + ciphertext, ciphertext_len, CIPHER_TYPE_CFENGINE, KEY); + + assert_int_equal(plaintext_len, strlen(PLAINTEXT) + 1); + + assert_string_equal(plaintext_out, PLAINTEXT); +} + +static void test_cipher_block_size(void) +{ + assert_int_equal(CipherBlockSizeBytes(EVP_bf_cbc()), 8); + + assert_int_equal(CipherBlockSizeBytes(EVP_aes_256_cbc()), 16); +} + +static void test_cipher_text_size_max(void) +{ + assert_int_equal(CipherTextSizeMax(EVP_aes_256_cbc(), 1), 32); + + assert_int_equal(CipherTextSizeMax(EVP_aes_256_cbc(), CF_BUFSIZE), 4127); + + assert_int_equal(CipherTextSizeMax(EVP_bf_cbc(), 1), 16); + + assert_int_equal(CipherTextSizeMax(EVP_bf_cbc(), CF_BUFSIZE), 4111); +} + +static void test_plain_text_size_max(void) +{ + assert_int_equal(PlainTextSizeMax(EVP_aes_256_cbc(), 1), 33); + + assert_int_equal(PlainTextSizeMax(EVP_aes_256_cbc(), CF_BUFSIZE), 4128); + + assert_int_equal(PlainTextSizeMax(EVP_bf_cbc(), 1), 17); + + assert_int_equal(PlainTextSizeMax(EVP_bf_cbc(), CF_BUFSIZE), 4112); +} + +int main() +{ + PRINT_TEST_BANNER(); + CryptoInitialize(); + + const UnitTest tests[] = + { + unit_test(test_cipher_init), + unit_test(test_symmetric_encrypt), + unit_test(test_symmetric_decrypt), + unit_test(test_cipher_block_size), + unit_test(test_cipher_text_size_max), + unit_test(test_plain_text_size_max), + }; + + return run_tests(tests); +} diff --git a/tests/unit/data/benchmark.cf b/tests/unit/data/benchmark.cf new file mode 100644 index 0000000000..52c8d66921 --- /dev/null +++ b/tests/unit/data/benchmark.cf @@ -0,0 +1,38 @@ +# This file is intended to have as much feature variation as possible, +# while still being correct. + +body common control +{ + bundlesequence => { "main" }; +} + +bundle agent main +{ + reports: + cfengine:: + "Hello, CFEngine" + friend_pattern => hash("abc", "md5"); + any:: + "Hello, world" + friend_pattern => hash("abc", "md5"); + + files: + "/tmp/stuff" -> { "stakeholder" } + create => "true", + perms => myperms; + + processes: + "/bin/stuff" -> { "stakeholder" } + process_count => any_count("stuff_running"); +} + +body process_count any_count(cl) +{ + match_range => "0,0"; + out_of_range_define => { "$(cl)" }; +} + +body perms myperms +{ + mode => "555"; +} diff --git a/tests/unit/data/benchmark.json b/tests/unit/data/benchmark.json new file mode 100644 index 0000000000..8d0917fb2f --- /dev/null +++ b/tests/unit/data/benchmark.json @@ -0,0 +1,186 @@ +{ + "bundles": [ + { + "sourcePath": "tests/unit/data/benchmark.cf", + "line": 9, + "namespace": "default", + "name": "main", + "bundleType": "agent", + "arguments": [], + "promiseTypes": [ + { + "line": 11, + "name": "reports", + "contexts": [ + { + "name": "cfengine", + "promises": [ + { + "line": 13, + "promiser": "Hello, CFEngine", + "attributes": [ + { + "line": 14, + "lval": "friend_pattern", + "rval": { + "type": "functionCall", + "name": "hash", + "arguments": [ + { + "type": "string", + "value": "abc" + }, + { + "type": "string", + "value": "md5" + } + ] + } + } + ] + } + ] + }, + { + "name": "any", + "promises": [ + { + "line": 16, + "promiser": "Hello, world", + "attributes": [ + { + "line": 17, + "lval": "friend_pattern", + "rval": { + "type": "functionCall", + "name": "hash", + "arguments": [ + { + "type": "string", + "value": "abc" + }, + { + "type": "string", + "value": "md5" + } + ] + } + } + ] + } + ] + } + ] + }, + { + "line": 19, + "name": "files", + "contexts": [ + { + "name": "any", + "promises": [ + { + "line": 20, + "promiser": "/tmp/stuff", + "promisee": [ + "stakeholder" + ], + "attributes": [ + { + "line": 21, + "lval": "create", + "rval": { + "type": "string", + "value": "true" + } + }, + { + "line": 22, + "lval": "perms", + "rval": { + "type": "symbol", + "value": "myperms" + } + } + ] + } + ] + } + ] + }, + { + "line": 24, + "name": "processes", + "contexts": [ + { + "name": "any", + "promises": [ + { + "line": 25, + "promiser": "/bin/stuff", + "promisee": [ + "stakeholder" + ], + "attributes": [] + } + ] + } + ] + } + ] + } + ], + "bodies": [ + { + "sourcePath": "tests/unit/data/benchmark.cf", + "line": 4, + "namespace": "default", + "name": "control", + "bodyType": "common", + "arguments": [], + "contexts": [ + { + "name": "any", + "attributes": [ + { + "line": 6, + "lval": "bundlesequence", + "rval": { + "type": "list", + "value": [ + { + "type": "string", + "value": "main" + } + ] + } + } + ] + } + ] + }, + { + "sourcePath": "tests/unit/data/benchmark.cf", + "line": 28, + "namespace": "default", + "name": "myperms", + "bodyType": "perms", + "arguments": [], + "contexts": [ + { + "name": "any", + "attributes": [ + { + "line": 30, + "lval": "mode", + "rval": { + "type": "string", + "value": "555" + } + } + ] + } + ] + } + ] +} diff --git a/tests/unit/data/body_body_forget_cb_body.cf b/tests/unit/data/body_body_forget_cb_body.cf new file mode 100644 index 0000000000..83eff64012 --- /dev/null +++ b/tests/unit/data/body_body_forget_cb_body.cf @@ -0,0 +1,7 @@ +body classes repaired(x) +{ + promise_repaired => { "$(x)_repaired" }; + +body classes foo +{ +} diff --git a/tests/unit/data/body_body_forget_cb_bundle.cf b/tests/unit/data/body_body_forget_cb_bundle.cf new file mode 100644 index 0000000000..8940199962 --- /dev/null +++ b/tests/unit/data/body_body_forget_cb_bundle.cf @@ -0,0 +1,7 @@ +body classes repaired(x) +{ + promise_repaired => { "$(x)_repaired" }; + +bundle agent foo +{ +} diff --git a/tests/unit/data/body_body_forget_cb_eof.cf b/tests/unit/data/body_body_forget_cb_eof.cf new file mode 100644 index 0000000000..75cc0efe63 --- /dev/null +++ b/tests/unit/data/body_body_forget_cb_eof.cf @@ -0,0 +1,5 @@ +body classes repaired(x) +{ + promise_repaired => { "$(x)_repaired" }; + + diff --git a/tests/unit/data/body_control_no_arguments.cf b/tests/unit/data/body_control_no_arguments.cf new file mode 100644 index 0000000000..d8ffdaf746 --- /dev/null +++ b/tests/unit/data/body_control_no_arguments.cf @@ -0,0 +1,4 @@ +body common control(arg) +{ + bundlesequence => { test }; +} diff --git a/tests/unit/data/body_edit_line_common_constraints.cf b/tests/unit/data/body_edit_line_common_constraints.cf new file mode 100644 index 0000000000..398d14645b --- /dev/null +++ b/tests/unit/data/body_edit_line_common_constraints.cf @@ -0,0 +1,6 @@ +bundle edit_line test +{ + delete_lines: + "abc" + select_region => test_select; +} diff --git a/tests/unit/data/body_edit_xml_common_constraints.cf b/tests/unit/data/body_edit_xml_common_constraints.cf new file mode 100644 index 0000000000..58bff998c5 --- /dev/null +++ b/tests/unit/data/body_edit_xml_common_constraints.cf @@ -0,0 +1,6 @@ +bundle edit_xml test +{ + build_xpath: + "foo" + select_xpath => "abc"; +} diff --git a/tests/unit/data/body_executor_control_agent_expireafter_only.cf b/tests/unit/data/body_executor_control_agent_expireafter_only.cf new file mode 100644 index 0000000000..f77b10500d --- /dev/null +++ b/tests/unit/data/body_executor_control_agent_expireafter_only.cf @@ -0,0 +1,4 @@ +body executor control +{ + agent_expireafter => "121"; +} diff --git a/tests/unit/data/body_executor_control_empty.cf b/tests/unit/data/body_executor_control_empty.cf new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/unit/data/body_executor_control_empty.cf @@ -0,0 +1 @@ + diff --git a/tests/unit/data/body_executor_control_full.cf b/tests/unit/data/body_executor_control_full.cf new file mode 100644 index 0000000000..66fad8cdb3 --- /dev/null +++ b/tests/unit/data/body_executor_control_full.cf @@ -0,0 +1,13 @@ +body executor control +{ + splaytime => "1"; + mailto => "cfengine_mail@example.org"; + mailfrom => "cfengine@example.org"; + mailsubject => "Test [localhost/127.0.0.1]"; + smtpserver => "localhost"; + mailmaxlines => "50"; + schedule => { "Min00_05", "Min05_10" }; + executorfacility => "LOG_LOCAL6"; + agent_expireafter => "120"; + exec_command => "/bin/echo"; +} diff --git a/tests/unit/data/body_redefinition.cf b/tests/unit/data/body_redefinition.cf new file mode 100644 index 0000000000..bf1fe68734 --- /dev/null +++ b/tests/unit/data/body_redefinition.cf @@ -0,0 +1,9 @@ +body agent control +{ + ifelapsed => "60"; +} + +body agent control +{ + ifelapsed => "120"; +} diff --git a/tests/unit/data/body_selection_forgot_semicolon.cf b/tests/unit/data/body_selection_forgot_semicolon.cf new file mode 100644 index 0000000000..da25136254 --- /dev/null +++ b/tests/unit/data/body_selection_forgot_semicolon.cf @@ -0,0 +1,8 @@ +body classes surfsara_all_classes(x) +{ + promise_repaired => { "$(x)_repaired" }; + promise_kept => { "$(x)_kept" } + repair_failed => { "$(x)_failed" }; + repair_denied => { "$(x)_denied" }; + repair_timeout => { "$(x)_timeout" }; +} diff --git a/tests/unit/data/body_selection_unknown_selection_id.cf b/tests/unit/data/body_selection_unknown_selection_id.cf new file mode 100644 index 0000000000..cb0df4a0cd --- /dev/null +++ b/tests/unit/data/body_selection_unknown_selection_id.cf @@ -0,0 +1,8 @@ +body classes surfsara_all_classes(x) +{ + promise_repaired => { "$(x)_repaired" }; + promise_kpt => { "$(x)_kept" }; + repair_failed => { "$(x)_failed" }; + repair_denied => { "$(x)_denied" }; + repair_timeout => { "$(x)_timeout" }; +} diff --git a/tests/unit/data/body_selection_wrong_token.cf b/tests/unit/data/body_selection_wrong_token.cf new file mode 100644 index 0000000000..d6d195100a --- /dev/null +++ b/tests/unit/data/body_selection_wrong_token.cf @@ -0,0 +1,8 @@ +body classes surfsara_all_classes(x) +{ + "promise_repaired" => { "$(x)_repaired" }; + promise_kept => { "$(x)_kept" }; + repair_failed => { "$(x)_failed" }; + repair_denied => { "$(x)_denied" }; + repair_timeout => { "$(x)_timeout" }; +} diff --git a/tests/unit/data/bundle_args_forgot_cp.cf b/tests/unit/data/bundle_args_forgot_cp.cf new file mode 100644 index 0000000000..149b3a365e --- /dev/null +++ b/tests/unit/data/bundle_args_forgot_cp.cf @@ -0,0 +1,3 @@ +bundle agent foo(a + { + } diff --git a/tests/unit/data/bundle_args_invalid_type.cf b/tests/unit/data/bundle_args_invalid_type.cf new file mode 100644 index 0000000000..ed9fe815d6 --- /dev/null +++ b/tests/unit/data/bundle_args_invalid_type.cf @@ -0,0 +1,3 @@ +bundle agent foo(a, "b") +{ +} diff --git a/tests/unit/data/bundle_body_forget_cb_body.cf b/tests/unit/data/bundle_body_forget_cb_body.cf new file mode 100644 index 0000000000..ac143f4d0f --- /dev/null +++ b/tests/unit/data/bundle_body_forget_cb_body.cf @@ -0,0 +1,14 @@ +bundle agent foo +{ + + vars: + + debian:: + "bas" string => "van der vlies", + comment => "lastname"; + + +body classes repaired(x) +{ + promise_repaired => { "$(x)_repaired" }; +} diff --git a/tests/unit/data/bundle_body_forget_cb_bundle.cf b/tests/unit/data/bundle_body_forget_cb_bundle.cf new file mode 100644 index 0000000000..3e3e8cc3a8 --- /dev/null +++ b/tests/unit/data/bundle_body_forget_cb_bundle.cf @@ -0,0 +1,13 @@ +bundle agent foo +{ + + vars: + + debian:: + "bas" string => "van der vlies", + comment => "lastname"; + + +bundle agent bar +{ +} diff --git a/tests/unit/data/bundle_body_forget_cb_eof.cf b/tests/unit/data/bundle_body_forget_cb_eof.cf new file mode 100644 index 0000000000..1478876a05 --- /dev/null +++ b/tests/unit/data/bundle_body_forget_cb_eof.cf @@ -0,0 +1,10 @@ +bundle agent foo +{ + + vars: + + debian:: + "bas" string => "van der vlies", + comment => "lastname"; + + diff --git a/tests/unit/data/bundle_body_forgot_ob.cf b/tests/unit/data/bundle_body_forgot_ob.cf new file mode 100644 index 0000000000..467bf792ef --- /dev/null +++ b/tests/unit/data/bundle_body_forgot_ob.cf @@ -0,0 +1,3 @@ +bundle agent foo(a) + +} diff --git a/tests/unit/data/bundle_body_forgot_semicolon.cf b/tests/unit/data/bundle_body_forgot_semicolon.cf new file mode 100644 index 0000000000..10f87d0bc8 --- /dev/null +++ b/tests/unit/data/bundle_body_forgot_semicolon.cf @@ -0,0 +1,12 @@ +bundle agent foo +{ + + vars: + + debian:: + + "bas" string => "van der vlies", + handle => "netherlands", + comment => "lastname" + +} diff --git a/tests/unit/data/bundle_body_promise_missing_arrow.cf b/tests/unit/data/bundle_body_promise_missing_arrow.cf new file mode 100644 index 0000000000..fcb98f3810 --- /dev/null +++ b/tests/unit/data/bundle_body_promise_missing_arrow.cf @@ -0,0 +1,10 @@ +bundle agent foo +{ + + vars: + + debian:: + "HvB" - { "@(stakeholders[$(service)])" } + comment => "foobar"; + +} diff --git a/tests/unit/data/bundle_body_promisee_no_colon_allowed.cf b/tests/unit/data/bundle_body_promisee_no_colon_allowed.cf new file mode 100644 index 0000000000..b004522c0c --- /dev/null +++ b/tests/unit/data/bundle_body_promisee_no_colon_allowed.cf @@ -0,0 +1,11 @@ +bundle agent foo +{ + + vars: + + debian:: + "foo" -> "bar", + handle => "foo" + comment => "test"; +} + diff --git a/tests/unit/data/bundle_body_promiser_forgot_colon.cf b/tests/unit/data/bundle_body_promiser_forgot_colon.cf new file mode 100644 index 0000000000..60c2fcb06b --- /dev/null +++ b/tests/unit/data/bundle_body_promiser_forgot_colon.cf @@ -0,0 +1,10 @@ +bundle agent foo +{ + + vars: + + "foo" string => "bar", + handle => "foo" + comment => "test"; +} + diff --git a/tests/unit/data/bundle_body_promiser_statement_contains_colon.cf b/tests/unit/data/bundle_body_promiser_statement_contains_colon.cf new file mode 100644 index 0000000000..369cf0243f --- /dev/null +++ b/tests/unit/data/bundle_body_promiser_statement_contains_colon.cf @@ -0,0 +1,9 @@ +bundle agent foo +{ + + reports: + + "This is a test", + comment => "will report an error"; + +} diff --git a/tests/unit/data/bundle_body_promiser_statement_missing_assign.cf b/tests/unit/data/bundle_body_promiser_statement_missing_assign.cf new file mode 100644 index 0000000000..4aef9cc8c8 --- /dev/null +++ b/tests/unit/data/bundle_body_promiser_statement_missing_assign.cf @@ -0,0 +1,10 @@ +bundle agent foo +{ + + vars: + + debian:: + "foo" string > "bar", + comment => "test"; +} + diff --git a/tests/unit/data/bundle_body_promiser_unknown_constraint_id.cf b/tests/unit/data/bundle_body_promiser_unknown_constraint_id.cf new file mode 100644 index 0000000000..547647ea5a --- /dev/null +++ b/tests/unit/data/bundle_body_promiser_unknown_constraint_id.cf @@ -0,0 +1,10 @@ +bundle agent foo +{ + + vars: + + debian:: + "foo" string => "bar", + cmment => "test"; +} + diff --git a/tests/unit/data/bundle_body_promiser_wrong_constraint_token.cf b/tests/unit/data/bundle_body_promiser_wrong_constraint_token.cf new file mode 100644 index 0000000000..b16cdc39a2 --- /dev/null +++ b/tests/unit/data/bundle_body_promiser_wrong_constraint_token.cf @@ -0,0 +1,8 @@ +bundle agent foo +{ + + vars: + "a" string => "foo", + "comment" => "bar"; + +} diff --git a/tests/unit/data/bundle_body_wrong_promise_type_token.cf b/tests/unit/data/bundle_body_wrong_promise_type_token.cf new file mode 100644 index 0000000000..59f940c0f2 --- /dev/null +++ b/tests/unit/data/bundle_body_wrong_promise_type_token.cf @@ -0,0 +1,6 @@ +bundle agent foo +{ + + invalid:: + +} diff --git a/tests/unit/data/bundle_body_wrong_statement.cf b/tests/unit/data/bundle_body_wrong_statement.cf new file mode 100644 index 0000000000..58f3ebf725 --- /dev/null +++ b/tests/unit/data/bundle_body_wrong_statement.cf @@ -0,0 +1,8 @@ +bundle agent foo +{ + + vars: + + invalid string => "foo"; + +} diff --git a/tests/unit/data/bundle_custom_promise_type.cf b/tests/unit/data/bundle_custom_promise_type.cf new file mode 100644 index 0000000000..2ab37741d7 --- /dev/null +++ b/tests/unit/data/bundle_custom_promise_type.cf @@ -0,0 +1,7 @@ +# This policy should be parseable since the promise type might be +# defined in another policy file +bundle agent foo +{ +custom_promise_type: + "var" string => "test"; +} diff --git a/tests/unit/data/bundle_invalid_type.cf b/tests/unit/data/bundle_invalid_type.cf new file mode 100644 index 0000000000..ef3d8fd5c2 --- /dev/null +++ b/tests/unit/data/bundle_invalid_type.cf @@ -0,0 +1,3 @@ +bundle invalid foo +{ +} diff --git a/tests/unit/data/bundle_redefinition.cf b/tests/unit/data/bundle_redefinition.cf new file mode 100644 index 0000000000..38b93f944b --- /dev/null +++ b/tests/unit/data/bundle_redefinition.cf @@ -0,0 +1,13 @@ +bundle agent foo +{ + reports: + cfengine3:: + "hello world"; +} + +bundle agent foo +{ + reports: + cfengine3:: + "other stuff"; +} diff --git a/tests/unit/data/bundle_reserved_name.cf b/tests/unit/data/bundle_reserved_name.cf new file mode 100644 index 0000000000..d9dfe7415d --- /dev/null +++ b/tests/unit/data/bundle_reserved_name.cf @@ -0,0 +1,6 @@ +bundle agent sys +{ + reports: + cfengine3:: + "hello world"; +} diff --git a/tests/unit/data/constraint_comment_nonscalar.cf b/tests/unit/data/constraint_comment_nonscalar.cf new file mode 100644 index 0000000000..e935dc2e7d --- /dev/null +++ b/tests/unit/data/constraint_comment_nonscalar.cf @@ -0,0 +1,6 @@ +bundle agent foo +{ + reports: + "hello" + comment => { "a" }; +} diff --git a/tests/unit/data/constraint_ifvarclass_invalid.cf b/tests/unit/data/constraint_ifvarclass_invalid.cf new file mode 100644 index 0000000000..b442b38a0a --- /dev/null +++ b/tests/unit/data/constraint_ifvarclass_invalid.cf @@ -0,0 +1,6 @@ +bundle agent foo +{ + reports: + "hello" + if => "*"; +} diff --git a/tests/unit/data/constraint_lval_invalid.cf b/tests/unit/data/constraint_lval_invalid.cf new file mode 100644 index 0000000000..588f84ec38 --- /dev/null +++ b/tests/unit/data/constraint_lval_invalid.cf @@ -0,0 +1,7 @@ +bundle agent test +{ + files: + "$(G.testdir)/shouldnotexist" + create => "true", + nonexistent_attribute => "abc"; +} diff --git a/tests/unit/data/csv_file.csv b/tests/unit/data/csv_file.csv new file mode 100644 index 0000000000..0b670c5d91 --- /dev/null +++ b/tests/unit/data/csv_file.csv @@ -0,0 +1,5 @@ +field_1, field_2 +field_1, "value1 +value2 +value3" +field_1, "field,2" diff --git a/tests/unit/data/csv_file_edge_cases.csv b/tests/unit/data/csv_file_edge_cases.csv new file mode 100644 index 0000000000..cf9a8401df --- /dev/null +++ b/tests/unit/data/csv_file_edge_cases.csv @@ -0,0 +1,6 @@ +Empty,Empty,One double quote,Two double quotes,LF,CRLF,CRLFCRLF,Empty +"",,"""",""""""," +"," +"," + +", diff --git a/tests/unit/data/methods_invalid_arity.cf b/tests/unit/data/methods_invalid_arity.cf new file mode 100644 index 0000000000..2b1188cb0f --- /dev/null +++ b/tests/unit/data/methods_invalid_arity.cf @@ -0,0 +1,13 @@ +bundle agent foo(one, two) +{ + reports: + cfengine3:: + "$(one), $(two)"; +} + +bundle agent bar +{ + methods: + "any" + usebundle => foo("snookie"); +} diff --git a/tests/unit/data/mustache_comments.json b/tests/unit/data/mustache_comments.json new file mode 100644 index 0000000000..eb2fc158f9 --- /dev/null +++ b/tests/unit/data/mustache_comments.json @@ -0,0 +1,83 @@ +{ + "__ATTN__": "Do not edit this file; changes belong in the appropriate YAML file.", + "overview": "Comment tags represent content that should never appear in the resulting\noutput.\n\nThe tag's content may contain any substring (including newlines) EXCEPT the\nclosing delimiter.\n\nComment tags SHOULD be treated as standalone when appropriate.\n", + "tests": [ + { + "name": "Inline", + "data": {}, + "expected": "1234567890", + "template": "12345{{! Comment Block! }}67890", + "desc": "Comment blocks should be removed from the template." + }, + { + "name": "Multiline", + "data": {}, + "expected": "1234567890\n", + "template": "12345{{!\n This is a\n multi-line comment...\n}}67890\n", + "desc": "Multiline comments should be permitted." + }, + { + "name": "Standalone", + "data": {}, + "expected": "Begin.\nEnd.\n", + "template": "Begin.\n{{! Comment Block! }}\nEnd.\n", + "desc": "All standalone comment lines should be removed." + }, + { + "name": "Indented Standalone", + "data": {}, + "expected": "Begin.\nEnd.\n", + "template": "Begin.\n {{! Indented Comment Block! }}\nEnd.\n", + "desc": "All standalone comment lines should be removed." + }, + { + "name": "Standalone Line Endings", + "data": {}, + "expected": "|\r\n|", + "template": "|\r\n{{! Standalone Comment }}\r\n|", + "desc": "\"\\r\\n\" should be considered a newline for standalone tags." + }, + { + "name": "Standalone Without Previous Line", + "data": {}, + "expected": "!", + "template": " {{! I'm Still Standalone }}\n!", + "desc": "Standalone tags should not require a newline to precede them." + }, + { + "name": "Standalone Without Newline", + "data": {}, + "expected": "!\n", + "template": "!\n {{! I'm Still Standalone }}", + "desc": "Standalone tags should not require a newline to follow them." + }, + { + "name": "Multiline Standalone", + "data": {}, + "expected": "Begin.\nEnd.\n", + "template": "Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n", + "desc": "All standalone comment lines should be removed." + }, + { + "name": "Indented Multiline Standalone", + "data": {}, + "expected": "Begin.\nEnd.\n", + "template": "Begin.\n {{!\n Something's going on here...\n }}\nEnd.\n", + "desc": "All standalone comment lines should be removed." + }, + { + "name": "Indented Inline", + "data": {}, + "expected": " 12 \n", + "template": " 12 {{! 34 }}\n", + "desc": "Inline comments should not strip whitespace" + }, + { + "name": "Surrounding Whitespace", + "data": {}, + "expected": "12345 67890", + "template": "12345 {{! Comment Block! }} 67890", + "desc": "Comment removal should preserve surrounding whitespace." + } + ] +} diff --git a/tests/unit/data/mustache_delimiters.json b/tests/unit/data/mustache_delimiters.json new file mode 100644 index 0000000000..de1c22ba0a --- /dev/null +++ b/tests/unit/data/mustache_delimiters.json @@ -0,0 +1,77 @@ +{ + "tests": [ + { + "name": "Pair Behavior", + "data": { + "text": "Hey!" + }, + "expected": "(Hey!)", + "template": "{{=<% %>=}}(<%text%>)", + "desc": "The equals sign (used on both sides) should permit delimiter changes." + }, + { + "name": "Special Characters", + "data": { + "text": "It worked!" + }, + "expected": "(It worked!)", + "template": "({{=[ ]=}}[text])", + "desc": "Characters with special meaning regexen should be valid delimiters." + }, + { + "name": "Sections", + "data": { + "section": true, + "data": "I got interpolated." + }, + "expected": "[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n", + "template": "[\n{{#section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|#section|\n {{data}}\n |data|\n|/section|\n]\n", + "desc": "Delimiters set outside sections should persist." + }, + { + "name": "Inverted Sections", + "data": { + "section": false, + "data": "I got interpolated." + }, + "expected": "[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n", + "template": "[\n{{^section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|^section|\n {{data}}\n |data|\n|/section|\n]\n", + "desc": "Delimiters set outside inverted sections should persist." + }, + { + "name": "Surrounding Whitespace", + "data": {}, + "expected": "| |", + "template": "| {{=@ @=}} |", + "desc": "Surrounding whitespace should be left untouched." + }, + { + "name": "Outlying Whitespace (Inline)", + "data": {}, + "expected": " | \n", + "template": " | {{=@ @=}}\n", + "desc": "Whitespace should be left untouched." + }, + { + "name": "Standalone Tag", + "data": {}, + "expected": "Begin.\nEnd.\n", + "template": "Begin.\n{{=@ @=}}\nEnd.\n", + "desc": "Standalone lines should be removed from the template." + }, + { + "name": "Indented Standalone Tag", + "data": {}, + "expected": "Begin.\nEnd.\n", + "template": "Begin.\n {{=@ @=}}\nEnd.\n", + "desc": "Indented standalone lines should be removed from the template." + }, + { + "name": "Pair with Padding", + "data": {}, + "expected": "||", + "template": "|{{= @ @ =}}|", + "desc": "Superfluous in-tag whitespace should be ignored." + } + ] +} diff --git a/tests/unit/data/mustache_extra.json b/tests/unit/data/mustache_extra.json new file mode 100644 index 0000000000..e76a54a688 --- /dev/null +++ b/tests/unit/data/mustache_extra.json @@ -0,0 +1,66 @@ +{ + "tests": [ + { + "name": "Demo", + "data": { + "header": "Colors", + "items": [ + {"name": "red", "first": true, "url": "#Red"}, + {"name": "green", "link": true, "url": "#Green"}, + {"name": "blue", "link": true, "url": "#Blue"} + ], + "empty": false + }, + "expected": "

    Colors

    \n\n
  • red
  • \n
  • green
  • \n
  • blue
  • \n\n", + "template": "

    {{header}}

    \n{{#bug}}\n{{/bug}}\n\n{{#items}}\n {{#first}}\n
  • {{name}}
  • \n {{/first}}\n {{#link}}\n
  • {{name}}
  • \n {{/link}}\n{{/items}}\n\n{{#empty}}\n

    The list is empty.

    \n{{/empty}}\n" + }, + { + "name": "Ted's Abusive Test 1", + "data": { "x": 123 }, + "template": "{{x}}", + "expected": "123" + }, + { + "name": "Ted's Abusive Test 2", + "data": { "x": 123, "y": 456 }, + "template": "{{x}} {{y}}", + "expected": "123 456" + }, + { + "name": "Ted's Abusive Test 3", + "data": [ null ], + "template": "{{null}}", + "expected": "" + }, + { + "name": "Ted's Abusive Test 4", + "data": { "x": 123, "y": 456 }, + "template": "{{}}", + "expected": "{{}}" + }, + { + "name": "Ted's Abusive Test 5", + "data": { "boolean": true}, + "template": "{{#boolean}}IT IS TRUE{{/boolean}}", + "expected": "IT IS TRUE" + }, + { + "name": "Ted's Abusive Test 6", + "data": { "boolean": false}, + "template": "{{^boolean}}IT IS FALSE{{/boolean}}", + "expected": "IT IS FALSE" + }, + { + "name": "Ted's Abusive Test 7", + "data": { "list": + [ + { "k": 789, "v": 0 }, + { "k": null, "v": true }, + { "k": -1, "v": -2 } + ] + }, + "template": "{{#list}}{{k}}={{v}}, {{/list}}", + "expected": "789=0, =true, -1=-2, " + }, + ] +} diff --git a/tests/unit/data/mustache_interpolation.json b/tests/unit/data/mustache_interpolation.json new file mode 100644 index 0000000000..0b2c03aa4c --- /dev/null +++ b/tests/unit/data/mustache_interpolation.json @@ -0,0 +1,182 @@ +{ + "tests": [ + { + "name": "No Interpolation", + "data": {}, + "expected": "Hello from {Mustache}!\n", + "template": "Hello from {Mustache}!\n", + "desc": "Mustache-free templates should render as-is." + }, + { + "name": "Basic Interpolation", + "data": { + "subject": "world" + }, + "expected": "Hello, world!\n", + "template": "Hello, {{subject}}!\n", + "desc": "Unadorned tags should interpolate content into the template." + }, + { + "name": "HTML Escaping", + "data": { + "forbidden": "& \" < >" + }, + "expected": "These characters should be HTML escaped: & " < >\n", + "template": "These characters should be HTML escaped: {{forbidden}}\n", + "desc": "Basic interpolation should be HTML escaped." + }, + { + "name": "Triple Mustache", + "data": { + "forbidden": "& \" < >" + }, + "expected": "These characters should not be HTML escaped: & \" < >\n", + "template": "These characters should not be HTML escaped: {{{forbidden}}}\n", + "desc": "Triple mustaches should interpolate without HTML escaping." + }, + { + "name": "Ampersand", + "data": { + "forbidden": "& \" < >" + }, + "expected": "These characters should not be HTML escaped: & \" < >\n", + "template": "These characters should not be HTML escaped: {{&forbidden}}\n", + "desc": "Ampersand should interpolate without HTML escaping." + }, + { + "name": "Basic Integer Interpolation", + "data": { + "mph": 85 + }, + "expected": "\"85 miles an hour!\"", + "template": "\"{{mph}} miles an hour!\"", + "desc": "Integers should interpolate seamlessly." + }, + { + "name": "Triple Mustache Integer Interpolation", + "data": { + "mph": 85 + }, + "expected": "\"85 miles an hour!\"", + "template": "\"{{{mph}}} miles an hour!\"", + "desc": "Integers should interpolate seamlessly." + }, + { + "name": "Ampersand Integer Interpolation", + "data": { + "mph": 85 + }, + "expected": "\"85 miles an hour!\"", + "template": "\"{{&mph}} miles an hour!\"", + "desc": "Integers should interpolate seamlessly." + }, + { + "name": "Basic Decimal Interpolation", + "data": { + "power": 1.21 + }, + "expected": "\"1.21 jiggawatts!\"", + "template": "\"{{power}} jiggawatts!\"", + "desc": "Decimals should interpolate seamlessly with proper significance." + }, + { + "name": "Triple Mustache Decimal Interpolation", + "data": { + "power": 1.21 + }, + "expected": "\"1.21 jiggawatts!\"", + "template": "\"{{{power}}} jiggawatts!\"", + "desc": "Decimals should interpolate seamlessly with proper significance." + }, + { + "name": "Ampersand Decimal Interpolation", + "data": { + "power": 1.21 + }, + "expected": "\"1.21 jiggawatts!\"", + "template": "\"{{&power}} jiggawatts!\"", + "desc": "Decimals should interpolate seamlessly with proper significance." + }, + { + "name": "Interpolation - Surrounding Whitespace", + "data": { + "string": "---" + }, + "expected": "| --- |", + "template": "| {{string}} |", + "desc": "Interpolation should not alter surrounding whitespace." + }, + { + "name": "Triple Mustache - Surrounding Whitespace", + "data": { + "string": "---" + }, + "expected": "| --- |", + "template": "| {{{string}}} |", + "desc": "Interpolation should not alter surrounding whitespace." + }, + { + "name": "Ampersand - Surrounding Whitespace", + "data": { + "string": "---" + }, + "expected": "| --- |", + "template": "| {{&string}} |", + "desc": "Interpolation should not alter surrounding whitespace." + }, + { + "name": "Interpolation - Standalone", + "data": { + "string": "---" + }, + "expected": " ---\n", + "template": " {{string}}\n", + "desc": "Standalone interpolation should not alter surrounding whitespace." + }, + { + "name": "Triple Mustache - Standalone", + "data": { + "string": "---" + }, + "expected": " ---\n", + "template": " {{{string}}}\n", + "desc": "Standalone interpolation should not alter surrounding whitespace." + }, + { + "name": "Ampersand - Standalone", + "data": { + "string": "---" + }, + "expected": " ---\n", + "template": " {{&string}}\n", + "desc": "Standalone interpolation should not alter surrounding whitespace." + }, + { + "name": "Interpolation With Padding", + "data": { + "string": "---" + }, + "expected": "|---|", + "template": "|{{ string }}|", + "desc": "Superfluous in-tag whitespace should be ignored." + }, + { + "name": "Triple Mustache With Padding", + "data": { + "string": "---" + }, + "expected": "|---|", + "template": "|{{{ string }}}|", + "desc": "Superfluous in-tag whitespace should be ignored." + }, + { + "name": "Ampersand With Padding", + "data": { + "string": "---" + }, + "expected": "|---|", + "template": "|{{& string }}|", + "desc": "Superfluous in-tag whitespace should be ignored." + } + ] +} diff --git a/tests/unit/data/mustache_inverted.json b/tests/unit/data/mustache_inverted.json new file mode 100644 index 0000000000..aae4cc3f8f --- /dev/null +++ b/tests/unit/data/mustache_inverted.json @@ -0,0 +1,214 @@ +{ + "__ATTN__": "Do not edit this file; changes belong in the appropriate YAML file.", + "overview": "Inverted Section tags and End Section tags are used in combination to wrap a\nsection of the template.\n\nThese tags' content MUST be a non-whitespace character sequence NOT\ncontaining the current closing delimiter; each Inverted Section tag MUST be\nfollowed by an End Section tag with the same content within the same\nsection.\n\nThis tag's content names the data to replace the tag. Name resolution is as\nfollows:\n 1) Split the name on periods; the first part is the name to resolve, any\n remaining parts should be retained.\n 2) Walk the context stack from top to bottom, finding the first context\n that is a) a hash containing the name as a key OR b) an object responding\n to a method with the given name.\n 3) If the context is a hash, the data is the value associated with the\n name.\n 4) If the context is an object and the method with the given name has an\n arity of 1, the method SHOULD be called with a String containing the\n unprocessed contents of the sections; the data is the value returned.\n 5) Otherwise, the data is the value returned by calling the method with\n the given name.\n 6) If any name parts were retained in step 1, each should be resolved\n against a context stack containing only the result from the former\n resolution. If any part fails resolution, the result should be considered\n falsey, and should interpolate as the empty string.\nIf the data is not of a list type, it is coerced into a list as follows: if\nthe data is truthy (e.g. `!!data == true`), use a single-element list\ncontaining the data, otherwise use an empty list.\n\nThis section MUST NOT be rendered unless the data list is empty.\n\nInverted Section and End Section tags SHOULD be treated as standalone when\nappropriate.\n", + "tests": [ + { + "name": "Falsey", + "data": { + "boolean": false + }, + "expected": "\"This should be rendered.\"", + "template": "\"{{^boolean}}This should be rendered.{{/boolean}}\"", + "desc": "Falsey sections should have their contents rendered." + }, + { + "name": "Truthy", + "data": { + "boolean": true + }, + "expected": "\"\"", + "template": "\"{{^boolean}}This should not be rendered.{{/boolean}}\"", + "desc": "Truthy sections should have their contents omitted." + }, + { + "name": "Context", + "data": { + "context": { + "name": "Joe" + } + }, + "expected": "\"\"", + "template": "\"{{^context}}Hi {{name}}.{{/context}}\"", + "desc": "Objects and hashes should behave like truthy values." + }, + { + "name": "List", + "data": { + "list": [ + { + "n": 1 + }, + { + "n": 2 + }, + { + "n": 3 + } + ] + }, + "expected": "\"\"", + "template": "\"{{^list}}{{n}}{{/list}}\"", + "desc": "Lists should behave like truthy values." + }, + { + "name": "Empty List", + "data": { + "list": [] + }, + "expected": "\"Yay lists!\"", + "template": "\"{{^list}}Yay lists!{{/list}}\"", + "desc": "Empty lists should behave like falsey values." + }, + { + "name": "Doubled", + "data": { + "two": "second", + "bool": false + }, + "expected": "* first\n* second\n* third\n", + "template": "{{^bool}}\n* first\n{{/bool}}\n* {{two}}\n{{^bool}}\n* third\n{{/bool}}\n", + "desc": "Multiple inverted sections per template should be permitted." + }, + { + "name": "Nested (Falsey)", + "data": { + "bool": false + }, + "expected": "| A B C D E |", + "template": "| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |", + "desc": "Nested falsey sections should have their contents rendered." + }, + { + "name": "Nested (Truthy)", + "data": { + "bool": true + }, + "expected": "| A E |", + "template": "| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |", + "desc": "Nested truthy sections should be omitted." + }, + { + "name": "Context Misses", + "data": {}, + "expected": "[Cannot find key 'missing'!]", + "template": "[{{^missing}}Cannot find key 'missing'!{{/missing}}]", + "desc": "Failed context lookups should be considered falsey." + }, + { + "name": "Dotted Names - Truthy", + "data": { + "a": { + "b": { + "c": true + } + } + }, + "expected": "\"\" == \"\"", + "template": "\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"\"", + "desc": "Dotted names should be valid for Inverted Section tags." + }, + { + "name": "Dotted Names - Falsey", + "data": { + "a": { + "b": { + "c": false + } + } + }, + "expected": "\"Not Here\" == \"Not Here\"", + "template": "\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"Not Here\"", + "desc": "Dotted names should be valid for Inverted Section tags." + }, + { + "name": "Dotted Names - Broken Chains", + "data": { + "a": {} + }, + "expected": "\"Not Here\" == \"Not Here\"", + "template": "\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"Not Here\"", + "desc": "Dotted names that cannot be resolved should be considered falsey." + }, + { + "name": "Surrounding Whitespace", + "data": { + "boolean": false + }, + "expected": " | \t|\t | \n", + "template": " | {{^boolean}}\t|\t{{/boolean}} | \n", + "desc": "Inverted sections should not alter surrounding whitespace." + }, + { + "name": "Internal Whitespace", + "data": { + "boolean": false + }, + "expected": " | \n | \n", + "template": " | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n", + "desc": "Inverted should not alter internal whitespace." + }, + { + "name": "Indented Inline Sections", + "data": { + "boolean": false + }, + "expected": " NO\n WAY\n", + "template": " {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n", + "desc": "Single-line sections should not alter surrounding whitespace." + }, + { + "name": "Standalone Lines", + "data": { + "boolean": false + }, + "expected": "| This Is\n|\n| A Line\n", + "template": "| This Is\n{{^boolean}}\n|\n{{/boolean}}\n| A Line\n", + "desc": "Standalone lines should be removed from the template." + }, + { + "name": "Standalone Indented Lines", + "data": { + "boolean": false + }, + "expected": "| This Is\n|\n| A Line\n", + "template": "| This Is\n {{^boolean}}\n|\n {{/boolean}}\n| A Line\n", + "desc": "Standalone indented lines should be removed from the template." + }, + { + "name": "Standalone Line Endings", + "data": { + "boolean": false + }, + "expected": "|\r\n|", + "template": "|\r\n{{^boolean}}\r\n{{/boolean}}\r\n|", + "desc": "\"\\r\\n\" should be considered a newline for standalone tags." + }, + { + "name": "Standalone Without Previous Line", + "data": { + "boolean": false + }, + "expected": "^\n/", + "template": " {{^boolean}}\n^{{/boolean}}\n/", + "desc": "Standalone tags should not require a newline to precede them." + }, + { + "name": "Standalone Without Newline", + "data": { + "boolean": false + }, + "expected": "^\n/\n", + "template": "^{{^boolean}}\n/\n {{/boolean}}", + "desc": "Standalone tags should not require a newline to follow them." + }, + { + "name": "Padding", + "data": { + "boolean": false + }, + "expected": "|=|", + "template": "|{{^ boolean }}={{/ boolean }}|", + "desc": "Superfluous in-tag whitespace should be ignored." + } + ] +} diff --git a/tests/unit/data/mustache_sections.json b/tests/unit/data/mustache_sections.json new file mode 100644 index 0000000000..56279257f5 --- /dev/null +++ b/tests/unit/data/mustache_sections.json @@ -0,0 +1,282 @@ +{ + "__ATTN__": "Do not edit this file; changes belong in the appropriate YAML file.", + "overview": "Section tags and End Section tags are used in combination to wrap a section\nof the template for iteration\n\nThese tags' content MUST be a non-whitespace character sequence NOT\ncontaining the current closing delimiter; each Section tag MUST be followed\nby an End Section tag with the same content within the same section.\n\nThis tag's content names the data to replace the tag. Name resolution is as\nfollows:\n 1) Split the name on periods; the first part is the name to resolve, any\n remaining parts should be retained.\n 2) Walk the context stack from top to bottom, finding the first context\n that is a) a hash containing the name as a key OR b) an object responding\n to a method with the given name.\n 3) If the context is a hash, the data is the value associated with the\n name.\n 4) If the context is an object and the method with the given name has an\n arity of 1, the method SHOULD be called with a String containing the\n unprocessed contents of the sections; the data is the value returned.\n 5) Otherwise, the data is the value returned by calling the method with\n the given name.\n 6) If any name parts were retained in step 1, each should be resolved\n against a context stack containing only the result from the former\n resolution. If any part fails resolution, the result should be considered\n falsey, and should interpolate as the empty string.\nIf the data is not of a list type, it is coerced into a list as follows: if\nthe data is truthy (e.g. `!!data == true`), use a single-element list\ncontaining the data, otherwise use an empty list.\n\nFor each element in the data list, the element MUST be pushed onto the\ncontext stack, the section MUST be rendered, and the element MUST be popped\noff the context stack.\n\nSection and End Section tags SHOULD be treated as standalone when\nappropriate.\n", + "tests": [ + { + "name": "Truthy", + "data": { + "boolean": true + }, + "expected": "\"This should be rendered.\"", + "template": "\"{{#boolean}}This should be rendered.{{/boolean}}\"", + "desc": "Truthy sections should have their contents rendered." + }, + { + "name": "Falsey", + "data": { + "boolean": false + }, + "expected": "\"\"", + "template": "\"{{#boolean}}This should not be rendered.{{/boolean}}\"", + "desc": "Falsey sections should have their contents omitted." + }, + { + "name": "Context", + "data": { + "context": { + "name": "Joe" + } + }, + "expected": "\"Hi Joe.\"", + "template": "\"{{#context}}Hi {{name}}.{{/context}}\"", + "desc": "Objects and hashes should be pushed onto the context stack." + }, + { + "name": "Deeply Nested Contexts", + "data": { + "a": { + "one": 1 + }, + "b": { + "two": 2 + }, + "c": { + "three": 3 + }, + "d": { + "four": 4 + }, + "e": { + "five": 5 + } + }, + "expected": "1\n121\n12321\n1234321\n123454321\n1234321\n12321\n121\n1\n", + "template": "{{#a}}\n{{one}}\n{{#b}}\n{{one}}{{two}}{{one}}\n{{#c}}\n{{one}}{{two}}{{three}}{{two}}{{one}}\n{{#d}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}\n{{#e}}\n{{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}}\n{{/e}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}\n{{/d}}\n{{one}}{{two}}{{three}}{{two}}{{one}}\n{{/c}}\n{{one}}{{two}}{{one}}\n{{/b}}\n{{one}}\n{{/a}}\n", + "desc": "All elements on the context stack should be accessible." + }, + { + "name": "List", + "data": { + "list": [ + { + "item": 1 + }, + { + "item": 2 + }, + { + "item": 3 + } + ] + }, + "expected": "\"123\"", + "template": "\"{{#list}}{{item}}{{/list}}\"", + "desc": "Lists should be iterated; list items should visit the context stack." + }, + { + "name": "Empty List", + "data": { + "list": [] + }, + "expected": "\"\"", + "template": "\"{{#list}}Yay lists!{{/list}}\"", + "desc": "Empty lists should behave like falsey values." + }, + { + "name": "Doubled", + "data": { + "two": "second", + "bool": true + }, + "expected": "* first\n* second\n* third\n", + "template": "{{#bool}}\n* first\n{{/bool}}\n* {{two}}\n{{#bool}}\n* third\n{{/bool}}\n", + "desc": "Multiple sections per template should be permitted." + }, + { + "name": "Nested (Truthy)", + "data": { + "bool": true + }, + "expected": "| A B C D E |", + "template": "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |", + "desc": "Nested truthy sections should have their contents rendered." + }, + { + "name": "Nested (Falsey)", + "data": { + "bool": false + }, + "expected": "| A E |", + "template": "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |", + "desc": "Nested falsey sections should be omitted." + }, + { + "name": "Context Misses", + "data": {}, + "expected": "[]", + "template": "[{{#missing}}Found key 'missing'!{{/missing}}]", + "desc": "Failed context lookups should be considered falsey." + }, + { + "name": "Implicit Iterator - String", + "data": { + "list": [ + "a", + "b", + "c", + "d", + "e" + ] + }, + "expected": "\"(a)(b)(c)(d)(e)\"", + "template": "\"{{#list}}({{.}}){{/list}}\"", + "desc": "Implicit iterators should directly interpolate strings." + }, + { + "name": "Implicit Iterator - Integer", + "data": { + "list": [ + 1, + 2, + 3, + 4, + 5 + ] + }, + "expected": "\"(1)(2)(3)(4)(5)\"", + "template": "\"{{#list}}({{.}}){{/list}}\"", + "desc": "Implicit iterators should cast integers to strings and interpolate." + }, + { + "name": "Implicit Iterator - Decimal", + "data": { + "list": [ + 1.1, + 2.2, + 3.3, + 4.4, + 5.5 + ] + }, + "expected": "\"(1.10)(2.20)(3.30)(4.40)(5.50)\"", + "template": "\"{{#list}}({{.}}){{/list}}\"", + "desc": "Implicit iterators should cast decimals to strings and interpolate." + }, + { + "name": "Dotted Names - Truthy", + "data": { + "a": { + "b": { + "c": true + } + } + }, + "expected": "\"Here\" == \"Here\"", + "template": "\"{{#a.b.c}}Here{{/a.b.c}}\" == \"Here\"", + "desc": "Dotted names should be valid for Section tags." + }, + { + "name": "Dotted Names - Falsey", + "data": { + "a": { + "b": { + "c": false + } + } + }, + "expected": "\"\" == \"\"", + "template": "\"{{#a.b.c}}Here{{/a.b.c}}\" == \"\"", + "desc": "Dotted names should be valid for Section tags." + }, + { + "name": "Dotted Names - Broken Chains", + "data": { + "a": {} + }, + "expected": "\"\" == \"\"", + "template": "\"{{#a.b.c}}Here{{/a.b.c}}\" == \"\"", + "desc": "Dotted names that cannot be resolved should be considered falsey." + }, + { + "name": "Surrounding Whitespace", + "data": { + "boolean": true + }, + "expected": " | \t|\t | \n", + "template": " | {{#boolean}}\t|\t{{/boolean}} | \n", + "desc": "Sections should not alter surrounding whitespace." + }, + { + "name": "Internal Whitespace", + "data": { + "boolean": true + }, + "expected": " | \n | \n", + "template": " | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n", + "desc": "Sections should not alter internal whitespace." + }, + { + "name": "Indented Inline Sections", + "data": { + "boolean": true + }, + "expected": " YES\n GOOD\n", + "template": " {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n", + "desc": "Single-line sections should not alter surrounding whitespace." + }, + { + "name": "Standalone Lines", + "data": { + "boolean": true + }, + "expected": "| This Is\n|\n| A Line\n", + "template": "| This Is\n{{#boolean}}\n|\n{{/boolean}}\n| A Line\n", + "desc": "Standalone lines should be removed from the template." + }, + { + "name": "Indented Standalone Lines", + "data": { + "boolean": true + }, + "expected": "| This Is\n|\n| A Line\n", + "template": "| This Is\n {{#boolean}}\n|\n {{/boolean}}\n| A Line\n", + "desc": "Indented standalone lines should be removed from the template." + }, + { + "name": "Standalone Line Endings", + "data": { + "boolean": true + }, + "expected": "|\r\n|", + "template": "|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|", + "desc": "\"\\r\\n\" should be considered a newline for standalone tags." + }, + { + "name": "Standalone Without Previous Line", + "data": { + "boolean": true + }, + "expected": "#\n/", + "template": " {{#boolean}}\n#{{/boolean}}\n/", + "desc": "Standalone tags should not require a newline to precede them." + }, + { + "name": "Standalone Without Newline", + "data": { + "boolean": true + }, + "expected": "#\n/\n", + "template": "#{{#boolean}}\n/\n {{/boolean}}", + "desc": "Standalone tags should not require a newline to follow them." + }, + { + "name": "Padding", + "data": { + "boolean": true + }, + "expected": "|=|", + "template": "|{{# boolean }}={{/ boolean }}|", + "desc": "Superfluous in-tag whitespace should be ignored." + } + ] +} diff --git a/tests/unit/data/no_bundle_or_body_keyword.cf b/tests/unit/data/no_bundle_or_body_keyword.cf new file mode 100644 index 0000000000..be72385205 --- /dev/null +++ b/tests/unit/data/no_bundle_or_body_keyword.cf @@ -0,0 +1,6 @@ +bunndle agent foo +{ + report: + any:: + "Hello World"; +} diff --git a/tests/unit/data/promise_duplicate_handle.cf b/tests/unit/data/promise_duplicate_handle.cf new file mode 100644 index 0000000000..97363a8be6 --- /dev/null +++ b/tests/unit/data/promise_duplicate_handle.cf @@ -0,0 +1,9 @@ +bundle agent foo +{ + reports: + cfengine3:: + "hello world" + handle => "stuff"; + "quux" + handle => "stuff"; +} diff --git a/tests/unit/data/promise_promiser_nonscalar.cf b/tests/unit/data/promise_promiser_nonscalar.cf new file mode 100644 index 0000000000..84c9c48a3e --- /dev/null +++ b/tests/unit/data/promise_promiser_nonscalar.cf @@ -0,0 +1,5 @@ +bundle agent foo +{ + vars: + a string => "bar"; +} diff --git a/tests/unit/data/promiser_empty_varref.cf b/tests/unit/data/promiser_empty_varref.cf new file mode 100644 index 0000000000..63b7053f77 --- /dev/null +++ b/tests/unit/data/promiser_empty_varref.cf @@ -0,0 +1,5 @@ +bundle agent foo +{ + reports: + "$()"; +} diff --git a/tests/unit/data/rval_function_forgot_colon.cf b/tests/unit/data/rval_function_forgot_colon.cf new file mode 100644 index 0000000000..bb7a4f5198 --- /dev/null +++ b/tests/unit/data/rval_function_forgot_colon.cf @@ -0,0 +1,9 @@ +bundle agent my_commands +{ + + vars: + + any:: + "env" string => getenv("PATH" "20"); + +} diff --git a/tests/unit/data/rval_function_forgot_cp_colon.cf b/tests/unit/data/rval_function_forgot_cp_colon.cf new file mode 100644 index 0000000000..4136f9c687 --- /dev/null +++ b/tests/unit/data/rval_function_forgot_cp_colon.cf @@ -0,0 +1,10 @@ +bundle agent my_commands +{ + + vars: + + any:: + "env" string => getenv("PATH", "20" , + comment => 'get path"; + +} diff --git a/tests/unit/data/rval_function_forgot_cp_semicolon.cf b/tests/unit/data/rval_function_forgot_cp_semicolon.cf new file mode 100644 index 0000000000..9380795d61 --- /dev/null +++ b/tests/unit/data/rval_function_forgot_cp_semicolon.cf @@ -0,0 +1,8 @@ +bundle agent my_commands +{ + + vars: + + any:: + "env" string => getenv("PATH", "20" ; + } diff --git a/tests/unit/data/rval_function_wrong_input_type.cf b/tests/unit/data/rval_function_wrong_input_type.cf new file mode 100644 index 0000000000..3745650d1d --- /dev/null +++ b/tests/unit/data/rval_function_wrong_input_type.cf @@ -0,0 +1,9 @@ +bundle agent my_commands +{ + + vars: + + any:: + "env" string => getenv("PATH", "bas ); + +} diff --git a/tests/unit/data/rval_list_forgot_cb_colon.cf b/tests/unit/data/rval_list_forgot_cb_colon.cf new file mode 100644 index 0000000000..bfe3d48bdb --- /dev/null +++ b/tests/unit/data/rval_list_forgot_cb_colon.cf @@ -0,0 +1,11 @@ +bundle agent my_commands +{ + + vars: + + any:: + + "family" slist => { "andre", "bas" , + comment => "brothers"; + + } diff --git a/tests/unit/data/rval_list_forgot_cb_semicolon.cf b/tests/unit/data/rval_list_forgot_cb_semicolon.cf new file mode 100644 index 0000000000..1f4f7a6bf4 --- /dev/null +++ b/tests/unit/data/rval_list_forgot_cb_semicolon.cf @@ -0,0 +1,10 @@ +bundle agent my_commands +{ + + vars: + + any:: + + "family" slist => { "andre", "bas" ; + + } diff --git a/tests/unit/data/rval_list_forgot_colon.cf b/tests/unit/data/rval_list_forgot_colon.cf new file mode 100644 index 0000000000..b46231cd5f --- /dev/null +++ b/tests/unit/data/rval_list_forgot_colon.cf @@ -0,0 +1,6 @@ +bundle agent my_commands +{ + vars: + + "family" slist => { "andre" "bas" }; +} diff --git a/tests/unit/data/rval_list_wrong_input_type.cf b/tests/unit/data/rval_list_wrong_input_type.cf new file mode 100644 index 0000000000..e720822870 --- /dev/null +++ b/tests/unit/data/rval_list_wrong_input_type.cf @@ -0,0 +1,6 @@ +bundle agent my_commands +{ + vars: + + "family" slist => { "andre", bas:: } ; +} diff --git a/tests/unit/data/rval_wrong_input_type.cf b/tests/unit/data/rval_wrong_input_type.cf new file mode 100644 index 0000000000..df6e8d7e39 --- /dev/null +++ b/tests/unit/data/rval_wrong_input_type.cf @@ -0,0 +1,9 @@ +bundle agent my_commands +{ + + vars: + + any:: + "lastname" string => 'vandervlies ; + +} diff --git a/tests/unit/data/vars_multiple_types.cf b/tests/unit/data/vars_multiple_types.cf new file mode 100644 index 0000000000..5198834ca8 --- /dev/null +++ b/tests/unit/data/vars_multiple_types.cf @@ -0,0 +1,7 @@ +bundle agent foo +{ + vars: + "bar" + string => "snookie", + int => 42; +} diff --git a/tests/unit/db_concurrent_test.c b/tests/unit/db_concurrent_test.c new file mode 100644 index 0000000000..0befdee270 --- /dev/null +++ b/tests/unit/db_concurrent_test.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include + +#include +#include +#include /* xsnprintf */ + + +char CFWORKDIR[CF_BUFSIZE]; + +void tests_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/db_concurrent_test.XXXXXX"; + + char *workdir = strchr(env, '=') + 1; /* start of the path */ + assert(workdir - 1 && workdir[0] == '/'); + + mkdtemp(workdir); + strlcpy(CFWORKDIR, workdir, CF_BUFSIZE); + putenv(env); + mkdir(GetStateDir(), (S_IRWXU | S_IRWXG | S_IRWXO)); +} + +void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +struct arg_struct { + int base; +}; + + +/***************************************************************** +* launch 5 threads +* fct(i) +* one by one insert +* batch insert +* one by one update +* batch update +* delete one by one +* batch delete +* +* join +* check +* 0 - 1999 +* first 100 +* last 1900 +* first 500, if %20, update +1 +* last 1500, if %20, update +1 +* first 500, if %50, delete +* last 1500, if %50, delete +* +* 2000- 3999 +* 4000- 5999 +* 6000- 7999 +* 8000- 9999 +*****************************************************************/ +static void *fct2(void *arguments) +{ + struct arg_struct *args = (struct arg_struct *)arguments; + int base = args->base; + + CF_DB *db; + char key[256]; + char val[256]; + OpenDB(&db, dbid_classes); + + for(int i = base*2000; i + +bool GetAmPolicyHub() +{ + return true; +} diff --git a/tests/unit/db_test.c b/tests/unit/db_test.c new file mode 100644 index 0000000000..51deb19259 --- /dev/null +++ b/tests/unit/db_test.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include /* xsnprintf */ + + +char CFWORKDIR[CF_BUFSIZE]; + +void tests_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/db_test.XXXXXX"; + + char *workdir = strchr(env, '=') + 1; /* start of the path */ + assert(workdir - 1 && workdir[0] == '/'); + + mkdtemp(workdir); + strlcpy(CFWORKDIR, workdir, CF_BUFSIZE); + putenv(env); + mkdir(GetStateDir(), (S_IRWXU | S_IRWXG | S_IRWXO)); +} + +void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +void test_open_close(void) +{ + // Test that we can simply open and close a database without doing anything. + CF_DB *db; + assert_int_equal(OpenDB(&db, dbid_classes), true); + CloseDB(db); +} + +void test_read_write(void) +{ + // Test that we can do normal reads and write, and that the values are + // reflected in the database. + CF_DB *db; + char value[CF_BUFSIZE]; + strcpy(value, "myvalue"); + int vsize = strlen(value) + 1; + + assert_int_equal(OpenDB(&db, dbid_classes), true); + + assert_int_equal(ReadDB(db, "written_entry", &value, vsize), false); + assert_string_equal(value, "myvalue"); + assert_int_equal(WriteDB(db, "written_entry", value, vsize), true); + strcpy(value, ""); + assert_int_equal(ReadDB(db, "written_entry", &value, vsize), true); + assert_string_equal(value, "myvalue"); + + CloseDB(db); + + // Check also after we reopen the database. + assert_int_equal(OpenDB(&db, dbid_classes), true); + strcpy(value, ""); + assert_int_equal(ReadDB(db, "written_entry", &value, vsize), true); + assert_string_equal(value, "myvalue"); + + CloseDB(db); +} + +void test_iter_modify_entry(void) +{ + /* Test that deleting entry under cursor does not interrupt iteration */ + + CF_DB *db; + assert_int_equal(OpenDB(&db, dbid_classes), true); + + assert_int_equal(WriteDB(db, "foobar", "abc", 3), true); + assert_int_equal(WriteDB(db, "bazbaz", "def", 3), true); + assert_int_equal(WriteDB(db, "booo", "ghi", 3), true); + + CF_DBC *cursor; + assert_int_equal(NewDBCursor(db, &cursor), true); + + char *key; + int ksize; + void *value; + int vsize; + + assert_int_equal(NextDB(cursor, &key, &ksize, &value, &vsize), true); + + assert_int_equal(DBCursorWriteEntry(cursor, "eee", 3), true); + + assert_int_equal(NextDB(cursor, &key, &ksize, &value, &vsize), true); + assert_int_equal(NextDB(cursor, &key, &ksize, &value, &vsize), true); + + assert_int_equal(DeleteDBCursor(cursor), true); + + CloseDB(db); +} + + +void test_iter_delete_entry(void) +{ + /* Test that deleting entry under cursor does not interrupt iteration */ + + CF_DB *db; + assert_int_equal(OpenDB(&db, dbid_classes), true); + + assert_int_equal(WriteDB(db, "foobar", "abc", 3), true); + assert_int_equal(WriteDB(db, "bazbaz", "def", 3), true); + assert_int_equal(WriteDB(db, "booo", "ghi", 3), true); + + CF_DBC *cursor; + assert_int_equal(NewDBCursor(db, &cursor), true); + + char *key; + int ksize; + void *value; + int vsize; + + assert_int_equal(NextDB(cursor, &key, &ksize, &value, &vsize), true); + + assert_int_equal(DBCursorDeleteEntry(cursor), true); + + assert_int_equal(NextDB(cursor, &key, &ksize, &value, &vsize), true); + assert_int_equal(NextDB(cursor, &key, &ksize, &value, &vsize), true); + + assert_int_equal(DeleteDBCursor(cursor), true); + + CloseDB(db); +} + +#if defined(HAVE_LIBTOKYOCABINET) || defined(HAVE_LIBQDBM) || defined(HAVE_LIBLMDB) +static void CreateGarbage(const char *filename) +{ + FILE *fh = fopen(filename, "w"); + for(int i = 0; i < 2; ++i) + { + fwrite("some garbage!", 14, 1, fh); + } + fclose(fh); +} +#endif /* HAVE_LIBTOKYOCABINET || HAVE_LIBQDBM */ + +void test_recreate(void) +{ + /* Test that recreating database works properly */ +#ifdef HAVE_LIBTOKYOCABINET + char tcdb_db[CF_BUFSIZE]; + xsnprintf(tcdb_db, CF_BUFSIZE, "%s/cf_classes.tcdb", GetStateDir()); + CreateGarbage(tcdb_db); +#endif +#ifdef HAVE_LIBQDBM + char qdbm_db[CF_BUFSIZE]; + xsnprintf(qdbm_db, CF_BUFSIZE, "%s/cf_classes.qdbm", GetStateDir()); + CreateGarbage(qdbm_db); +#endif +#ifdef HAVE_LIBLMDB + char lmdb_db[CF_BUFSIZE]; + xsnprintf(lmdb_db, CF_BUFSIZE, "%s/cf_classes.lmdb", CFWORKDIR); + CreateGarbage(lmdb_db); +#endif + + CF_DB *db; + assert_int_equal(OpenDB(&db, dbid_classes), true); + CloseDB(db); +} + +void test_old_workdir_db_location(void) +{ +#ifndef LMDB + // We manipulate the LMDB file name directly. Not adapted to the others. + return; +#endif + + CF_DB *db; + + char *state_dir; + + xasprintf(&state_dir, "%s%cstate", GetWorkDir(), FILE_SEPARATOR); + + if (strcmp(GetStateDir(), state_dir) != 0) + { + // Test only works when statedir is $(workdir)/state. + free(state_dir); + return; + } + + assert_true(OpenDB(&db, dbid_lastseen)); + assert_true(WriteDB(db, "key", "first_value", strlen("first_value") + 1)); + CloseDB(db); + + char *old_db, *orig_db, *new_db; + // Due to caching of the path we need to use a different db when opening the + // second time, otherwise the path is not rechecked. + xasprintf(&orig_db, "%s%ccf_lastseen.lmdb", GetStateDir(), FILE_SEPARATOR); + xasprintf(&old_db, "%s%ccf_audit.lmdb", GetWorkDir(), FILE_SEPARATOR); + xasprintf(&new_db, "%s%ccf_audit.lmdb", GetStateDir(), FILE_SEPARATOR); + + // Copy database to old location. + assert_true(CopyRegularFileDisk(orig_db, old_db)); + + // Change content. + assert_true(OpenDB(&db, dbid_lastseen)); + assert_true(WriteDB(db, "key", "second_value", strlen("second_value") + 1)); + CloseDB(db); + + // Copy database to new location. + assert_true(CopyRegularFileDisk(orig_db, new_db)); + + char value[CF_BUFSIZE]; + + // Old location should take precedence. + assert_true(OpenDB(&db, dbid_audit)); + assert_true(ReadDB(db, "key", value, sizeof(value))); + assert_string_equal(value, "first_value"); + CloseDB(db); + + free(state_dir); + free(old_db); + free(orig_db); + free(new_db); +} + +int main() +{ + PRINT_TEST_BANNER(); + tests_setup(); + + const UnitTest tests[] = + { + unit_test(test_open_close), + unit_test(test_read_write), + unit_test(test_iter_modify_entry), + unit_test(test_iter_delete_entry), + unit_test(test_recreate), + unit_test(test_old_workdir_db_location), + }; + + PRINT_TEST_BANNER(); + int ret = run_tests(tests); + + tests_teardown(); + return ret; +} + +/* STUBS */ + +void FatalError(ARG_UNUSED char *s, ...) +{ + fail(); + exit(42); +} + + diff --git a/tests/unit/domainname_test.c b/tests/unit/domainname_test.c new file mode 100644 index 0000000000..436830abca --- /dev/null +++ b/tests/unit/domainname_test.c @@ -0,0 +1,113 @@ +#include + +#include + +char fqname[CF_BUFSIZE]; +char uqname[CF_BUFSIZE]; +char domain[CF_BUFSIZE]; + +void CalculateDomainName(const char *nodename, const char *dnsname, + char *fqname, size_t fqname_size, + char *uqname, size_t uqname_size, + char *domain, size_t domain_size); + +static void test_fqname(void) +{ + const char nodename[] = "mylaptop.example.com"; + const char dnsname[] = "mylaptop.example.com"; + + CalculateDomainName(nodename, dnsname, + fqname, sizeof(fqname), uqname, sizeof(uqname), domain, sizeof(domain)); + + assert_string_equal(fqname, "mylaptop.example.com"); + assert_string_equal(uqname, "mylaptop"); + assert_string_equal(domain, "example.com"); +} + +static void test_uqname(void) +{ + CalculateDomainName("mylaptop", "mylaptop.example.com", + fqname, sizeof(fqname), uqname, sizeof(uqname), domain, sizeof(domain)); + + assert_string_equal(fqname, "mylaptop.example.com"); + assert_string_equal(uqname, "mylaptop"); + assert_string_equal(domain, "example.com"); +} + +static void test_uqname2(void) +{ + CalculateDomainName("user.laptop", "user.laptop.example.com", + fqname, sizeof(fqname), uqname, sizeof(uqname), domain, sizeof(domain)); + + assert_string_equal(fqname, "user.laptop.example.com"); + assert_string_equal(uqname, "user.laptop"); + assert_string_equal(domain, "example.com"); +} + +static void test_fqname_not_really_fq(void) +{ + CalculateDomainName("user.laptop", "user.laptop", + fqname, sizeof(fqname), uqname, sizeof(uqname), domain, sizeof(domain)); + + assert_string_equal(fqname, "user.laptop"); + assert_string_equal(uqname, "user"); + assert_string_equal(domain, "laptop"); +} + +static void test_fqname_not_really_fq2(void) +{ + CalculateDomainName("laptop", "laptop", + fqname, sizeof(fqname), uqname, sizeof(uqname), domain, sizeof(domain)); + + assert_string_equal(fqname, "laptop"); + assert_string_equal(uqname, "laptop"); + assert_string_equal(domain, ""); +} + +static void test_fqname_unresolvable(void) +{ + CalculateDomainName("laptop", "", + fqname, sizeof(fqname), uqname, sizeof(uqname), domain, sizeof(domain)); + + assert_string_equal(fqname, "laptop"); + assert_string_equal(uqname, "laptop"); + assert_string_equal(domain, ""); +} + +static void test_no_names(void) +{ + CalculateDomainName("", "", + fqname, sizeof(fqname), uqname, sizeof(uqname), domain, sizeof(domain)); + + assert_string_equal(fqname, ""); + assert_string_equal(uqname, ""); + assert_string_equal(domain, ""); +} + +static void test_wrong_fqname(void) +{ + CalculateDomainName("laptop", "a1006.cfengine.com", + fqname, sizeof(fqname), uqname, sizeof(uqname), domain, sizeof(domain)); + + assert_string_equal(fqname, "a1006.cfengine.com"); + assert_string_equal(uqname, "laptop"); + assert_string_equal(domain, ""); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_fqname), + unit_test(test_uqname), + unit_test(test_uqname2), + unit_test(test_fqname_not_really_fq), + unit_test(test_fqname_not_really_fq2), + unit_test(test_fqname_unresolvable), + unit_test(test_no_names), + unit_test(test_wrong_fqname), + }; + + return run_tests(tests); +} diff --git a/tests/unit/dynamic_dependency_test.sh b/tests/unit/dynamic_dependency_test.sh new file mode 100755 index 0000000000..aa62855ea1 --- /dev/null +++ b/tests/unit/dynamic_dependency_test.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +# Tests that the symbols in our static libraries do not occur twice in the +# output binaries. Most platforms don't warn about this, but it has potential +# ill effects. +# +# How it can happen: It can happen if we list a static library as a dependency +# for libpromises, and then we list the same static library as a dependency for +# a binary that depends on libpromises. Then the symbol will be included both +# in the library and in the binary. +# +# What effects does it have: Exactly how the symbols are resolved appears to be +# platform dependent, but what can happen is this: At any point, when you call +# a duplicate symbol, depending on where you call it from, you will either call +# the version in the binary or in the library. This is fine, since they both +# contain exactly the same code. However, they do not refer to the same global +# symbols. They each have their own set. This means that something that was +# initialized in the binary may not be initialized in the library, even though +# the symbol name is the same. This has weird effects, like a symbol suddenly +# switching from a valid value to zero when the stack trace crosses a library +# boundary. +# +# The problem has been observed on AIX, where the log level randomly switches +# between verbose and non-verbose, depending on where the Log() function was +# called from. It has also been observed on certain Linux versions (Ubuntu +# 12.04). +# +# How do we test for it: By making sure that functions that are known to be in +# the static libraries are undefined in the binaries, which means that they +# will link to the shared library version, instead of using their own version. + +# +# Detect and replace non-POSIX shell +# +try_exec() { + type "$1" > /dev/null 2>&1 && exec "$@" +} + +broken_posix_shell() +{ + unset foo + local foo=1 + test "$foo" != "1" +} + +if broken_posix_shell >/dev/null 2>&1; then + try_exec /usr/xpg4/bin/sh "$0" "$@" + echo "No compatible shell script interpreter found." + echo "Please find a POSIX shell for your system." + exit 42 +fi + + +cd ../.. + +# Sanity check that nm works. +if ! which nm | grep '^/' >/dev/null; then + echo "Could not find nm" + exit 2 +fi + +# libutils.a libenv.a libcfnet.a +# v v v +for symbol in LogSetGlobalLevel GetInterfacesInfo ConnectionInfoNew; do + for binary in cf-*; do + if test "$binary" = "cf-check" ; then + continue + fi + if test -e "$binary/.libs/$binary"; then + LOC="$binary/.libs/$binary" + else + LOC="$binary/$binary" + fi + if nm "$LOC" | grep "$symbol" >/dev/null 2>&1 && ! nm -u "$LOC" | grep "$symbol" >/dev/null 2>&1; then + echo "$symbol is defined in $binary, but should be undefined." + echo "Most likely a static library is listed in the Makefile.am which shouldn't be." + echo "Check the *_LDADD statements in $binary/Makefile.am." + exit 1 + fi + done +done + +exit 0 diff --git a/tests/unit/enterprise_extension_test.c b/tests/unit/enterprise_extension_test.c new file mode 100644 index 0000000000..b340098609 --- /dev/null +++ b/tests/unit/enterprise_extension_test.c @@ -0,0 +1,77 @@ +#include + +#include +#include + +#include + + +ENTERPRISE_FUNC_2ARG_DECLARE(int64_t, extension_function, int32_t, short_int, int64_t, long_int); +ENTERPRISE_FUNC_2ARG_DECLARE(int64_t, extension_function_broken, int32_t, short_int, int64_t, long_int); + +ENTERPRISE_FUNC_2ARG_DEFINE_STUB(int64_t, extension_function, int32_t, short_int, int64_t, long_int) +{ + return short_int + long_int; +} + +ENTERPRISE_FUNC_2ARG_DEFINE_STUB(int64_t, extension_function_broken, int32_t, short_int, int64_t, long_int) +{ + return short_int + long_int; +} + +static void test_extension_function_stub(void) +{ + putenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR=nonexistingdir"); + + assert_int_equal(extension_function(2, 3), 5); + + unsetenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR"); +} + +static void test_extension_function(void) +{ + putenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR=.libs"); + + assert_int_equal(extension_function(2, 3), 6); + + unsetenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR"); +} + +static void test_extension_function_broken(void) +{ + putenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR=.libs"); + + // This one should call the stub, even if the extension is available, because the + // function signature is different. + assert_int_equal(extension_function_broken(2, 3), 5); + + unsetenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR"); +} + +static void test_extension_function_version_mismatch() +{ + putenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR=.libs"); + putenv("CFENGINE_TEST_RETURN_VERSION=1.1.1"); + + // This one should call the stub, even if the extension is available, because the + // version is different. + assert_int_equal(extension_function(2, 3), 5); + + unsetenv("CFENGINE_TEST_RETURN_VERSION"); + unsetenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR"); +} + +int main() +{ + putenv("CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DO_CLOSE=1"); + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_extension_function_stub), + unit_test(test_extension_function), + unit_test(test_extension_function_broken), + unit_test(test_extension_function_version_mismatch), + }; + + return run_tests(tests); +} diff --git a/tests/unit/enterprise_extension_test_lib.c b/tests/unit/enterprise_extension_test_lib.c new file mode 100644 index 0000000000..f9de16f5ff --- /dev/null +++ b/tests/unit/enterprise_extension_test_lib.c @@ -0,0 +1,34 @@ +#include + +// Pretend we are an extension. +#define BUILDING_ENTERPRISE_EXTENSION +#include +#include + +ENTERPRISE_FUNC_2ARG_DECLARE(int64_t, extension_function, int32_t, short_int, int64_t, long_int); +ENTERPRISE_FUNC_3ARG_DECLARE(int64_t, extension_function_broken, int32_t, short_int, int64_t, unwanted_par, int64_t, long_int); + +ENTERPRISE_FUNC_2ARG_DEFINE(int64_t, extension_function, int32_t, short_int, int64_t, long_int) +{ + return short_int * long_int; +} + +// Notice that this function has a different signature from the one in the test .c file. +ENTERPRISE_FUNC_3ARG_DEFINE(int64_t, extension_function_broken, int32_t, short_int, int64_t, unwanted_par, int64_t, long_int) +{ + (void)unwanted_par; + return short_int * long_int; +} + +const char *GetExtensionLibraryVersion() +{ + const char *version = getenv("CFENGINE_TEST_RETURN_VERSION"); + if (version) + { + return version; + } + else + { + return VERSION; + } +} diff --git a/tests/unit/eval_context_test.c b/tests/unit/eval_context_test.c new file mode 100644 index 0000000000..2acfc3ec5c --- /dev/null +++ b/tests/unit/eval_context_test.c @@ -0,0 +1,144 @@ +#include + +#include +#include /* xsnprintf */ +#include + +char CFWORKDIR[CF_BUFSIZE]; + +void tests_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/CFENGINE_eval_context_test.XXXXXX"; + char *workdir = strchr(env, '='); + assert(workdir && workdir[1] == '/'); + workdir++; + + mkdtemp(workdir); + strlcpy(CFWORKDIR, workdir, CF_BUFSIZE); + putenv(env); + + mkdir(GetStateDir(), 0766); +} + +void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +static void test_class_persistence(void) +{ + EvalContext *ctx = EvalContextNew(); + + // simulate old version + { + CF_DB *dbp; + PersistentClassInfo i; + assert_true(OpenDB(&dbp, dbid_state)); + + i.expires = UINT_MAX; + i.policy = CONTEXT_STATE_POLICY_RESET; + + WriteDB(dbp, "old", &i, sizeof(PersistentClassInfo)); + + CloseDB(dbp); + } + + // e.g. by monitoring + EvalContextHeapPersistentSave(ctx, "class1", 3, CONTEXT_STATE_POLICY_PRESERVE, "a,b"); + + // e.g. by a class promise in a bundle with a namespace + { + Policy *p = PolicyNew(); + Bundle *bp = PolicyAppendBundle(p, "ns1", "bundle1", "agent", NULL, NULL); + + EvalContextStackPushBundleFrame(ctx, bp, NULL, false); + EvalContextHeapPersistentSave(ctx, "class2", 5, CONTEXT_STATE_POLICY_PRESERVE, "x"); + EvalContextStackPopFrame(ctx); + + PolicyDestroy(p); + } + + EvalContextHeapPersistentLoadAll(ctx); + + { + const Class *cls = EvalContextClassGet(ctx, "default", "old"); + assert_true(cls != NULL); + + assert_string_equal("old", cls->name); + assert_true(cls->tags != NULL); + assert_int_equal(1, StringSetSize(cls->tags)); + assert_true(StringSetContains(cls->tags, "source=persistent")); + } + + { + const Class *cls = EvalContextClassGet(ctx, "default", "class1"); + assert_true(cls != NULL); + + assert_string_equal("class1", cls->name); + assert_true(cls->tags != NULL); + assert_int_equal(3, StringSetSize(cls->tags)); + assert_true(StringSetContains(cls->tags, "source=persistent")); + assert_true(StringSetContains(cls->tags, "a")); + assert_true(StringSetContains(cls->tags, "b")); + } + + { + const Class *cls = EvalContextClassGet(ctx, "ns1", "class2"); + assert_true(cls != NULL); + + assert_string_equal("ns1", cls->ns); + assert_string_equal("class2", cls->name); + assert_true(cls->tags != NULL); + assert_int_equal(2, StringSetSize(cls->tags)); + assert_true(StringSetContains(cls->tags, "source=persistent")); + assert_true(StringSetContains(cls->tags, "x")); + } + + EvalContextDestroy(ctx); +} + +void test_changes_chroot(void) +{ + /* Should add '/' to the end implicitly. */ + SetChangesChroot("/changes/go/here"); + + /* The most trivial case. */ + const char *chrooted = ToChangesChroot("/etc/issue"); + assert_string_equal(chrooted, "/changes/go/here/etc/issue"); + + /* A shorter path to test that NUL-byte is added/copied properly. */ + chrooted = ToChangesChroot("/etc/ab"); + assert_string_equal(chrooted, "/changes/go/here/etc/ab"); + + /* And a longer path again. */ + chrooted = ToChangesChroot("/etc/sysctl.d/00-default.conf"); + assert_string_equal(chrooted, "/changes/go/here/etc/sysctl.d/00-default.conf"); + +#ifndef __MINGW32__ + /* Inverse should work as expected */ + const char *normal = ToNormalRoot(chrooted); + assert_string_equal(normal, "/etc/sysctl.d/00-default.conf"); +#endif +} + +int main() +{ + PRINT_TEST_BANNER(); + tests_setup(); + + const UnitTest tests[] = + { + unit_test(test_class_persistence), + unit_test(test_changes_chroot), + }; + + int ret = run_tests(tests); + + tests_teardown(); + + return ret; +} + diff --git a/tests/unit/evalfunction_test.c b/tests/unit/evalfunction_test.c new file mode 100644 index 0000000000..4a69f438f7 --- /dev/null +++ b/tests/unit/evalfunction_test.c @@ -0,0 +1,135 @@ +#include + +#include +#include + +static bool netgroup_more = false; + +#if SETNETGRENT_RETURNS_INT +int +#else +void +#endif +setnetgrent(const char *netgroup) +{ + if (strcmp(netgroup, "valid_netgroup") == 0) + { + netgroup_more = true; +#if SETNETGRENT_RETURNS_INT + return 1; +#else + return; +#endif + } + netgroup_more = false; + +#if SETNETGRENT_RETURNS_INT + return 0; +#endif +} + +int getnetgrent(char **hostp, char **userp, char **domainp) +{ + if (netgroup_more) + { + *hostp = NULL; + *userp = "user"; + *domainp = NULL; + + netgroup_more = false; + return 1; + } + else + { + return 0; + } +} + +static void test_hostinnetgroup_found(void) +{ + EvalContext *ctx = EvalContextNew(); + + FnCallResult res; + Rlist *args = NULL; + + RlistAppendScalar(&args, "valid_netgroup"); + + res = FnCallHostInNetgroup(ctx, NULL, NULL, args); + assert_string_equal("any", (char *) res.rval.item); + + RvalDestroy(res.rval); + RlistDestroy(args); + EvalContextDestroy(ctx); +} + +static void test_hostinnetgroup_not_found(void) +{ + EvalContext *ctx = EvalContextNew(); + + FnCallResult res; + Rlist *args = NULL; + + RlistAppendScalar(&args, "invalid_netgroup"); + + res = FnCallHostInNetgroup(ctx, NULL, NULL, args); + assert_string_equal("!any", (char *) res.rval.item); + + RvalDestroy(res.rval); + RlistDestroy(args); + EvalContextDestroy(ctx); +} + +#define basename_single_testcase(input, suffix, expected) \ + { \ + FnCallResult res; \ + Rlist *args = NULL; \ + \ + RlistAppendScalar(&args, input); \ + if (suffix != NULL) \ + { \ + RlistAppendScalar(&args, suffix); \ + } \ + \ + FnCall *call = FnCallNew("basename", args); \ + \ + res = FnCallBasename(NULL, NULL, call, args); \ + assert_string_equal(expected, (char *) res.rval.item); \ + \ + RvalDestroy(res.rval); \ + FnCallDestroy(call); \ + } + +static void test_basename(void) +{ + basename_single_testcase("/", NULL, "/"); + basename_single_testcase("//", NULL, "/"); + basename_single_testcase("///", NULL, "/"); + basename_single_testcase("///////", NULL, "/"); + basename_single_testcase("./", NULL, "."); + basename_single_testcase(".", NULL, "."); + basename_single_testcase("", NULL, ""); + + basename_single_testcase("/foo/bar", NULL, "bar"); + basename_single_testcase("/foo/bar/", NULL, "bar"); + basename_single_testcase("//a//b///c////", NULL, "c"); + + basename_single_testcase("", "", ""); + basename_single_testcase("/", "", "/"); + basename_single_testcase("/foo/bar.txt", ".txt", "bar"); + basename_single_testcase("/foo/bar.txt/", ".txt", "bar"); + basename_single_testcase("//a//b///c////", "", "c"); + basename_single_testcase("//a//b///c////", "blah", "c"); + basename_single_testcase("//a//b///c.csv////", ".csv", "c"); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = { + unit_test(test_hostinnetgroup_found), + unit_test(test_hostinnetgroup_not_found), + unit_test(test_basename), + }; + + return run_tests(tests); +} diff --git a/tests/unit/exec-config-test.c b/tests/unit/exec-config-test.c new file mode 100644 index 0000000000..8e4de9662c --- /dev/null +++ b/tests/unit/exec-config-test.c @@ -0,0 +1,172 @@ +#include + +#include +#include + +#include +#include +#include +#include /* xsnprintf */ + + +static Policy *TestParsePolicy(const char *filename) +{ + char path[PATH_MAX]; + xsnprintf(path, sizeof(path), "%s/%s", TESTDATADIR, filename); + + return ParserParseFile(AGENT_TYPE_COMMON, path, PARSER_WARNING_ALL, PARSER_WARNING_ALL); +} + +typedef void (*TestFn)(const EvalContext *ctx, const Policy *policy); + +static void run_test_in_policy(const char *policy_filename, TestFn fn) +{ + GenericAgentConfig *agent_config = GenericAgentConfigNewDefault(AGENT_TYPE_EXECUTOR, false); + EvalContext *ctx = EvalContextNew(); + Policy *policy = TestParsePolicy(policy_filename); + PolicyResolve(ctx, policy, agent_config); + + /* Setup global environment */ + strcpy(VFQNAME, "localhost.localdomain"); + strcpy(VIPADDRESS, "127.0.0.100"); + EvalContextAddIpAddress(ctx, "127.0.0.100", "eth0"); + EvalContextAddIpAddress(ctx, "127.0.0.101", "eth1"); + + fn(ctx, policy); + + PolicyDestroy(policy); + GenericAgentFinalize(ctx, agent_config); +} + +static void execd_config_empty_cb(const EvalContext *ctx, const Policy *policy) +{ + ExecdConfig *config = ExecdConfigNew(ctx, policy); + + assert_int_equal(12, StringSetSize(config->schedule)); + assert_int_equal(true, StringSetContains(config->schedule, "Min00")); + assert_int_equal(true, StringSetContains(config->schedule, "Min05")); + assert_int_equal(true, StringSetContains(config->schedule, "Min10")); + assert_int_equal(true, StringSetContains(config->schedule, "Min15")); + assert_int_equal(true, StringSetContains(config->schedule, "Min20")); + assert_int_equal(true, StringSetContains(config->schedule, "Min25")); + assert_int_equal(true, StringSetContains(config->schedule, "Min30")); + assert_int_equal(true, StringSetContains(config->schedule, "Min35")); + assert_int_equal(true, StringSetContains(config->schedule, "Min40")); + assert_int_equal(true, StringSetContains(config->schedule, "Min45")); + assert_int_equal(true, StringSetContains(config->schedule, "Min50")); + assert_int_equal(true, StringSetContains(config->schedule, "Min55")); + assert_int_equal(0, config->splay_time); + assert_string_equal("LOG_USER", config->log_facility); + + ExecdConfigDestroy(config); +} + +static void test_execd_config_empty(void) +{ + run_test_in_policy("body_executor_control_empty.cf", &execd_config_empty_cb); +} + +static void execd_config_full_cb(const EvalContext *ctx, const Policy *policy) +{ + ExecdConfig *config = ExecdConfigNew(ctx, policy); + + assert_int_equal(2, StringSetSize(config->schedule)); + assert_int_equal(true, StringSetContains(config->schedule, "Min00_05")); + assert_int_equal(true, StringSetContains(config->schedule, "Min05_10")); + /* Splay calculation uses FQNAME and getuid(), so can't predict + actual splay value */ + assert_int_equal(true, config->splay_time>=0 && config->splay_time<60); + assert_string_equal("LOG_LOCAL6", config->log_facility); + + ExecdConfigDestroy(config); +} + +static void test_execd_config_full(void) +{ + run_test_in_policy("body_executor_control_full.cf", &execd_config_full_cb); +} + + +static void exec_config_empty_cb(const EvalContext *ctx, const Policy *policy) +{ + ExecConfig *config = ExecConfigNew(false, ctx, policy); + + assert_int_equal(false, config->scheduled_run); + /* FIXME: exec-config should provide default exec_command */ + assert_string_equal("", config->exec_command); + assert_string_equal("", config->mail_server); + /* FIXME: exec-config should provide default from address */ + assert_string_equal("", config->mail_from_address); + assert_string_equal("", config->mail_to_address); + /* FIXME: exec-config should provide default subject */ + assert_string_equal("", config->mail_subject); + assert_int_equal(30, config->mail_max_lines); + assert_string_equal("localhost.localdomain", config->fq_name); + assert_string_equal("127.0.0.100", config->ip_address); + assert_string_equal("127.0.0.100 127.0.0.101", config->ip_addresses); + + ExecConfigDestroy(config); +} + +static void test_exec_config_empty(void) +{ + run_test_in_policy("body_executor_control_empty.cf", &exec_config_empty_cb); +} + +static void CheckFullExecConfig(const ExecConfig *config) +{ + assert_int_equal(true, config->scheduled_run); + assert_string_equal("/bin/echo", config->exec_command); + assert_int_equal(120, config->agent_expireafter); + assert_string_equal("localhost", config->mail_server); + assert_string_equal("cfengine@example.org", config->mail_from_address); + assert_string_equal("cfengine_mail@example.org", config->mail_to_address); + assert_string_equal("Test [localhost/127.0.0.1]", config->mail_subject); + assert_int_equal(50, config->mail_max_lines); + assert_string_equal("localhost.localdomain", config->fq_name); + assert_string_equal("127.0.0.100", config->ip_address); + assert_string_equal("127.0.0.100 127.0.0.101", config->ip_addresses); +} + +static void exec_config_full_cb(const EvalContext *ctx, const Policy *policy) +{ + ExecConfig *config = ExecConfigNew(true, ctx, policy); + CheckFullExecConfig(config); + ExecConfigDestroy(config); +} + +static void test_exec_config_full(void) +{ + run_test_in_policy("body_executor_control_full.cf", &exec_config_full_cb); +} + +static void exec_config_copy_cb(const EvalContext *ctx, const Policy *policy) +{ + ExecConfig *config = ExecConfigNew(true, ctx, policy); + ExecConfig *config2 = ExecConfigCopy(config); + ExecConfigDestroy(config); + + CheckFullExecConfig(config2); + + ExecConfigDestroy(config2); +} + +static void test_exec_config_copy(void) +{ + run_test_in_policy("body_executor_control_full.cf", &exec_config_copy_cb); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_execd_config_empty), + unit_test(test_execd_config_full), + unit_test(test_exec_config_empty), + unit_test(test_exec_config_full), + unit_test(test_exec_config_copy), + }; + + return run_tests(tests); +} diff --git a/tests/unit/expand_test.c b/tests/unit/expand_test.c new file mode 100644 index 0000000000..45715b4d11 --- /dev/null +++ b/tests/unit/expand_test.c @@ -0,0 +1,600 @@ +#include + +#include +#include +#include +#include +#include + +static void test_extract_scalar_prefix() +{ + Buffer *b = BufferNew(); + assert_int_equal(sizeof("hello ") - 1, ExtractScalarPrefix(b, "hello $(world) xy", sizeof("hello $(world) xy") -1)); + assert_string_equal("hello ", BufferData(b)); + + BufferClear(b); + assert_int_equal(sizeof("hello (world) xy") -1, ExtractScalarPrefix(b, "hello (world) xy", sizeof("hello (world) xy") -1)); + assert_string_equal("hello (world) xy", BufferData(b)); + + BufferClear(b); + assert_int_equal(sizeof("hello$)") -1, ExtractScalarPrefix(b, "hello$)$(world)xy", sizeof("hello$)$(world)xy") -1)); + assert_string_equal("hello$)", BufferData(b)); + + BufferClear(b); + assert_int_equal(0, ExtractScalarPrefix(b, "", 0)); + assert_string_equal("", BufferData(b)); + + BufferDestroy(b); +} + +static void test_extract_reference_(const char *scalar, bool expect_success, const char *outer, const char *inner) +{ + Buffer *b = BufferNew(); + size_t len = strlen(scalar); + + bool success = ExtractScalarReference(b, scalar, len, false); + assert_true(success == expect_success); + assert_string_equal(outer, BufferData(b)); + + BufferClear(b); + success = ExtractScalarReference(b, scalar, len, true); + assert_true(success == expect_success); + assert_string_equal(inner, BufferData(b)); + + BufferDestroy(b); +} + +static void test_extract_reference(void) +{ + test_extract_reference_("${stuff}", true, "${stuff}", "stuff"); + test_extract_reference_("$(stuff)", true, "$(stuff)", "stuff"); + test_extract_reference_("abc $def ${x} y", true, "${x}", "x"); + test_extract_reference_("${stuff)", false, "", ""); + test_extract_reference_("abc $def", false, "", ""); + test_extract_reference_("stuff", false, "", ""); + test_extract_reference_("", false, "", ""); + test_extract_reference_("abc $xa ", false, "", ""); + test_extract_reference_("${}", false, "", ""); + test_extract_reference_("x$()a", false, "", ""); + + test_extract_reference_("$($(x))", true, "$($(x))", "$(x)"); + test_extract_reference_("$(x${$(y)})", true, "$(x${$(y)})", "x${$(y)}"); + test_extract_reference_("$(x${$(y)}) $(y) ${x${z}}", true, "$(x${$(y)})", "x${$(y)}"); +} + +static void test_isnakedvar() +{ + assert_true(IsNakedVar("$(whatever)", '$')); + assert_true(IsNakedVar("${whatever}", '$')); + assert_true(IsNakedVar("$(blah$(blue))", '$')); + + assert_false(IsNakedVar("$(blah)blue", '$')); + assert_false(IsNakedVar("blah$(blue)", '$')); + assert_false(IsNakedVar("$(blah)$(blue)", '$')); + assert_false(IsNakedVar("$(blah}", '$')); +} + + +#if 0 +static void test_map_iterators_from_rval_empty(void **state) +{ + EvalContext *ctx = *state; + + Policy *p = PolicyNew(); + Bundle *bp = PolicyAppendBundle(p, "default", "none", "agent", NULL, NULL); + + Rlist *lists = NULL; + Rlist *scalars = NULL; + Rlist *containers = NULL; + MapIteratorsFromRval(ctx, bp, (Rval) { "", RVAL_TYPE_SCALAR }, &scalars, &lists, &containers); + + assert_int_equal(0, RlistLen(lists)); + assert_int_equal(0, RlistLen(scalars)); + assert_int_equal(0, RlistLen(containers)); + + PolicyDestroy(p); +} + +static void test_map_iterators_from_rval_literal(void **state) +{ + EvalContext *ctx = *state; + Policy *p = PolicyNew(); + Bundle *bp = PolicyAppendBundle(p, "default", "none", "agent", NULL, NULL); + + Rlist *lists = NULL; + Rlist *scalars = NULL; + Rlist *containers = NULL; + MapIteratorsFromRval(ctx, bp, (Rval) { "snookie", RVAL_TYPE_SCALAR }, &scalars, &lists, &containers); + + assert_int_equal(0, RlistLen(lists)); + assert_int_equal(0, RlistLen(scalars)); + assert_int_equal(0, RlistLen(containers)); + + PolicyDestroy(p); +} + +static void test_map_iterators_from_rval_naked_list_var(void **state) +{ + EvalContext *ctx = *state; + Policy *p = PolicyNew(); + Bundle *bp = PolicyAppendBundle(p, "default", "scope", "agent", NULL, NULL); + + { + Rlist *list = NULL; + RlistAppend(&list, "jersey", RVAL_TYPE_SCALAR); + VarRef *lval = VarRefParse("scope.jwow"); + + EvalContextVariablePut(ctx, lval, list, CF_DATA_TYPE_STRING_LIST, NULL); + + VarRefDestroy(lval); + RlistDestroy(list); + } + + EvalContextStackPushBundleFrame(ctx, bp, NULL, false); + + { + Rlist *lists = NULL; + Rlist *scalars = NULL; + Rlist *containers = NULL; + MapIteratorsFromRval(ctx, bp, (Rval) { "${jwow}", RVAL_TYPE_SCALAR }, &scalars, &lists, &containers); + + assert_int_equal(1, RlistLen(lists)); + assert_string_equal("jwow", RlistScalarValue(lists)); + assert_int_equal(0, RlistLen(scalars)); + assert_int_equal(0, RlistLen(containers)); + + RlistDestroy(lists); + } + + { + Rlist *lists = NULL; + Rlist *scalars = NULL; + Rlist *containers = NULL; + char *str = xstrdup("${scope.jwow}"); + MapIteratorsFromRval(ctx, bp, (Rval) { str, RVAL_TYPE_SCALAR }, &scalars, &lists, &containers); + + assert_string_equal("${scope#jwow}", str); + free(str); + + assert_int_equal(1, RlistLen(lists)); + assert_string_equal("scope#jwow", RlistScalarValue(lists)); + assert_int_equal(0, RlistLen(scalars)); + assert_int_equal(0, RlistLen(containers)); + + RlistDestroy(lists); + } + + { + Rlist *lists = NULL; + Rlist *scalars = NULL; + Rlist *containers = NULL; + char *str = xstrdup("${default:scope.jwow}"); + MapIteratorsFromRval(ctx, bp, (Rval) { str, RVAL_TYPE_SCALAR }, &scalars, &lists, &containers); + + assert_string_equal("${default*scope#jwow}", str); + free(str); + + assert_int_equal(1, RlistLen(lists)); + assert_string_equal("default*scope#jwow", RlistScalarValue(lists)); + assert_int_equal(0, RlistLen(scalars)); + assert_int_equal(0, RlistLen(containers)); + + RlistDestroy(lists); + } + + EvalContextStackPopFrame(ctx); + PolicyDestroy(p); +} + +static void test_map_iterators_from_rval_naked_list_var_namespace(void **state) +{ + EvalContext *ctx = *state; + Policy *p = PolicyNew(); + Bundle *bp = PolicyAppendBundle(p, "ns", "scope", "agent", NULL, NULL); + + { + Rlist *list = NULL; + RlistAppend(&list, "jersey", RVAL_TYPE_SCALAR); + VarRef *lval = VarRefParse("ns:scope.jwow"); + + EvalContextVariablePut(ctx, lval, list, CF_DATA_TYPE_STRING_LIST, NULL); + + VarRefDestroy(lval); + RlistDestroy(list); + } + + EvalContextStackPushBundleFrame(ctx, bp, NULL, false); + + { + Rlist *lists = NULL; + Rlist *scalars = NULL; + Rlist *containers = NULL; + MapIteratorsFromRval(ctx, bp, (Rval) { "${jwow}", RVAL_TYPE_SCALAR }, &scalars, &lists, &containers); + + assert_int_equal(1, RlistLen(lists)); + assert_string_equal("jwow", RlistScalarValue(lists)); + assert_int_equal(0, RlistLen(scalars)); + assert_int_equal(0, RlistLen(containers)); + + RlistDestroy(lists); + } + + { + Rlist *lists = NULL; + Rlist *scalars = NULL; + Rlist *containers = NULL; + char *str = xstrdup("${scope.jwow}"); + MapIteratorsFromRval(ctx, bp, (Rval) { str, RVAL_TYPE_SCALAR }, &scalars, &lists, &containers); + + assert_string_equal("${scope#jwow}", str); + free(str); + + assert_int_equal(1, RlistLen(lists)); + assert_string_equal("scope#jwow", RlistScalarValue(lists)); + assert_int_equal(0, RlistLen(scalars)); + assert_int_equal(0, RlistLen(containers)); + + RlistDestroy(lists); + } + + { + Rlist *lists = NULL; + Rlist *scalars = NULL; + Rlist *containers = NULL; + char *str = xstrdup("${ns:scope.jwow}"); + MapIteratorsFromRval(ctx, bp, (Rval) { str, RVAL_TYPE_SCALAR }, &scalars, &lists, &containers); + + assert_string_equal("${ns*scope#jwow}", str); + free(str); + + assert_int_equal(1, RlistLen(lists)); + assert_string_equal("ns*scope#jwow", RlistScalarValue(lists)); + assert_int_equal(0, RlistLen(scalars)); + assert_int_equal(0, RlistLen(containers)); + + RlistDestroy(lists); + } + + EvalContextStackPopFrame(ctx); + PolicyDestroy(p); +} +#endif +static void test_expand_scalar_two_scalars_concat(void **state) +{ + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.one"); + EvalContextVariablePut(ctx, lval, "first", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + { + VarRef *lval = VarRefParse("default:bundle.two"); + EvalContextVariablePut(ctx, lval, "second", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + + Buffer *res = BufferNew(); + ExpandScalar(ctx, "default", "bundle", "a $(one) b $(two)c", res); + + assert_string_equal("a first b secondc", BufferData(res)); + BufferDestroy(res); +} + +static void test_expand_scalar_two_scalars_nested(void **state) +{ + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.one"); + EvalContextVariablePut(ctx, lval, "first", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + { + VarRef *lval = VarRefParse("default:bundle.two"); + EvalContextVariablePut(ctx, lval, "one", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + + Buffer *res = BufferNew(); + ExpandScalar(ctx, "default", "bundle", "a $($(two))b", res); + + assert_string_equal("a firstb", BufferData(res)); + BufferDestroy(res); +} + +static void test_expand_scalar_array_concat(void **state) +{ + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.foo[one]"); + EvalContextVariablePut(ctx, lval, "first", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + { + VarRef *lval = VarRefParse("default:bundle.foo[two]"); + EvalContextVariablePut(ctx, lval, "second", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + + Buffer *res = BufferNew(); + ExpandScalar(ctx, "default", "bundle", "a $(foo[one]) b $(foo[two])c", res); + + assert_string_equal("a first b secondc", BufferData(res)); + BufferDestroy(res); +} + +static void test_expand_scalar_array_with_scalar_arg(void **state) +{ + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.foo[one]"); + EvalContextVariablePut(ctx, lval, "first", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + { + VarRef *lval = VarRefParse("default:bundle.bar"); + EvalContextVariablePut(ctx, lval, "one", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + + Buffer *res = BufferNew(); + ExpandScalar(ctx, "default", "bundle", "a$(foo[$(bar)])b", res); + + assert_string_equal("afirstb", BufferData(res)); + BufferDestroy(res); +} + +static void test_expand_scalar_undefined(void **state) +{ + EvalContext *ctx = *state; + + Buffer *res = BufferNew(); + ExpandScalar(ctx, "default", "bundle", "a$(undefined)b", res); + + assert_string_equal("a$(undefined)b", BufferData(res)); + BufferDestroy(res); +} + +static void test_expand_scalar_nested_inner_undefined(void **state) +{ + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.foo[one]"); + EvalContextVariablePut(ctx, lval, "first", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + + Buffer *res = BufferNew(); + ExpandScalar(ctx, "default", "bundle", "a$(foo[$(undefined)])b", res); + + assert_string_equal("a$(foo[$(undefined)])b", BufferData(res)); + BufferDestroy(res); +} + +static void test_expand_list_nested(void **state) +{ + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.i"); + EvalContextVariablePut(ctx, lval, "one", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + { + VarRef *lval = VarRefParse("default:bundle.inner[one]"); + Rlist *list = NULL; + RlistAppendScalar(&list, "foo"); + EvalContextVariablePut(ctx, lval, list, CF_DATA_TYPE_STRING_LIST, NULL); + RlistDestroy(list); + VarRefDestroy(lval); + } + + Rlist *outer = NULL; + RlistAppendScalar(&outer, "@{inner[$(i)]}"); + + Rlist *expanded = ExpandList(ctx, "default", "bundle", outer, true); + + assert_int_equal(1, RlistLen(expanded)); + assert_string_equal("foo", RlistScalarValue(expanded)); + + RlistDestroy(outer); + RlistDestroy(expanded); +} + +static PromiseResult actuator_expand_promise_array_with_scalar_arg( + ARG_UNUSED EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param) +{ + assert_string_equal("first", pp->promiser); + return PROMISE_RESULT_NOOP; +} + +static void test_expand_promise_array_with_scalar_arg(void **state) +{ + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.foo[one]"); + EvalContextVariablePut(ctx, lval, "first", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + { + VarRef *lval = VarRefParse("default:bundle.bar"); + EvalContextVariablePut(ctx, lval, "one", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + + Policy *policy = PolicyNew(); + Bundle *bundle = PolicyAppendBundle(policy, NamespaceDefault(), "bundle", "agent", NULL, NULL); + BundleSection *section = BundleAppendSection(bundle, "dummy"); + Promise *promise = BundleSectionAppendPromise(section, "$(foo[$(bar)])", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, "any", NULL); + + EvalContextStackPushBundleFrame(ctx, bundle, NULL, false); + EvalContextStackPushBundleSectionFrame(ctx, section); + ExpandPromise(ctx, promise, actuator_expand_promise_array_with_scalar_arg, NULL); + EvalContextStackPopFrame(ctx); + EvalContextStackPopFrame(ctx); + + PolicyDestroy(policy); +} + + +static int actuator_state = 0; + +static PromiseResult actuator_expand_promise_slist( + ARG_UNUSED EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param) +{ + if (strcmp("a", pp->promiser) == 0) + { + assert_int_equal(0, actuator_state); + actuator_state++; + } + else if (strcmp("b", pp->promiser) == 0) + { + assert_int_equal(1, actuator_state); + actuator_state++; + } + else + { + fail(); + } + return PROMISE_RESULT_NOOP; +} + +static void test_expand_promise_slist(void **state) +{ + actuator_state = 0; + + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.foo"); + Rlist *list = NULL; + RlistAppendScalar(&list, "a"); + RlistAppendScalar(&list, "b"); + + EvalContextVariablePut(ctx, lval, list, CF_DATA_TYPE_STRING_LIST, NULL); + + RlistDestroy(list); + VarRefDestroy(lval); + } + + + Policy *policy = PolicyNew(); + Bundle *bundle = PolicyAppendBundle(policy, NamespaceDefault(), "bundle", "agent", NULL, NULL); + BundleSection *section = BundleAppendSection(bundle, "dummy"); + Promise *promise = BundleSectionAppendPromise(section, "$(foo)", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, "any", NULL); + + EvalContextStackPushBundleFrame(ctx, bundle, NULL, false); + EvalContextStackPushBundleSectionFrame(ctx, section); + ExpandPromise(ctx, promise, actuator_expand_promise_slist, NULL); + EvalContextStackPopFrame(ctx); + EvalContextStackPopFrame(ctx); + + assert_int_equal(2, actuator_state); + + PolicyDestroy(policy); +} + + +static PromiseResult actuator_expand_promise_array_with_slist_arg( + ARG_UNUSED EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param) +{ + if (strcmp("first", pp->promiser) == 0) + { + assert_int_equal(0, actuator_state); + actuator_state++; + } + else if (strcmp("second", pp->promiser) == 0) + { + assert_int_equal(1, actuator_state); + actuator_state++; + } + else + { + fprintf(stderr, "Got promiser: '%s'\n", pp->promiser); + fail(); + } + return PROMISE_RESULT_NOOP; +} + +static void test_expand_promise_array_with_slist_arg(void **state) +{ + actuator_state = 0; + + EvalContext *ctx = *state; + { + VarRef *lval = VarRefParse("default:bundle.keys"); + Rlist *list = NULL; + RlistAppendScalar(&list, "one"); + RlistAppendScalar(&list, "two"); + + EvalContextVariablePut(ctx, lval, list, CF_DATA_TYPE_STRING_LIST, NULL); + + RlistDestroy(list); + VarRefDestroy(lval); + } + + { + VarRef *lval = VarRefParse("default:bundle.arr[one]"); + EvalContextVariablePut(ctx, lval, "first", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + + { + VarRef *lval = VarRefParse("default:bundle.arr[two]"); + EvalContextVariablePut(ctx, lval, "second", CF_DATA_TYPE_STRING, NULL); + VarRefDestroy(lval); + } + + + Policy *policy = PolicyNew(); + Bundle *bundle = PolicyAppendBundle(policy, NamespaceDefault(), "bundle", "agent", NULL, NULL); + BundleSection *section = BundleAppendSection(bundle, "dummy"); + Promise *promise = BundleSectionAppendPromise(section, "$(arr[$(keys)])", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, "any", NULL); + + EvalContextStackPushBundleFrame(ctx, bundle, NULL, false); + EvalContextStackPushBundleSectionFrame(ctx, section); + ExpandPromise(ctx, promise, actuator_expand_promise_array_with_slist_arg, NULL); + EvalContextStackPopFrame(ctx); + EvalContextStackPopFrame(ctx); + + assert_int_equal(2, actuator_state); + + PolicyDestroy(policy); +} + +static void test_setup(void **state) +{ + *state = EvalContextNew(); +} + +static void test_teardown(void **state) +{ + EvalContext *ctx = *state; + EvalContextDestroy(ctx); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_extract_scalar_prefix), + unit_test(test_extract_reference), + unit_test(test_isnakedvar), +#if 0 + unit_test_setup_teardown(test_map_iterators_from_rval_empty, test_setup, test_teardown), + unit_test_setup_teardown(test_map_iterators_from_rval_literal, test_setup, test_teardown), + unit_test_setup_teardown(test_map_iterators_from_rval_naked_list_var, test_setup, test_teardown), + unit_test_setup_teardown(test_map_iterators_from_rval_naked_list_var_namespace, test_setup, test_teardown), +#endif + unit_test_setup_teardown(test_expand_scalar_two_scalars_concat, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_scalar_two_scalars_nested, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_scalar_array_concat, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_scalar_array_with_scalar_arg, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_scalar_undefined, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_scalar_nested_inner_undefined, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_list_nested, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_promise_array_with_scalar_arg, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_promise_slist, test_setup, test_teardown), + unit_test_setup_teardown(test_expand_promise_array_with_slist_arg, test_setup, test_teardown) + }; + + return run_tests(tests); +} diff --git a/tests/unit/file_name_test.c b/tests/unit/file_name_test.c new file mode 100644 index 0000000000..a106a41a22 --- /dev/null +++ b/tests/unit/file_name_test.c @@ -0,0 +1,324 @@ +#include + +#include +#include + +static void test_first_file_separator(void) +{ + const char *out; + + const char *in = "/tmp/myfile"; + out = FirstFileSeparator(in); + assert_true(out == in); + + in = "tmp/myfile"; + out = FirstFileSeparator(in); + assert_true(out == in + 3); + + in = "c:/tmp/myfile"; + out = FirstFileSeparator(in); + assert_true(out == in + 2); + + in = "\\\\my\\windows\\share"; + out = FirstFileSeparator(in); + assert_true(out == in + 1); + +} + +static void test_get_parent_directory_copy(void) +{ + char *out; + +#ifndef _WIN32 + + /* unix, will fail on windows because of IsFileSep */ + + out = GetParentDirectoryCopy("/some/path/here"); + assert_string_equal(out, "/some/path"); + free(out); + + out = GetParentDirectoryCopy("/some/path/here/dir/"); + assert_string_equal(out, "/some/path/here/dir"); + free(out); + + out = GetParentDirectoryCopy("/some/path/here/dir/."); + assert_string_equal(out, "/some/path/here/dir"); + free(out); + + out = GetParentDirectoryCopy("/some"); + assert_string_equal(out, "/"); + free(out); + +#else /* _WIN32 */ + + /* windows, will fail on unix because of IsFileSep */ + + out = GetParentDirectoryCopy("c:\\some\\path with space\\here and now"); + assert_string_equal(out, "c:\\some\\path with space"); + free(out); + + out = GetParentDirectoryCopy("c:\\some"); + assert_string_equal(out, "c:\\"); + free(out); + + out = GetParentDirectoryCopy("\\\\some\\path"); + assert_string_equal(out, "\\\\some"); + free(out); + + out = GetParentDirectoryCopy("\\\\some"); + assert_string_equal(out, "\\\\"); + free(out); + +#endif /* _WIN32 */ +} + +static void test_delete_redundant_slashes(void) +{ + { + char str[] = "///a//b////c/"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "/a/b/c/"); + } + + { + char str[] = "a//b////c/"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "a/b/c/"); + } + + { + char str[] = "/a/b/c/"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "/a/b/c/"); + } + + { + char str[] = "a///b////c///"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "a/b/c/"); + } + + { + char str[] = "a///b////c"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "a/b/c"); + } + + { + char str[] = "a///b/c"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "a/b/c"); + } + + { + char str[] = "alpha///beta/charlie///zeta"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "alpha/beta/charlie/zeta"); + } + + { + char str[] = "////alpha///beta/charlie///zeta///"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "/alpha/beta/charlie/zeta/"); + } + + { + char str[] = "/a"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "/a"); + } + + { + char str[] = "/alpha"; + DeleteRedundantSlashes(str); + assert_string_equal(str, "/alpha"); + } +} + +static void test_join_paths(void) +{ + char joined[PATH_MAX] = { 0 }; + +#ifndef _WIN32 + /* unix, will fail on windows because of different file separator */ + + strlcat(joined, "/tmp", PATH_MAX); + JoinPaths(joined, PATH_MAX, "test"); + assert_string_equal(joined, "/tmp/test"); + + JoinPaths(joined, PATH_MAX, "/test2"); + assert_string_equal(joined, "/tmp/test/test2"); + + strlcat(joined, "/", PATH_MAX); + JoinPaths(joined, PATH_MAX, "test3"); + assert_string_equal(joined, "/tmp/test/test2/test3"); + + strlcat(joined, "/", PATH_MAX); + JoinPaths(joined, PATH_MAX, "/test4"); + assert_string_equal(joined, "/tmp/test/test2/test3/test4"); + + memset(joined, 0, PATH_MAX); + JoinPaths(joined, PATH_MAX, "test5"); + assert_string_equal(joined, "test5"); + + memset(joined, 0, PATH_MAX); + JoinPaths(joined, PATH_MAX, "/test6"); + assert_string_equal(joined, "/test6"); + + memset(joined, 0, PATH_MAX); + strlcat(joined, "test6", PATH_MAX); + JoinPaths(joined, PATH_MAX, "test7"); + assert_string_equal(joined, "test6/test7"); + +#else /* _WIN32 */ + /* windows, will fail on unix because of different file separator */ + + strlcat(joined, "C:\\tmp", PATH_MAX); + JoinPaths(joined, PATH_MAX, "test"); + assert_string_equal(joined, "C:\\tmp\\test"); + + JoinPaths(joined, PATH_MAX, "\\test2"); + assert_string_equal(joined, "C:\\tmp\\test\\test2"); + + strlcat(joined, "\\", PATH_MAX); + JoinPaths(joined, PATH_MAX, "test3"); + assert_string_equal(joined, "C:\\tmp\\test\\test2\\test3"); + + strlcat(joined, "\\", PATH_MAX); + JoinPaths(joined, PATH_MAX, "\\test4"); + assert_string_equal(joined, "C:\\tmp\\test\\test2\\test3\\test4"); + + memset(joined, 0, PATH_MAX); + JoinPaths(joined, PATH_MAX, "test5"); + assert_string_equal(joined, "test5"); + + memset(joined, 0, PATH_MAX); + JoinPaths(joined, PATH_MAX, "C:\\test6"); + assert_string_equal(joined, "C:\\test6"); + + memset(joined, 0, PATH_MAX); + strlcat(joined, "test6", PATH_MAX); + JoinPaths(joined, PATH_MAX, "test7"); + assert_string_equal(joined, "test6\\test7"); +#endif +} + +static void test_get_absolute_path(void) +{ + char *abs_path = NULL; + char expected[PATH_MAX] = { 0 }; + char orig[PATH_MAX] = { 0 }; + +#ifndef _WIN32 + /* unix, will fail on windows because of different file separator */ + + abs_path = GetAbsolutePath("/tmp/test"); + assert_string_equal(abs_path, "/tmp/test"); + free(abs_path); + + abs_path = GetAbsolutePath("/tmp/test/../test2"); + assert_string_equal(abs_path, "/tmp/test2"); + free(abs_path); + + getcwd(expected, PATH_MAX); + abs_path = GetAbsolutePath("test/test2"); + assert_true(IsAbsoluteFileName(abs_path)); + strlcat(expected, "/test/test2", PATH_MAX); + assert_string_equal(abs_path, expected); + free(abs_path); + memset(expected, 0, PATH_MAX); + + getcwd(expected, PATH_MAX); + abs_path = GetAbsolutePath("./test"); + assert_true(IsAbsoluteFileName(abs_path)); + strlcat(expected, "/test", PATH_MAX); + assert_string_equal(abs_path, expected); + free(abs_path); + memset(expected, 0, PATH_MAX); + + getcwd(expected, PATH_MAX); + abs_path = GetAbsolutePath("test/../test2"); + assert_true(IsAbsoluteFileName(abs_path)); + strlcat(expected, "/test2", PATH_MAX); + assert_string_equal(abs_path, expected); + free(abs_path); + memset(expected, 0, PATH_MAX); + + getcwd(expected, PATH_MAX); + strlcat(orig, expected, PATH_MAX); + chdir(".."); + ChopLastNode(expected); + abs_path = GetAbsolutePath("test/test2"); + assert_true(IsAbsoluteFileName(abs_path)); + strlcat(expected, "/test/test2", PATH_MAX); + assert_string_equal(abs_path, expected); + free(abs_path); + memset(expected, 0, PATH_MAX); + chdir(orig); + memset(orig, 0, PATH_MAX); + +#else /* _WIN32 */ + /* windows, will fail on unix because of different file separator */ + + abs_path = GetAbsolutePath("C:\\tmp\\test"); + assert_string_equal(abs_path, "C:\\tmp\\test"); + free(abs_path); + + abs_path = GetAbsolutePath("C:\\tmp\\test\\..\\test2"); + assert_string_equal(abs_path, "C:\\tmp\\test2"); + free(abs_path); + + getcwd(expected, PATH_MAX); + abs_path = GetAbsolutePath("test\\test2"); + assert_true(IsAbsoluteFileName(abs_path)); + strlcat(expected, "\\test\\test2", PATH_MAX); + assert_string_equal(abs_path, expected); + free(abs_path); + memset(expected, 0, PATH_MAX); + + getcwd(expected, PATH_MAX); + abs_path = GetAbsolutePath(".\\test"); + assert_true(IsAbsoluteFileName(abs_path)); + strlcat(expected, "\\test", PATH_MAX); + assert_string_equal(abs_path, expected); + free(abs_path); + memset(expected, 0, PATH_MAX); + + getcwd(expected, PATH_MAX); + abs_path = GetAbsolutePath("test\\..\\test2"); + assert_true(IsAbsoluteFileName(abs_path)); + strlcat(expected, "\\test2", PATH_MAX); + assert_string_equal(abs_path, expected); + free(abs_path); + memset(expected, 0, PATH_MAX); + + getcwd(expected, PATH_MAX); + strlcat(orig, expected, PATH_MAX); + chdir(".."); + ChopLastNode(expected); + abs_path = GetAbsolutePath("test\\test2"); + assert_true(IsAbsoluteFileName(abs_path)); + strlcat(expected, "\\test\\test2", PATH_MAX); + assert_string_equal(abs_path, expected); + free(abs_path); + memset(expected, 0, PATH_MAX); + chdir(orig); + memset(orig, 0, PATH_MAX); + +#endif +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_first_file_separator), + unit_test(test_get_parent_directory_copy), + unit_test(test_delete_redundant_slashes), + unit_test(test_join_paths), + unit_test(test_get_absolute_path) + }; + + return run_tests(tests); +} diff --git a/tests/unit/files_copy_test.c b/tests/unit/files_copy_test.c new file mode 100644 index 0000000000..8c6081e9cd --- /dev/null +++ b/tests/unit/files_copy_test.c @@ -0,0 +1,574 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include + +#include /* FullWrite */ +#include /* xsnprintf */ + + +/* CopyRegularFileDisk() is the function we are testing. */ +#include + + +/* WARNING on Solaris 11 with ZFS, stat.st_nblocks==1 if you check the file + * right after closing it, and it changes to the right value (i.e. how many + * 512 chunks the file has) a few seconds later!!! So we force a sync() on + * all tests to avoid this! */ +bool do_sync = true; +// TODO detect in runtime if the filesystem needs to be synced! + +#define MAYBE_SYNC_NOW if (do_sync) sync() + + +char TEST_DIR[] = "/tmp/files_copy_test-XXXXXX"; +char TEST_SUBDIR[256]; +char TEST_SRC_FILE[256]; +char TEST_DST_FILE[256]; +#define TEST_FILENAME "testfile" + +/* Size must be enough to contain several disk blocks. */ +int blk_size; /* defined during init() */ +#define TESTFILE_SIZE (blk_size * 8) +#define SHORT_REGION (blk_size / 2) /* not enough for hole */ +#define LONG_REGION (blk_size * 2) /* hole for sure */ +#define GARBAGE "blahblehblohbluebloblebli" +#define GARBAGE_LEN (sizeof(GARBAGE) - 1) + + +/* Notice if even one test failed so that we don't clean up. */ +#define NTESTS 8 +bool test_has_run[NTESTS + 1]; +bool success [NTESTS + 1]; + + +/* Some filesystems don't support sparse files at all (swap fs on Solaris is + * one example). We create a fully sparse file (seek 1MB further) and check if + * it's sparse. If not, WE SKIP ALL SPARSENESS TESTS but we still run this + * unit test in order to test for integrity of data. */ +bool SPARSE_SUPPORT_OK = true; + +static bool FsSupportsSparseFiles(const char *filename) +{ +#ifdef __hpux + Log(LOG_LEVEL_NOTICE, "HP-UX detected, skipping sparseness tests!" + " Not sure why, but on HP-UX with 'vxfs' filesystem," + " the sparse files generated have /sometimes/ greater" + " 'disk usage' than their true size, and this is verified by du"); + return false; +#endif +#ifdef __APPLE__ + Log(LOG_LEVEL_NOTICE, "OS X detected, skipping sparseness tests!"); + return false; +#endif + + int fd = open(filename, O_CREAT | O_WRONLY | O_BINARY, 0700); + assert_int_not_equal(fd, -1); + + /* 8MB for our temporary sparse file sounds good. */ + const int sparse_file_size = 8 * 1024 * 1024; + + off_t s_ret = lseek(fd, sparse_file_size, SEEK_CUR); + assert_int_equal(s_ret, sparse_file_size); + + /* Make sure the file is not truncated by writing one byte + and taking it back. */ + ssize_t w_ret = write(fd, "", 1); + assert_int_equal(w_ret, 1); + + int tr_ret = ftruncate(fd, sparse_file_size); + assert_int_equal(tr_ret, 0); + + /* On ZFS the file needs to be synced, else stat() + reports a temporary value for st_blocks! */ + fsync(fd); + + int c_ret = close(fd); + assert_int_equal(c_ret, 0); + + struct stat statbuf; + int st_ret = stat(filename, &statbuf); + assert_int_not_equal(st_ret, -1); + + int u_ret = unlink(filename); /* clean up */ + assert_int_equal(u_ret, 0); + + /* ACTUAL TEST: IS THE FILE SPARSE? */ + if (ST_NBYTES(statbuf) < statbuf.st_size) + { + return true; + } + else + { + return false; + } +} + + + +static void init(void) +{ + LogSetGlobalLevel(LOG_LEVEL_DEBUG); + + char *ok = mkdtemp(TEST_DIR); + assert_int_not_equal(ok, NULL); + + /* Set blk_size */ + struct stat statbuf; + int ret1 = stat(TEST_DIR, &statbuf); + assert_int_not_equal(ret1, -1); + + blk_size = ST_BLKSIZE(statbuf); + Log(LOG_LEVEL_NOTICE, + "Running sparse file tests with blocksize=%d TESTFILE_SIZE=%d", + blk_size, TESTFILE_SIZE); + Log(LOG_LEVEL_NOTICE, "Temporary directory: %s", TEST_DIR); + + // /tmp/files_copy_test-XXXXXX/subdir + xsnprintf(TEST_SUBDIR, sizeof(TEST_SUBDIR), "%s/%s", + TEST_DIR, "subdir"); + // /tmp/files_copy_test-XXXXXX/testfile + xsnprintf(TEST_SRC_FILE, sizeof(TEST_SRC_FILE), "%s/%s", + TEST_DIR, TEST_FILENAME); + // /tmp/files_copy_test-XXXXXX/subdir/testfile + xsnprintf(TEST_DST_FILE, sizeof(TEST_DST_FILE), "%s/%s", + TEST_SUBDIR, TEST_FILENAME); + + int ret2 = mkdir(TEST_SUBDIR, 0700); + assert_int_equal(ret2, 0); + + SPARSE_SUPPORT_OK = true; + if (!FsSupportsSparseFiles(TEST_DST_FILE)) + { + Log(LOG_LEVEL_NOTICE, + "filesystem for directory '%s' doesn't seem to support sparse files!" + " TEST WILL ONLY VERIFY FILE INTEGRITY!", TEST_DIR); + + SPARSE_SUPPORT_OK = false; + } + + test_has_run[0] = true; + success[0] = true; +} + +static void finalise(void) +{ + /* Do not remove evidence if even one test has failed. */ + bool all_success = true; + for (int i = 0; i < NTESTS; i++) + { + if (test_has_run[i]) + { + all_success = all_success && success[i]; + } + } + if (!all_success) + { + Log(LOG_LEVEL_NOTICE, + "Skipping cleanup of test data because of tests failing"); + return; + } + + int ret1 = unlink(TEST_DST_FILE); + assert_int_equal(ret1, 0); + int ret2 = rmdir(TEST_SUBDIR); + assert_int_equal(ret2, 0); + int ret3 = unlink(TEST_SRC_FILE); + assert_int_equal(ret3, 0); + int ret5 = rmdir(TEST_DIR); + assert_int_equal(ret5, 0); +} + + +/* Fill a buffer with non-NULL garbage. */ +static void FillBufferWithGarbage(char *buf, size_t buf_size) +{ + for (size_t i = 0; i < TESTFILE_SIZE; i += GARBAGE_LEN) + { + memcpy(&buf[i], GARBAGE, + MIN(buf_size - i, GARBAGE_LEN)); + } +} + +static void WriteBufferToFile(const char *name, const void *buf, const size_t count) +{ + int fd = open(name, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, 0700); + assert_int_not_equal(fd, -1); + + int ret = ftruncate(fd, count); + assert_int_not_equal(ret, -1); + + size_t remaining = count; + bool last_wrote_hole; + while (remaining > 0) + { + size_t to_write = MIN(remaining, blk_size); + bool success = FileSparseWrite(fd, buf, to_write, &last_wrote_hole); + assert_true(success); + remaining -= to_write; + buf += to_write; + } + + bool success = FileSparseClose(fd, name, true, count, last_wrote_hole); + assert_true(success); +} + +static bool CompareFileToBuffer(const char *filename, + const void *buf, size_t buf_size) +{ + FILE *f = fopen(filename, "rb"); + assert_int_not_equal(f, NULL); + + size_t total = 0; + char filebuf[DEV_BSIZE]; + size_t n; + while ((n = fread(filebuf, 1, sizeof(filebuf), f)) != 0) + { + bool differ = (total + n > buf_size); + + differ = differ || (memcmp(filebuf, buf + total, n) != 0); + + if (differ) + { + Log(LOG_LEVEL_DEBUG, + "file differs from buffer at pos %zu len %zu", + total, n); + fclose(f); + return false; + } + + total += n; + } + + bool error_happened = (ferror(f) != 0); + assert_false(error_happened); + + if (total != buf_size) + { + Log(LOG_LEVEL_DEBUG, "filesize:%zu buffersize:%zu ==> differ", + total, buf_size); + fclose(f); + return false; + } + + fclose(f); + return true; +} + +/* TODO isolate important code and move to files_lib.c. */ +static bool FileIsSparse(const char *filename) +{ + MAYBE_SYNC_NOW; + + struct stat statbuf; + int ret = stat(filename, &statbuf); + assert_int_not_equal(ret, -1); + + Log(LOG_LEVEL_DEBUG, + " st_size=%ju ST_NBYTES=%ju ST_NBLOCKS=%ju ST_BLKSIZE=%ju DEV_BSIZE=%ju", + (uintmax_t) statbuf.st_size, (uintmax_t) ST_NBYTES(statbuf), + (uintmax_t) ST_NBLOCKS(statbuf), (uintmax_t) ST_BLKSIZE(statbuf), + (uintmax_t) DEV_BSIZE); + + if (statbuf.st_size <= ST_NBYTES(statbuf)) + { + Log(LOG_LEVEL_DEBUG, "File is probably non-sparse"); + return false; + } + else + { + /* We definitely know the file is sparse, since the allocated bytes + * are less than the real size. */ + Log(LOG_LEVEL_DEBUG, "File is definitely sparse"); + return true; + } +} + +const char *srcfile = TEST_SRC_FILE; +const char *dstfile = TEST_DST_FILE; + +static void test_sparse_files_1(void) +{ + Log(LOG_LEVEL_VERBOSE, + "No zeros in the file, the output file must be non-sparse"); + + char *buf = xmalloc(TESTFILE_SIZE); + + FillBufferWithGarbage(buf, TESTFILE_SIZE); + + WriteBufferToFile(srcfile, buf, TESTFILE_SIZE); + + /* ACTUAL TEST */ + bool ret = CopyRegularFileDisk(srcfile, dstfile); + assert_true(ret); + + if (SPARSE_SUPPORT_OK) + { + bool is_sparse = FileIsSparse(dstfile); + assert_false(is_sparse); + } + + bool data_ok = CompareFileToBuffer(dstfile, buf, TESTFILE_SIZE); + assert_true(data_ok); + + free(buf); + test_has_run[1] = true; + success [1] = true; +} + +static void test_sparse_files_2(void) +{ + Log(LOG_LEVEL_VERBOSE, + "File starting with few zeroes, the output file must be non-sparse."); + + char *buf = xmalloc(TESTFILE_SIZE); + + FillBufferWithGarbage(buf, TESTFILE_SIZE); + memset(buf, 0, SHORT_REGION); + + WriteBufferToFile(srcfile, buf, TESTFILE_SIZE); + + /* ACTUAL TEST */ + bool ret = CopyRegularFileDisk(srcfile, dstfile); + assert_true(ret); + + if (SPARSE_SUPPORT_OK) + { + bool is_sparse = FileIsSparse(dstfile); + assert_false(is_sparse); + } + + bool data_ok = CompareFileToBuffer(dstfile, buf, TESTFILE_SIZE); + assert_true(data_ok); + + free(buf); + test_has_run[2] = true; + success [2] = true; +} + +static void test_sparse_files_3(void) +{ + Log(LOG_LEVEL_VERBOSE, + "File with few zeroes in the middle, the output file must be non-sparse"); + + char *buf = xmalloc(TESTFILE_SIZE); + + FillBufferWithGarbage(buf, TESTFILE_SIZE); + memset(&buf[TESTFILE_SIZE / 2], 0, SHORT_REGION); + + WriteBufferToFile(srcfile, buf, TESTFILE_SIZE); + + /* ACTUAL TEST */ + bool ret = CopyRegularFileDisk(srcfile, dstfile); + assert_true(ret); + + if (SPARSE_SUPPORT_OK) + { + bool is_sparse = FileIsSparse(dstfile); + assert_false(is_sparse); + } + + bool data_ok = CompareFileToBuffer(dstfile, buf, TESTFILE_SIZE); + assert_true(data_ok); + + free(buf); + test_has_run[3] = true; + success [3] = true; +} + +static void test_sparse_files_4(void) +{ + Log(LOG_LEVEL_VERBOSE, + "File ending with few zeroes, the output file must be non-sparse"); + + char *buf = xmalloc(TESTFILE_SIZE); + + FillBufferWithGarbage(buf, TESTFILE_SIZE); + memset(&buf[TESTFILE_SIZE - SHORT_REGION], 0, SHORT_REGION); + + WriteBufferToFile(srcfile, buf, TESTFILE_SIZE); + + /* ACTUAL TEST */ + bool ret = CopyRegularFileDisk(srcfile, dstfile); + assert_true(ret); + + if (SPARSE_SUPPORT_OK) + { + bool is_sparse = FileIsSparse(dstfile); + assert_false(is_sparse); + } + + bool data_ok = CompareFileToBuffer(dstfile, buf, TESTFILE_SIZE); + assert_true(data_ok); + + free(buf); + test_has_run[4] = true; + success [4] = true; +} + +static void test_sparse_files_5(void) +{ + Log(LOG_LEVEL_VERBOSE, + "File starting with many zeroes, the output file must be sparse"); + + char *buf = xmalloc(TESTFILE_SIZE); + + FillBufferWithGarbage(buf, TESTFILE_SIZE); + memset(buf, 0, LONG_REGION); + + WriteBufferToFile(srcfile, buf, TESTFILE_SIZE); + + /* ACTUAL TEST */ + bool ret = CopyRegularFileDisk(srcfile, dstfile); + assert_true(ret); + + if (SPARSE_SUPPORT_OK) + { + bool is_sparse = FileIsSparse(dstfile); + assert_true(is_sparse); + } + + bool data_ok = CompareFileToBuffer(dstfile, buf, TESTFILE_SIZE); + assert_true(data_ok); + + free(buf); + test_has_run[5] = true; + success [5] = true; +} + +static void test_sparse_files_6(void) +{ + Log(LOG_LEVEL_VERBOSE, + "Many zeroes in the middle of the file, the output file must be sparse"); + + char *buf = xmalloc(TESTFILE_SIZE); + + FillBufferWithGarbage(buf, TESTFILE_SIZE); + memset(&buf[TESTFILE_SIZE / 2 - 7], 0, LONG_REGION); + + WriteBufferToFile(srcfile, buf, TESTFILE_SIZE); + + /* ACTUAL TEST */ + bool ret = CopyRegularFileDisk(srcfile, dstfile); + assert_true(ret); + + if (SPARSE_SUPPORT_OK) + { + bool is_sparse = FileIsSparse(dstfile); + assert_true(is_sparse); + } + + bool data_ok = CompareFileToBuffer(dstfile, buf, TESTFILE_SIZE); + assert_true(data_ok); + + free(buf); + test_has_run[6] = true; + success [6] = true; +} + +static void test_sparse_files_7(void) +{ + Log(LOG_LEVEL_VERBOSE, + "File ending with many zeroes, the output file must be sparse"); + + char *buf = xmalloc(TESTFILE_SIZE); + + FillBufferWithGarbage(buf, TESTFILE_SIZE); + memset(&buf[TESTFILE_SIZE - LONG_REGION], 0, LONG_REGION); + + WriteBufferToFile(srcfile, buf, TESTFILE_SIZE); + + /* ACTUAL TEST */ + bool ret = CopyRegularFileDisk(srcfile, dstfile); + assert_true(ret); + + if (SPARSE_SUPPORT_OK) + { + bool is_sparse = FileIsSparse(dstfile); + assert_true(is_sparse); + } + + bool data_ok = CompareFileToBuffer(dstfile, buf, TESTFILE_SIZE); + assert_true(data_ok); + + free(buf); + test_has_run[7] = true; + success [7] = true; +} + +static void test_sparse_files_8(void) +{ + Log(LOG_LEVEL_VERBOSE, + "Special Case: File ending with few (DEV_BSIZE-1) zeroes," + " at block size barrier; so the last bytes written are a hole and are seek()ed," + " but the output file can't be sparse since the hole isn't block-sized"); + + char *buf = xmalloc(TESTFILE_SIZE + DEV_BSIZE - 1); + + FillBufferWithGarbage(buf, TESTFILE_SIZE); + memset(&buf[TESTFILE_SIZE], 0, DEV_BSIZE - 1); + + WriteBufferToFile(srcfile, buf, TESTFILE_SIZE + DEV_BSIZE - 1); + + /* ACTUAL TEST */ + bool ret = CopyRegularFileDisk(srcfile, dstfile); + assert_true(ret); + + if (SPARSE_SUPPORT_OK) + { + bool is_sparse = FileIsSparse(dstfile); + assert_false(is_sparse); + } + + bool data_ok = CompareFileToBuffer(dstfile, buf, TESTFILE_SIZE + DEV_BSIZE - 1); + assert_true(data_ok); + + free(buf); + test_has_run[8] = true; + success [8] = true; +} + + + +int main() +{ + PRINT_TEST_BANNER(); + + const UnitTest tests[] = { + unit_test(init), + unit_test(test_sparse_files_1), + unit_test(test_sparse_files_2), + unit_test(test_sparse_files_3), + unit_test(test_sparse_files_4), + unit_test(test_sparse_files_5), + unit_test(test_sparse_files_6), + unit_test(test_sparse_files_7), + unit_test(test_sparse_files_8), + unit_test(finalise), + }; + + int ret = run_tests(tests); + + return ret; +} diff --git a/tests/unit/files_interfaces_test.c b/tests/unit/files_interfaces_test.c new file mode 100644 index 0000000000..cb3c1cf55a --- /dev/null +++ b/tests/unit/files_interfaces_test.c @@ -0,0 +1,112 @@ +#include + +#include +#include /* xsnprintf */ +#include // CfReadLine() + + +#define FILE_SIZE (sizeof(FILE_CONTENTS) - 1) +#define FILE_LINE "some garbage!" +#define FILE_CORRUPTED_LINE "some \0 , gar\0bage!" +#define LINE_SIZE (sizeof(FILE_LINE) - 1) + +char CFWORKDIR[CF_BUFSIZE]; +char FILE_NAME[CF_BUFSIZE]; +char FILE_NAME_CORRUPT[CF_BUFSIZE]; +char FILE_NAME_EMPTY[CF_BUFSIZE]; + +static void tests_setup(void) +{ + xsnprintf(CFWORKDIR, CF_BUFSIZE, "/tmp/files_interfaces_test.XXXXXX"); + mkdtemp(CFWORKDIR); + xsnprintf(FILE_NAME, CF_BUFSIZE, "%s/cf_files_interfaces_test", CFWORKDIR); + xsnprintf(FILE_NAME_CORRUPT, CF_BUFSIZE, "%s/cf_files_interfaces_test_corrupt", CFWORKDIR); + xsnprintf(FILE_NAME_EMPTY, CF_BUFSIZE, "%s/cf_files_interfaces_test_empty", CFWORKDIR); +} + +static void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +static void CreateGarbage(const char *filename) +{ + FILE *fh = fopen(filename, "w"); + for(int i = 0; i < 10; ++i) + { + fwrite(FILE_LINE, 14, 1, fh); + } + fclose(fh); +} + +static void CreateCorruptedGarbage(const char *filename) +{ + FILE *fh = fopen(filename, "w"); + for(int i = 0; i < 10; ++i) + { + fwrite(FILE_CORRUPTED_LINE, 18, 1, fh); + } + fclose(fh); +} + +static void test_cfreadline_valid(void) +{ + CreateGarbage(FILE_NAME); + FILE *fin = fopen(FILE_NAME, "r"); + + //test with non-empty file and valid file pointer + size_t bs = CF_BUFSIZE; + char *b = xmalloc(bs); + + ssize_t read = CfReadLine(&b, &bs, fin); + assert_true(read > 0); + assert_string_equal(b, FILE_LINE); + + if (fin) + { + fclose(fin); + } + + free(b); +} + +static void test_cfreadline_corrupted(void) +{ + CreateCorruptedGarbage(FILE_NAME); + FILE *fin = fopen(FILE_NAME, "r"); + + size_t bs = CF_BUFSIZE; + char *b = xmalloc(bs); + + //test with non-empty file and valid file pointer + ssize_t read = CfReadLine(&b, &bs, fin); + assert_true(read > 0); + assert_string_not_equal(b, FILE_LINE); + + if (fin) + { + fclose(fin); + } + + free(b); +} + +int main() +{ + PRINT_TEST_BANNER(); + tests_setup(); + + const UnitTest tests[] = + { + unit_test(test_cfreadline_valid), + unit_test(test_cfreadline_corrupted), + }; + + PRINT_TEST_BANNER(); + int ret = run_tests(tests); + + tests_teardown(); + return ret; +} diff --git a/tests/unit/files_lib_test.c b/tests/unit/files_lib_test.c new file mode 100644 index 0000000000..969d911504 --- /dev/null +++ b/tests/unit/files_lib_test.c @@ -0,0 +1,129 @@ +#include + +#include +#include +#include /* xsnprintf */ + + +#define FILE_CONTENTS "8aysd9a8ydhsdkjnaldn12lk\njndl1jndljewnbfdhwjebfkjhbnkjdn1lkdjn1lkjn38aysd9a8ydhsdkjnaldn12lkjndl1jndljewnbfdhwjebfkjhbnkjdn1lkdjn1lkjn38aysd9a8ydhsdkjnaldn12lkjndl1jndljewnbfdhwjebfkjhbnkjdn1lkdjn1lkjn38aysd9a8ydhsdkjnaldn12lkjndl1jndljewnbfdhwjebfkjhbnkjdn1lkdjn1lkjn38aysd9a8ydhsdkjnaldn12lkjndl1jndljew\nnbfdhwjebfkjhbnkjdn1lkdjn1lkjn38aysd9a8ydhsdkjnaldn12lkjndl1jndljewnbfdhwjebfkjhbnkjdn1lkdjn1l\rkjn38aysd9a8ydhsdkjnaldn12lkjndl1jndljewnbfdhwjebfkjhbnkjdn1lkdjn1\r\nlkjn38aysd9a8ydhsdkjnaldn12lkjndl1jndljewnbfdhwjebfkjhbnkjdn1lkdjn1lkjn38aysd9a8ydhsdkjnaldn12lkjndl1jndljewnbfdhwjebfkjhbnkjdn1lkdjn1lkjn3" +#define FILE_SIZE (sizeof(FILE_CONTENTS) - 1) + +char CFWORKDIR[CF_BUFSIZE]; + +char FILE_NAME[CF_BUFSIZE]; +char FILE_NAME_EMPTY[CF_BUFSIZE]; + +static void tests_setup(void) +{ + xsnprintf(CFWORKDIR, CF_BUFSIZE, "/tmp/files_lib_test.XXXXXX"); + mkdtemp(CFWORKDIR); + + xsnprintf(FILE_NAME, CF_BUFSIZE, "%s/cfengine_file_test", CFWORKDIR); + xsnprintf(FILE_NAME_EMPTY, CF_BUFSIZE, "%s/cfengine_file_test_empty", CFWORKDIR); +} + +static void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +void test_file_write(void) +{ + bool res = FileWriteOver(FILE_NAME, FILE_CONTENTS); + assert_true(res); +} + +void test_file_read_all(void) +{ + bool truncated; + Writer *w = FileRead(FILE_NAME, FILE_SIZE, &truncated); + assert_int_equal(StringWriterLength(w), FILE_SIZE); + assert_string_equal(StringWriterData(w), FILE_CONTENTS); + assert_int_equal(truncated, false); + WriterClose(w); + + Writer *w2 = FileRead(FILE_NAME, FILE_SIZE * 10, &truncated); + assert_int_equal(StringWriterLength(w2), FILE_SIZE); + assert_string_equal(StringWriterData(w2), FILE_CONTENTS); + assert_int_equal(truncated, false); + WriterClose(w2); + + Writer *w3 = FileRead(FILE_NAME, FILE_SIZE * 10, NULL); + assert_int_equal(StringWriterLength(w3), FILE_SIZE); + assert_string_equal(StringWriterData(w3), FILE_CONTENTS); + WriterClose(w3); +} + +void test_file_read_truncate(void) +{ + char expected_output[FILE_SIZE + 1]; + + bool truncated = false; + Writer *w = FileRead(FILE_NAME, FILE_SIZE - 1, &truncated); + assert_int_equal(StringWriterLength(w), FILE_SIZE - 1); + strlcpy(expected_output, FILE_CONTENTS, FILE_SIZE); + assert_string_equal(StringWriterData(w), expected_output); + assert_int_equal(truncated, true); + WriterClose(w); + + bool truncated2 = false; + Writer *w2 = FileRead(FILE_NAME, 10, &truncated2); + assert_int_equal(StringWriterLength(w2), 10); + strlcpy(expected_output, FILE_CONTENTS, 11); + assert_string_equal(StringWriterData(w2), expected_output); + assert_int_equal(truncated2, true); + WriterClose(w2); + + bool truncated3 = false; + Writer *w3 = FileRead(FILE_NAME, 1, &truncated3); + assert_int_equal(StringWriterLength(w3), 1); + strlcpy(expected_output, FILE_CONTENTS, 2); + assert_string_equal(StringWriterData(w3), expected_output); + assert_int_equal(truncated3, true); + WriterClose(w3); +} + +void test_file_read_empty(void) +{ + int creat_fd = creat(FILE_NAME_EMPTY, 0600); + assert_true(creat_fd > -1); + + int close_res = close(creat_fd); + assert_int_equal(close_res, 0); + + bool truncated = true; + Writer *w = FileRead(FILE_NAME_EMPTY, 100, &truncated); + assert_int_equal(StringWriterLength(w), 0); + assert_string_equal(StringWriterData(w), ""); + assert_int_equal(truncated, false); + WriterClose(w); +} + +void test_file_read_invalid(void) +{ + Writer *w = FileRead("nonexisting file", 100, NULL); + assert_false(w); +} + +int main() +{ + PRINT_TEST_BANNER(); + tests_setup(); + + const UnitTest tests[] = + { + unit_test(test_file_write), + unit_test(test_file_read_all), + unit_test(test_file_read_truncate), + unit_test(test_file_read_empty), + unit_test(test_file_read_invalid), + }; + + int ret = run_tests(tests); + + tests_teardown(); + + return ret; +} diff --git a/tests/unit/findhub_test.c b/tests/unit/findhub_test.c new file mode 100644 index 0000000000..98c3a4e631 --- /dev/null +++ b/tests/unit/findhub_test.c @@ -0,0 +1,317 @@ +#include + +#include +#include +#include /* xsnprintf */ + +#include +#include + +#include +#include +#include +#include + + +void (*avahi_simple_poll_quit_ptr)(AvahiSimplePoll *); +char* (*avahi_address_snprint_ptr)(char *, size_t , const AvahiAddress *); +int (*avahi_service_resolver_free_ptr)(AvahiServiceResolver *); +int (*avahi_client_errno_ptr)(AvahiClient *); +const char* (*avahi_strerror_ptr)(int); +AvahiServiceResolver* (*avahi_service_resolver_new_ptr)(AvahiClient *, AvahiIfIndex, AvahiProtocol, const char *, const char *, const char *, AvahiProtocol, AvahiLookupFlags, AvahiServiceResolverCallback, void *); +AvahiClient* (*avahi_service_browser_get_client_ptr)(AvahiServiceBrowser *); +AvahiClient* (*avahi_service_resolver_get_client_ptr)(AvahiServiceResolver *); +AvahiSimplePoll* (*avahi_simple_poll_new_ptr)(); +const AvahiPoll* (*avahi_simple_poll_get_ptr)(AvahiSimplePoll *s); +AvahiClient* (*avahi_client_new_ptr)(const AvahiPoll *, AvahiClientFlags, AvahiClientCallback, void *, int *); +int (*avahi_simple_poll_loop_ptr)(AvahiSimplePoll *); +int (*avahi_service_browser_free_ptr)(AvahiServiceBrowser *); +void (*avahi_client_free_ptr)(AvahiClient *client); +void (*avahi_simple_poll_free_ptr)(AvahiSimplePoll *); +AvahiServiceBrowser* (*avahi_service_browser_new_ptr)(AvahiClient *, AvahiIfIndex, AvahiProtocol, const char *, const char *, AvahiLookupFlags, AvahiServiceBrowserCallback, void*); + +int hostcount; + +void *dlopen(const char *filename, int flag) +{ + return (void*)1; +} + +void *dlsym(void *handle, const char *symbol) +{ + if (strcmp(symbol, "avahi_simple_poll_quit") == 0) + { + return &avahi_simple_poll_quit; + } + else if (strcmp(symbol, "avahi_address_snprint") == 0) + { + return &avahi_address_snprint; + } + else if (strcmp(symbol, "avahi_service_resolver_free") == 0) + { + return &avahi_service_resolver_free; + } + else if (strcmp(symbol, "avahi_client_errno") == 0) + { + return &avahi_client_errno; + } + else if (strcmp(symbol, "avahi_strerror") == 0) + { + return &avahi_strerror; + } + else if (strcmp(symbol, "avahi_service_resolver_new") == 0) + { + return &avahi_service_resolver_new; + } + else if (strcmp(symbol, "avahi_service_browser_get_client") == 0) + { + return &avahi_service_browser_get_client; + } + else if (strcmp(symbol, "avahi_service_resolver_get_client") == 0) + { + return &avahi_service_resolver_get_client; + } + else if (strcmp(symbol, "avahi_simple_poll_new") == 0) + { + return &avahi_simple_poll_new; + } + else if (strcmp(symbol, "avahi_simple_poll_get") == 0) + { + return &avahi_simple_poll_get; + } + else if (strcmp(symbol, "avahi_client_new") == 0) + { + return &avahi_client_new; + } + else if (strcmp(symbol, "avahi_simple_poll_loop") == 0) + { + return &avahi_simple_poll_loop; + } + else if (strcmp(symbol, "avahi_service_browser_free") == 0) + { + return &avahi_service_browser_free; + } + else if (strcmp(symbol, "avahi_client_free") == 0) + { + return &avahi_client_free; + } + else if (strcmp(symbol, "avahi_simple_poll_free") == 0) + { + return &avahi_simple_poll_free; + } + else if (strcmp(symbol, "avahi_service_browser_new") == 0) + { + return &avahi_service_browser_new; + } + + return NULL; +} + +int dlclose(void *handle) +{ + return 0; +} + +AvahiSimplePoll *avahi_simple_poll_new() +{ + AvahiSimplePoll *sp = (AvahiSimplePoll*)1; + + return sp; +} + +void avahi_simple_poll_free(AvahiSimplePoll *poll) +{ + +} + +const AvahiPoll *avahi_simple_poll_get(AvahiSimplePoll *s) +{ + AvahiPoll *p = { 0 }; + + return p; +} + +void avahi_simple_poll_quit(AvahiSimplePoll *poll) +{ + +} + +char *avahi_address_snprint(char *buffer, size_t size, const AvahiAddress *address) +{ + xsnprintf(buffer, size, "10.0.0.100"); + + return NULL; +} + +int avahi_service_resolver_free(AvahiServiceResolver *resolver) +{ + return 0; +} + +int avahi_client_errno(AvahiClient *c) +{ + return 1; +} + +const char *avahi_strerror(int error) +{ + return "Avahi error occurred"; +} + +AvahiServiceResolver *avahi_service_resolver_new(AvahiClient *c, AvahiIfIndex index, AvahiProtocol protocol, const char * s1, const char *s2, const char *s3, + AvahiProtocol protocol2, AvahiLookupFlags flags, AvahiServiceResolverCallback callback, void *data) +{ + return (AvahiServiceResolver *)1; +} + +AvahiClient *avahi_service_browser_get_client(AvahiServiceBrowser *sb) +{ + return (AvahiClient *)1; +} + +AvahiClient *avahi_service_resolver_get_client(AvahiServiceResolver *sr) +{ + return (AvahiClient *)1; +} + +AvahiClient *avahi_client_new(const AvahiPoll *poll, AvahiClientFlags cf, AvahiClientCallback callback, void *data, int *stat) +{ + if (hostcount == 4) + { + return NULL; + } + + return (AvahiClient *)1; +} + +int avahi_simple_poll_loop(AvahiSimplePoll *sp) +{ + AvahiAddress *addr = calloc(1, sizeof(AvahiAddress)); + AvahiServiceResolver *sr = { (AvahiServiceResolver*)1 }; + switch(hostcount) + { + case 1: + resolve_callback(sr, 0, 0, AVAHI_RESOLVER_FOUND, "cfenginehub", "tcp", "local", "host1", addr, 5308, NULL, 0, NULL); + return 0; + + case 2: + resolve_callback(sr, 0, 0, AVAHI_RESOLVER_FOUND, "cfenginehub", "tcp", "local", "host1", addr, 5308, NULL, 0, NULL); + resolve_callback(sr, 0, 0, AVAHI_RESOLVER_FOUND, "cfenginehub", "tcp", "local", "host2", addr, 1234, NULL, 0, NULL); + resolve_callback(sr, 0, 0, AVAHI_RESOLVER_FOUND, "cfenginehub", "tcp", "local", "host3", addr, 4321, NULL, 0, NULL); + return 0; + + default: + free(addr); + }; + + return 0; +} + +int avahi_service_browser_free(AvahiServiceBrowser *sb) +{ + return 0; +} + +void avahi_client_free(AvahiClient *c) +{ + +} + +AvahiServiceBrowser *avahi_service_browser_new(AvahiClient *c, AvahiIfIndex index, AvahiProtocol protocol, const char *s1, const char *s2, AvahiLookupFlags flags, + AvahiServiceBrowserCallback callback, void *data) +{ + AvahiServiceBrowser *browser = (AvahiServiceBrowser *)1; + + return browser; +} + +static void test_noHubsFound(void) +{ + List *list = NULL; + + hostcount = 0; + + assert_int_equal(ListHubs(&list), 0); + assert_int_not_equal(list, NULL); + + ListDestroy(&list); +} + +static void test_oneHubFound(void) +{ + List *list = NULL; + + hostcount = 1; + + assert_int_equal(ListHubs(&list), 1); + assert_int_not_equal(list, NULL); + + ListIterator *i = NULL; + i = ListIteratorGet(list); + assert_true(i != NULL); + HostProperties *host = (HostProperties *)ListIteratorData(i); + + assert_int_equal(host->Port,5308); + assert_string_equal(host->Hostname, "host1"); + assert_string_equal(host->IPAddress, "10.0.0.100"); + + ListIteratorDestroy(&i); + ListDestroy(&list); +} + +static void test_multipleHubsFound(void) +{ + List *list = NULL; + + hostcount = 2; + + assert_int_equal(ListHubs(&list), 3); + assert_int_not_equal(list, NULL); + + ListIterator *i = NULL; + i = ListIteratorGet(list); + + HostProperties *host1 = (HostProperties *)ListIteratorData(i); + assert_int_not_equal(ListIteratorNext(i), -1); + HostProperties *host2 = (HostProperties *)ListIteratorData(i); + assert_int_not_equal(ListIteratorNext(i), -1); + HostProperties *host3 = (HostProperties *)ListIteratorData(i); + + assert_int_equal(host1->Port, 5308); + assert_string_equal(host1->Hostname, "host1"); + assert_string_equal(host1->IPAddress, "10.0.0.100"); + + assert_int_equal(host2->Port, 1234); + assert_string_equal(host2->Hostname, "host2"); + assert_string_equal(host2->IPAddress, "10.0.0.100"); + + assert_int_equal(host3->Port, 4321); + assert_string_equal(host3->Hostname, "host3"); + assert_string_equal(host3->IPAddress, "10.0.0.100"); + + ListIteratorDestroy(&i); + ListDestroy(&list); +} + +static void test_errorOccurred(void) +{ + List *list = NULL; + + hostcount = 4; + + assert_int_equal(ListHubs(&list), -1); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_noHubsFound), + unit_test(test_oneHubFound), + unit_test(test_multipleHubsFound), + unit_test(test_errorOccurred) + }; + + return run_tests(tests); +} diff --git a/tests/unit/gcov-stub.c b/tests/unit/gcov-stub.c new file mode 100644 index 0000000000..1e2c015684 --- /dev/null +++ b/tests/unit/gcov-stub.c @@ -0,0 +1,50 @@ +#include +#include + +/* + * Stubs which allow CFEngine compiled with gcov support to link against unit + * test code which has gcov disabled. + */ + +void __gcov_init(ARG_UNUSED void *p) +{ +} + +void __gcov_merge_add(ARG_UNUSED void *p, ARG_UNUSED unsigned n_counters) +{ +} + +int __gcov_execv(const char *path, char *const argv[]) +{ + return execv(path, argv); +} + +int __gcov_execl(const char *path, char *arg, ...) +{ + va_list ap, aq; + unsigned i, length; + char **args; + + va_start(ap, arg); + va_copy(aq, ap); + + length = 2; + while (va_arg(ap, char *)) + length++; + + va_end(ap); + + args = (char **) xmalloc(length * sizeof(void *)); + args[0] = arg; + for (i = 1; i < length; i++) + args[i] = va_arg(aq, char *); + + va_end(aq); + + return execv(path, args); +} + +pid_t __gcov_fork(void) +{ + return fork(); +} diff --git a/tests/unit/generic_agent_test.c b/tests/unit/generic_agent_test.c new file mode 100644 index 0000000000..805e564bd0 --- /dev/null +++ b/tests/unit/generic_agent_test.c @@ -0,0 +1,162 @@ +#include + +#include +#include +#include +#include +#include +#include +#include /* xsnprintf */ +#include +#include + +char TEMPDIR[] = "/tmp/generic_agent_test_XXXXXX"; + +void test_have_tty_interactive_failsafe_is_not_created(void) +{ + CryptoInitialize(); + + bool simulate_tty_interactive = true; + + EvalContext *ctx = EvalContextNew(); + GenericAgentConfig *config = + GenericAgentConfigNewDefault(AGENT_TYPE_COMMON, + simulate_tty_interactive); + + /* Just make sure that file doesn't exist. */ + GenericAgentConfigSetInputFile(config, NULL, + "/masterfiles/non_existing.cf"); + + /* This is where failsafe.cf will be created. */ + char *failsafe_file = StringFormat("%s%c%s", + GetInputDir(), + FILE_SEPARATOR, + "failsafe.cf"); + Policy *policy = SelectAndLoadPolicy(config, ctx, false, false); + struct stat buf; + + /* failsafe.cf shouldn't be created as we have tty_interactive. */ + assert_int_equal(stat(failsafe_file, &buf), -1); + + free(failsafe_file); + + PolicyDestroy(policy); + GenericAgentFinalize(ctx, config); + +} + +void test_dont_have_tty_interactive_failsafe_is_created(void) +{ + CryptoInitialize(); + + bool simulate_tty_interactive = false; + + EvalContext *ctx = EvalContextNew(); + GenericAgentConfig *config = + GenericAgentConfigNewDefault(AGENT_TYPE_COMMON, + simulate_tty_interactive); + + /* Just make sure that file doesn't exist. */ + GenericAgentConfigSetInputFile(config, + NULL, + "/masterfiles/non_existing.cf"); + + /* This is where failsafe.cf will be created. */ + char *failsafe_file = + StringFormat("%s%c%s", GetInputDir(), FILE_SEPARATOR, "failsafe.cf"); + Policy *policy = SelectAndLoadPolicy(config, ctx, false, false); + struct stat buf; + + /* failsafe.cf should be created as we don't have tty_interactive. */ + assert_int_equal(stat(failsafe_file, &buf), 0); + + unlink(failsafe_file); + free(failsafe_file); + + PolicyDestroy(policy); + GenericAgentFinalize(ctx, config); + +} + +void test_resolve_absolute_input_path(void) +{ + assert_string_equal("/abs/aux.cf", GenericAgentResolveInputPath(NULL, "/abs/aux.cf")); +} + +void test_resolve_non_anchored_base_path(void) +{ + char inputdir[CF_BUFSIZE] = ""; + + strlcpy(inputdir, GetInputDir(), sizeof(inputdir)); + + GenericAgentConfig *config = GenericAgentConfigNewDefault(AGENT_TYPE_COMMON, false); + GenericAgentConfigSetInputFile(config, inputdir, "promises.cf"); + + char testpath[CF_BUFSIZE]; + + xsnprintf(testpath, sizeof(testpath), "%s%s", TEMPDIR, "/inputs"); + assert_string_equal(testpath, config->input_dir); + xsnprintf(testpath, sizeof(testpath), "%s%s", TEMPDIR, "/inputs/promises.cf"); + assert_string_equal(testpath, config->input_file); + + xsnprintf(testpath, sizeof(testpath), "%s%s", TEMPDIR, "/inputs/aux.cf"); + assert_string_equal(testpath, GenericAgentResolveInputPath(config, "aux.cf")); + xsnprintf(testpath, sizeof(testpath), "%s%s", TEMPDIR, "/inputs/rel/aux.cf"); + assert_string_equal(testpath, GenericAgentResolveInputPath(config, "rel/aux.cf")); + xsnprintf(testpath, sizeof(testpath), "%s%s", TEMPDIR, "/inputs/./aux.cf"); + assert_string_equal(testpath, GenericAgentResolveInputPath(config, "./aux.cf")); + xsnprintf(testpath, sizeof(testpath), "%s%s", TEMPDIR, "/inputs/./rel/aux.cf"); + assert_string_equal(testpath, GenericAgentResolveInputPath(config, "./rel/aux.cf")); + + GenericAgentConfigDestroy(config); +} + +void test_resolve_relative_base_path(void) +{ + GenericAgentConfig *config = GenericAgentConfigNewDefault(AGENT_TYPE_COMMON, false); + GenericAgentConfigSetInputFile(config, GetWorkDir(), "./inputs/promises.cf"); + + assert_string_equal("./inputs/aux.cf", GenericAgentResolveInputPath(config, "aux.cf")); + assert_string_equal("./inputs/rel/aux.cf", GenericAgentResolveInputPath(config, "rel/aux.cf")); + assert_string_equal("./inputs/./aux.cf", GenericAgentResolveInputPath(config, "./aux.cf")); + assert_string_equal("./inputs/./rel/aux.cf", GenericAgentResolveInputPath(config, "./rel/aux.cf")); + + GenericAgentConfigDestroy(config); +} + +int main() +{ + if (mkdtemp(TEMPDIR) == NULL) + { + fprintf(stderr, "Could not create temporary directory\n"); + return 1; + } + char *inputs = NULL; + xasprintf(&inputs, "%s/inputs", TEMPDIR); + mkdir(inputs, 0755); + free(inputs); + + char *env_var = NULL; + xasprintf(&env_var, "CFENGINE_TEST_OVERRIDE_WORKDIR=%s", TEMPDIR); + // Will leak, but that's how crappy putenv() is. + putenv(env_var); + + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_resolve_absolute_input_path), + unit_test(test_resolve_non_anchored_base_path), + unit_test(test_resolve_relative_base_path), + unit_test(test_have_tty_interactive_failsafe_is_not_created), + unit_test(test_dont_have_tty_interactive_failsafe_is_created), + }; + + int ret = run_tests(tests); + + char rm_rf[] = "rm -rf "; + char cmd[sizeof(rm_rf) + sizeof(TEMPDIR)]; + xsnprintf(cmd, sizeof(cmd), "%s%s", rm_rf, TEMPDIR); + ARG_UNUSED int ignore = system(cmd); + + return ret; +} diff --git a/tests/unit/getopt_test.c b/tests/unit/getopt_test.c new file mode 100644 index 0000000000..ab3f45f6d7 --- /dev/null +++ b/tests/unit/getopt_test.c @@ -0,0 +1,192 @@ +#include + +#include +#include +#include +#include +#include + +/* Test that we have a working version of getopt functionality, either + * system-provided or our own in libcfecompat. */ + +static Seq *CheckOpts(int argc, char **argv) +{ + Seq *seq = SeqNew(3, free); + + const struct option OPTIONS[] = + { + {"no-arg", no_argument, 0, 'n'}, + {"opt-arg", optional_argument, 0, 'o'}, + {"req-arg", required_argument, 0, 'r'}, + {NULL, 0, 0, '\0'} + }; + + /* Reset optind to 1 in order to restart scanning of argv + * - getopt(3) – Linux Manual + */ + optind = 1; + + int c; + while ((c = getopt_long(argc, argv, "no::r:", OPTIONS, NULL)) != -1) + { + switch (c) + { + case 'n': /* no argument */ + SeqAppend(seq, xstrdup("none")); + break; + + case 'o': /* optional argument */ + if (OPTIONAL_ARGUMENT_IS_PRESENT) + { + SeqAppend(seq, xstrdup(optarg)); + } + else + { + SeqAppend(seq, xstrdup("default")); + } + break; + + case 'r': /* required argument */ + SeqAppend(seq, xstrdup(optarg)); + break; + + default: + assert(false); + break; + } + } + + return seq; +} + +static void test_GET_OPTIONAL_ARGUMENT(void) +{ + /* optional as middle option */ + int argc1 = 6; + char *argv1[] = + { + "program", + "-n", + "-o", "foo", + "-r", "bar" + }; + Seq *seq1 = CheckOpts(argc1, argv1); + assert_int_equal(SeqLength(seq1), 3); + assert_string_equal((char *) SeqAt(seq1, 0), "none"); + assert_string_equal((char *) SeqAt(seq1, 1), "foo"); + assert_string_equal((char *) SeqAt(seq1, 2), "bar"); + SeqDestroy(seq1); + + /* optional as last option */ + int argc2 = 6; + char *argv2[] = + { + "program", + "-r", "bar", + "-n", + "-o", "foo" + }; + Seq *seq2 = CheckOpts(argc2, argv2); + assert_int_equal(SeqLength(seq2), 3); + assert_string_equal((char *) SeqAt(seq2, 0), "bar"); + assert_string_equal((char *) SeqAt(seq2, 1), "none"); + assert_string_equal((char *) SeqAt(seq2, 2), "foo"); + SeqDestroy(seq2); + + /* optional as first option */ + int argc3 = 6; + char *argv3[] = + { + "program", + "-o", "foo", + "-r", "bar", + "-n" + }; + Seq *seq3 = CheckOpts(argc3, argv3); + assert_int_equal(SeqLength(seq3), 3); + assert_string_equal((char *) SeqAt(seq3, 0), "foo"); + assert_string_equal((char *) SeqAt(seq3, 1), "bar"); + assert_string_equal((char *) SeqAt(seq3, 2), "none"); + SeqDestroy(seq3); + + /* optional with no argument */ + int argc4 = 5; + char *argv4[] = + { + "program", + "-n", + "-o", + "-r", "bar" + }; + Seq *seq4 = CheckOpts(argc4, argv4); + assert_int_equal(SeqLength(seq4), 3); + assert_string_equal((char *) SeqAt(seq4, 0), "none"); + assert_string_equal((char *) SeqAt(seq4, 1), "default"); + assert_string_equal((char *) SeqAt(seq4, 2), "bar"); + SeqDestroy(seq4); + + /* optional with argument immediately after */ + int argc5 = 5; + char *argv5[] = + { + "program", + "-n", + "-ofoo", + "-r", "bar" + }; + Seq *seq5 = CheckOpts(argc5, argv5); + assert_int_equal(SeqLength(seq5), 3); + assert_string_equal((char *) SeqAt(seq5, 0), "none"); + assert_string_equal((char *) SeqAt(seq5, 1), "foo"); + assert_string_equal((char *) SeqAt(seq5, 2), "bar"); + SeqDestroy(seq5); + + /* optional as only option with argument */ + int argc6 = 3; + char *argv6[] = + { + "program", + "-o", "foo" + }; + Seq *seq6 = CheckOpts(argc6, argv6); + assert_int_equal(SeqLength(seq6), 1); + assert_string_equal((char *) SeqAt(seq6, 0), "foo"); + SeqDestroy(seq6); + + /* optional as only option with argument immediately after */ + int argc7 = 2; + char *argv7[] = + { + "program", + "-ofoo" + }; + Seq *seq7 = CheckOpts(argc7, argv7); + assert_int_equal(SeqLength(seq7), 1); + assert_string_equal((char *) SeqAt(seq7, 0), "foo"); + SeqDestroy(seq7); + + /* optinal as only option with no argument */ + int argc8 = 2; + char *argv8[] = + { + "program", + "-o" + }; + Seq *seq8 = CheckOpts(argc8, argv8); + assert_int_equal(SeqLength(seq8), 1); + assert_string_equal((char *) SeqAt(seq8, 0), "default"); + SeqDestroy(seq8); +} + +int main() +{ + srand48(time(NULL)); + + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_GET_OPTIONAL_ARGUMENT), + }; + + return run_tests(tests); +} diff --git a/tests/unit/granules_test.c b/tests/unit/granules_test.c new file mode 100644 index 0000000000..5fa662506c --- /dev/null +++ b/tests/unit/granules_test.c @@ -0,0 +1,63 @@ +#include + +#include + +/* +char *GenTimeKey(time_t now); +const char *ShiftSlotToString(int shift_slot); +int GetTimeSlot(time_t here_and_now); +int GetShiftSlot(time_t here_and_now); + +time_t GetShiftSlotStart(time_t t); +time_t MeasurementSlotStart(time_t t); +time_t MeasurementSlotTime(size_t slot, size_t num_slots, time_t now); +*/ + +static void test_get_time_slot(void) +{ + assert_int_equal(0, GetTimeSlot(1325462400)); // monday 00:00 + assert_int_equal(1152, GetTimeSlot(1325808000)); // friday 00:00 + assert_int_equal(2015, GetTimeSlot(1326067020)); // sunday 23:57 +} + +static void test_measurement_slot_start(void) +{ + assert_int_equal(1326066900, MeasurementSlotStart(1326067020)); // sunday 23:57 -> 23:55 +} + +static void test_measurement_slot_time(void) +{ + static const time_t now = 1326066900; // sunday 23:55 + assert_int_equal(1325462400, MeasurementSlotTime(0, 2016, now)); // monday 00:00 + assert_int_equal(1325808000, MeasurementSlotTime(1152, 2016, now)); // friday 00:00 + assert_int_equal(1326066900, MeasurementSlotTime(2015, 2016, now)); // sunday 23:55 +} + +static void test_gen_time_key(void) +{ + assert_string_equal("Mon:Hr00:Min00_05", GenTimeKey(1325462400)); + assert_string_equal("Mon:Hr00:Min00_05", GenTimeKey(1325462460)); + assert_string_equal("Mon:Hr00:Min00_05", GenTimeKey(1325462520)); + assert_string_equal("Mon:Hr00:Min00_05", GenTimeKey(1325462580)); + assert_string_equal("Mon:Hr00:Min00_05", GenTimeKey(1325462640)); + + assert_string_equal("Mon:Hr00:Min05_10", GenTimeKey(1325462700)); + assert_string_equal("Mon:Hr00:Min05_10", GenTimeKey(1325462760)); + assert_string_equal("Mon:Hr00:Min05_10", GenTimeKey(1325462820)); + assert_string_equal("Mon:Hr00:Min05_10", GenTimeKey(1325462880)); + assert_string_equal("Mon:Hr00:Min05_10", GenTimeKey(1325462940)); +} + +int main() +{ + const UnitTest tests[] = + { + unit_test(test_get_time_slot), + unit_test(test_measurement_slot_start), + unit_test(test_measurement_slot_time), + unit_test(test_gen_time_key) + }; + + PRINT_TEST_BANNER(); + return run_tests(tests); +} diff --git a/tests/unit/init_script_test.sh b/tests/unit/init_script_test.sh new file mode 100755 index 0000000000..f105ede55d --- /dev/null +++ b/tests/unit/init_script_test.sh @@ -0,0 +1,377 @@ +#!/bin/sh +################################################################################ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +################################################################################ + + +################################################################################ +# Test whether the init script correctly starts and kills processes. +################################################################################ + + +# +# Detect and replace non-POSIX shell +# +try_exec() { + type "$1" > /dev/null 2>&1 && exec "$@" +} + +broken_posix_shell() +{ + unset foo + local foo=1 + test "$foo" != "1" +} + +if broken_posix_shell >/dev/null 2>&1 +then + try_exec /usr/xpg4/bin/sh "$0" "$@" + echo "No compatible shell script interpreter found." + echo "Please find a POSIX shell for your system." + exit 42 +fi + + +################################################################################ +# Preparation +################################################################################ + + +export CFTEST_PREFIX=/tmp/init_script_test.sh.$$ +# Use alternate binary names to avoid killing actual system processes. +export CFTEST_CFEXECD=$CFTEST_PREFIX/bin/cf-test-execd +export CFTEST_CFSERVD=$CFTEST_PREFIX/bin/cf-test-serverd +export CFTEST_CFMOND=$CFTEST_PREFIX/bin/cf-test-monitord +export CFTEST_CFAGENT=$CFTEST_PREFIX/bin/cf-test-agent +export OUTFILE=$CFTEST_PREFIX/outfile + +rm -rf $CFTEST_PREFIX +mkdir -p $CFTEST_PREFIX/bin +mkdir -p $CFTEST_PREFIX/inputs + + +# CALL OURSELVES AND EXIT. W-H-Y-? +if [ "$1" != "sub-invocation" ] +then + # Redirect to log, and only print if there's an error. + if ! sh -x "$0" sub-invocation > $CFTEST_PREFIX/test-output.log 2>&1 + then + # Sub-invocation FAILED! + echo "FAIL: Output from test:" + cat $CFTEST_PREFIX/test-output.log + exit 1 + else + # Sub-invocation SUCCESS + exit 0 + fi +fi + +# Fail on any error. +set -e + +cp init_script_test_helper $CFTEST_PREFIX/bin/cf-test-execd +cp init_script_test_helper $CFTEST_PREFIX/bin/cf-test-serverd +cp init_script_test_helper $CFTEST_PREFIX/bin/cf-test-monitord +cp init_script_test_helper $CFTEST_PREFIX/bin/cf-test-agent + +touch $CFTEST_PREFIX/inputs/promises.cf + +if ps --help | egrep -e '--cols\b' > /dev/null +then + # There is a bug in SUSE which means that ps output will be truncated even + # when piped to grep, if the terminal size is small. However using --cols + # will override it. + PS_OPTIONS="--cols 200" +else + PS_OPTIONS= +fi + + +################################################################################ +# Functions +################################################################################ + + +match_pid() +{ + if [ "$(ps $PS_OPTIONS -ef|grep -v grep|grep "$1"|awk -F' ' '{print $2}')" != "" ] + then + return 0 # PID found + else + return 1 # PID not found + fi +} + +matching_pid_exists() +{ + if ! match_pid "$1" + then + echo "FAIL: No such process: $1" + return 1 + fi +} + +no_matching_pid_exists() +{ + if match_pid "$1" + then + echo "FAIL: Unexpected process: $1" + return 1 + fi +} + +# Shortcut to check that no daemons/agents are running. +verify_none_running() +{ + # Save some verbosity + ( set +x + no_matching_pid_exists $CFTEST_PREFIX/bin/cf-test-execd + no_matching_pid_exists $CFTEST_PREFIX/bin/cf-test-serverd + no_matching_pid_exists $CFTEST_PREFIX/bin/cf-test-monitord + no_matching_pid_exists $CFTEST_PREFIX/bin/cf-test-agent + ) +} + +# Simply a workaround for the fact that '!' disables error checking with "set +# -e". By using it inside this function, the caller preserves error checking. +negate() +{ + ! "$@" +} + + +################################################################################ +# Test +################################################################################ + + +echo "TEST: Normal starting" +../../misc/init.d/cfengine3 start +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-execd +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-serverd +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-monitord + +echo "TEST: Normal stopping" +../../misc/init.d/cfengine3 stop +verify_none_running + +echo "TEST: Stopping when a daemon is missing" +$CFTEST_PREFIX/bin/cf-test-execd +../../misc/init.d/cfengine3 stop +verify_none_running +$CFTEST_PREFIX/bin/cf-test-monitord +../../misc/init.d/cfengine3 stop +verify_none_running + +echo "TEST: Stopping daemons and agents" +$CFTEST_PREFIX/bin/cf-test-execd --spawn-process $CFTEST_PREFIX/bin cf-test-agent +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-execd +../../misc/init.d/cfengine3 stop +verify_none_running + +echo "TEST: Stopping daemons and an agent launched just at signal time" +$CFTEST_PREFIX/bin/cf-test-execd --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-execd +no_matching_pid_exists $CFTEST_PREFIX/bin/cf-test-agent +../../misc/init.d/cfengine3 stop +verify_none_running + +echo "TEST: Stopping a chain of daemons and agents each spawning each other" +$CFTEST_PREFIX/bin/cf-test-execd \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-execd +no_matching_pid_exists $CFTEST_PREFIX/bin/cf-test-agent +../../misc/init.d/cfengine3 stop +verify_none_running + +echo "TEST: KILLing a bunch of agents and daemons" +# Stopping a ludicrously long chain of daemons and agents each spawning each +# other. This will test the SIGKILL capacity of the script, since normal killing +# won't be enough before the iteration count runs out. +$CFTEST_PREFIX/bin/cf-test-execd \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-execd +no_matching_pid_exists $CFTEST_PREFIX/bin/cf-test-agent +../../misc/init.d/cfengine3 stop +verify_none_running + +echo "TEST: Stopping a daemon that just won't die" +$CFTEST_PREFIX/bin/cf-test-execd --refuse-to-die +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-execd +../../misc/init.d/cfengine3 stop +verify_none_running + +echo "TEST: Most extreme case, a long chain of daemons and agents where they all refuse to die" +$CFTEST_PREFIX/bin/cf-test-execd \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-execd --pass-to-next-process \ + --refuse-to-die --spawn-process-on-signal $CFTEST_PREFIX/bin cf-test-agent +matching_pid_exists $CFTEST_PREFIX/bin/cf-test-execd +no_matching_pid_exists $CFTEST_PREFIX/bin/cf-test-agent +../../misc/init.d/cfengine3 stop +verify_none_running + +echo "TEST: Verify that status of one daemon works" +verify_none_running +$CFTEST_PREFIX/bin/cf-test-execd +../../misc/init.d/cfengine3 status > $OUTFILE \ + && retcode=$? || retcode=$? +for i in cf-test-serverd cf-test-monitord +do + cat $OUTFILE | grep "$i.*is not running" +done +cat $OUTFILE | egrep "cf-test-execd.*(\.|is )running" +cat $OUTFILE | negate grep -i "Warning" +# When a process is missing retcode must be 3 +test $retcode = 3 + +echo "TEST: Verify that status of all daemons works" +$CFTEST_PREFIX/bin/cf-test-serverd +$CFTEST_PREFIX/bin/cf-test-monitord +../../misc/init.d/cfengine3 status > $OUTFILE \ + && retcode=$? || retcode=$? +for i in cf-test-execd cf-test-serverd cf-test-monitord +do + cat $OUTFILE | egrep "$i.*(\.|is )running" +done +cat $OUTFILE | negate grep -i "is not running" +cat $OUTFILE | negate grep -i "Warning" +test $retcode = 0 + +echo "TEST: Verify that status of no daemons works" +../../misc/init.d/cfengine3 stop +verify_none_running +../../misc/init.d/cfengine3 status > $OUTFILE \ + && retcode=$? || retcode=$? +for i in cf-test-execd cf-test-serverd cf-test-monitord +do + cat $OUTFILE | grep "$i.*is not running" +done +cat $OUTFILE | negate egrep -i "(\.|is )running" +cat $OUTFILE | negate grep -i "Warning" +# When a process is missing retcode must be 3 +test $retcode = 3 + +echo "TEST: Verify that we get warnings about multiple daemons" +verify_none_running +$CFTEST_PREFIX/bin/cf-test-execd +$CFTEST_PREFIX/bin/cf-test-execd +../../misc/init.d/cfengine3 status > $OUTFILE \ + && retcode=$? || retcode=$? +for i in cf-test-serverd cf-test-monitord +do + cat $OUTFILE | grep "$i.*is not running" +done +cat $OUTFILE | egrep "cf-test-execd.*(\.|is )running" +cat $OUTFILE | grep -i "Warning.*multiple" +test $retcode = 3 # TODO should the retcode really be 3 ? + +echo "TEST: Verify that we get warnings about wrong PID file" +../../misc/init.d/cfengine3 stop +verify_none_running +$CFTEST_PREFIX/bin/cf-test-execd +echo 9999999 > $CFTEST_PREFIX/cf-test-execd.pid +../../misc/init.d/cfengine3 status > $OUTFILE \ + && retcode=$? || retcode=$? +for i in cf-test-serverd cf-test-monitord +do + cat $OUTFILE | grep "$i.*is not running" +done +cat $OUTFILE | egrep "cf-test-execd.*(\.|is )running" +cat $OUTFILE | grep -i "Warning.*$CFTEST_PREFIX/cf-test-execd.pid" +test $retcode = 3 # TODO should the retcode really be 3 ? + + +################################################################################ +# Cleanup +################################################################################ + +../../misc/init.d/cfengine3 stop + +rm -rf $CFTEST_PREFIX diff --git a/tests/unit/init_script_test_helper.c b/tests/unit/init_script_test_helper.c new file mode 100644 index 0000000000..ab3501d9e8 --- /dev/null +++ b/tests/unit/init_script_test_helper.c @@ -0,0 +1,206 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include +#include +#include + +char *SPAWN_PROCESS = NULL; +char *SPAWN_PROCESS_ON_SIGNAL = NULL; +int REFUSE_TO_DIE = 0; +char **NEXT_PROCESS_ARGV = NULL; +int NEXT_PROCESS_ARGC = 0; + +char *PIDFILE = NULL; + +void spawn_process(const char *program) +{ + pid_t pid = fork(); + if (pid < 0) + { + printf("Could not fork\n"); + exit(1); + } + else if (pid == 0) + { + const char * args[NEXT_PROCESS_ARGC + 2]; // One for program and one for NULL. + args[0] = program; + for (int c = 1; c <= NEXT_PROCESS_ARGC; c++) + { + args[c] = NEXT_PROCESS_ARGV[c-1]; + } + args[NEXT_PROCESS_ARGC + 1] = NULL; + execv(program, (char * const *)args); + + printf("Could not execute %s\n", program); + exit(1); + } +} + +void signal_handler(int signal) +{ + (void)signal; + // No-op. All the handling is in the main loop +} + +void process_signal(void) +{ + if (SPAWN_PROCESS_ON_SIGNAL) + { + // Insert artificial delay so that a match for the agent right after + // attempting to kill the daemon will not work. Trying to make it as + // difficult as possible for the killing script! :-) + sleep(1); + + spawn_process(SPAWN_PROCESS_ON_SIGNAL); + } + + if (!REFUSE_TO_DIE) + { + if (PIDFILE) + { + unlink(PIDFILE); + } + exit(0); + } +} + +int main(int argc, char **argv) +{ + sigset_t mask; + sigemptyset(&mask); + struct sigaction sig; + memset(&sig, 0, sizeof(sig)); + sig.sa_handler = &signal_handler; + sig.sa_mask = mask; + const int signals[] = { SIGTERM, SIGQUIT, SIGINT, SIGHUP, 0 }; + + for (int c = 0; signals[c]; c++) + { + if (sigaction(signals[c], &sig, NULL) != 0) + { + printf("Unable to set signal handlers\n"); + return 1; + } + } + + for (int c = 1; c < argc; c++) + { + if (strcmp(argv[c], "--spawn-process") == 0) + { + if (++c + 1 >= argc) + { + printf("%s requires two arguments\n", argv[c]); + return 1; + } + // The reason for splitting the argument into two parts is to avoid + // a false match on a process just because the it has an argument + // containing the string we are looking for. + SPAWN_PROCESS = malloc(strlen(argv[c]) + strlen(argv[c+1]) + 2); + sprintf(SPAWN_PROCESS, "%s/%s", argv[c], argv[c+1]); + + c++; + } + else if (strcmp(argv[c], "--spawn-process-on-signal") == 0) + { + if (++c + 1 >= argc) + { + printf("%s requires two arguments\n", argv[c]); + return 1; + } + // See comment for SPAWN_PROCESS. + SPAWN_PROCESS_ON_SIGNAL = malloc(strlen(argv[c]) + strlen(argv[c+1]) + 2); + sprintf(SPAWN_PROCESS_ON_SIGNAL, "%s/%s", argv[c], argv[c+1]); + + c++; + } + else if (strcmp(argv[c], "--refuse-to-die") == 0) + { + REFUSE_TO_DIE = 1; + } + else if (strcmp(argv[c], "--pass-to-next-process") == 0) + { + // Stops argument processing and passes all remaining arguments to + // the spawned process instead. + NEXT_PROCESS_ARGV = &argv[++c]; + NEXT_PROCESS_ARGC = argc - c; + break; + } + else + { + printf("Unknown argument: %s\n", argv[c]); + return 1; + } + } + + const char *piddir = getenv("CFTEST_PREFIX"); + if (piddir) + { + const char *file = strrchr(argv[0], '/'); + file++; + PIDFILE = malloc(strlen(piddir) + strlen(file) + 6); + sprintf(PIDFILE, "%s/%s.pid", piddir, file); + } + + pid_t child = fork(); + if (child < 0) + { + printf("Could not fork\n"); + exit(1); + } + else if (child > 0) + { + // Daemonize. + if (PIDFILE) + { + FILE *fptr = fopen(PIDFILE, "w"); + if (!fptr) + { + printf("Could not open file %s\n", PIDFILE); + exit(1); + } + fprintf(fptr, "%d\n", (int)child); + fclose(fptr); + } + + return 0; + } + + if (SPAWN_PROCESS) + { + spawn_process(SPAWN_PROCESS); + } + + // 60 seconds of consecutive sleep will end the program, just so that we do + // in fact terminate if we are left over. + while (sleep(60) != 0) + { + // If we didn't sleep the whole time, then it must be a signal. + process_signal(); + } + + return 1; +} diff --git a/tests/unit/item_lib_test.c b/tests/unit/item_lib_test.c new file mode 100644 index 0000000000..918c48b520 --- /dev/null +++ b/tests/unit/item_lib_test.c @@ -0,0 +1,54 @@ +#include + +#include +#include + +/* FIXME: MatchRegion is now internal function of cf-agent */ +#if 0 +static void test_match_region(void) +{ + EvalContext *ctx =EvalContextNew(); + Item *items = NULL; + Item *begin, *end; + + PrependItem(&items, "third", NULL); + assert_true(MatchRegion(ctx, "third", items, NULL, false)); + + end = items; + PrependItem(&items, "second", NULL); + PrependItem(&items, "first", NULL); + begin = items; + + assert_true(MatchRegion(ctx, "first", begin, end, false)); + assert_false(MatchRegion(ctx, "second", begin, end, false)); + assert_false(MatchRegion(ctx, "third", begin, end, false)); + + assert_true(MatchRegion(ctx, "first\nsecond", begin, end, false)); + assert_false(MatchRegion(ctx, "first\nthird", begin, end, false)); + assert_false(MatchRegion(ctx, "second\nthird", begin, end, false)); + + assert_false(MatchRegion(ctx, "first\nsecond\nthird", begin, end, false)); + + assert_true(MatchRegion(ctx, "first", begin, NULL, false)); + assert_false(MatchRegion(ctx, "second", begin, NULL, false)); + assert_false(MatchRegion(ctx, "third", begin, NULL, false)); + + assert_true(MatchRegion(ctx, "first\nsecond", begin, NULL, false)); + assert_false(MatchRegion(ctx, "first\nthird", begin, NULL, false)); + assert_false(MatchRegion(ctx, "second\nthird", begin, NULL, false)); + + assert_true(MatchRegion(ctx, "first\nsecond\nthird", begin, NULL, false)); +} +#endif + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + }; + + return run_tests(tests); +} + +// STUBS diff --git a/tests/unit/item_test.c b/tests/unit/item_test.c new file mode 100644 index 0000000000..b6d20a4e0d --- /dev/null +++ b/tests/unit/item_test.c @@ -0,0 +1,389 @@ +#include + +#include +#include + +static void test_prepend_item(void) +{ + Item *ip = NULL, *list = NULL; + + ip = PrependItem(&list, "hello", "classes"); + assert_int_not_equal(ip, NULL); + assert_int_not_equal(list, NULL); + DeleteItem(&list, ip); + assert_int_equal(list, NULL); +} + +static void test_list_len(void) +{ + Item *list = NULL; + + PrependItem(&list, "one", "classes"); + PrependItem(&list, "two", NULL); + PrependItem(&list, "three", NULL); + assert_int_equal(ListLen(list), 3); + DeleteItemList(list); +} + +/* FIXME: those functions are now internal to cf-agent */ +#if 0 +static void test_list_select_last_matching_finds_first(void) +{ + EvalContext *ctx = EvalContextNew(); + Item *list = NULL, *match = NULL, *prev = NULL; + bool result = false; + + AppendItem(&list, "abc", NULL); + AppendItem(&list, "def", NULL); + AppendItem(&list, "ghi", NULL); + AppendItem(&list, "jkl", NULL); + + result = SelectLastItemMatching(ctx, "abc", list, NULL, &match, &prev); + assert_true(result); + assert_int_equal(match, list); + assert_int_equal(prev, CF_UNDEFINED_ITEM); + DeleteItemList(list); + EvalContextDestroy(ctx); +} + +static void test_list_select_last_matching_finds_last(void) +{ + EvalContext *ctx = EvalContextNew(); + Item *list = NULL, *match = NULL, *prev = NULL; + bool result; + + AppendItem(&list, "abc", NULL); + AppendItem(&list, "def", NULL); + AppendItem(&list, "ghi", NULL); + AppendItem(&list, "abc", NULL); + + result = SelectLastItemMatching(ctx, "abc", list, NULL, &match, &prev); + assert_true(result); + assert_int_equal(match, list->next->next->next); + assert_int_equal(prev, list->next->next); + DeleteItemList(list); + EvalContextDestroy(ctx); +} + +static void test_list_select_last_matching_not_found(void) +{ + EvalContext *ctx = EvalContextNew(); + Item *list = NULL, *match = NULL, *prev = NULL; + bool result; + + AppendItem(&list, "abc", NULL); + AppendItem(&list, "def", NULL); + AppendItem(&list, "ghi", NULL); + AppendItem(&list, "abc", NULL); + + result = SelectLastItemMatching(ctx, "xyz", list, NULL, &match, &prev); + assert_false(result); + assert_int_equal(match, CF_UNDEFINED_ITEM); + assert_int_equal(prev, CF_UNDEFINED_ITEM); + DeleteItemList(list); + EvalContextDestroy(ctx); +} +#endif + +static void test_list_compare(void) +{ + Item *list1 = NULL, *list2 = NULL; + bool result; + + result = ListsCompare(list1, list2); + assert_true(result); + + AppendItem(&list1, "abc", NULL); + AppendItem(&list1, "def", NULL); + + result = ListsCompare(list1, list2); + assert_false(result); + + AppendItem(&list2, "def", NULL); + AppendItem(&list2, "abc", NULL); + + result = ListsCompare(list1, list2); + + assert_true(result); + + DeleteItemList(list1); + DeleteItemList(list2); +} + +static void test_split_string(void) +{ + Item *actual = NULL, *expected = NULL; + + /* Simple strings. */ + + actual = SplitString("", ':'); + AppendItem(&expected, "", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("foo", ':'); + AppendItem(&expected, "foo", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("foo:bar", ':'); + AppendItem(&expected, "foo", NULL); + AppendItem(&expected, "bar", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString(":", ':'); + AppendItem(&expected, "", NULL); + AppendItem(&expected, "", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString(":blah", ':'); + AppendItem(&expected, "", NULL); + AppendItem(&expected, "blah", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah:", ':'); + AppendItem(&expected, "blah", NULL); + AppendItem(&expected, "", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah::blue", ':'); + AppendItem(&expected, "blah", NULL); + AppendItem(&expected, "", NULL); + AppendItem(&expected, "blue", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + /* Escaped separator. */ + + actual = SplitString("foo\\:bar", ':'); + AppendItem(&expected, "foo:bar", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("foo:bar\\:baz", ':'); + AppendItem(&expected, "foo", NULL); + AppendItem(&expected, "bar:baz", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("\\:", ':'); + AppendItem(&expected, ":", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("\\:blah", ':'); + AppendItem(&expected, ":blah", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah\\:", ':'); + AppendItem(&expected, "blah:", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah\\:\\:blue", ':'); + AppendItem(&expected, "blah::blue", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah\\::blue", ':'); + AppendItem(&expected, "blah:", NULL); + AppendItem(&expected, "blue", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah:\\:blue", ':'); + AppendItem(&expected, "blah", NULL); + AppendItem(&expected, ":blue", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + /* Escaped backslash. */ + + actual = SplitString("\\\\", ':'); + AppendItem(&expected, "\\", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + + actual = SplitString("blah\\\\blue", ':'); + AppendItem(&expected, "blah\\blue", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah\\\\blue\\:", ':'); + AppendItem(&expected, "blah\\blue:", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("\\\\:", ':'); + AppendItem(&expected, "\\", NULL); + AppendItem(&expected, "", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString(":\\\\", ':'); + AppendItem(&expected, "", NULL); + AppendItem(&expected, "\\", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("\\\\:blah", ':'); + AppendItem(&expected, "\\", NULL); + AppendItem(&expected, "blah", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("\\\\\\:", ':'); + AppendItem(&expected, "\\:", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("\\\\\\:blah", ':'); + AppendItem(&expected, "\\:blah", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah\\\\:", ':'); + AppendItem(&expected, "blah\\", NULL); + AppendItem(&expected, "", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("\\\\:\\\\", ':'); + AppendItem(&expected, "\\", NULL); + AppendItem(&expected, "\\", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + + actual = SplitString("blah:\\\\:blue", ':'); + AppendItem(&expected, "blah", NULL); + AppendItem(&expected, "\\", NULL); + AppendItem(&expected, "blue", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah:\\\\\\:blue", ':'); + AppendItem(&expected, "blah", NULL); + AppendItem(&expected, "\\:blue", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("blah\\\\blue", ':'); + AppendItem(&expected, "blah\\blue", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + /* End string with backslash, danger of buffer overrun! */ + + actual = SplitString("blah\\", ':'); + AppendItem(&expected, "blah\\", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + actual = SplitString("\\", ':'); + AppendItem(&expected, "\\", NULL); + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + /* Backslash as separator - CURRENTLY NOT SUPPORTED! */ + /* TODO FIX. */ + + actual = SplitString("C:\\blah\\blue\\", '\\'); + /* + for (Item *ip = actual; ip != NULL; ip = ip->next) + { + printf("%s\n", ip->name); + } + */ + AppendItem(&expected, "C:\\blah\\blue\\", NULL); /* TODO FIX! */ + /* AppendItem(&expected, "C:", NULL); */ + /* AppendItem(&expected, "blah", NULL); */ + /* AppendItem(&expected, "blue", NULL); */ + /* AppendItem(&expected, "", NULL); */ + assert_true(ListsCompare(actual, expected)); + DeleteItemList(actual); actual = NULL; + DeleteItemList(expected); expected = NULL; + test_progress(); + + test_progress_end(); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_prepend_item), + unit_test(test_list_len), + unit_test(test_list_compare), + unit_test(test_split_string) + }; + + return run_tests(tests); +} diff --git a/tests/unit/iteration_test.c b/tests/unit/iteration_test.c new file mode 100644 index 0000000000..3d0dac1727 --- /dev/null +++ b/tests/unit/iteration_test.c @@ -0,0 +1,366 @@ +#include + + +#include + + + +static void test_FindDollarParen(void) +{ + /* not found */ + assert_int_equal(FindDollarParen("", 1), 0); + assert_int_equal(FindDollarParen(" ", 2), 1); + assert_int_equal(FindDollarParen("$", 2), 1); + assert_int_equal(FindDollarParen("(", 2), 1); + assert_int_equal(FindDollarParen("{", 2), 1); + assert_int_equal(FindDollarParen("$ ", 3), 2); + assert_int_equal(FindDollarParen("$$", 3), 2); + assert_int_equal(FindDollarParen("$[", 3), 2); + assert_int_equal(FindDollarParen("($", 3), 2); + assert_int_equal(FindDollarParen(" $", 3), 2); + assert_int_equal(FindDollarParen(" $[", 4), 3); + assert_int_equal(FindDollarParen("$ (", 4), 3); + assert_int_equal(FindDollarParen("$ {", 4), 3); + + /* found */ + assert_int_equal(FindDollarParen("${", 3), 0); + assert_int_equal(FindDollarParen("$(", 3), 0); + assert_int_equal(FindDollarParen(" $(", 4), 1); + assert_int_equal(FindDollarParen(" ${", 4), 1); + assert_int_equal(FindDollarParen("$$(", 4), 1); + assert_int_equal(FindDollarParen("$${", 4), 1); + + // Detect out of bounds read: + // If max is 0, it shouldn't try to deref these invalid pointers: + char a = 'a'; + assert_int_equal(FindDollarParen((char *)0x1, 0), 0); + assert_int_equal(FindDollarParen((&a) + 1, 0), 0); + + // Should not read past max bytes: + char b[1] = {'b'}; + assert_int_equal(FindDollarParen(b, 1), 1); + char c[8] = {'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c'}; + assert_int_equal(FindDollarParen(c, 8), 8); + + // We had some problems with FindDollarParen reading outside a buffer + // so I added these cryptic but useful tests. Some of them will only + // fail if you run tests with ASAN, valgrind or similar. +} + + +static void test_IsMangled(void) +{ + /* Simply true. */ + assert_false(IsMangled("")); + assert_false(IsMangled("blah")); + assert_false(IsMangled("namespace:blah")); + assert_false(IsMangled("scope.blah")); + assert_false(IsMangled("namespace:scope.blah")); + + /* Simply false. */ + assert_true(IsMangled("scope#blah")); + assert_true(IsMangled("namespace*blah")); + assert_true(IsMangled("namespace*scope.blah")); + assert_true(IsMangled("namespace:scope#blah")); + + /* Complicated: nested expansions shouldn't affect result */ + assert_false(IsMangled("$(")); + assert_false(IsMangled("${")); + assert_false(IsMangled("blah$(blue)")); + assert_false(IsMangled("blah$(scope.blue)")); + assert_false(IsMangled("blah$(scope#blue)")); + assert_false(IsMangled("blah$(namespace:blue)")); + assert_false(IsMangled("blah$(namespace*blue)")); + assert_false(IsMangled("blah$(namespace:scope.blue)")); + assert_false(IsMangled("blah$(namespace:scope#blue)")); + assert_false(IsMangled("blah$(namespace*scope.blue)")); + assert_false(IsMangled("blah$(namespace*scope#blue)")); + + assert_false(IsMangled("scope.blah$(blue)")); + assert_false(IsMangled("scope.blah$(scope.blue)")); + assert_false(IsMangled("scope.blah$(scope#blue)")); + assert_false(IsMangled("scope.blah$(namespace:blue)")); + assert_false(IsMangled("scope.blah$(namespace*blue)")); + assert_false(IsMangled("scope.blah$(namespace:scope.blue)")); + assert_false(IsMangled("scope.blah$(namespace:scope#blue)")); + assert_false(IsMangled("scope.blah$(namespace*scope.blue)")); + assert_false(IsMangled("scope.blah$(namespace*scope#blue)")); + + assert_true(IsMangled("scope#blah$(blue)")); + assert_true(IsMangled("scope#blah$(scope.blue)")); + assert_true(IsMangled("scope#blah$(scope#blue)")); + assert_true(IsMangled("scope#blah$(namespace:blue)")); + assert_true(IsMangled("scope#blah$(namespace*blue)")); + assert_true(IsMangled("scope#blah$(namespace:scope.blue)")); + assert_true(IsMangled("scope#blah$(namespace:scope#blue)")); + assert_true(IsMangled("scope#blah$(namespace*scope.blue)")); + assert_true(IsMangled("scope#blah$(namespace*scope#blue)")); + + assert_true(IsMangled("namespace*blah$(blue)")); + assert_true(IsMangled("namespace*blah$(scope.blue)")); + assert_true(IsMangled("namespace*blah$(scope#blue)")); + assert_true(IsMangled("namespace*blah$(namespace:blue)")); + assert_true(IsMangled("namespace*blah$(namespace*blue)")); + assert_true(IsMangled("namespace*blah$(namespace:scope.blue)")); + assert_true(IsMangled("namespace*blah$(namespace:scope#blue)")); + assert_true(IsMangled("namespace*blah$(namespace*scope.blue)")); + assert_true(IsMangled("namespace*blah$(namespace*scope#blue)")); + + assert_false(IsMangled("$(scope#blah)")); + assert_false(IsMangled("$(namespace*blah)")); + assert_false(IsMangled("$(namespace*scope#blah)")); + + /* Multiple nested expansions, again, none should matter. */ + assert_false(IsMangled("blah$(blue$(bleh))")); + assert_false(IsMangled("blah$(scope.blue$(scope#bleh))")); + + /* Array indexes shouldn't affect the result either. */ + assert_false(IsMangled("[")); + assert_false(IsMangled("blah[$(blue)]")); + assert_false(IsMangled("blah$(blue[bleh])")); + assert_false(IsMangled("blah[S#i]")); + assert_false(IsMangled("blah[S#i][N*i]")); + assert_false(IsMangled("blah[S#i][N*i]")); + + assert_true(IsMangled("S#blah[S.blue]")); + assert_true(IsMangled("S#blah[N:blue]")); + assert_true(IsMangled("S#blah[S#blue]")); + assert_true(IsMangled("N*blah[S.blue]")); + assert_true(IsMangled("N*blah[N:blue]")); + assert_true(IsMangled("N*S.blah[N:blue]")); + assert_true(IsMangled("N*S.blah[S.blue]")); + + assert_false(IsMangled("S.blah[S#i][N*i]")); + assert_true (IsMangled("S#blah[S#i][N*i]")); + + assert_false(IsMangled("[scope#blah]")); + assert_false(IsMangled("[namespace*blah]")); + assert_false(IsMangled("[namespace*scope#blah]")); + + /* Complicated: combine nested variables with array expansions. */ + assert_false(IsMangled("[$(")); + assert_false(IsMangled("S.blah[$(")); + assert_false(IsMangled("S.blah[i]$(blah)")); + assert_false(IsMangled("S.blah[$(i)]")); + assert_false(IsMangled("S.v[$(i)]")); + assert_true (IsMangled("S#v[$(i)]")); + assert_true (IsMangled("N*v[$(i)]")); + assert_false(IsMangled("N:v[$(i)]")); + assert_false(IsMangled("N:v[$(N*i)]")); + assert_false(IsMangled("N:v[$(S#i)]")); + assert_true (IsMangled("N*v[$(S#i)]")); + assert_true (IsMangled("S#v[$(S#i)]")); + assert_false(IsMangled("v[$(N*S#i)]")); + assert_false(IsMangled("v[$(N:S#i)]")); + assert_false(IsMangled("v[$(N*S.i)]")); + assert_false(IsMangled("S.v[$(N*S#i)]")); + assert_true (IsMangled("S#v[$(N*S#i)]")); + assert_false(IsMangled("N:v[$(N*S#i)]")); + assert_true (IsMangled("N*v[$(N*S#i)]")); +} + + +static void Mangle_TestHelper(const char *orig, const char *expected) +{ + printf("Testing MangleVarRefString: %s\n", orig); + + char *s = xstrdup(orig); + MangleVarRefString(s, strlen(s)); + assert_string_equal(s, expected); + free(s); +} +static void Mangle_TestHelperWithLength1(const char *orig, size_t orig_len, + const char *expected) +{ + printf("Testing MangleVarRefString: %.*s\n", (int) orig_len, orig); + + char *s = xstrdup(orig); + MangleVarRefString(s, orig_len); + assert_string_equal(s, expected); + free(s); +} +/* NOTE: this one changes "orig" in-place. */ +static void Mangle_TestHelperWithLength2(char *orig, size_t orig_len, + const char *expected, size_t expected_len) +{ + printf("Testing MangleVarRefString: %.*s\n", (int) orig_len, orig); + + MangleVarRefString(orig, orig_len); + assert_memory_equal(orig, expected, expected_len); +} +static void test_MangleVarRefString(void) +{ + Mangle_TestHelper ("", ""); + Mangle_TestHelperWithLength1 ("a", 0, "a"); + + Mangle_TestHelper ("a.b", "a#b"); + /* Force length to 1, no change should occur. */ + Mangle_TestHelperWithLength1 ("a.b", 1, "a.b"); + + Mangle_TestHelper ("a:b", "a*b"); + /* Force length to 1, no change should occur. */ + Mangle_TestHelperWithLength1 ("a:b", 1, "a:b"); + + Mangle_TestHelper ("a:b.c", "a*b#c"); + /* Force length to 1, no change should occur. */ + Mangle_TestHelperWithLength1 ("a:b.c", 1, "a:b.c"); + + /* Never mangle after array indexing */ + Mangle_TestHelper ("a[b.c]", "a[b.c]"); + + /* "this" scope never gets mangled. */ + Mangle_TestHelper ("this.a", "this.a"); + + /* Inner expansions never get mangled. */ + Mangle_TestHelper ("a_$(s.i)", "a_$(s.i)"); + Mangle_TestHelper ("a_$(n:i)", "a_$(n:i)"); + + /* Only before the inner expansion it gets mangled. */ + Mangle_TestHelper ("s.a_$(s.i)", "s#a_$(s.i)"); + Mangle_TestHelper ("n:a_$(n:i)", "n*a_$(n:i)"); + + /* Testing non '\0'-terminated char arrays. */ + + char A[4] = {'a','b','.','c'}; + Mangle_TestHelperWithLength2(A, 0, "ab.c", 4); + Mangle_TestHelperWithLength2(A, 4, "ab#c", 4); + char B[3] = {'a',':','b'}; + Mangle_TestHelperWithLength2(B, 0, "a:b", 3); + Mangle_TestHelperWithLength2(B, 3, "a*b", 3); + char C[1] = {'a'}; + Mangle_TestHelperWithLength2(C, 0, "a", 1); + Mangle_TestHelperWithLength2(C, 1, "a", 1); +} + + +/* NOTE: the two variables "i" and "j" are defined as empty slists in the + * EvalContext. */ +static void IteratorPrepare_TestHelper( + const char *promiser, + size_t expected_wheels_num, + const char **expected_wheels) +{ + /* INIT EvalContext and Promise. */ + EvalContext *evalctx = EvalContextNew(); + Policy *policy = PolicyNew(); + Bundle *bundle = PolicyAppendBundle(policy, "ns1", "bundle1", "agent", + NULL, NULL); + BundleSection *section = BundleAppendSection(bundle, "dummy"); + Promise *promise = BundleSectionAppendPromise(section, promiser, + (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, + "any", NULL); + EvalContextStackPushBundleFrame(evalctx, bundle, NULL, false); + EvalContextStackPushBundleSectionFrame(evalctx, section); + PromiseIterator *iterctx = PromiseIteratorNew(promise); + char *promiser_copy = xstrdup(promiser); + + /* Insert the variables "i" and "j" as empty Rlists in the EvalContext, so + * that the iteration engine creates wheels for them. */ + { + VarRef *ref_i = VarRefParseFromBundle("i", bundle); + EvalContextVariablePut(evalctx, ref_i, + (Rlist *) NULL, CF_DATA_TYPE_STRING_LIST, + NULL); + VarRefDestroy(ref_i); + + VarRef *ref_j = VarRefParseFromBundle("j", bundle); + EvalContextVariablePut(evalctx, ref_j, + (Rlist *) NULL, CF_DATA_TYPE_STRING_LIST, + NULL); + VarRefDestroy(ref_j); + } + + /* TEST */ + printf("Testing PromiseIteratorPrepare: %s\n", promiser); + PromiseIteratorPrepare(iterctx, evalctx, promiser_copy); + + /* CHECK */ + assert_true(iterctx->wheels != NULL); + + size_t wheels_num = SeqLength(iterctx->wheels); + assert_int_equal(wheels_num, expected_wheels_num); + + for (size_t i = 0; i < expected_wheels_num; i++) + { + const Wheel *wheel = SeqAt(iterctx->wheels, i); + assert_string_equal(wheel->varname_unexp, expected_wheels[i]); + } + + /* CLEANUP */ + free(promiser_copy); + PromiseIteratorDestroy(iterctx); + EvalContextStackPopFrame(evalctx); + EvalContextStackPopFrame(evalctx); + PolicyDestroy(policy); + EvalContextDestroy(evalctx); +} +static void test_PromiseIteratorPrepare(void) +{ + IteratorPrepare_TestHelper("", 0, NULL); + /* No wheel added for "blah" because this variable does not resolve. */ + IteratorPrepare_TestHelper("$(blah)", 0, NULL); + /* "$(i)" is a valid list, but these syntaxes are not correct. */ + IteratorPrepare_TestHelper("i", 0, NULL); + IteratorPrepare_TestHelper("$i", 0, NULL); + IteratorPrepare_TestHelper("$(i", 0, NULL); + /* The following however is correct and should add one wheel */ + IteratorPrepare_TestHelper("$(i)", 1, + (const char *[]) {"i"}); + IteratorPrepare_TestHelper("$(i))", 1, + (const char *[]) {"i"}); + IteratorPrepare_TestHelper("$(i)(", 1, + (const char *[]) {"i"}); + IteratorPrepare_TestHelper("$(i)$(", 1, + (const char *[]) {"i"}); + /* The same variable twice should just add one wheel. */ + IteratorPrepare_TestHelper("$(i)$(i)", 1, + (const char *[]) {"i"}); + /* "$(ij)" does not resolve. */ + IteratorPrepare_TestHelper("$(i)$(ij)", 1, + (const char *[]) {"i"}); + /* Both "$(i)" and "$(j)" resolve. */ + IteratorPrepare_TestHelper("$(i)$(j)", 2, + (const char *[]) {"i","j"}); + IteratorPrepare_TestHelper("$(i))$(j)", 2, + (const char *[]) {"i","j"}); + IteratorPrepare_TestHelper("0$(i)$(j)", 2, + (const char *[]) {"i","j"}); + IteratorPrepare_TestHelper("$(i)1$(j)", 2, + (const char *[]) {"i","j"}); + IteratorPrepare_TestHelper("$(i)$(j)2", 2, + (const char *[]) {"i","j"}); + IteratorPrepare_TestHelper("0$(i)1$(j)", 2, + (const char *[]) {"i","j"}); + IteratorPrepare_TestHelper("0$(i)1$(j)2", 2, + (const char *[]) {"i","j"}); + /* Any variable dependent on other variables should be added as a wheel. */ + IteratorPrepare_TestHelper("$(A[$(i)][$(j)])", 3, + (const char *[]) {"i","j", "A[$(i)][$(j)]"}); + /* Even if the inner variables don't resolve. */ + IteratorPrepare_TestHelper("$(A[$(blah)][$(blue)])", 1, + (const char *[]) {"A[$(blah)][$(blue)]"}); + IteratorPrepare_TestHelper("$(A[1][2]) $(A[$(i)][$(j)])", 3, + (const char *[]) {"i","j", "A[$(i)][$(j)]"}); + IteratorPrepare_TestHelper("$(A[$(B[$(i)])][$(j)])", 4, + (const char *[]) {"i", "B[$(i)]", "j", "A[$(B[$(i)])][$(j)]"}); + IteratorPrepare_TestHelper("$(A[$(B[$(i)][$(j)])])", 4, + (const char *[]) {"i","j", "B[$(i)][$(j)]", "A[$(B[$(i)][$(j)])]"}); +} + + + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_FindDollarParen), + unit_test(test_IsMangled), + unit_test(test_MangleVarRefString), + unit_test(test_PromiseIteratorPrepare), + }; + + int ret = run_tests(tests); + + return ret; +} diff --git a/tests/unit/key_test.c b/tests/unit/key_test.c new file mode 100644 index 0000000000..56e823a379 --- /dev/null +++ b/tests/unit/key_test.c @@ -0,0 +1,104 @@ +#include + +#include +#include +#include +#include +#include +#include + +/* + * Initialization + */ +static int initialized = 0; +static RSA *rsa = NULL; +void test_setup() +{ + rsa = RSA_new(); + if (rsa) + { + BIGNUM *bn = NULL; + bn = BN_new(); + if (!bn) + { + RSA_free(rsa); + initialized = 0; + return; + } + BN_set_word(bn, RSA_F4); + RSA_generate_key_ex(rsa, 1024, bn, NULL); + BN_free(bn); + } + initialized = 1; +} + +void test_teardown() +{ + rsa = NULL; + initialized = 0; +} +#define ASSERT_IF_NOT_INITIALIZED \ + assert_int_equal(1, initialized) +/* + * Tests + */ +static void test_key_basic(void) +{ + test_setup(); + ASSERT_IF_NOT_INITIALIZED; + Key *key = NULL; + assert_true(key == NULL); + key = KeyNew(rsa, HASH_METHOD_MD5); + assert_true(key != NULL); + assert_int_equal(HASH_METHOD_MD5, KeyHashMethod(key)); + assert_true(rsa == KeyRSA(key)); + unsigned int length = 0; + assert_true(KeyBinaryHash(key, &length) != NULL); + assert_int_equal(CF_MD5_LEN, length); + assert_true(KeyPrintableHash(key) != NULL); + /* Negative cases */ + assert_true(KeyNew(NULL, HASH_METHOD_MD5) == NULL); + assert_true(KeyNew(rsa, HASH_METHOD_NONE) == NULL); + assert_true(KeyNew(NULL, HASH_METHOD_NONE) == NULL); + /* Finish */ + KeyDestroy(&key); + assert_true(key == NULL); + test_teardown(); +} + +static void test_key_hash(void) +{ + test_setup(); + ASSERT_IF_NOT_INITIALIZED; + Key *key = NULL; + assert_true(key == NULL); + key = KeyNew(rsa, HASH_METHOD_MD5); + assert_true(key != NULL); + assert_int_equal(HASH_METHOD_MD5, KeyHashMethod(key)); + /* We now examine the first four bytes of the hash, to check the printable bit */ + const char *md5_hash = KeyPrintableHash(key); + assert_true((md5_hash[0] == 'M') && (md5_hash[1] == 'D') && (md5_hash[2] == '5') && (md5_hash[3] == '=')); + /* When we change the hashing algorithm, a new hash is automatically generated. */ + assert_int_equal(0, KeySetHashMethod(key, HASH_METHOD_SHA256)); + const char *sha256_hash = KeyPrintableHash(key); + assert_true((sha256_hash[0] == 'S') && (sha256_hash[1] == 'H') && (sha256_hash[2] == 'A') && (sha256_hash[3] == '=')); + KeyDestroy(&key); + test_teardown(); +} + +/* + * Main routine + * Notice the calls to both setup and teardown. + */ +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_key_basic), + unit_test(test_key_hash) + }; + OpenSSL_add_all_digests(); + int result = run_tests(tests); + return result; +} diff --git a/tests/unit/lastseen_migration_test.c b/tests/unit/lastseen_migration_test.c new file mode 100644 index 0000000000..0970481aec --- /dev/null +++ b/tests/unit/lastseen_migration_test.c @@ -0,0 +1,286 @@ +#include + +#include +#include +#include +#include /* xsnprintf */ +#include + +#ifdef LMDB +int main() +{ + return 0; +} +#else + +typedef struct +{ + char address[128]; + double q; + double expect; + double var; +} KeyHostSeen0; + +char CFWORKDIR[CF_BUFSIZE]; + +void tests_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/lastseen_migration_test.XXXXXX"; + + char *workdir = strchr(env, '=') + 1; /* start of the path */ + assert(workdir - 1 && workdir[0] == '/'); + + mkdtemp(workdir); + strlcpy(CFWORKDIR, workdir, CF_BUFSIZE); + putenv(env); + mkdir(GetStateDir(), (S_IRWXU | S_IRWXG | S_IRWXO)); +} + +/* + * Provides empty lastseen DB + */ +static DBHandle *setup(bool clean) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'/*", GetStateDir()); + system(cmd); + + DBHandle *db; + OpenDB(&db, dbid_lastseen); + + if (clean) + { + /* There is no way to disable hook in OpenDB yet, so just undo + * everything */ + + DBCursor *cursor; + if (!NewDBCursor(db, &cursor)) + { + return NULL; + } + + char *key; + void *value; + int ksize, vsize; + + while (NextDB(cursor, &key, &ksize, &value, &vsize)) + { + DBCursorDeleteEntry(cursor); + } + + if (!DeleteDBCursor(cursor)) + { + return NULL; + } + } + + return db; +} + +static void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +static void test_no_migration(void) +{ + DBHandle *db = setup(true); + + CloseDB(db); + + /* Migration on empty DB should produce single "version" key */ + + assert_int_equal(OpenDB(&db, dbid_lastseen), true); + + DBCursor *cursor; + assert_int_equal(NewDBCursor(db, &cursor), true); + + char *key; + void *value; + int ksize, vsize; + + while (NextDB(cursor, &key, &ksize, &value, &vsize)) + { + assert_int_equal(ksize, strlen("version") + 1); + assert_string_equal(key, "version"); + assert_int_equal(vsize, 2); + assert_string_equal(value, "1"); + } + + assert_int_equal(DeleteDBCursor(cursor), true); + + CloseDB(db); +} + +static void test_up_to_date(void) +{ + /* Test that upgrade is not performed if there is already a version + * marker */ + + DBHandle *db = setup(false); + const char *value = "+"; + assert_int_equal(WriteDB(db, "+++", value, 2), true); + CloseDB(db); + + /* Test that manually inserted key which matches the format of old-style + * keys is still present next time database is open, which is an indicator + * of database not being upgraded */ + + assert_int_equal(OpenDB(&db, dbid_lastseen), true); + + char read_value[CF_BUFSIZE]; + + assert_int_equal(ReadDB(db, "+++", &read_value, sizeof(read_value)), true); + assert_string_equal(read_value, "+"); + + CloseDB(db); +} + +#define KEYHASH \ + "SHA=f7b335bef201230c7bf573b8dedf299fa745efe71e34a9002369248ff8519089" +#define KEYHASH_KEY "k" KEYHASH + +#define KEYHASH_IN "-" KEYHASH +#define QUALITY_IN "qi" KEYHASH + +#define KEYHASH_OUT "+" KEYHASH +#define QUALITY_OUT "qo" KEYHASH + +void test_migrate_single(const char *expected_old_key, + const char *expected_quality_key) +{ + /* Test migration of single entry */ + + DBHandle *db = setup(true); + KeyHostSeen0 khs0 = { + .q = 666777.0, + .expect = 12345.0, + .var = 6543210.0, + }; + strcpy(khs0.address, "1.2.3.4"); + assert_int_equal(WriteDB(db, expected_old_key, &khs0, sizeof(khs0)), true); + CloseDB(db); + + assert_int_equal(OpenDB(&db, dbid_lastseen), true); + + /* Old entry migrated */ + assert_int_equal(HasKeyDB(db, expected_old_key, + strlen(expected_old_key) + 1), false); + + /* Version marker */ + assert_int_equal(HasKeyDB(db, "version", strlen("version") + 1), true); + + /* Incoming connection quality */ + KeyHostSeen khs; + + assert_int_equal(ReadDB(db, expected_quality_key, &khs, sizeof(khs)), true); + + assert_int_equal(khs.lastseen, 666777); + assert_double_close(khs.Q.q, 12345.0); + assert_double_close(khs.Q.expect, 12345.0); + assert_double_close(khs.Q.var, 6543210.0); + + /* Address mapping */ + char address[CF_BUFSIZE]; + + assert_int_equal(ReadDB(db, KEYHASH_KEY, address, sizeof(address)), true); + assert_string_equal(address, "1.2.3.4"); + + /* Reverse mapping */ + + char keyhash[CF_BUFSIZE]; + + assert_int_equal(ReadDB(db, "a1.2.3.4", keyhash, sizeof(keyhash)), true); + assert_string_equal(keyhash, KEYHASH); + + CloseDB(db); +} + +void test_migrate_incoming(void) +{ + test_migrate_single(KEYHASH_IN, QUALITY_IN); +} + +void test_migrate_outgoing(void) +{ + test_migrate_single(KEYHASH_OUT, QUALITY_OUT); +} + +void test_ignore_wrong_sized(void) +{ + /* Test that malformed values are discarded */ + + DBHandle *db = setup(true); + const char *value = "+"; + assert_int_equal(WriteDB(db, "+++", value, 2), true); + CloseDB(db); + + assert_int_equal(OpenDB(&db, dbid_lastseen), true); + + assert_int_equal(HasKeyDB(db, "+++", 4), false); + assert_int_equal(HasKeyDB(db, "k++", 4), false); + assert_int_equal(HasKeyDB(db, "qi++", 5), false); + assert_int_equal(HasKeyDB(db, "qo++", 5), false); + assert_int_equal(HasKeyDB(db, "a+", 3), false); + + CloseDB(db); +} + +int main() +{ + tests_setup(); + + const UnitTest tests[] = + { + unit_test(test_no_migration), + unit_test(test_up_to_date), + unit_test(test_migrate_incoming), + unit_test(test_migrate_outgoing), + unit_test(test_ignore_wrong_sized), + }; + + PRINT_TEST_BANNER(); + int ret = run_tests(tests); + + tests_teardown(); + + return ret; +} + +/* STUBS */ + +void FatalError(ARG_UNUSED char *s, ...) +{ + fail(); + exit(42); +} + +HashMethod CF_DEFAULT_DIGEST; +pthread_mutex_t *cft_output; +char VIPADDRESS[CF_MAX_IP_LEN]; +RSA *PUBKEY; +bool MINUSF; + +char *MapAddress(ARG_UNUSED char *addr) +{ + fail(); +} + +char *HashPrintSafe(ARG_UNUSED char *dst, ARG_UNUSED size_t dst_size, + ARG_UNUSED const unsigned char *digest, + ARG_UNUSED HashMethod type, ARG_UNUSED bool use_prefix) +{ + fail(); +} + +void HashPubKey(ARG_UNUSED const RSA *key, + ARG_UNUSED unsigned char digest[EVP_MAX_MD_SIZE + 1], + ARG_UNUSED HashMethod type) +{ + fail(); +} + +#endif // LMDB diff --git a/tests/unit/lastseen_test.c b/tests/unit/lastseen_test.c new file mode 100644 index 0000000000..0344a2e59b --- /dev/null +++ b/tests/unit/lastseen_test.c @@ -0,0 +1,683 @@ +#include + +#include +#include +#include +#include +#include /* xsnprintf */ +#include + + +char CFWORKDIR[CF_BUFSIZE]; + +void UpdateLastSawHost(const char *hostkey, const char *address, + bool incoming, time_t timestamp); + +/* For abbreviation of tests. */ +#define IP1 "127.0.0.121" +#define IP2 "127.0.0.122" +#define IP3 "127.0.0.123" +#define KEY1 "SHA=key1" +#define KEY2 "SHA=key2" +#define KEY3 "SHA=key3" +#define ACC LAST_SEEN_ROLE_ACCEPT +#define DBHasStr(dbh, s) HasKeyDB(dbh, s, strlen(s)+1) +#define DBPutStr(dbh, k, s) WriteDB(dbh, k, s, strlen(s)+1) +char tmpbuf[CF_BUFSIZE]; +#define DBGetStr(dbh, s) (ReadDB(dbh, s, tmpbuf, sizeof(tmpbuf)) ? tmpbuf : NULL) + + +static void tests_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/lastseen_test.XXXXXX"; + + char *workdir = strchr(env, '=') + 1; /* start of the path */ + assert(workdir - 1 && workdir[0] == '/'); + + mkdtemp(workdir); + strlcpy(CFWORKDIR, workdir, CF_BUFSIZE); + putenv(env); + mkdir(GetStateDir(), (S_IRWXU | S_IRWXG | S_IRWXO)); +} + +static void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", GetStateDir()); + system(cmd); + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", GetWorkDir()); + system(cmd); +} + + +static void setup(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'/*", GetStateDir()); + system(cmd); +} + +static void test_newentry(void) +{ + setup(); + + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 666); + + DBHandle *db; + OpenDB(&db, dbid_lastseen); + + KeyHostSeen q; + assert_int_equal(ReadDB(db, "qiSHA-12345", &q, sizeof(q)), true); + + assert_int_equal(q.lastseen, 666); + assert_double_close(q.Q.q, 0.0); + assert_double_close(q.Q.dq, 0.0); + assert_double_close(q.Q.expect, 0.0); + assert_double_close(q.Q.var, 0.0); + + assert_int_equal(ReadDB(db, "qoSHA-12345", &q, sizeof(q)), false); + + char address[CF_BUFSIZE]; + assert_int_equal(ReadDB(db, "kSHA-12345", address, sizeof(address)), true); + assert_string_equal(address, "127.0.0.64"); + + char hostkey[CF_BUFSIZE]; + assert_int_equal(ReadDB(db, "a127.0.0.64", hostkey, sizeof(hostkey)), true); + assert_string_equal(hostkey, "SHA-12345"); + + CloseDB(db); +} + +static void test_update(void) +{ + setup(); + + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 555); + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 1110); + + DBHandle *db; + OpenDB(&db, dbid_lastseen); + + KeyHostSeen q; + assert_int_equal(ReadDB(db, "qiSHA-12345", &q, sizeof(q)), true); + + assert_int_equal(q.lastseen, 1110); + assert_double_close(q.Q.q, 555.0); + assert_double_close(q.Q.dq, 555.0); + assert_double_close(q.Q.expect, 222.0); + assert_double_close(q.Q.var, 123210.0); + + CloseDB(db); +} + +static void test_HostkeyToAddress(void) +{ + setup(); + + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 555); + char *address = HostkeyToAddress("SHA-12345"); + assert_string_equal(address, "127.0.0.64"); + free(address); +} + +static void test_reverse_missing(void) +{ + setup(); + + /* Check that resolution return false */ + char result[CF_BUFSIZE]; + assert_int_equal(Address2Hostkey(result, sizeof(result), "127.0.0.64"), false); +} + +static void test_reverse_conflict(void) +{ + setup(); + + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 555); + + /* Overwrite reverse entry with different one. */ + DBHandle *db; + OpenDB(&db, dbid_lastseen); + assert_int_equal(WriteDB(db, "a127.0.0.64", "SHA-98765", strlen("SHA-98765") + 1), true); + + /* Check that resolution returns the last forced entry and is not bothered + * by the inconsistency (despite outputing a warning). */ + char result[CF_BUFSIZE]; + assert_int_equal(Address2Hostkey(result, sizeof(result), "127.0.0.64"), true); + assert_string_equal(result, "SHA-98765"); + + /* Both entries still exist despite inconsistency. */ + assert_int_equal(DBHasStr(db, "a127.0.0.64"), true); + assert_int_equal(DBHasStr(db, "kSHA-12345"), true); + /* And the inconsistency (missing entry) is not auto-fixed... :-( */ + assert_int_equal(DBHasStr(db, "kSHA-98765"), false); + + CloseDB(db); +} + +static void test_reverse_missing_forward(void) +{ + setup(); + + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 555); + + DBHandle *db; + OpenDB(&db, dbid_lastseen); + + assert_int_equal(DeleteDB(db, "kSHA-12345"), true); + + /* Check that resolution returns true despite the missing entry (a warning + * should be printed though). */ + char result[CF_BUFSIZE]; + assert_int_equal(Address2Hostkey(result, sizeof(result), "127.0.0.64"), true); + + /* Entry still exists despite inconsistency. */ + assert_int_equal(DBHasStr(db, "a127.0.0.64"), true); + /* And the inconsistency was not auto-fixed, entry is still missing. :-( */ + assert_int_equal(DBHasStr(db, "kSHA-12345"), false); + + CloseDB(db); +} + +static void test_remove(void) +{ + setup(); + + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 555); + UpdateLastSawHost("SHA-12345", "127.0.0.64", false, 556); + + //RemoveHostFromLastSeen("SHA-12345"); + DeleteDigestFromLastSeen("SHA-12345", NULL, 0, true); + + DBHandle *db; + OpenDB(&db, dbid_lastseen); + + assert_int_equal(HasKeyDB(db, "qiSHA-12345", strlen("qiSHA-12345") + 1), false); + assert_int_equal(HasKeyDB(db, "qoSHA-12345", strlen("qoSHA-12345") + 1), false); + assert_int_equal(HasKeyDB(db, "kSHA-12345", strlen("kSHA-12345") + 1), false); + assert_int_equal(HasKeyDB(db, "a127.0.0.64", strlen("a127.0.0.64") + 1), false); + + CloseDB(db); +} + +static void test_remove_no_a_entry(void) +{ + setup(); + + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 555); + UpdateLastSawHost("SHA-12345", "127.0.0.64", false, 556); + + DBHandle *db; + OpenDB(&db, dbid_lastseen); + + assert_true(DeleteDB(db, "a127.0.0.64")); + assert_false(DeleteDigestFromLastSeen("SHA-12345", NULL, 0, true)); + assert_true(DeleteDigestFromLastSeen("SHA-12345", NULL, 0, false)); + + assert_false(HasKeyDB(db, "qiSHA-12345", strlen("qiSHA-12345") + 1)); + assert_false(HasKeyDB(db, "qoSHA-12345", strlen("qoSHA-12345") + 1)); + assert_false(HasKeyDB(db, "kSHA-12345", strlen("kSHA-12345") + 1)); + assert_false(HasKeyDB(db, "a127.0.0.64", strlen("a127.0.0.64") + 1)); + + CloseDB(db); +} + +static void test_remove_ip(void) +{ + setup(); + + UpdateLastSawHost("SHA-12345", "127.0.0.64", true, 555); + UpdateLastSawHost("SHA-12345", "127.0.0.64", false, 556); + + char digest[CF_BUFSIZE]; + DeleteIpFromLastSeen("127.0.0.64", digest, sizeof(digest)); + + DBHandle *db; + OpenDB(&db, dbid_lastseen); + + assert_int_equal(HasKeyDB(db, "qiSHA-12345", strlen("qiSHA-12345") + 1), false); + assert_int_equal(HasKeyDB(db, "qoSHA-12345", strlen("qoSHA-12345") + 1), false); + assert_int_equal(HasKeyDB(db, "kSHA-12345", strlen("kSHA-12345") + 1), false); + assert_int_equal(HasKeyDB(db, "a127.0.0.64", strlen("a127.0.0.64") + 1), false); + + CloseDB(db); +} + + +/* These tests can't be multi-threaded anyway. */ +static DBHandle *DBH; + +static void begin() +{ + bool b = OpenDB(&DBH, dbid_lastseen); + assert_int_equal(b, true); + + //*state = db; +} +static void end() +{ + CloseDB(DBH); + DBH = NULL; + + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, sizeof(cmd), "rm -rf '%s'/*", GetStateDir()); + system(cmd); +} + + +/** + * ============== DB CONSISTENCY TESTS =============== + * + * @WARNING TO CODER: think twice before you change this test. Changing it to + * accommodate your needs means that you change what "consistent" + * lastseen database is. Lastseen consistency was transcribed as this + * test after a lot of late-hours pondering. + * + * Are you sure you want to continue? (y/n) + */ + + +/** + * ===== CONSISTENT CASES ===== + * + * + * 1. Everything is as expected here. + * + * aIP1 -> KEY1 + * kKEY1 -> IP1 + * aIP2 -> KEY2 + * kKEY2 -> IP2 + * + * consistent_1a: Fill lastseen DB using the lastseen.h API. + * consistent_1b: Same, but fill lastseen DB directly by use of dbm_api.h API. + * + * + * 2. A host connecting from IP1, then connects from IP2. + * + * aIP1 -> KEY1 + * aIP2 -> KEY1 + * kKEY1 -> IP2 + * + * consistent_2a: lastseen.h API. + * consistent_2b: dbm_api.h API. + * + * + * 3. The host at IP1 gets reinstalled and changes key from KEY1 to KEY2. + * + * aIP1 -> KEY2 + * kKEY1 -> aIP1 + * kKEY2 -> aIP1 + * + * consistent_3a: lastseen.h API. + * consistent_3b: dbm_api.h API. + * + * + * 4. Many hosts can use the same key - a mess, but consistent + * (usecase by Bas van der Vlies in scientific clusters). + * + * aIP1 -> KEY1 + * aIP2 -> KEY1 + * aIP3 -> KEY1 + * kKEY1 -> aIP1 + * + * + * 5. Host connects from IP1 with KEY1, then changes address to IP2 keeps the + * same key, later changes key to KEY2. + * + * aIP1 -> KEY1 + * aIP2 -> KEY2 + * kKEY1 -> aIP2 + * kKEY2 -> aIP2 + * + * + * 6. Similar to previous but can't occur unless somebody restores an old key + * on a host. Still the db should be considered consistent. + * + * aIP1 -> KEY1 + * aIP2 -> KEY1 + * kKEY1 -> aIP2 + * kKEY2 -> aIP2 + * + * + * 7. Similarly messed-up but not inconsistent state. + * + * aIP1 -> KEY1 + * aIP2 -> KEY1 + * kKEY1 -> aIP2 + * kKEY2 -> aIP1 + * + * + */ + +/* TODO assert the two DBs from "a" and "b" tests are exactly the same! */ + +static void test_consistent_1a() +{ + LastSaw1(IP1, KEY1, ACC); + LastSaw1(IP2, KEY2, ACC); + + assert_string_equal(DBGetStr(DBH, "a"IP1), KEY1); + assert_string_equal(DBGetStr(DBH, "a"IP2), KEY2); + assert_string_equal(DBGetStr(DBH, "k"KEY1), IP1); + assert_string_equal(DBGetStr(DBH, "k"KEY2), IP2); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_1b() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP1), true); + assert_int_equal(DBPutStr(DBH, "a"IP2, KEY2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY2, IP2), true); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_2a() +{ + LastSaw1(IP1, KEY1, ACC); + LastSaw1(IP2, KEY1, ACC); + + assert_string_equal(DBGetStr(DBH, "a"IP1), KEY1); + assert_string_equal(DBGetStr(DBH, "a"IP2), KEY1); + assert_string_equal(DBGetStr(DBH, "k"KEY1), IP2); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_2b() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "a"IP2, KEY1), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP2), true); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_3a() +{ + LastSaw1(IP1, KEY1, ACC); + LastSaw1(IP1, KEY2, ACC); + + assert_string_equal(DBGetStr(DBH, "a"IP1), KEY2); + assert_string_equal(DBGetStr(DBH, "k"KEY1), IP1); + assert_string_equal(DBGetStr(DBH, "k"KEY2), IP1); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_3b() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP1), true); + assert_int_equal(DBPutStr(DBH, "k"KEY2, IP1), true); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_4a() +{ + LastSaw1(IP1, KEY1, ACC); + LastSaw1(IP2, KEY1, ACC); + LastSaw1(IP3, KEY1, ACC); + + assert_string_equal(DBGetStr(DBH, "a"IP1), KEY1); + assert_string_equal(DBGetStr(DBH, "a"IP2), KEY1); + assert_string_equal(DBGetStr(DBH, "a"IP3), KEY1); + assert_string_equal(DBGetStr(DBH, "k"KEY1), IP3); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_4b() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "a"IP2, KEY1), true); + assert_int_equal(DBPutStr(DBH, "a"IP3, KEY1), true); + /* Just a bit different than what the lastseen API created in the 4a case, + * but still consistent. */ + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP1), true); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_5a() +{ + LastSaw1(IP1, KEY1, ACC); + LastSaw1(IP2, KEY1, ACC); + LastSaw1(IP2, KEY2, ACC); + + assert_string_equal(DBGetStr(DBH, "a"IP1), KEY1); + assert_string_equal(DBGetStr(DBH, "a"IP2), KEY2); + assert_string_equal(DBGetStr(DBH, "k"KEY1), IP2); + assert_string_equal(DBGetStr(DBH, "k"KEY2), IP2); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_5b() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "a"IP2, KEY2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY2, IP2), true); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_6a() +{ + LastSaw1(IP1, KEY1, ACC); /* initial bootstrap */ + LastSaw1(IP2, KEY1, ACC); /* move to new IP */ + LastSaw1(IP2, KEY2, ACC); /* issue new key */ + LastSaw1(IP2, KEY1, ACC); /* restore old key */ + + assert_string_equal(DBGetStr(DBH, "a"IP1), KEY1); + assert_string_equal(DBGetStr(DBH, "a"IP2), KEY1); + assert_string_equal(DBGetStr(DBH, "k"KEY1), IP2); + assert_string_equal(DBGetStr(DBH, "k"KEY2), IP2); + + assert_int_equal(IsLastSeenCoherent(), true); +} +static void test_consistent_6b() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "a"IP2, KEY1), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY2, IP2), true); + + assert_int_equal(IsLastSeenCoherent(), true); +} +/** + * @NOTE I haven't been able to get the consistent_7 case with regular + * dbm_api.h calls. Maybe it should be considered inconsistent state of + * the db? + */ +/* +static void test_consistent_7a() +{ + LastSaw1(IP2, KEY1, ACC); + LastSaw1(IP1, KEY2, ACC); + LastSaw1(IP2, KEY1, ACC); + LastSaw1(IP1, KEY1, ACC); + + assert_string_equal(DBGetStr(DBH, "a"IP1), KEY1); + assert_string_equal(DBGetStr(DBH, "a"IP2), KEY1); + assert_string_equal(DBGetStr(DBH, "k"KEY1), IP2); + assert_string_equal(DBGetStr(DBH, "k"KEY2), IP1); + + assert_int_equal(IsLastSeenCoherent(), true); +} +*/ +static void test_consistent_7b() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "a"IP2, KEY1), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY2, IP1), true); + + assert_int_equal(IsLastSeenCoherent(), true); +} + + +/** + * ===== INCONSISTENT CASES ===== + * Should never happen if our software is bug-free! + * + * + * 1. KEY2 appears as a value but not "kKEY2" as a key. + * + * aIP1 -> KEY2 + * + * + * 2. Same case, a bit more complex example. + * + * aIP1 -> KEY1 + * aIP2 -> KEY2 + * aKEY1 -> IP1 + * + * + * 3. IP2 appears as a value but not "aIP2" as a key. + * + * kKEY1 -> IP2 + * + * + * 4. Same case, a bit more complex example. + * + * aIP1 -> KEY1 + * kKEY1 -> aIP1 + * kKEY2 -> aIP2 + * + * + * 5. The two previous cases at the same time! + * + * kKEY1 -> IP2 + * aIP1 -> KEY2 + * + * + * 6. Same, a bit more complex example. + * + * aIP1 -> KEY1 + * aIP3 -> KEY2 + * kKEY1 -> IP2 + * kKEY3 -> IP2 + * + */ + +static void test_inconsistent_1() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY2), true); + + assert_int_equal(IsLastSeenCoherent(), false); +} +static void test_inconsistent_2() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "a"IP2, KEY2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP1), true); + + assert_int_equal(IsLastSeenCoherent(), false); +} +static void test_inconsistent_3() +{ + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP2), true); + + assert_int_equal(IsLastSeenCoherent(), false); +} +static void test_inconsistent_4() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP1), true); + assert_int_equal(DBPutStr(DBH, "k"KEY2, IP2), true); + + assert_int_equal(IsLastSeenCoherent(), false); +} +static void test_inconsistent_5() +{ + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP2), true); + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY2), true); + + assert_int_equal(IsLastSeenCoherent(), false); +} +static void test_inconsistent_6() +{ + assert_int_equal(DBPutStr(DBH, "a"IP1, KEY1), true); + assert_int_equal(DBPutStr(DBH, "a"IP3, KEY2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY1, IP2), true); + assert_int_equal(DBPutStr(DBH, "k"KEY3, IP2), true); + + assert_int_equal(IsLastSeenCoherent(), false); +} + + + +/* TODO run lastseen consistency checks after every cf-serverd *acceptance* + * test, deployment test, and stress test! */ + + + +int main() +{ + tests_setup(); + + const UnitTest tests[] = + { + unit_test(test_newentry), + unit_test(test_update), + unit_test(test_HostkeyToAddress), + unit_test(test_reverse_missing), + unit_test(test_reverse_conflict), + unit_test(test_reverse_missing_forward), + unit_test(test_remove), + unit_test(test_remove_no_a_entry), + unit_test(test_remove_ip), + + unit_test_setup_teardown(test_consistent_1a, begin, end), + unit_test_setup_teardown(test_consistent_1b, begin, end), + unit_test_setup_teardown(test_consistent_2a, begin, end), + unit_test_setup_teardown(test_consistent_2b, begin, end), + unit_test_setup_teardown(test_consistent_3a, begin, end), + unit_test_setup_teardown(test_consistent_3b, begin, end), + unit_test_setup_teardown(test_consistent_4a, begin, end), + unit_test_setup_teardown(test_consistent_4b, begin, end), + unit_test_setup_teardown(test_consistent_5a, begin, end), + unit_test_setup_teardown(test_consistent_5b, begin, end), + unit_test_setup_teardown(test_consistent_6a, begin, end), + unit_test_setup_teardown(test_consistent_6b, begin, end), +// unit_test_setup_teardown(test_consistent_7a, begin, end), + unit_test_setup_teardown(test_consistent_7b, begin, end), + unit_test_setup_teardown(test_inconsistent_1, begin, end), + unit_test_setup_teardown(test_inconsistent_2, begin, end), + unit_test_setup_teardown(test_inconsistent_3, begin, end), + unit_test_setup_teardown(test_inconsistent_4, begin, end), + unit_test_setup_teardown(test_inconsistent_5, begin, end), + unit_test_setup_teardown(test_inconsistent_6, begin, end), + }; + + PRINT_TEST_BANNER(); + int ret = run_tests(tests); + + tests_teardown(); + + return ret; +} + +/* STUBS */ + +void FatalError(ARG_UNUSED char *s, ...) +{ + fail(); + exit(42); +} + +HashMethod CF_DEFAULT_DIGEST; +pthread_mutex_t *cft_output; +char VIPADDRESS[CF_MAX_IP_LEN]; +RSA *PUBKEY; +bool MINUSF; + +char *HashPrintSafe(ARG_UNUSED char *dst, ARG_UNUSED size_t dst_size, + ARG_UNUSED const unsigned char *digest, + ARG_UNUSED HashMethod type, ARG_UNUSED bool use_prefix) +{ + fail(); +} + +void HashPubKey(ARG_UNUSED const RSA *key, + ARG_UNUSED unsigned char digest[EVP_MAX_MD_SIZE + 1], + ARG_UNUSED HashMethod type) +{ + fail(); +} diff --git a/tests/unit/linux_process_test.c b/tests/unit/linux_process_test.c new file mode 100644 index 0000000000..586b163e5b --- /dev/null +++ b/tests/unit/linux_process_test.c @@ -0,0 +1,168 @@ +#include + +#include +#include +#include + +/* Stubs for /proc//stat. */ + +static const char *filecontents[2] = { + "1 (i()))))))-:!#!#@)) S 1 3927 3927 1025 3927 4202752 359 0 2 0 0 0 0 0 20 0 1 0 65535 19234816 226 18446744073709551615 1 1 0 0 0 0 0 6 0 18446744073709551615 0 0 17 2 0 0 40 0 0", + "3929 (getty) T 1 3929 3929 1027 3929 4202752 359 0 1 0 0 0 0 0 20 0 1 0 100000 19234816 225 18446744073709551615 1 1 0 0 0 0 0 6 0 18446744073709551615 0 0 17 0 0 0 42 0 0", +}; + +static int filepos[2]; + +int open(const char *filename, ARG_UNUSED int flags, ...) +{ + if (!strcmp(filename, "/proc/1/stat")) + { + filepos[0] = 0; + return 0; + } + else if (!strcmp(filename, "/proc/2/stat")) + { + filepos[1] = 0; + static int got_intr = false; + if (!got_intr) + { + got_intr = true; + errno = EINTR; + return -1; + } + + return 1; + } + else if (!strcmp(filename, "/proc/666/stat")) + { + errno = EACCES; + return -1; + } + else + { + errno = ENOENT; + return -1; + } +} + +ssize_t read(int fd, void *buffer, ARG_UNUSED size_t buf_size) +{ + if (fd == 0) + { + if (filepos[0] < strlen(filecontents[0])) + { + memcpy(buffer, filecontents[0], strlen(filecontents[0])); + filepos[0] = strlen(filecontents[0]); + return strlen(filecontents[0]); + } + else + { + return 0; + } + } + + if (fd == 1) + { + static bool got_eintr = false; + + if (!got_eintr) + { + got_eintr = true; + errno = EINTR; + return -1; + } + else + { + got_eintr = false; + } + + if (filepos[1] < strlen(filecontents[1])) + { + memcpy(buffer, filecontents[1] + filepos[1], 1); + filepos[1]++; + return 1; + } + else + { + return 0; + } + } + + errno = EIO; + return -1; +} + +int close(ARG_UNUSED int fd) +{ + return 0; +} + +static void test_get_start_time_process1(void) +{ + time_t t = GetProcessStartTime(1); + assert_int_equal(t, 65535 / sysconf(_SC_CLK_TCK)); +} + + +static void test_get_start_time_process2(void) +{ + time_t t2 = GetProcessStartTime(2); + assert_int_equal(t2, 100000 / sysconf(_SC_CLK_TCK)); +} + +static void test_get_start_time_process3(void) +{ + time_t t3 = GetProcessStartTime(3); + assert_int_equal(t3, PROCESS_START_TIME_UNKNOWN); +} + +static void test_get_start_time_process666(void) +{ + time_t t4 = GetProcessStartTime(666); + assert_int_equal(t4, PROCESS_START_TIME_UNKNOWN); +} + + +static void test_get_state_process1(void) +{ + ProcessState s = GetProcessState(1); + assert_int_equal(s, PROCESS_STATE_RUNNING); +} + +static void test_get_state_process2(void) +{ + ProcessState s = GetProcessState(2); + assert_int_equal(s, PROCESS_STATE_STOPPED); +} + +static void test_get_state_process3(void) +{ + ProcessState s = GetProcessState(3); + assert_int_equal(s, PROCESS_STATE_DOES_NOT_EXIST); +} + +static void test_get_state_process666(void) +{ + ProcessState s = GetProcessState(666); + assert_int_equal(s, PROCESS_STATE_DOES_NOT_EXIST); +} + + +int main() +{ + PRINT_TEST_BANNER(); + + const UnitTest tests[] = + { + unit_test(test_get_start_time_process1), + unit_test(test_get_start_time_process2), + unit_test(test_get_start_time_process3), + unit_test(test_get_start_time_process666), + unit_test(test_get_state_process1), + unit_test(test_get_state_process2), + unit_test(test_get_state_process3), + unit_test(test_get_state_process666), + }; + + return run_tests(tests); +} diff --git a/tests/unit/logging_test.c b/tests/unit/logging_test.c new file mode 100644 index 0000000000..cc4468edd0 --- /dev/null +++ b/tests/unit/logging_test.c @@ -0,0 +1,115 @@ +#include + +#include +#include + +#include +#include + +// This test uses syslog_client.c directly, without libpromises, +// this is necessary so we don't get "undefined symbol" errors: +char VFQNAME[CF_MAXVARSIZE]; + +static struct sockaddr *got_address; + +#if SENDTO_RETURNS_SSIZE_T > 0 +ssize_t sendto(ARG_UNUSED int sockfd, ARG_UNUSED const void *buf, + size_t len, + ARG_UNUSED int flags, + const struct sockaddr *dest_addr, + ARG_UNUSED socklen_t addrlen) +{ + free(got_address); // RemoteSysLog can call this multiple times + got_address = xmemdup(dest_addr, sizeof(struct sockaddr_in)); + return len; +} +#else +/* + * We might be naives by thinking that size_t, socklen_t and such are the same size as int. + * Given that we are not using them here, we can live with that assumption. + */ +int sendto(ARG_UNUSED int sockfd, ARG_UNUSED const void *buf, + int len, + ARG_UNUSED int flags, + const void *dest_addr, + ARG_UNUSED int addrlen) +{ + free(got_address); // RemoteSysLog can call this multiple times + got_address = xmemdup(dest_addr, sizeof(struct sockaddr_in)); + return len; +} +#endif // SENDTO_RETURNS_SSIZE_T > 0 + +static void test_set_port(void) +{ + SetSyslogPort(5678); + RemoteSysLog(LOG_EMERG, "Test string"); + + if (got_address->sa_family == AF_INET) + { + assert_int_equal(ntohs(((struct sockaddr_in *) got_address)->sin_port), 5678); + } + else if (got_address->sa_family == AF_INET6) + { + assert_int_equal(ntohs(((struct sockaddr_in6 *) got_address)->sin6_port), 5678); + } + + free(got_address); + got_address = NULL; // Safe to free(NULL) in another test +} + +static void test_set_host(void) +{ + SetSyslogHost("127.0.0.55"); + RemoteSysLog(LOG_EMERG, "Test string"); + + assert_int_equal(got_address->sa_family, AF_INET); + + assert_int_equal(ntohl(((struct sockaddr_in *) got_address)->sin_addr.s_addr), 0x7f000037); + free(got_address); + got_address = NULL; // Safe to free(NULL) in another test +} + +#define check_level(str, lvl) \ +{\ + assert_int_equal(LogLevelFromString(str), lvl);\ + assert_true(StringEqual_IgnoreCase(str, LogLevelToString(lvl)));\ +} + +static void test_log_level(void) +{ + check_level("CRITICAL", LOG_LEVEL_CRIT); + check_level("Error", LOG_LEVEL_ERR); + check_level("warning", LOG_LEVEL_WARNING); + check_level("notice", LOG_LEVEL_NOTICE); + check_level("info", LOG_LEVEL_INFO); + check_level("verbose", LOG_LEVEL_VERBOSE); + check_level("debug", LOG_LEVEL_DEBUG); + + // LogLevelFromString should accept half typed strings: + assert_int_equal(LogLevelFromString("CRIT"), LOG_LEVEL_CRIT); + assert_int_equal(LogLevelFromString("ERR"), LOG_LEVEL_ERR); + assert_int_equal(LogLevelFromString("warn"), LOG_LEVEL_WARNING); + assert_int_equal(LogLevelFromString("I"), LOG_LEVEL_INFO); + assert_int_equal(LogLevelFromString("i"), LOG_LEVEL_INFO); + assert_int_equal(LogLevelFromString("information"), LOG_LEVEL_INFO); + assert_int_equal(LogLevelFromString("v"), LOG_LEVEL_VERBOSE); + + //LogLevelFromString should return NOTHING in case of error: + assert_int_equal(LogLevelFromString(""), LOG_LEVEL_NOTHING); + assert_int_equal(LogLevelFromString("IX"), LOG_LEVEL_NOTHING); + assert_int_equal(LogLevelFromString("Infotmation"), LOG_LEVEL_NOTHING); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_set_port), + unit_test(test_set_host), + unit_test(test_log_level), + }; + + return run_tests(tests); +} diff --git a/tests/unit/matching_test.c b/tests/unit/matching_test.c new file mode 100644 index 0000000000..ad6138dff6 --- /dev/null +++ b/tests/unit/matching_test.c @@ -0,0 +1,47 @@ +#include + +#include +#include +#include + +void test_has_regex_meta_chars(void) +{ + assert_false(HasRegexMetaChars("string")); + assert_false(HasRegexMetaChars("ala ma kota a kot ma ale")); + + assert_true(HasRegexMetaChars("^string")); + assert_true(HasRegexMetaChars("^(string)")); + assert_true(HasRegexMetaChars("string$")); + assert_true(HasRegexMetaChars("(string)$")); + assert_true(HasRegexMetaChars("string.*")); + assert_true(HasRegexMetaChars("string*")); + assert_true(HasRegexMetaChars("string.")); + assert_true(HasRegexMetaChars("a?")); + assert_true(HasRegexMetaChars("a*")); + assert_true(HasRegexMetaChars("a+")); + assert_true(HasRegexMetaChars("a{3}")); + assert_true(HasRegexMetaChars("a{3,6}")); + assert_true(HasRegexMetaChars("(a|b)")); + assert_true(HasRegexMetaChars("a(bcd)*")); + assert_true(HasRegexMetaChars("c.+t")); + assert_true(HasRegexMetaChars("a(bcd)?e")); + assert_true(HasRegexMetaChars("a(bcd){2,3}e")); + assert_true(HasRegexMetaChars("(yes)|(no)")); + assert_true(HasRegexMetaChars("pro(b|n|r|l)ate")); + assert_true(HasRegexMetaChars("c[aeiou]t")); + assert_true(HasRegexMetaChars("^[a-zA-Z0-9_]+$")); + assert_true(HasRegexMetaChars("\\d{5}")); + assert_true(HasRegexMetaChars("\\d")); +} + +int main() +{ + const UnitTest tests[] = + { + unit_test(test_has_regex_meta_chars), + }; + + PRINT_TEST_BANNER(); + return run_tests(tests); +} + diff --git a/tests/unit/mon_cpu_test.c b/tests/unit/mon_cpu_test.c new file mode 100644 index 0000000000..d8eafadd74 --- /dev/null +++ b/tests/unit/mon_cpu_test.c @@ -0,0 +1,86 @@ +#include "test.h" + +#include "generic_agent.h" +#include "mon.h" + +static double GetCpuStat() +{ + double q, dq = -1.0; + long total_time = 1; + FILE *fp; + long userticks = 0, niceticks = 0, systemticks = 0, + idle = 0, iowait = 0, irq = 0, softirq = 0; + char cpuname[CF_MAXVARSIZE], buf[CF_BUFSIZE]; + + + if ((fp = fopen("/proc/stat", "r")) == NULL) + { + return -1.0; + } + + while (!feof(fp)) + { + if (fgets(buf, sizeof(buf), fp) == NULL) + { + break; + } + + if (sscanf(buf, "%s%ld%ld%ld%ld%ld%ld%ld", cpuname, &userticks, + &niceticks, &systemticks, &idle, &iowait, &irq, + &softirq) != 8) + { + continue; + } + + total_time = (userticks + niceticks + systemticks + idle); + + q = 100.0 * (double) (total_time - idle); + + if (strcmp(cpuname, "cpu") == 0) + { + dq = q / (double) total_time; + if ((dq > 100) || (dq < 0)) + { + dq = 50; + } + } + } + + fclose(fp); + return dq; +} + +void test_cpu_monitor(void) +{ + double cf_this[100]; + double dq1 = GetCpuStat(); + if (dq1 == -1.0) + { + assert_true(1); + return; + } + MonCPUGatherData(cf_this); + double dq2 = GetCpuStat(); + if (dq2 == -1.0) + { + assert_true(1); + return; + } + double min = (double) (dq2=lower && cf_this[ob_cpuall]<=upper); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_cpu_monitor), + }; + + return run_tests(tests); +} diff --git a/tests/unit/mon_load_test.c b/tests/unit/mon_load_test.c new file mode 100644 index 0000000000..c25789f9ac --- /dev/null +++ b/tests/unit/mon_load_test.c @@ -0,0 +1,38 @@ +#include "test.h" + +#include "generic_agent.h" +#include "mon.h" +#include /* getloadavg() */ + +void test_load_monitor(void) +{ + double cf_this[100]; + MonLoadGatherData(cf_this); + double load1[2] = {0,0}; + double load2[2] = {0,0}; + int n1 = getloadavg(load1, 1); + int n2 = getloadavg(load2, 1); + if (n1==-1 || n2==-1) + { + assert_true(1); + return; + } + + double min = (double) (load2[0]=lower && cf_this[ob_loadavg]<=upper); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_load_monitor), + }; + + return run_tests(tests); +} diff --git a/tests/unit/mon_processes_test.c b/tests/unit/mon_processes_test.c new file mode 100644 index 0000000000..50b59fc620 --- /dev/null +++ b/tests/unit/mon_processes_test.c @@ -0,0 +1,159 @@ +#include "test.h" +#include "systype.h" +#include "generic_agent.h" +#include "item_lib.h" +#include "mon.h" +#include /* LogSetGlobalLevel */ +#include /* xsnprintf */ +#include + +char CFWORKDIR[CF_BUFSIZE]; + +static void tests_setup(void) +{ + xsnprintf(CFWORKDIR, CF_BUFSIZE, "/tmp/mon_processes_test.XXXXXX"); + mkdtemp(CFWORKDIR); + + char buf[CF_BUFSIZE]; + xsnprintf(buf, CF_BUFSIZE, "%s", GetStateDir()); + mkdir(buf, 0755); +} + +static void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +static bool GetSysUsers( int *userListSz, int *numRootProcs, int *numOtherProcs) +{ + FILE *fp; + char user[CF_BUFSIZE]; + char vbuff[CF_BUFSIZE]; + char cbuff[CF_BUFSIZE]; + + /* + * The best would be to ask only "user" field from ps, but we are asking + * for "user,pid". The reason is that we try to mimic cf-monitord's + * behaviour, else a different number of users might be detected by the + * test, as printing "user,pid" truncates the user column. TODO fix the + * ps command to use only "-o user" in both mon_processes.c and this test. + */ +#if defined(__sun) + xsnprintf(cbuff, CF_BUFSIZE, "/bin/ps -eo user,pid > %s/users.txt", CFWORKDIR); +#elif defined(_AIX) + xsnprintf(cbuff, CF_BUFSIZE, "/bin/ps -N -eo user,pid > %s/users.txt", CFWORKDIR); +#elif defined(__hpux) + xsnprintf(cbuff, CF_BUFSIZE, "UNIX95=1 /bin/ps -eo user,pid > %s/users.txt", CFWORKDIR); + /* SKIP on HP-UX since cf-monitord doesn't count processes correctly! */ + return false; +#else + xsnprintf(cbuff, CF_BUFSIZE, "ps -eo user:30,pid > %s/users.txt", CFWORKDIR); +#endif + + Item *userList = NULL; + system(cbuff); + xsnprintf(cbuff, CF_BUFSIZE, "%s/users.txt", CFWORKDIR); + if ((fp = fopen(cbuff, "r")) == NULL) + { + return false; + } + while (fgets(vbuff, CF_BUFSIZE, fp) != NULL) + { + int ret = sscanf(vbuff, " %s ", user); + + if (ret != 1 || + strcmp(user, "") == 0 || + strcmp(user, "USER") == 0 || + isdigit(user[0])) + { + continue; + } + + if (!IsItemIn(userList, user)) + { + PrependItem(&userList, user, NULL); + (*userListSz)++; + } + + if (strcmp(user, "root") == 0) + { + (*numRootProcs)++; + } + else + { + (*numOtherProcs)++; + } + } + fclose(fp); + + if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) + { + char *s = ItemList2CSV(userList); + Log(LOG_LEVEL_DEBUG, "Users in the process table detected from the test: (%s)", s); + free(s); + } + DeleteItemList(userList); + return true; +} + +void test_processes_monitor(void) +{ + double cf_this[100] = { 0.0 }; + MonProcessesGatherData(cf_this); + MonProcessesGatherData(cf_this); + MonProcessesGatherData(cf_this); + + int usr, rusr, ousr; + usr = rusr = ousr = 0; + + bool res = GetSysUsers(&usr, &rusr, &ousr); + if (!res) + { + Log(LOG_LEVEL_NOTICE, "TEST SKIPPED!"); + return; + } + + usr = 3*usr; + rusr = 3*rusr; + ousr = 3*ousr; + + Log(LOG_LEVEL_NOTICE, "Counted %d/3 different users on the process table," + " while CFEngine counted %f/3", usr, cf_this[ob_users]); + Log(LOG_LEVEL_NOTICE, "This is a non-deterministic test," + " the two numbers should be *about* the same since the 'ps'" + " commands run very close to each other"); + + int upper = (int) ((double) usr*1.20); + int lower = (int) ((double) usr*0.80); + assert_in_range((long long) cf_this[ob_users], lower, upper); +} + +int main() +{ + LogSetGlobalLevel(LOG_LEVEL_DEBUG); + strcpy(CFWORKDIR, "data"); + +#if defined(__sun) + VSYSTEMHARDCLASS = PLATFORM_CONTEXT_SOLARIS; + VPSHARDCLASS = PLATFORM_CONTEXT_SOLARIS; +#elif defined(_AIX) + VSYSTEMHARDCLASS = PLATFORM_CONTEXT_AIX; + VPSHARDCLASS = PLATFORM_CONTEXT_AIX; +#elif defined(__linux__) + VSYSTEMHARDCLASS = PLATFORM_CONTEXT_LINUX; + VPSHARDCLASS = PLATFORM_CONTEXT_LINUX; +#endif + + PRINT_TEST_BANNER(); + tests_setup(); + const UnitTest tests[] = + { + unit_test(test_processes_monitor), + }; + + int ret = run_tests(tests); + tests_teardown(); + return ret; +} diff --git a/tests/unit/mustache_test.c b/tests/unit/mustache_test.c new file mode 100644 index 0000000000..6e6eec717f --- /dev/null +++ b/tests/unit/mustache_test.c @@ -0,0 +1,104 @@ +#include + +#include +#include +#include /* xsnprintf */ + + +size_t TestSpecFile(const char *testfile) +{ + char path[PATH_MAX]; + xsnprintf(path, sizeof(path), "%s/mustache_%s.json", + TESTDATADIR, testfile); + + Writer *w = FileRead(path, SIZE_MAX, NULL); + if (w == NULL) + { + Log(LOG_LEVEL_ERR, "Error reading JSON input file '%s'", path); + fail(); + } + JsonElement *spec = NULL; + const char *data = StringWriterData(w); + if (JsonParse(&data, &spec) != JSON_PARSE_OK) + { + Log(LOG_LEVEL_ERR, "Error parsing JSON input file '%s'", path); + fail(); + } + WriterClose(w); + + JsonElement *tests = JsonObjectGetAsArray(spec, "tests"); + + size_t num_failures = 0; + for (size_t i = 0; i < JsonLength(tests); i++) + { + JsonElement *test_obj = JsonAt(tests, i); + + fprintf(stdout, "Testing %s:%s ...", testfile, JsonObjectGetAsString(test_obj, "name")); + + Buffer *out = BufferNew(); + + const char *templ = JsonObjectGetAsString(test_obj, "template"); + const char *expected = JsonObjectGetAsString(test_obj, "expected"); + const JsonElement *data = JsonObjectGet(test_obj, "data"); + + if (!MustacheRender(out, templ, data) || strcmp(expected, BufferData(out)) != 0) + { + num_failures++; + fprintf(stdout, "FAIL \n%s\n != \n%s\n", expected, BufferData(out)); + } + else + { + fprintf(stdout, "OK\n"); + } + + BufferDestroy(out); + } + + JsonDestroy(spec); + + return num_failures; +} + +static void test_spec(void) +{ + size_t num_failures = 0; + + size_t comments_fail = TestSpecFile("comments"); + size_t interpolation_fail = TestSpecFile("interpolation"); + size_t sections_fail = TestSpecFile("sections"); + size_t delimiters_fail = TestSpecFile("delimiters"); + size_t inverted_fail = TestSpecFile("inverted"); + size_t extra_fail = TestSpecFile("extra"); + + num_failures = comments_fail + interpolation_fail + sections_fail + delimiters_fail + inverted_fail + extra_fail; + if (num_failures > 0) + { + fprintf(stdout, "Failures in comments: %llu\n", + (unsigned long long)comments_fail); + fprintf(stdout, "Failures in interpolation: %llu\n", + (unsigned long long)interpolation_fail); + fprintf(stdout, "Failures in sections: %llu\n", + (unsigned long long)sections_fail); + fprintf(stdout, "Failures in delimiters: %llu\n", + (unsigned long long)delimiters_fail); + fprintf(stdout, "Failures in inverted: %llu\n", + (unsigned long long)inverted_fail); + fprintf(stdout, "Failures in extra: %llu\n", + (unsigned long long)inverted_fail); + fprintf(stdout, "TOTAL FAILURES: %llu\n", + (unsigned long long)num_failures); + + fail(); + } +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_spec), + }; + + return run_tests(tests); +} diff --git a/tests/unit/new_packages_promise_test.c b/tests/unit/new_packages_promise_test.c new file mode 100644 index 0000000000..926597471e --- /dev/null +++ b/tests/unit/new_packages_promise_test.c @@ -0,0 +1,89 @@ +#include + +#include +#include + +static inline +PackageModuleBody *make_mock_package_module(const char *name, int updates_ifel, int installed_ifel, Rlist *options) +{ + PackageModuleBody *pm = xcalloc(1, sizeof(PackageModuleBody)); + pm->name = SafeStringDuplicate(name); + pm->installed_ifelapsed = installed_ifel; + pm->updates_ifelapsed = updates_ifel; + pm->options = RlistCopy(options); + return pm; +} + +static void test_add_module_to_context() +{ + EvalContext *ctx = EvalContextNew(); + + PackageModuleBody *pm = make_mock_package_module("apt_get", 120, 240, NULL); + AddPackageModuleToContext(ctx, pm); + + PackageModuleBody *pm2 = make_mock_package_module("yum", 220, 440, NULL); + AddPackageModuleToContext(ctx, pm2); + + PackagePromiseContext *pp_ctx = GetPackagePromiseContext(ctx); + + assert_true(pp_ctx != NULL); + assert_int_equal(2, SeqLength(pp_ctx->package_modules_bodies)); + + PackageModuleBody *yum = GetPackageModuleFromContext(ctx, "yum"); + assert_true(yum != NULL); + assert_int_equal(220, yum->updates_ifelapsed); + assert_int_equal(440, yum->installed_ifelapsed); + + /* make sure that adding body with the same name will not make set larger */ + PackageModuleBody *pm3 = make_mock_package_module("yum", 330, 550, NULL); + AddPackageModuleToContext(ctx, pm3); + + assert_int_equal(2, SeqLength(pp_ctx->package_modules_bodies)); + + /* check if parameters are updated */ + yum = GetPackageModuleFromContext(ctx, "yum"); + assert_int_equal(330, yum->updates_ifelapsed); + assert_int_equal(550, yum->installed_ifelapsed); + + EvalContextDestroy(ctx); +} + +static void test_default_package_module_settings() +{ + EvalContext *ctx = EvalContextNew(); + + PackageModuleBody *pm = make_mock_package_module("apt_get", 120, 240, NULL); + AddPackageModuleToContext(ctx, pm); + + PackageModuleBody *pm2 = make_mock_package_module("yum", 220, 440, NULL); + AddPackageModuleToContext(ctx, pm2); + + PackageModuleBody *pm3 = make_mock_package_module("yum_2", 220, 440, NULL); + AddPackageModuleToContext(ctx, pm3); + + AddDefaultPackageModuleToContext(ctx, "apt_get"); + PackageModuleBody *def_pm = GetDefaultPackageModuleFromContext(ctx); + assert_string_equal("apt_get", def_pm->name); + + AddDefaultPackageModuleToContext(ctx, "yum"); + def_pm = GetDefaultPackageModuleFromContext(ctx); + assert_string_equal("yum", def_pm->name); + + EvalContextDestroy(ctx); +} + + +int main() +{ + PRINT_TEST_BANNER(); + + const UnitTest tests[] = + { + unit_test(test_default_package_module_settings), + unit_test(test_add_module_to_context), + }; + + int ret = run_tests(tests); + + return ret; +} diff --git a/tests/unit/nfs_test.c b/tests/unit/nfs_test.c new file mode 100644 index 0000000000..8e5d8ab78a --- /dev/null +++ b/tests/unit/nfs_test.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +static void test_MatchFSInFstab(void) +{ + AppendItem(&FSTABLIST, "fileserver1:/vol/vol10 /mnt/fileserver1/vol10 nfs rw,intr,tcp,fg,rdirplus,noatime,_netdev", NULL); + AppendItem(&FSTABLIST, "fileserver1:/vol/vol11 /mnt/fileserver1/vol11 nfs rw,intr,tcp,fg,rdirplus,noatime,_netdev", NULL); + AppendItem(&FSTABLIST, "#fileserver1:/vol/vol12 /mnt/fileserver1/vol12 nfs rw,intr,tcp,fg,rdirplus,noatime,_netdev", NULL); + AppendItem(&FSTABLIST, "UUID=4a147232-42f7-4e56-aa9e-744b09bce719 / ext4 errors=remount-ro 0 1", NULL); + AppendItem(&FSTABLIST, "UUID=b2cf5462-a10f-4d7d-b356-ecec5aea2103 none swap sw 0 0", NULL); + AppendItem(&FSTABLIST, "none /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0", NULL); + AppendItem(&FSTABLIST, "fileserver2:/vol/vol10 /mnt/fileserver2/vol10 nfs rw,intr,tcp,fg,noatime", NULL); + AppendItem(&FSTABLIST, "fileserver2:/vol/vol11 /mnt/fileserver2/vol11 nfs rw,intr,tcp,fg,noatime", NULL); + AppendItem(&FSTABLIST, "#fileserver2:/vol/vol12 /mnt/fileserver2/vol12 nfs rw,intr,tcp,fg,noatime", NULL); + AppendItem(&FSTABLIST, "fileserver3:/vol/vol10 /mnt/fileserver3/vol10 nfs rw,intr,tcp,fg #,noatime", NULL); + AppendItem(&FSTABLIST, "fileserver3:/vol/vol11 /mnt/fileserver3/vol11 nfs rw,intr,tcp,fg ,noatime # do we want noatime?", NULL); + + assert_true(MatchFSInFstab("/mnt/fileserver1/vol10")); + assert_true(MatchFSInFstab("/mnt/fileserver1/vol11")); + assert_false(MatchFSInFstab("/mnt/fileserver1/vol1")); + + assert_true(MatchFSInFstab("/mnt/fileserver2/vol10")); + assert_true(MatchFSInFstab("/mnt/fileserver2/vol11")); + assert_false(MatchFSInFstab("/mnt/fileserver2/vol1")); + + assert_false(MatchFSInFstab("/mnt/fileserver1/vol12")); + assert_false(MatchFSInFstab("/mnt/fileserver2/vol12")); + + assert_true(MatchFSInFstab("/mnt/fileserver3/vol10")); + assert_true(MatchFSInFstab("/mnt/fileserver3/vol11")); + assert_false(MatchFSInFstab("/mnt/fileserver3/vol1")); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = { + unit_test(test_MatchFSInFstab), + }; + + return run_tests(tests); +} + + diff --git a/tests/unit/package_versions_compare_test.c b/tests/unit/package_versions_compare_test.c new file mode 100644 index 0000000000..8e8a366c3f --- /dev/null +++ b/tests/unit/package_versions_compare_test.c @@ -0,0 +1,324 @@ +#include + +#include +#include +#include + +void test_different_name(void) +{ + EvalContext *ctx = EvalContextNew(); + PromiseResult result; + + PackageItem pi = { + .name = "pkgone", + .version = "1", + .arch = "arch" + }; + Attributes attr = { + .packages = { + .package_select = PACKAGE_VERSION_COMPARATOR_EQ + } + }; + + assert_int_equal(ComparePackages(ctx, "pkgtwo", "1", "arch", &pi, &attr, NULL, "test", &result), VERCMP_NO_MATCH); + + EvalContextDestroy(ctx); +} + +void test_wildcard_arch(void) +{ + EvalContext *ctx = EvalContextNew(); + PromiseResult result; + + PackageItem pi = { + .name = "foobar", + .version = "1", + .arch = "arch" + }; + Attributes attr = { + .packages = { + .package_select = PACKAGE_VERSION_COMPARATOR_EQ + } + }; + + assert_int_equal(ComparePackages(ctx, "foobar", "1", "*", &pi, &attr, NULL, "test", &result), VERCMP_MATCH); + + EvalContextDestroy(ctx); +} + +void test_non_matching_arch(void) +{ + EvalContext *ctx = EvalContextNew(); + PromiseResult result; + + PackageItem pi = { + .name = "foobar", + .version = "1", + .arch = "s390x" + }; + Attributes attr = { + .packages = { + .package_select = PACKAGE_VERSION_COMPARATOR_EQ + } + }; + + assert_int_equal(ComparePackages(ctx, "foobar", "1", "s390", &pi, &attr, NULL, "test", &result), VERCMP_NO_MATCH); + + EvalContextDestroy(ctx); +} + +VersionCmpResult DoCompare(const char *lhs, const char *rhs, PackageVersionComparator cmp) +{ + EvalContext *ctx = EvalContextNew(); + PromiseResult result; + + PackageItem pi = { + .name = "foobar", + .version = (char*)lhs, + .arch = "somearch" + }; + Attributes a = { + .packages = { + .package_select = cmp, + } + }; + + VersionCmpResult cmp_result = ComparePackages(ctx, "foobar", rhs, "somearch", &pi, &a, NULL, "test", &result); + + EvalContextDestroy(ctx); + + return cmp_result; +} + +void test_wildcard_version(void) +{ + assert_int_equal(DoCompare("1.0-1", "*", PACKAGE_VERSION_COMPARATOR_EQ), VERCMP_MATCH); +} + +void test_eq(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0-1", PACKAGE_VERSION_COMPARATOR_EQ), VERCMP_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-1", PACKAGE_VERSION_COMPARATOR_NONE), VERCMP_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-2", PACKAGE_VERSION_COMPARATOR_EQ), VERCMP_NO_MATCH); +} + +void test_ne(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0-1", PACKAGE_VERSION_COMPARATOR_NEQ), VERCMP_NO_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-2", PACKAGE_VERSION_COMPARATOR_NEQ), VERCMP_MATCH); +} + +void test_gt_lt(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0-1", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_NO_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-2", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_NO_MATCH); + assert_int_equal(DoCompare("1.0-2", "1.0-1", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-1", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_NO_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-2", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_MATCH); + assert_int_equal(DoCompare("1.0-2", "1.0-1", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_NO_MATCH); +} + +void test_gte_lte(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0-1", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-2", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_NO_MATCH); + assert_int_equal(DoCompare("1.0-2", "1.0-1", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-1", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_MATCH); + assert_int_equal(DoCompare("1.0-1", "1.0-2", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_MATCH); + assert_int_equal(DoCompare("1.0-2", "1.0-1", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_NO_MATCH); +} + +void wrong_separators(void) +{ + assert_int_equal(DoCompare("1.0", "1,0", PACKAGE_VERSION_COMPARATOR_EQ), VERCMP_ERROR); +} + +void uneven_lengths_1(void) +{ + assert_int_equal(DoCompare("1.0.1", "1.0", PACKAGE_VERSION_COMPARATOR_EQ), VERCMP_NO_MATCH); +} + +void uneven_lengths_2(void) +{ + assert_int_equal(DoCompare("1.0.1", "1.0", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_MATCH); +} + +void uneven_lengths_3(void) +{ + assert_int_equal(DoCompare("1.0.1", "1.0", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_NO_MATCH); +} + +void uneven_lengths_4(void) +{ + assert_int_equal(DoCompare("1.0.1", "1.0", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_MATCH); +} + +void uneven_lengths_5(void) +{ + assert_int_equal(DoCompare("1.0.1", "1.0", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_NO_MATCH); +} + +void uneven_lengths_6(void) +{ + assert_int_equal(DoCompare("1.0", "1.0.1", PACKAGE_VERSION_COMPARATOR_EQ), VERCMP_NO_MATCH); +} + +void uneven_lengths_7(void) +{ + assert_int_equal(DoCompare("1.0", "1.0.1", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_NO_MATCH); +} + +void uneven_lengths_8(void) +{ + assert_int_equal(DoCompare("1.0", "1.0.1", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_MATCH); +} + +void uneven_lengths_9(void) +{ + assert_int_equal(DoCompare("1.0", "1.0.1", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_NO_MATCH); +} + +void uneven_lengths_10(void) +{ + assert_int_equal(DoCompare("1.0", "1.0.1", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_MATCH); +} + +void uneven_lengths_11(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0", PACKAGE_VERSION_COMPARATOR_EQ), VERCMP_NO_MATCH); +} + +void uneven_lengths_12(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_MATCH); +} + +void uneven_lengths_13(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_NO_MATCH); +} + +void uneven_lengths_14(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_MATCH); +} + +void uneven_lengths_15(void) +{ + assert_int_equal(DoCompare("1.0-1", "1.0", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_NO_MATCH); +} + +void uneven_lengths_16(void) +{ + assert_int_equal(DoCompare("1.0", "1.0-1", PACKAGE_VERSION_COMPARATOR_EQ), VERCMP_NO_MATCH); +} + +void uneven_lengths_17(void) +{ + assert_int_equal(DoCompare("1.0", "1.0-1", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_NO_MATCH); +} + +void uneven_lengths_18(void) +{ + assert_int_equal(DoCompare("1.0", "1.0-1", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_MATCH); +} + +void uneven_lengths_19(void) +{ + assert_int_equal(DoCompare("1.0", "1.0-1", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_NO_MATCH); +} + +void uneven_lengths_20(void) +{ + assert_int_equal(DoCompare("1.0", "1.0-1", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_MATCH); +} + +void invalid_01(void) +{ + assert_int_equal(DoCompare("text-1.0", "1.0", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_ERROR); +} + +void invalid_02(void) +{ + assert_int_equal(DoCompare("text-1.0", "1.0", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_ERROR); +} + +void invalid_03(void) +{ + assert_int_equal(DoCompare("1.0", "text-1.0", PACKAGE_VERSION_COMPARATOR_LE), VERCMP_ERROR); +} + +void invalid_04(void) +{ + assert_int_equal(DoCompare("1.0", "text-1.0", PACKAGE_VERSION_COMPARATOR_GE), VERCMP_ERROR); +} + +void invalid_05(void) +{ + assert_int_equal(DoCompare("text-1.0", "1.0", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_ERROR); +} + +void invalid_06(void) +{ + assert_int_equal(DoCompare("text-1.0", "1.0", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_ERROR); +} + +void invalid_07(void) +{ + assert_int_equal(DoCompare("1.0", "text-1.0", PACKAGE_VERSION_COMPARATOR_LT), VERCMP_ERROR); +} + +void invalid_08(void) +{ + assert_int_equal(DoCompare("1.0", "text-1.0", PACKAGE_VERSION_COMPARATOR_GT), VERCMP_ERROR); +} + + +int main() +{ + PRINT_TEST_BANNER(); + LogSetGlobalLevel(LOG_LEVEL_VERBOSE); + + const UnitTest tests[] = + { + unit_test(test_different_name), + unit_test(test_wildcard_arch), + unit_test(test_non_matching_arch), + unit_test(test_wildcard_version), + unit_test(test_eq), + unit_test(test_ne), + unit_test(test_gt_lt), + unit_test(test_gte_lte), + unit_test(wrong_separators), + unit_test(uneven_lengths_1), + unit_test(uneven_lengths_2), + unit_test(uneven_lengths_3), + unit_test(uneven_lengths_4), + unit_test(uneven_lengths_5), + unit_test(uneven_lengths_6), + unit_test(uneven_lengths_7), + unit_test(uneven_lengths_8), + unit_test(uneven_lengths_9), + unit_test(uneven_lengths_10), + unit_test(uneven_lengths_11), + unit_test(uneven_lengths_12), + unit_test(uneven_lengths_13), + unit_test(uneven_lengths_14), + unit_test(uneven_lengths_15), + unit_test(uneven_lengths_16), + unit_test(uneven_lengths_17), + unit_test(uneven_lengths_18), + unit_test(uneven_lengths_19), + unit_test(uneven_lengths_20), + unit_test(invalid_01), + unit_test(invalid_02), + unit_test(invalid_03), + unit_test(invalid_04), + unit_test(invalid_05), + unit_test(invalid_06), + unit_test(invalid_07), + unit_test(invalid_08), + }; + + return run_tests(tests); +} diff --git a/tests/unit/parsemode_test.c b/tests/unit/parsemode_test.c new file mode 100644 index 0000000000..8bb0119dbb --- /dev/null +++ b/tests/unit/parsemode_test.c @@ -0,0 +1,70 @@ +#include + +#include + +typedef struct { + char *string; + mode_t plus; + mode_t minus; +} mode_definition; + +mode_definition modes[] = { + { "666", S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH, S_IXUSR|S_IXGRP|S_IXOTH|S_ISUID|S_ISGID|S_ISVTX }, + { "g+w", S_IWGRP, 0 }, + { "u+r,u+w,g-w,o-rw", S_IRUSR|S_IWUSR, S_IWGRP|S_IWOTH|S_IROTH }, + { NULL, 0, 0 } // last case, still tested +}; + +void test_mode(void) +{ + bool ret = false; + mode_t plus = 0; + mode_t minus = 0; + + int mode = 0; + do + { + ret = ParseModeString(modes[mode].string, &plus, &minus); + assert_true(ret); + assert_int_equal(modes[mode].plus, plus); + assert_int_equal(modes[mode].minus, minus); + } while (modes[mode++].string); +} + +typedef struct { + char *string; + bool valid; +} validation_mode; + +validation_mode validation_modes[] = { + { "", false }, + { "abc", false }, + { "222222", false }, + { "22222", true }, + { NULL, true } // last case, still tested +}; + +void test_validation(void) +{ + int ret = false; + mode_t minus = 0; + mode_t plus = 0; + + int mode = 0; + do + { + ret = ParseModeString( validation_modes[mode].string, &plus, &minus); + assert_int_equal(validation_modes[mode].valid, ret); + } while (validation_modes[mode++].string); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_validation), + unit_test(test_mode) + }; + return run_tests(tests); +} diff --git a/tests/unit/parser_test.c b/tests/unit/parser_test.c new file mode 100644 index 0000000000..f0e6739bba --- /dev/null +++ b/tests/unit/parser_test.c @@ -0,0 +1,267 @@ +#include + +#include +#include /* xsnprintf */ +#include + + +static bool TestParsePolicy(const char *filename) +{ + char path[PATH_MAX]; + xsnprintf(path, sizeof(path), "%s/%s", TESTDATADIR, filename); + Policy *p = ParserParseFile(AGENT_TYPE_COMMON, path, PARSER_WARNING_ALL, PARSER_WARNING_ALL); + bool res = (p != NULL); + PolicyDestroy(p); + return res; +} + +void test_benchmark(void) +{ + assert_true(TestParsePolicy("benchmark.cf")); +} + +void test_no_bundle_or_body_keyword(void) +{ + assert_false(TestParsePolicy("no_bundle_or_body_keyword.cf")); +} + +void test_bundle_invalid_type(void) +{ + assert_false(TestParsePolicy("bundle_invalid_type.cf")); +} + +void test_constraint_ifvarclass_invalid(void) +{ + assert_false(TestParsePolicy("constraint_ifvarclass_invalid.cf")); +} + +void test_bundle_args_invalid_type(void) +{ + assert_false(TestParsePolicy("bundle_args_invalid_type.cf")); +} + +void test_bundle_args_forgot_cp(void) +{ + assert_false(TestParsePolicy("bundle_args_forgot_cp.cf")); +} + +void test_bundle_body_forgot_ob(void) +{ + assert_false(TestParsePolicy("bundle_body_forgot_ob.cf")); +} + +void test_bundle_custom_promise_type(void) +{ + assert_true(TestParsePolicy("bundle_custom_promise_type.cf")); +} + +void test_bundle_body_wrong_promise_type_token(void) +{ + assert_false(TestParsePolicy("bundle_body_wrong_promise_type_token.cf")); +} + +void test_bundle_body_wrong_statement(void) +{ + assert_false(TestParsePolicy("bundle_body_wrong_statement.cf")); +} + +void test_bundle_body_forgot_semicolon(void) +{ + assert_false(TestParsePolicy("bundle_body_forgot_semicolon.cf")); +} + +void test_bundle_body_promiser_statement_contains_colon(void) +{ + assert_false(TestParsePolicy("bundle_body_promiser_statement_contains_colon.cf")); +} + +void test_bundle_body_promiser_statement_missing_assign(void) +{ + assert_false(TestParsePolicy("bundle_body_promiser_statement_missing_assign.cf")); +} + +void test_bundle_body_promisee_missing_arrow(void) +{ + assert_false(TestParsePolicy("bundle_body_promise_missing_arrow.cf")); +} + +void test_bundle_body_promiser_wrong_constraint_token(void) +{ + assert_false(TestParsePolicy("bundle_body_promiser_wrong_constraint_token.cf")); +} + +void test_bundle_body_promiser_unknown_constraint_id(void) +{ + assert_false(TestParsePolicy("bundle_body_promiser_unknown_constraint_id.cf")); +} + +void test_body_edit_line_common_constraints(void) +{ + assert_true(TestParsePolicy("body_edit_line_common_constraints.cf")); +} + +void test_body_edit_xml_common_constraints(void) +{ + assert_true(TestParsePolicy("body_edit_xml_common_constraints.cf")); +} + +void test_promise_promiser_nonscalar(void) +{ + assert_false(TestParsePolicy("promise_promiser_nonscalar.cf")); +} + +void test_bundle_body_promiser_forgot_colon(void) +{ + assert_false(TestParsePolicy("bundle_body_promiser_forgot_colon.cf")); +} + +void test_bundle_body_promisee_no_colon_allowed(void) +{ + assert_false(TestParsePolicy("bundle_body_promisee_no_colon_allowed.cf")); +} + +void test_bundle_body_forget_cb_eof(void) +{ + assert_false(TestParsePolicy("bundle_body_forget_cb_eof.cf")); +} + +void test_bundle_body_forget_cb_body(void) +{ + assert_false(TestParsePolicy("bundle_body_forget_cb_body.cf")); +} + +void test_bundle_body_forget_cb_bundle(void) +{ + assert_false(TestParsePolicy("bundle_body_forget_cb_bundle.cf")); +} + +void test_body_selection_wrong_token(void) +{ + assert_false(TestParsePolicy("body_selection_wrong_token.cf")); +} + +void test_body_selection_forgot_semicolon(void) +{ + assert_false(TestParsePolicy("body_selection_forgot_semicolon.cf")); +} + +void test_body_selection_unknown_selection_id(void) +{ + assert_false(TestParsePolicy("body_selection_unknown_selection_id.cf")); +} + +void test_body_body_forget_cb_eof(void) +{ + assert_false(TestParsePolicy("body_body_forget_cb_eof.cf")); +} + +void test_body_body_forget_cb_body(void) +{ + assert_false(TestParsePolicy("body_body_forget_cb_body.cf")); +} + +void test_body_body_forget_cb_bundle(void) +{ + assert_false(TestParsePolicy("body_body_forget_cb_bundle.cf")); +} + +void test_rval_list_forgot_colon(void) +{ + assert_false(TestParsePolicy("rval_list_forgot_colon.cf")); +} + +void test_rval_list_wrong_input_type(void) +{ + assert_false(TestParsePolicy("rval_list_wrong_input_type.cf")); +} + +void test_rval_function_forgot_colon(void) +{ + assert_false(TestParsePolicy("rval_function_forgot_colon.cf")); +} + +void test_rval_function_wrong_input_type(void) +{ + assert_false(TestParsePolicy("rval_function_wrong_input_type.cf")); +} + +void test_rval_wrong_input_type(void) +{ + assert_false(TestParsePolicy("rval_wrong_input_type.cf")); +} + +void test_rval_list_forgot_cb_semicolon(void) +{ + assert_false(TestParsePolicy("rval_list_forgot_cb_semicolon.cf")); +} + +void test_rval_list_forgot_cb_colon(void) +{ + assert_false(TestParsePolicy("rval_list_forgot_cb_colon.cf")); +} + +void test_rval_function_forgot_cp_semicolon(void) +{ + assert_false(TestParsePolicy("rval_function_forgot_cp_semicolon.cf")); +} + +void test_rval_function_forgot_cp_colon(void) +{ + assert_false(TestParsePolicy("rval_function_forgot_cp_colon.cf")); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_benchmark), + + unit_test(test_bundle_invalid_type), + unit_test(test_bundle_args_invalid_type), + unit_test(test_bundle_args_forgot_cp), + unit_test(test_bundle_body_forgot_ob), + unit_test(test_bundle_custom_promise_type), + unit_test(test_bundle_body_wrong_promise_type_token), + unit_test(test_bundle_body_wrong_statement), + unit_test(test_bundle_body_forgot_semicolon), + unit_test(test_bundle_body_promiser_statement_contains_colon), + unit_test(test_bundle_body_promiser_statement_missing_assign), + unit_test(test_bundle_body_promiser_wrong_constraint_token), + unit_test(test_bundle_body_promiser_unknown_constraint_id), + unit_test(test_bundle_body_promiser_forgot_colon), + unit_test(test_bundle_body_promisee_no_colon_allowed), + unit_test(test_bundle_body_forget_cb_eof), + unit_test(test_bundle_body_forget_cb_body), + unit_test(test_bundle_body_forget_cb_bundle), + unit_test(test_body_edit_line_common_constraints), + unit_test(test_body_edit_xml_common_constraints), + unit_test(test_bundle_body_promisee_missing_arrow), + + unit_test(test_body_selection_wrong_token), + unit_test(test_body_selection_forgot_semicolon), + unit_test(test_body_selection_unknown_selection_id), + unit_test(test_body_body_forget_cb_eof), + unit_test(test_body_body_forget_cb_body), + unit_test(test_body_body_forget_cb_bundle), + + unit_test(test_promise_promiser_nonscalar), + + unit_test(test_constraint_ifvarclass_invalid), + + unit_test(test_rval_list_forgot_colon), + unit_test(test_rval_list_wrong_input_type), + unit_test(test_rval_list_forgot_cb_semicolon), + unit_test(test_rval_list_forgot_cb_colon), + unit_test(test_rval_function_forgot_colon), + unit_test(test_rval_function_wrong_input_type), + unit_test(test_rval_function_forgot_cp_semicolon), + unit_test(test_rval_function_forgot_cp_colon), + unit_test(test_rval_wrong_input_type), + + unit_test(test_no_bundle_or_body_keyword) + + }; + + return run_tests(tests); +} diff --git a/tests/unit/passopenfile_test.c b/tests/unit/passopenfile_test.c new file mode 100644 index 0000000000..ba16a9f5da --- /dev/null +++ b/tests/unit/passopenfile_test.c @@ -0,0 +1,1411 @@ +#include +#include + +#include +#include + +#ifndef __MINGW32__ +#include +#include +#include +#endif /* !__MINGW32__ */ +#include + +/* Whether to re-use Unix Domain Sockets + * + * Strictly, the API is only advertised to be fit for sending *one* + * descriptor over each UDS; but there's no reason it shouldn't work + * for more - and stressing it is good. Enabling REUSE_UDS thus + * semi-abuses the API to stress it a bit more. +#define REUSE_UDS + */ + +/* Globals used by the tests. */ + +static int SPAWNED_PID = -1; /* child used as other process in each test */ +static char DIALUP[PATH_MAX] = ""; /* synchronisation file for listen()/connect() */ +static bool DIALEDUP = false; + +/* TODO - not really testing this API, but testing we can use it + * securely: test that the listener can secure its socket to only be + * connect()ed to by the same user; likewise by one in its group. + * Probably put that test in some other file ! */ + + +/* Ensure no stray child is running. */ +static void wait_for_child(bool impatient) +{ + if (SPAWNED_PID == -1) /* it's BAD to run kill(-1) */ + { + return; + } + + while(true) + { + if (impatient) + { + errno = 0; + int ret = kill(SPAWNED_PID, SIGKILL); + if (ret == -1 && errno == ESRCH) + { + Log(LOG_LEVEL_VERBOSE, + "Child process to be killed does not exist (PID %jd)", + (intmax_t) SPAWNED_PID); + + break; /* process does not exist */ + } + + Log(LOG_LEVEL_NOTICE, + "Killed previous child process (PID %jd)", + (intmax_t) SPAWNED_PID); + } + + if (waitpid(SPAWNED_PID, NULL, 0) > 0) + { + Log(LOG_LEVEL_VERBOSE, + "Child process is dead and has been reaped (PID %jd)", + (intmax_t) SPAWNED_PID); + + break; /* zombie reaped properly */ + } + } + + SPAWNED_PID = -1; +} + +static bool wait_for_io(int whom, bool write) +{ + struct timeval tv; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(whom, &fds); + tv.tv_sec = 10; /* 10s timeout for select() */ + tv.tv_usec = 0; + + int ret; + if (write) + { + ret = select(whom + 1, NULL, &fds, NULL, &tv); + } + else + { + ret = select(whom + 1, &fds, NULL, NULL, &tv); + } + + if (ret < 0) + { + Log(LOG_LEVEL_ERR, "Failed select: %s", GetErrorStr()); + } + else if (!FD_ISSET(whom, &fds)) + { + Log(LOG_LEVEL_ERR, "Timed out select() for descriptor %d", whom); + } + + return ret > 0 && FD_ISSET(whom, &fds); +} + +/* Whoever connect()s needs to wait for the other to bind() and listen(): */ + +static bool wait_for_dialup(bool write) +{ + assert(DIALUP[0] != '\0'); + + for (int i = 5; i-- > 0;) + { + if (access(DIALUP, write ? W_OK : R_OK) == 0) /* bind() has happened */ + { + sleep(1); /* To let listen() happen, too. */ + return true; + } + sleep(1); + } + Log(LOG_LEVEL_ERR, + "Failed to access %s as synchronisation file: %s", + DIALUP, GetErrorStr()); + + return false; +} + +/* Used, via atexit(), to ensure we delete the file if we create it at the + * exit of all processes, either parent or children. */ + +static void clear_listener(void) +{ + if (DIALEDUP) + { + assert(DIALUP[0] != '\0'); + Log(LOG_LEVEL_VERBOSE, "Cleaning up UDS file"); + unlink(DIALUP); + DIALEDUP = false; + } +} + +static void clear_previous_test(void) +{ + /* KILL possible child. */ + wait_for_child(true); + + /* Clear possible UDS file created by this process (parent). */ + if (DIALEDUP) + { + clear_listener(); + } + + /* What if child got KILLed and never cleaned its UDS file? */ + struct stat statbuf; + if (stat(DIALUP, &statbuf) != -1) + { + Log(LOG_LEVEL_NOTICE, "UDS file from previous test still exists, " + "maybe child did not clean up? Removing and continuing..."); + unlink(DIALUP); + } +} + +/* Choose a file for the Unix Domain Socket, by which to connect() to the + * bind()er. */ + +static bool choose_dialup_UDS_file(void) +{ + if (DIALUP[0] == '\0') + { + /* Using insecure tempnam(). + * + * There really isn't any sensible alternative. What we need + * is a file *name* for use in the address we bind() and + * connect() to. A file descriptor (from mkstemp) or FILE* + * (from tmpfile) does us no good. The former would at least + * give us a unique filename, but we'd be unlink()ing the file + * it creates in order to actually bind() to it, and we'd have + * the exact same race condition as tempnam between when we + * bind() and when we connect(). This is a deficiency of the + * UDS APIs that we just have to live with. In production, we + * need to make sure we use a UDS based on a file somewhere we + * secure suitably (e.g. in a directory owned by root and + * inaccessible to anyone else). The "cfpof" stands for + * CF(Engine) Pass Open File, in case you wondered. + */ + char *using = tempnam("/tmp", "cfpof"); + if (using == NULL) + { + Log(LOG_LEVEL_ERR, "Failed tempnam: %s", GetErrorStr()); + } + else + { + assert(using[0]); /* non-empty */ + strlcpy(DIALUP, using, sizeof(DIALUP)); + free(using); + Log(LOG_LEVEL_VERBOSE, "Synchronising UDS via %s", DIALUP); + } + } + return DIALUP[0] != '\0'; +} + +/* Set up listen()ing socket; used by one process in each test. */ + +static int setup_listener(void) +{ + /* Create "server" socket, bind() it, listen() on it, return it. */ + assert(DIALUP[0] != '\0'); + { + /* The Unix Domain Socket file was cleaned up in the previous test. */ + struct stat statbuf; + bool UDS_exists = (stat(DIALUP, &statbuf) != -1); + assert_false(UDS_exists); + } + + int server = socket(AF_UNIX, SOCK_STREAM, 0); + if (server >= 0) + { + Log(LOG_LEVEL_VERBOSE, "Opened %d to bind to and listen on.", server); + if (server < FD_SETSIZE) /* else problem for FD_SET when select()ing */ + { + struct sockaddr_un address = { 0 }; + assert(strlen(DIALUP) < sizeof(address.sun_path)); + address.sun_family = AF_UNIX; + strlcpy(address.sun_path, DIALUP, sizeof(address.sun_path)); + + Log(LOG_LEVEL_VERBOSE, "Attempting to bind(%d, ...)", server); + if (bind(server, (struct sockaddr *)&address, sizeof(address)) == 0) + { + /* That's created the file DIALUP, that we now have a + * duty to tidy away. */ + DIALEDUP = true; + + Log(LOG_LEVEL_VERBOSE, "Calling listen(%d, 1)", server); + if (listen(server, 2) == 0) + { + /* Success */ + return server; + } + else + { + Log(LOG_LEVEL_ERR, "Failed listen: %s", GetErrorStr()); + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed bind: %s", GetErrorStr()); + } + } + cf_closesocket(server); + } + else + { + Log(LOG_LEVEL_ERR, "Failed socket: %s", GetErrorStr()); + } + return -1; +} + +/* Set up Unix Domain Sockets. + * + * One of parent and child uses setup_accept(); the other uses + * setup_connect(). Each gets a UDS back. One wants to write to the + * result, the other wants to read from it. These two two-way choices + * give us four tests. */ + +static int setup_accept(int server, bool write) +{ + /* When listening socket is ready, accept(); wait for the result + * to be ready to read/write. */ + assert(server >= 0); + Log(LOG_LEVEL_VERBOSE, "Calling accept(%d, NULL, NULL)", server); + if (wait_for_io(server, false)) + { + int uds = accept(server, NULL, NULL); + if (uds == -1) + { + Log(LOG_LEVEL_ERR, "Failed accept: %s", GetErrorStr()); + } + else if (uds < FD_SETSIZE && /* else problem for FD_SET when select()ing */ + wait_for_io(uds, write)) + { + Log(LOG_LEVEL_VERBOSE, "Ready to use accept()ed UDS %d", uds); + + return uds; /* Success */ + } + else + { + Log(LOG_LEVEL_ERR, + "Unable to %s on accept()ed Unix Domain Socket", + write ? "write" : "read"); + cf_closesocket(uds); + } + } + return -1; +} + +static int setup_connect(bool write) +{ + /* Create socket, connect() to the listening socket, wait until + * ready to read/write */ + int uds = socket(AF_UNIX, SOCK_STREAM, 0); + if (uds >= 0) + { + if (uds < FD_SETSIZE && /* else problem for FD_SET when select()ing */ + wait_for_dialup(write)) + { + struct sockaddr_un address; + assert(strlen(DIALUP) < sizeof(address.sun_path)); + address.sun_family = AF_UNIX; + strlcpy(address.sun_path, DIALUP, sizeof(address.sun_path)); + + if (connect(uds, (struct sockaddr *)&address, sizeof(address)) == 0 && + wait_for_io(uds, write)) + { + Log(LOG_LEVEL_VERBOSE, + "Ready to use connect()ed UDS %d", uds); + + return uds; /* Success */ + } + else + { + Log(LOG_LEVEL_ERR, + "Failed connect (or select): %s", GetErrorStr()); + } + } + cf_closesocket(uds); + } + else + { + Log(LOG_LEVEL_ERR, "Failed socket: %s", GetErrorStr()); + } + return -1; +} + +/* Wrapper to set up a Unix Domain Socket. + * + * Variants on the child/parent processes are given a listening socket + * on which to accept() or -1 to tell them to connect(). This + * function mediates that choice for them. It probably gets inlined. */ +static int setup_uds(int server, bool write) +{ + return server < 0 ? setup_connect(write) : setup_accept(server, write); +} + +/* Reverse of setup_pipe: */ + +static void close_pipe(int pair[2]) +{ + int idx = 2; + while (idx-- > 0) + { + if (pair[idx] >= 0) + { + cf_closesocket(pair[idx]); + pair[idx] = -1; + } + } +} + +/* Socket pair used by the conversation between processes over the + * exchanged descriptors. */ + +static bool setup_pipe(int pipe[2]) +{ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipe) == 0) + { + /* No need to make them non-blocking ! */ + return true; + } + Log(LOG_LEVEL_ERR, "Failed socketpair: %s", GetErrorStr()); + return false; +} + +/* Check we can use transferred sockets. + * + * For the parent-child dialogs, we only try one direction. Each end + * actually has both ends of the pipe, but trying to communicate both + * ways gets mixed up with the communication we're doing with the + * given ends. */ +const char FALLBACK[] = "\0Fallback Message"; + +/* Parent's half: checks everything. */ +static bool check_hail(int sock, const char *message) +{ + const char *msg = message ? message : (FALLBACK + 1); + const size_t msglen = message ? strlen(message) : sizeof(FALLBACK); + const char noise[] = "The quick brown fox jumps over the lazy dog"; + char buffer[80]; + assert(msglen && msglen < sizeof(buffer)); + strlcpy(buffer, noise, sizeof(buffer)); + + errno = 0; + ssize_t sent; + if (!wait_for_io(sock, true)) + { + /* wait_for_io already Log()ged. */ + + } + else if (0 > (sent = send(sock, "hello", 6, 0))) + { + Log(LOG_LEVEL_ERR, + "Parent failed to send 'hello': %s", + GetErrorStr()); + } + else if (sent != 6) + { + Log(LOG_LEVEL_ERR, + "Parent sent wrong length (%zd != 5) for 'hello'", + sent); + } + else if (!wait_for_io(sock, false)) + { + /* wait_for_io already Log()ged. */ + + } + else if (0 > (sent = recv(sock, buffer, sizeof(buffer), 0))) + { + Log(LOG_LEVEL_ERR, + "Parent failed to receive '%s': %s", + msg, GetErrorStr()); + } + else if (sent != msglen) + { + buffer[MIN(sent, sizeof(buffer) - 1)] = '\0'; + Log(LOG_LEVEL_ERR, + "Parent received wrong length (%zd != %zd) for '%s' != %s", + sent, msglen, + (message || buffer[0]) ? buffer : (buffer + 1), + msg); + } + else if (strcmp(buffer + msglen, noise + msglen) != 0) + { + buffer[sizeof(buffer) - 1] = '\0'; + Log(LOG_LEVEL_ERR, "Parent recv() trampled buffer: %s", buffer); + } + else + { + buffer[msglen] = '\0'; + + if (message ? strcmp(buffer, message) == 0 : + (buffer[0] == '\0' && + strcmp(buffer + 1, FALLBACK + 1) == 0)) + { + return true; + } + Log(LOG_LEVEL_ERR, + "Parent received wrong text '%s' != '%s'", + (message || buffer[0]) ? buffer : (buffer + 1), + msg); + } + + return false; +} + +/* Dummy conversational partner for check_hail (used in child). */ +static void child_hail(int sock, const char *message) +{ + const size_t msglen = message ? strlen(message) : sizeof(FALLBACK); + char buffer[80]; + if (wait_for_io(sock, false) && + recv(sock, buffer, sizeof(buffer), 0) == 6 && + strcmp(buffer, "hello") == 0 && + wait_for_io(sock, true)) + { + send(sock, message ? message : FALLBACK, msglen, 0); + } +} + +/* Similar but for a parent talking to self after child died. + * + * For this one, we test two-way communications, since we're inside a + * single process, where that should all work fine. OTOH, we don't + * bother with the buffer-overrun checks and other complications + * addressed by check_hail(). */ + +static bool self_hail(int pair[2]) +{ + /* We send these with sizeof(), so include the '\0' endings. + * They should thus be received with those endings at buffer[sent - 1]. + */ + const char exclaim[] = "Dead! Dead!", + lament[] = "And never called me mother"; + char buffer[80]; + ssize_t sent; + if (!wait_for_io(pair[0], true)) + { + /* wait_for_io already Log()ged. */ + + } + else if (0 > (sent = send(pair[0], exclaim, sizeof(exclaim), 0))) + { + Log(LOG_LEVEL_ERR, "Failed to send exclamation (send: %s)", + GetErrorStr()); + } + else if (sent != sizeof(exclaim)) + { + Log(LOG_LEVEL_ERR, "Sent wrong-sized exclamation: %zd != %zu", + sent, sizeof(exclaim)); + } + else if (!wait_for_io(pair[1], false)) + { + /* wait_for_io already Log()ged. */ + + } + else if (0 > (sent = recv(pair[1], buffer, sizeof(buffer), 0))) + { + Log(LOG_LEVEL_ERR, "Failed to receive exclamation (recv: %s)", + GetErrorStr()); + } + else if (sent != sizeof(exclaim) || buffer[sent - 1]) + { + buffer[MIN(sent, sizeof(buffer) - 1)] = '\0'; + Log(LOG_LEVEL_ERR, "Received wrong-sized exclamation (%zd != %zu): %s", + sent, sizeof(exclaim), buffer); + } + else if (strcmp(buffer, exclaim) != 0) + { + buffer[MIN(sent, sizeof(buffer) - 1)] = '\0'; + Log(LOG_LEVEL_ERR, "Mismatch in exclamation: '%s' != '%s'", + buffer, exclaim); + } + else if (!wait_for_io(pair[1], true)) + { + /* wait_for_io already Log()ged. */ + + } + else if (0 > (sent = send(pair[1], lament, sizeof(lament), 0))) + { + Log(LOG_LEVEL_ERR, "Failed to send lament (send: %s)", + GetErrorStr()); + } + else if (sent != sizeof(lament)) + { + Log(LOG_LEVEL_ERR, "Sent wrong-sized lament: %zd != %zu", + sent, sizeof(lament)); + } + else if (!wait_for_io(pair[0], false)) + { + /* wait_for_io already Log()ged. */ + + } + else if (0 > (sent = recv(pair[0], buffer, sizeof(buffer), 0))) + { + Log(LOG_LEVEL_ERR, "Failed to receive lament (recv: %s)", + GetErrorStr()); + } + else if (sent != sizeof(lament)) + { + buffer[MIN(sent, sizeof(buffer) - 1)] = '\0'; + Log(LOG_LEVEL_ERR, "Received wrong-sized lament (%zd != %zu): %s", + sent, sizeof(lament), buffer); + } + else if (strcmp(buffer, lament) != 0) + { + Log(LOG_LEVEL_ERR, "Mismatch in lament: '%s' != '%s'", + buffer, lament); + } + else + { + return true; + } + return false; +} + +/* Variants on the child/parent process. + * + * In each case the sending process creates a socket pair (setup_pipe) + * and passes both ends to the other process; after that, they have + * the conversation above (see *_hail) using these sockets. + * + * For the two-process tests, three things are tested (each setting a + * bool variable for the parent): sending a descriptor attending to + * any accompanying message (word), sending a descriptor ignoring + * message (none) and using one each of the shared descriptors to + * communicate between processes (chat). For the test by a parent + * after its child has died, only success of the conversation is + * recorded to pass back to the parent. + * + * Naturally, the details (particularly synchronisation) are trickier + * than that. See REUSE_UDS, above, for one complication. + * + * We have one parent_*()/child_*() pair for the parent as sending + * process with the child receiving, one pair the other way round and + * one for the parent that outlives its child. In each test, one of + * parent and child is passed a listening socket on which to accept() + * UDS connections; the other is passed -1 and connect()s to it. The + * former has a duty to close the listening socket. For each of the + * resulting combinations, the two-process tests then have one test in + * which the non-ignored message is NULL (*_silent) and another in + * which it has some content (*_message). */ + +static void child_for_outlive(int server) +{ + /* Parent takes, so the child must send. */ + int pipe[2]; + if (setup_pipe(pipe)) + { + int uds = setup_uds(server, true); + if (uds >= 0) + { + PassOpenFile_Put(uds, pipe[1], NULL); +#ifndef REUSE_UDS + cf_closesocket(uds); + } + uds = setup_uds(server, true); + if (uds >= 0) + { +#endif /* REUSE_UDS */ +#ifdef REUSE_UDS + wait_for_io(uds, true) && +#endif + PassOpenFile_Put(uds, pipe[0], NULL); + cf_closesocket(uds); + } + close_pipe(pipe); + } + + if (server != -1) + { + cf_closesocket(server); + } +} + +static bool parent_outlive(int server, bool *chat) +{ + int pair[2] = { -1, -1 }; + bool result = true; + *chat = false; + + int uds = setup_uds(server, false); +#ifdef REUSE_UDS + if (server != -1) + { + cf_closesocket(server); + } +#endif + if (uds < 0) + { + result = false; + } + else + { + char *text = NULL; + pair[1] = PassOpenFile_Get(uds, &text); + free(text); + text = NULL; +#ifndef REUSE_UDS + cf_closesocket(uds); + } + uds = setup_uds(server, false); + if (server != -1) + { + cf_closesocket(server); + } + + if (uds < 0) + { + result = false; + } + else + { +#endif /* REUSE_UDS */ + pair[0] = +#ifdef REUSE_UDS + !wait_for_io(uds, false) ? -1 : +#endif + PassOpenFile_Get(uds, NULL); + + cf_closesocket(uds); + if (pair[0] < 0) + { + Log(LOG_LEVEL_ERR, + "Parent failed to receive descriptor (ignoring text)"); + } + else if (pair[1] >= 0) + { + Log(LOG_LEVEL_VERBOSE, "Parent received both descriptors"); + wait_for_child(false); /* Don't kill, be patient. */ + *chat = self_hail(pair); + } + } + + close_pipe(pair); + return result; +} + +/* Child sends descriptors to parent. + * + * In this case, verifying we get the right message can be done + * directly by the parent on receiving the message. */ + +static void child_for_take(int server, const char *message) +{ + /* Of course, for the parent to take, the child must send. */ + int pipe[2]; + if (setup_pipe(pipe)) + { + bool sent = false; + int uds = setup_uds(server, true); + if (uds >= 0) + { + sent = PassOpenFile_Put(uds, pipe[1], message); +#ifndef REUSE_UDS + cf_closesocket(uds); + } + uds = setup_uds(server, true); + if (uds >= 0) + { +#endif /* REUSE_UDS */ + sent = +#ifdef REUSE_UDS + wait_for_io(uds, true) && +#endif + PassOpenFile_Put(uds, pipe[0], message) && sent; + cf_closesocket(uds); + } + if (sent) + { + Log(LOG_LEVEL_VERBOSE, "Child delivered both descriptors"); + child_hail(pipe[0], message); + } + close_pipe(pipe); + } + + if (server != -1) + { + cf_closesocket(server); + } +} + +static bool parent_take(int server, const char *message, + bool *word, bool *none, bool *chat) +{ + int pair[2] = { -1, -1 }; + bool result = true; + *word = *none = *chat = false; + + int uds = setup_uds(server, false); +#ifdef REUSE_UDS + if (server != -1) + { + cf_closesocket(server); + } +#endif + if (uds < 0) + { + result = false; + } + else + { + char *text = NULL; + pair[1] = PassOpenFile_Get(uds, &text); + *word = pair[1] >= 0 && (message ? text && strcmp(text, message) == 0 : !text); + free(text); + text = NULL; +#ifndef REUSE_UDS + cf_closesocket(uds); + } + uds = setup_uds(server, false); + if (server != -1) + { + cf_closesocket(server); + } + + if (uds < 0) + { + result = false; + } + else + { +#endif /* REUSE_UDS */ + pair[0] = +#ifdef REUSE_UDS + !wait_for_io(uds, false) ? -1 : +#endif + PassOpenFile_Get(uds, NULL); + + cf_closesocket(uds); + if (pair[0] < 0) + { + Log(LOG_LEVEL_ERR, "Parent failed to receive descriptor (ignoring text)"); + } + else + { + *none = true; + if (pair[1] >= 0) + { + Log(LOG_LEVEL_VERBOSE, "Parent received both descriptors"); + *chat = check_hail(pair[1], message); + } + } + } + + close_pipe(pair); + return result; +} + +/* Parent sends to child. + * + * Verifying the message is transmitted correctly depends on the child + * sending the message back as part of the conversation after we + * exchange descriptors. */ + +static void child_for_send(int server) +{ + /* Of course, for the parent to send, the child must take. */ + int pair[2] = { -1, -1 }; + + int uds = setup_uds(server, false); +#ifdef REUSE_UDS + if (server != -1) + { + cf_closesocket(server); + } +#endif + if (uds >= 0) + { + pair[1] = PassOpenFile_Get(uds, NULL); + if (pair[1] < 0) + { + Log(LOG_LEVEL_ERR, + "Child failed to receive descriptor (ignoring text)"); + } +#ifndef REUSE_UDS + cf_closesocket(uds); + } + + uds = setup_uds(server, false); + if (server >= 0) + { + cf_closesocket(server); + } + + if (uds >= 0) + { +#endif /* REUSE_UDS */ + char *text = NULL; + pair[0] = +#ifdef REUSE_UDS + !wait_for_io(uds, false) ? -1 : +#endif + PassOpenFile_Get(uds, &text); + cf_closesocket(uds); + if (pair[0] < 0) + { + Log(LOG_LEVEL_ERR, + "Child failed to receive descriptor"); + } + else if (pair[1] >= 0) + { + Log(LOG_LEVEL_VERBOSE, + "Child received both descriptors (%s %s)", + text ? "text:" : "no", + text ? text : "text"); + + child_hail(pair[1], text); + } + free(text); + } + close_pipe(pair); +} + +static bool parent_send(int server, const char *message, + bool *word, bool *none, bool *chat) +{ + /* We have a duty to close(server) if not -1 */ + bool result = false; + int pipe[2]; + *word = *none = *chat = false; + + if (setup_pipe(pipe)) + { + result = true; + *word = *none = *chat = false; + int uds = setup_uds(server, true); + if (uds < 0) + { + result = false; + } + else + { + *none = PassOpenFile_Put(uds, pipe[1], message); +#ifndef REUSE_UDS + cf_closesocket(uds); + } + uds = setup_uds(server, true); + if (uds < 0) + { + result = false; + } + else + { +#endif /* REUSE_UDS */ + *word = +#ifdef REUSE_UDS + wait_for_io(uds, true) && +#endif + PassOpenFile_Put(uds, pipe[0], message); + cf_closesocket(uds); + } + + if (*word && *none) + { + Log(LOG_LEVEL_VERBOSE, "Parent delivered both descriptors"); + *chat = check_hail(pipe[0], message); + } + close_pipe(pipe); + } + + if (server != -1) + { + cf_closesocket(server); + } + + return result; +} + +/* The actual tests, built on those tools. + * + * Classified according to whether parent is listen()ing or + * connect()ing, with the child doing the other, and whether parent is + * sending or taking descriptors from child. The parent is + * responsible for all testing, although the child helps out via the + * chatter on exchanged sockets. */ + +static void test_take_listen_message(void) +{ + clear_previous_test(); + const char message[] = "verbiage"; + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + child_for_take(-1, message); + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Parent failed to set up listener"); + } + else + { + bool word, none, chat; + if (parent_take(server, message, &word, &none, &chat)) + { + assert_true(word && "can receive fd with a message"); + assert_true(none && "can receive fd ignoring message"); + assert_true(chat && "can use the received fds"); + + if (waitpid(new_pid, NULL, 1) > 0) + { + SPAWNED_PID = -1; + } + return; + } + Log(LOG_LEVEL_ERR, "Failed to set up UDS"); + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_take_connect_message(void) +{ + clear_previous_test(); + const char message[] = "waffle"; + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Child failed to set up listener"); + } + else + { + child_for_take(server, message); + } + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + bool word, none, chat; + if (parent_take(-1, message, &word, &none, &chat)) + { + assert_true(word && "can receive fd with a message"); + assert_true(none && "can receive fd ignoring message"); + assert_true(chat && "can use the received fds"); + + if (waitpid(new_pid, NULL, 1) > 0) + { + SPAWNED_PID = -1; + } + return; + } + Log(LOG_LEVEL_ERR, "Failed to set up parent"); + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_send_listen_message(void) +{ + clear_previous_test(); + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + child_for_send(-1); + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Parent failed to set up listener"); + } + else + { + bool word, none, chat; + if (parent_send(server, "mumble", &word, &none, &chat)) + { + assert_true(word && "can transmit fd with a message"); + assert_true(none && "can transmit fd ignoring message"); + assert_true(chat && "can use the transmitted fds"); + + if (waitpid(new_pid, NULL, 1) > 0) + { + SPAWNED_PID = -1; + } + return; + } + Log(LOG_LEVEL_ERR, "Failed to set up parent"); + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_send_connect_message(void) +{ + clear_previous_test(); + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Child failed to set up listener"); + } + else + { + child_for_send(server); + } + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + bool word, none, chat; + if (parent_send(-1, "mutter", &word, &none, &chat)) + { + assert_true(word && "can transmit fd with a message"); + assert_true(none && "can transmit fd ignoring message"); + assert_true(chat && "can use the transmitted fds"); + + if (waitpid(new_pid, NULL, 1) > 0) + { + SPAWNED_PID = -1; + } + return; + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_take_listen_silent(void) +{ + clear_previous_test(); + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + child_for_take(-1, NULL); + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Parent failed to set up listener"); + } + else + { + bool word, none, chat; + if (parent_take(server, NULL, &word, &none, &chat)) + { + assert_true(word && "can receive fd with no message"); + assert_true(none && "can receive fd ignoring no message"); + assert_true(chat && "can use the received fds"); + + if (waitpid(new_pid, NULL, 1) > 0) + { + SPAWNED_PID = -1; + } + return; + } + Log(LOG_LEVEL_ERR, "Failed to set up UDS"); + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_take_connect_silent(void) +{ + clear_previous_test(); + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Child failed to set up listener"); + } + else + { + child_for_take(server, NULL); + } + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + bool word, none, chat; + if (parent_take(-1, NULL, &word, &none, &chat)) + { + assert_true(word && "can receive fd with no message"); + assert_true(none && "can receive fd ignoring no message"); + assert_true(chat && "can use the received fds"); + + if (waitpid(new_pid, NULL, 1) > 0) + { + SPAWNED_PID = -1; + } + return; + } + Log(LOG_LEVEL_ERR, "Failed to set up parent"); + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_send_listen_silent(void) +{ + clear_previous_test(); + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + child_for_send(-1); + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Parent failed to set up listener"); + } + else + { + bool word, none, chat; + if (parent_send(server, NULL, &word, &none, &chat)) + { + assert_true(word && "can transmit fd with no message"); + assert_true(none && "can transmit fd ignoring no message"); + assert_true(chat && "can use the transmitted fds"); + + if (waitpid(new_pid, NULL, 1) > 0) + { + SPAWNED_PID = -1; + } + return; + } + Log(LOG_LEVEL_ERR, "Failed to set up parent"); + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_send_connect_silent(void) +{ + clear_previous_test(); + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Child failed to set up listener"); + } + else + { + child_for_send(server); + } + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + bool word, none, chat; + if (parent_send(-1, NULL, &word, &none, &chat)) + { + assert_true(word && "can transmit fd with no message"); + assert_true(none && "can transmit fd ignoring no message"); + assert_true(chat && "can use the transmitted fds"); + + if (waitpid(new_pid, NULL, 1) > 0) + { + SPAWNED_PID = -1; + } + return; + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_connect_outlive(void) +{ +#ifdef __APPLE__ + // FIXME: This test has spurios errors on OS X in travis + return; +#endif + clear_previous_test(); + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + child_for_outlive(-1); + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + bool chat; + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Parent failed to set up listener"); + } + else if (parent_outlive(server, &chat)) + { + assert_true(chat && "can use received fds after sender exits"); + return; + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +static void test_listen_outlive(void) +{ +#ifdef __APPLE__ + // FIXME: This test has spurios errors on OS X in travis + return; +#endif + clear_previous_test(); + pid_t new_pid = fork(); + if (new_pid == 0) + { + /* child */ + int server = setup_listener(); + if (server < 0) + { + Log(LOG_LEVEL_ERR, "Child failed to set up listener"); + } + else + { + child_for_outlive(server); + } + exit(0); + } + else if (new_pid > 0) + { + SPAWNED_PID = new_pid; + + /* parent */ + bool chat; + if (parent_outlive(-1, &chat)) + { + assert_true(chat && "can use received fds after sender exits"); + return; + } + } + else + { + Log(LOG_LEVEL_ERR, "Failed fork: %s", GetErrorStr()); + } + assert_true(false); +} + +/* The driver */ + +int main(void) +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_take_listen_message), + unit_test(test_take_connect_message), + unit_test(test_send_listen_message), + unit_test(test_send_connect_message), + unit_test(test_take_listen_silent), + unit_test(test_take_connect_silent), + unit_test(test_send_listen_silent), + unit_test(test_send_connect_silent), + unit_test(test_connect_outlive), + unit_test(test_listen_outlive) + }; + +#if 0 /* 1: toggle as needed. */ + LogSetGlobalLevel(LOG_LEVEL_VERBOSE); +#endif + + /* Needed to clean up the UDS created from the children, when they exit(), + for each test that the child calls setup_listener. Also cleans the last + socket (created by this parent process) when this testsuite exit. */ + atexit(clear_listener); + + int retval; + if (choose_dialup_UDS_file() == true) + { + retval = run_tests(tests); + } + else + { + retval = EXIT_FAILURE; + } + + /* Make sure no child is left behind. */ + if (SPAWNED_PID >= 0) + { + kill(SPAWNED_PID, SIGKILL); + } + + return retval; +} diff --git a/tests/unit/persistent_lock_test.c b/tests/unit/persistent_lock_test.c new file mode 100644 index 0000000000..435a7ae6d3 --- /dev/null +++ b/tests/unit/persistent_lock_test.c @@ -0,0 +1,45 @@ +#include + +#include +#include +#include /* xsnprintf */ +#include + + +char CFWORKDIR[CF_BUFSIZE]; + +static void tests_setup(void) +{ + OpenSSL_add_all_digests(); + /* FIXME: get rid of hardcoded filenames */ + xsnprintf(CFWORKDIR, CF_BUFSIZE, "/tmp/persistent_lock_test.XXXXXX"); + mkdtemp(CFWORKDIR); + + char buf[CF_BUFSIZE]; + xsnprintf(buf, CF_BUFSIZE, "%s", GetStateDir()); + mkdir(buf, 0755); +} + +static void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + +int main() +{ + PRINT_TEST_BANNER(); + tests_setup(); + + const UnitTest tests[] = + { + + }; + + int ret = run_tests(tests); + + tests_teardown(); + + return ret; +} diff --git a/tests/unit/policy_server_test.c b/tests/unit/policy_server_test.c new file mode 100644 index 0000000000..a5507b65c9 --- /dev/null +++ b/tests/unit/policy_server_test.c @@ -0,0 +1,192 @@ +#include + +#include +#include + +// PolicyServerGet functions should always return NULL instead of empty: +bool test_empty(const char *a) +{ + if(a == NULL) + { + return false; + } + if(a[0] == '\0') + { + return true; + } + return false; +} + +// assert_int_equal is not ideal for this(prints "1 != 0"), but works +#define test_never_empty(a, b, c, d)\ +{\ + assert_int_equal(test_empty(a), false);\ + assert_int_equal(test_empty(b), false);\ + assert_int_equal(test_empty(c), false);\ + assert_int_equal(test_empty(d), false);\ +}\ + +// General test of all variables (get functions) +#define test_one_case_generic(set, get, host, port, ip)\ +{\ + const char *a, *b, *c, *d;\ + PolicyServerSet(set);\ + assert_string_int_equal((a = PolicyServerGet()), get);\ + assert_string_int_equal((b = PolicyServerGetHost()), host);\ + assert_string_int_equal((c = PolicyServerGetPort()), port);\ + assert_string_int_equal((d = PolicyServerGetIP()), ip);\ + test_never_empty(a, b, c, d);\ +}\ + +// For testing hostnames, doesn't do any hostname->ip resolution: +#define test_no_ip_generic(set, get, host, port)\ +{\ + const char *a, *b, *c;\ + PolicyServerSet(set);\ + assert_string_int_equal((a = PolicyServerGet()), get);\ + assert_string_int_equal((b = PolicyServerGetHost()), host);\ + assert_string_int_equal((c = PolicyServerGetPort()), port);\ + test_never_empty(a, b, c, NULL);\ +}\ + +// Abbreviation of test_one_case_generic +#define test_one_case(bootstrap, host, port, ip)\ +{\ + test_one_case_generic(bootstrap, bootstrap, host, port, ip);\ +}\ + +// For inputs where we expect everything to return NULL +#define test_null_server(input)\ +{\ + test_one_case_generic(input, NULL, NULL, NULL, NULL);\ +}\ + +// For testing hostnames, doesn't do any hostname->ip resolution: +#define test_no_ip(bootstrap, host, port)\ +{\ + test_no_ip_generic(bootstrap, bootstrap, host, port);\ +}\ + + +static void test_PolicyServer_IPv4() +{ + // IPv4: + test_one_case( /* Set & Get */ "1.2.3.4", + /* Host,port */ NULL, NULL, + /* IP Addr */ "1.2.3.4"); + + test_one_case( /* Set & Get */ "255.255.255.255:80", + /* Host,port */ NULL, "80", + /* IP Addr */ "255.255.255.255"); + + test_one_case( /* Set & Get */ " 1.2.3.4:", + /* Host,port */ NULL, NULL, + /* IP Addr */ "1.2.3.4"); +} + +static void test_PolicyServer_IPv6() +{ + // IPv6 with square brackets: + test_one_case( /* Set & Get */ "[ffff::dd:12:34]", + /* Host,port */ NULL, NULL, + /* IP Addr */ "ffff::dd:12:34"); + + test_one_case( /* Set & Get */ "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:64000", + /* Host,port */ NULL, "64000", + /* IP Addr */ "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + + test_one_case( /* Set & Get */ "[ffff::dd:12:34]:", + /* Host,port */ NULL, NULL, + /* IP Addr */ "ffff::dd:12:34"); + + test_one_case( /* Set & Get */ "[FF01:0:0:0:0:0:0:FB]:12345", + /* Host,port */ NULL, "12345", + /* IP Addr */ "FF01:0:0:0:0:0:0:FB"); + + // IPv6 without square brackets: + test_one_case( /* Set & Get */ "ffff::dd:12:34", + /* Host,port */ NULL, NULL, + /* IP Addr */ "ffff::dd:12:34"); + + test_one_case( /* Set & Get */ "FF01:0:0:0:0:0:0:FB", + /* Host,port */ NULL, NULL, + /* IP Addr */ "FF01:0:0:0:0:0:0:FB"); + + test_one_case( /* Set & Get */ "::", + /* Host,port */ NULL, NULL, + /* IP Addr */ "::"); + + // IPv4 mapped IPv6 addresses: + test_one_case( /* Set & Get */ "::ffff:192.0.2.128", + /* Host,port */ NULL, NULL, + /* IP Addr */ "::ffff:192.0.2.128"); + + test_one_case( /* Set & Get */ "[::ffff:192.0.2.128]", + /* Host,port */ NULL, NULL, + /* IP Addr */ "::ffff:192.0.2.128"); + + // Other: + test_one_case( /* Set & Get */ "ff02::1:ff00:0/104", + /* Host,port */ NULL, NULL, + /* IP Addr */ "ff02::1:ff00:0/104"); + + + test_one_case( /* Set & Get */ "[ff02::1:ff00:0/104]:9", + /* Host,port */ NULL, "9", + /* IP Addr */ "ff02::1:ff00:0/104"); +} + +static void test_PolicyServer_Host() +{ + // Hostnames: + test_no_ip( /* Set & Get */ "localhost", + /* Host,port */ "localhost", NULL); + + test_no_ip( /* Set & Get */ "localhost:", + /* Host,port */ "localhost", NULL); + + test_no_ip( /* Set & Get */ "localhost:80", + /* Host,port */ "localhost", "80"); + + test_no_ip( /* Set & Get */ "[localhost]", + /* Host,port */ "localhost", NULL); + + test_no_ip( /* Set & Get */ "[localhost]:", + /* Host,port */ "localhost", NULL); + + test_no_ip( /* Set & Get */ "[localhost]:59999", + /* Host,port */ "localhost", "59999"); + + test_no_ip( /* Set & Get */ "[www.cfengine.com]", + /* Host,port */ "www.cfengine.com", NULL); + + test_no_ip( /* Set & Get */ "[www.cfengine.com]:8080", + /* Host,port */ "www.cfengine.com", "8080"); + + test_no_ip( /* Set & Get */ "[www.cfengine.com]:", + /* Host,port */ "www.cfengine.com", NULL); +} + +static void test_PolicyServer_Corner() +{ + // These tests expect PolicyServerGet() to return NULL: + test_null_server(""); + test_null_server(NULL); + test_null_server(" "); + test_null_server(" \n"); + test_null_server("\n"); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_PolicyServer_IPv4), + unit_test(test_PolicyServer_IPv6), + unit_test(test_PolicyServer_Host), + unit_test(test_PolicyServer_Corner) + }; + + return run_tests(tests); +} diff --git a/tests/unit/policy_test.c b/tests/unit/policy_test.c new file mode 100644 index 0000000000..11311d0c2c --- /dev/null +++ b/tests/unit/policy_test.c @@ -0,0 +1,489 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include /* xsnprintf */ + +static Policy *TestParsePolicy(const char *filename) +{ + char path[PATH_MAX]; + xsnprintf(path, sizeof(path), "%s/%s", TESTDATADIR, filename); + + return ParserParseFile(AGENT_TYPE_COMMON, path, PARSER_WARNING_ALL, PARSER_WARNING_ALL); +} + +static void DumpErrors(Seq *errs) +{ + if (SeqLength(errs) > 0) + { + Writer *writer = FileWriter(stdout); + for (size_t i = 0; i < errs->length; i++) + { + PolicyErrorWrite(writer, errs->data[i]); + } + FileWriterDetach(writer); + } +} + +static Seq *LoadAndCheck(const char *filename) +{ + Policy *p = TestParsePolicy(filename); + + Seq *errs = SeqNew(10, PolicyErrorDestroy); + PolicyCheckPartial(p, errs); + + DumpErrors(errs); + PolicyDestroy(p); + + return errs; +} + +static Seq *LoadAndCheckString(const char *policy_code) +{ + char tmp[] = TESTDATADIR "/cfengine_test.XXXXXX"; + mkstemp(tmp); + + { + FILE *out = fopen(tmp, "w"); + Writer *w = FileWriter(out); + WriterWrite(w, policy_code); + + WriterClose(w); + } + + Policy *p = ParserParseFile(AGENT_TYPE_COMMON, tmp, PARSER_WARNING_ALL, PARSER_WARNING_ALL); + assert_true(p); + + Seq *errs = SeqNew(10, PolicyErrorDestroy); + PolicyCheckPartial(p, errs); + + PolicyDestroy(p); + unlink(tmp); + return errs; +} + +static void test_failsafe(void) +{ + char tmp[] = TESTDATADIR "/cfengine_test.XXXXXX"; + mkstemp(tmp); + + WriteBuiltinFailsafePolicyToPath(tmp); + + Policy *failsafe = ParserParseFile(AGENT_TYPE_COMMON, tmp, PARSER_WARNING_ALL, PARSER_WARNING_ALL); + + unlink(tmp); + + assert_true(failsafe); + + Seq *errs = SeqNew(10, PolicyErrorDestroy); + PolicyCheckPartial(failsafe, errs); + + DumpErrors(errs); + assert_int_equal(0, SeqLength(errs)); + + { + EvalContext *ctx = EvalContextNew(); + + PolicyCheckRunnable(ctx, failsafe, errs); + + DumpErrors(errs); + assert_int_equal(0, SeqLength(errs)); + + EvalContextDestroy(ctx); + } + + assert_int_equal(0, (SeqLength(errs))); + + SeqDestroy(errs); + PolicyDestroy(failsafe); +} + + +static void test_bundle_redefinition(void) +{ + Seq *errs = LoadAndCheck("bundle_redefinition.cf"); + assert_int_equal(2, errs->length); + + SeqDestroy(errs); +} + +static void test_bundle_reserved_name(void) +{ + Seq *errs = LoadAndCheck("bundle_reserved_name.cf"); + assert_int_equal(1, errs->length); + + SeqDestroy(errs); +} + +static void test_body_redefinition(void) +{ + Seq *errs = LoadAndCheck("body_redefinition.cf"); + assert_int_equal(2, errs->length); + + SeqDestroy(errs); +} + +static void test_body_control_no_arguments(void) +{ + Seq *errs = LoadAndCheck("body_control_no_arguments.cf"); + assert_int_equal(1, errs->length); + + SeqDestroy(errs); +} + +static void test_vars_multiple_types(void) +{ + Seq *errs = LoadAndCheck("vars_multiple_types.cf"); + assert_int_equal(1, errs->length); + + SeqDestroy(errs); +} + +static void test_methods_invalid_arity(void) +{ + Seq *errs = LoadAndCheck("methods_invalid_arity.cf"); + assert_int_equal(1, errs->length); + + SeqDestroy(errs); +} + +static void test_promise_duplicate_handle(void) +{ + Seq *errs = LoadAndCheck("promise_duplicate_handle.cf"); + assert_int_equal(1, errs->length); + + SeqDestroy(errs); +} + +static void test_policy_json_to_from(void) +{ + EvalContext *ctx = EvalContextNew(); + Policy *policy = NULL; + { + Policy *original = TestParsePolicy("benchmark.cf"); + JsonElement *json = PolicyToJson(original); + PolicyDestroy(original); + policy = PolicyFromJson(json); + JsonDestroy(json); + } + assert_true(policy); + + assert_int_equal(1, SeqLength(policy->bundles)); + assert_int_equal(3, SeqLength(policy->bodies)); + + { + Bundle *main_bundle = PolicyGetBundle(policy, NULL, "agent", "main"); + assert_true(main_bundle); + { + { + const BundleSection *files = BundleGetSection(main_bundle, "files"); + assert_true(files); + assert_int_equal(1, SeqLength(files->promises)); + + for (size_t i = 0; i < SeqLength(files->promises); i++) + { + Promise *promise = SeqAt(files->promises, i); + + if (strcmp("/tmp/stuff", promise->promiser) == 0) + { + assert_string_equal("any", promise->classes); + + assert_int_equal(2, SeqLength(promise->conlist)); + + { + Constraint *create = PromiseGetConstraint(promise, "create"); + assert_true(create); + assert_string_equal("create", create->lval); + assert_string_equal("true", RvalScalarValue(create->rval)); + } + + { + Constraint *create = PromiseGetConstraint(promise, "perms"); + assert_true(create); + assert_string_equal("perms", create->lval); + assert_string_equal("myperms", RvalScalarValue(create->rval)); + } + } + else + { + fprintf(stderr, "Found unknown promise"); + fail(); + } + } + } + + { + const char* reportOutput[2] = { "Hello, CFEngine", "Hello, world" }; + const char* reportClass[2] = { "cfengine", "any" }; + const BundleSection *reports = BundleGetSection(main_bundle, "reports"); + assert_true(reports); + assert_int_equal(2, SeqLength(reports->promises)); + + for (size_t i = 0; i < SeqLength(reports->promises); i++) + { + Promise *promise = SeqAt(reports->promises, i); + + if (strcmp(reportOutput[i], promise->promiser) == 0) + { + assert_string_equal(reportClass[i], promise->classes); + + assert_int_equal(1, SeqLength(promise->conlist)); + + { + Constraint *friend_pattern = SeqAt(promise->conlist, 0); + assert_true(friend_pattern); + assert_string_equal("friend_pattern", friend_pattern->lval); + assert_int_equal(RVAL_TYPE_FNCALL, friend_pattern->rval.type); + FnCall *fn = RvalFnCallValue(friend_pattern->rval); + assert_string_equal("hash", fn->name); + assert_int_equal(2, RlistLen(fn->args)); + } + } + else + { + fprintf(stderr, "Found unknown promise"); + fail(); + } + } + } + } + } + + { + Body *myperms = PolicyGetBody(policy, NULL, "perms", "myperms"); + assert_true(myperms); + + { + Seq *mode_cps = BodyGetConstraint(myperms, "mode"); + assert_int_equal(1, SeqLength(mode_cps)); + + Constraint *mode = SeqAt(mode_cps, 0); + assert_string_equal("mode", mode->lval); + assert_string_equal("555", RvalScalarValue(mode->rval)); + SeqDestroy(mode_cps); + } + } + + PolicyDestroy(policy); + EvalContextDestroy(ctx); +} + +static void test_policy_json_offsets(void) +{ + JsonElement *json = NULL; + { + Policy *original = TestParsePolicy("benchmark.cf"); + json = PolicyToJson(original); + PolicyDestroy(original); + } + assert_true(json); + + JsonElement *json_bundles = JsonObjectGetAsArray(json, "bundles"); + { + JsonElement *main_bundle = JsonArrayGetAsObject(json_bundles, 0); + int line = JsonPrimitiveGetAsInteger(JsonObjectGet(main_bundle, "line")); + assert_int_equal(9, line); + + JsonElement *json_promise_types = JsonObjectGetAsArray(main_bundle, "promiseTypes"); + { + JsonElement *json_reports_type = JsonArrayGetAsObject(json_promise_types, 0); + line = JsonPrimitiveGetAsInteger(JsonObjectGet(json_reports_type, "line")); + assert_int_equal(11, line); + + JsonElement *json_contexts = JsonObjectGetAsArray(json_reports_type, "contexts"); + JsonElement *cf_context = JsonArrayGetAsObject(json_contexts, 0); + JsonElement *cf_context_promises = JsonObjectGetAsArray(cf_context, "promises"); + JsonElement *hello_cf_promise = JsonArrayGetAsObject(cf_context_promises, 0); + + line = JsonPrimitiveGetAsInteger(JsonObjectGet(hello_cf_promise, "line")); + assert_int_equal(13, line); + JsonElement *hello_cf_attribs = JsonObjectGetAsArray(hello_cf_promise, "attributes"); + { + JsonElement *friend_pattern_attrib = JsonArrayGetAsObject(hello_cf_attribs, 0); + + line = JsonPrimitiveGetAsInteger(JsonObjectGet(friend_pattern_attrib, "line")); + assert_int_equal(14, line); + } + } + } + + JsonElement *json_bodies = JsonObjectGetAsArray(json, "bodies"); + { + JsonElement *control_body = JsonArrayGetAsObject(json_bodies, 0); + int line = JsonPrimitiveGetAsInteger(JsonObjectGet(control_body, "line")); + assert_int_equal(4, line); + + JsonElement *myperms_body = JsonArrayGetAsObject(json_bodies, 1); + line = JsonPrimitiveGetAsInteger(JsonObjectGet(myperms_body, "line")); + assert_int_equal(29, line); + + JsonElement *myperms_contexts = JsonObjectGetAsArray(myperms_body, "contexts"); + JsonElement *any_context = JsonArrayGetAsObject(myperms_contexts, 0); + JsonElement *any_attribs = JsonObjectGetAsArray(any_context, "attributes"); + { + JsonElement *mode_attrib = JsonArrayGetAsObject(any_attribs, 0); + line = JsonPrimitiveGetAsInteger(JsonObjectGet(mode_attrib, "line")); + assert_int_equal(31, line); + } + } + + JsonDestroy(json); +} + + +static void test_util_bundle_qualified_name(void) +{ + Bundle *b = xcalloc(1, sizeof(struct Bundle_)); + assert_false(BundleQualifiedName(b)); + + b->name = "bar"; + + char *fqname = BundleQualifiedName(b); + assert_string_equal("default:bar", fqname); + free(fqname); + + b->ns = "foo"; + fqname = BundleQualifiedName(b); + assert_string_equal("foo:bar", fqname); + free(fqname); + + free(b); +} + +static void test_util_qualified_name_components(void) +{ + { + char *ns = QualifiedNameNamespaceComponent(":"); + assert_string_equal("", ns); + free(ns); + + char *sym = QualifiedNameScopeComponent(":"); + assert_string_equal("", sym); + free(sym); + } + + { + char *ns = QualifiedNameNamespaceComponent(""); + assert_false(ns); + free(ns); + + char *sym = QualifiedNameScopeComponent(""); + assert_string_equal("", sym); + free(sym); + } + + { + char *ns = QualifiedNameNamespaceComponent("foo"); + assert_false(ns); + free(ns); + + char *sym = QualifiedNameScopeComponent("foo"); + assert_string_equal("foo", sym); + free(sym); + } + + { + char *ns = QualifiedNameNamespaceComponent(":foo"); + assert_string_equal("", ns); + free(ns); + + char *sym = QualifiedNameScopeComponent(":foo"); + assert_string_equal("foo", sym); + free(sym); + } + + { + char *ns = QualifiedNameNamespaceComponent("foo:"); + assert_string_equal("foo", ns); + free(ns); + + char *sym = QualifiedNameScopeComponent("foo:"); + assert_string_equal("", sym); + free(sym); + } + + { + char *ns = QualifiedNameNamespaceComponent("foo:bar"); + assert_string_equal("foo", ns); + free(ns); + + char *sym = QualifiedNameScopeComponent("foo:bar"); + assert_string_equal("bar", sym); + free(sym); + } +} + +static void test_promiser_empty_varref(void) +{ + Seq *errs = LoadAndCheck("promiser_empty_varref.cf"); + assert_int_equal(1, errs->length); + + SeqDestroy(errs); +} + +static void test_constraint_comment_nonscalar(void) +{ + Seq *errs = LoadAndCheck("constraint_comment_nonscalar.cf"); + assert_int_equal(1, errs->length); + + SeqDestroy(errs); +} + +// TODO: consider moving this into a mod_common_test +static void test_body_action_with_log_repaired_needs_log_string(void) +{ + { + Seq *errs = LoadAndCheckString("body action foo {" + " log_repaired => '/tmp/abc';" + "}"); + assert_int_equal(1, errs->length); + SeqDestroy(errs); + } + + { + Seq *errs = LoadAndCheckString("body action foo {" + " log_repaired => '/tmp/abc';" + " log_string => 'stuff';" + "}"); + assert_int_equal(0, errs->length); + SeqDestroy(errs); + } +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_failsafe), + + unit_test(test_bundle_redefinition), + unit_test(test_bundle_reserved_name), + unit_test(test_body_redefinition), + unit_test(test_body_control_no_arguments), + unit_test(test_vars_multiple_types), + unit_test(test_methods_invalid_arity), + unit_test(test_promise_duplicate_handle), + + unit_test(test_policy_json_to_from), + unit_test(test_policy_json_offsets), + + unit_test(test_util_bundle_qualified_name), + unit_test(test_util_qualified_name_components), + + unit_test(test_constraint_comment_nonscalar), + + unit_test(test_promiser_empty_varref), + + unit_test(test_body_action_with_log_repaired_needs_log_string), + }; + + return run_tests(tests); +} + +// STUBS diff --git a/tests/unit/process_terminate_unix_test.c b/tests/unit/process_terminate_unix_test.c new file mode 100644 index 0000000000..3dffc6b1b4 --- /dev/null +++ b/tests/unit/process_terminate_unix_test.c @@ -0,0 +1,371 @@ +#include + +#include +#include +#include + +/* This mock implements single fake process with a several tunable parameters: + - process' start time, + - reaction time for STOP/CONT/INT/TERM/KILL signal, + - whether SIGINT/SIGTERM are blocked, + - does the caller have permission to send signals to fake process + + This mock also records what signals were delivered to the process to check later. +*/ + +/* Settings */ + +time_t proc_1_start_time; +time_t proc_1_reaction_time; +bool proc_1_int_blocked; +bool proc_1_term_blocked; +bool proc_1_have_access; + +/* State */ + +time_t current_time; + +bool exists; +bool stopped; + +time_t signal_time; +bool has_stop; +bool has_cont; +bool has_int; +bool has_term; +bool has_kill; + +/* History */ + +bool was_stopped; +int exit_signal; + +void InitTime(void) +{ + current_time = 1; +} + +void InitFakeProcess(time_t start_time, time_t reaction_time, + bool int_blocked, bool term_blocked, bool have_access) +{ + proc_1_start_time = start_time; + proc_1_reaction_time = reaction_time; + proc_1_int_blocked = int_blocked; + proc_1_term_blocked = term_blocked; + proc_1_have_access = have_access; + + exists = true; + stopped = false; + + signal_time = -1; + has_stop = false; + has_cont = false; + has_int = false; + has_term = false; + has_kill = false; + + was_stopped = false; + exit_signal = 0; +} + +time_t GetProcessStartTime(pid_t pid) +{ + assert_int_equal(pid, 1); + + if (proc_1_have_access && exists) + { + return proc_1_start_time; + } + else + { + return PROCESS_START_TIME_UNKNOWN; + } +} + +ProcessState GetProcessState(pid_t pid) +{ + assert_int_equal(pid, 1); + + if (!proc_1_have_access || !exists) + { + return PROCESS_STATE_DOES_NOT_EXIST; + } + + if (stopped) + { + return PROCESS_STATE_STOPPED; + } + else + { + return PROCESS_STATE_RUNNING; + } +} + +void FakeProcessDoSignals(void) +{ + /* React immediately to STOP and CONT in order to properly do the + * STOP-kill-CONT sequence inside SafeKill(). */ + if (has_stop) + { + stopped = true; + was_stopped = true; + has_stop = false; + } + + if (has_cont) + { + stopped = false; + has_cont = false; + } + + /* Do NOT react to other signals if it's not the time yet. */ + if (current_time < signal_time) + { + return; + } + + if (has_int) + { + if (!proc_1_int_blocked) + { + exists = false; + exit_signal = SIGINT; + stopped = false; + signal_time = -1; + } + has_int = false; + } + + if (has_term) + { + if (!proc_1_term_blocked) + { + exists = false; + exit_signal = SIGTERM; + stopped = false; + signal_time = -1; + } + has_term = false; + } + + if (has_kill) + { + exists = false; + exit_signal = SIGKILL; + stopped = false; + signal_time = -1; + + has_kill = false; + } + + signal_time = -1; +} + +int kill(pid_t pid, int signal) +{ + assert_int_equal(pid, 1); + + if (!proc_1_have_access) + { + errno = EPERM; + return -1; + } + + if (!exists) + { + errno = ESRCH; + return -1; + } + + if (signal == 0) + { + return 0; + } + + if (signal_time == -1) + { + signal_time = current_time + proc_1_reaction_time; + } + + if (signal == SIGSTOP) + { + has_stop = true; + } + else if (signal == SIGCONT) + { + has_cont = true; + } + else if (signal == SIGINT) + { + has_int = true; + } + else if (signal == SIGTERM) + { + has_term = true; + } + else if (signal == SIGKILL) + { + has_kill = true; + } + else + { + errno = EINVAL; + return -1; + } + + FakeProcessDoSignals(); + + return 0; +} + +int nanosleep(const struct timespec *req, struct timespec *rem) +{ + time_t sleep_time; + + /* Simulate EINTR every second time */ + + static bool got_eintr = false; + + if (!got_eintr) + { + got_eintr = true; + sleep_time = 2 * req->tv_nsec / 3; + } + else + { + got_eintr = false; + sleep_time = req->tv_nsec; + } + + time_t next_time = current_time + sleep_time; + if (signal_time != -1 && next_time >= signal_time) + { + FakeProcessDoSignals(); + } + + current_time = next_time; + + if (got_eintr) + { + rem->tv_sec = 0; + rem->tv_nsec = req->tv_nsec - sleep_time; + errno = EINTR; + return -1; + } + else + { + return 0; + } +} + +/* Tests */ + +void test_kill_simple_process(void) +{ + InitTime(); + InitFakeProcess(12345, 100, false, false, true); + + int res = GracefulTerminate(1, 12345); + assert_true(res); + + FakeProcessDoSignals(); + + assert_false(exists); + assert_int_equal(exit_signal, SIGINT); +} + +void test_kill_wrong_process(void) +{ + InitTime(); + InitFakeProcess(66666, 100, false, false, true); + + int res = GracefulTerminate(1, 12345); + assert_false(res); + + FakeProcessDoSignals(); + + assert_true(exists); + assert_false(stopped); + assert_false(was_stopped); /* We should not touch this process at all */ + assert_int_equal(signal_time, (time_t)-1); /* No pending signals either */ +} + +void test_kill_long_reacting_signal(void) +{ + /* This process is very slow in reaction. It should not be left stopped though */ + InitTime(); + InitFakeProcess(12345, 2000000000, false, false, true); + + int res = GracefulTerminate(1, 12345); + assert_true(res); + + FakeProcessDoSignals(); + + /* This process is not even reacting to SIGKILL. */ + assert_true(exists); +} + +void test_kill_no_sigint(void) +{ + /* This process blocks SIGINT */ + InitTime(); + InitFakeProcess(12345, 100, true, false, true); + + int res = GracefulTerminate(1, 12345); + assert_true(res); + + FakeProcessDoSignals(); + + assert_false(exists); + assert_int_equal(exit_signal, SIGTERM); +} + +void test_kill_no_sigint_sigterm(void) +{ + /* This process only can be killed by SIGKILL */ + InitTime(); + InitFakeProcess(12345, 100, true, true, true); + + int res = GracefulTerminate(1, 12345); + assert_true(res); + + /* Sleep a bit so that KILL is processed. */ + current_time += 100; + + FakeProcessDoSignals(); + + assert_false(exists); + assert_int_equal(exit_signal, SIGKILL); +} + +void test_kill_anothers_process(void) +{ + /* This process is not owned by killer */ + InitTime(); + InitFakeProcess(12345, 100, true, true, false); + + int res = GracefulTerminate(1, 12345); + assert_false(res); + + FakeProcessDoSignals(); + + assert_true(exists); + assert_false(was_stopped); +} + +int main() +{ + PRINT_TEST_BANNER(); + + const UnitTest tests[] = + { + unit_test(test_kill_simple_process), + unit_test(test_kill_wrong_process), + unit_test(test_kill_long_reacting_signal), + unit_test(test_kill_no_sigint), + unit_test(test_kill_no_sigint_sigterm), + unit_test(test_kill_anothers_process), + }; + + return run_tests(tests); +} diff --git a/tests/unit/process_test.c b/tests/unit/process_test.c new file mode 100644 index 0000000000..cd5da288ec --- /dev/null +++ b/tests/unit/process_test.c @@ -0,0 +1,262 @@ +/* + Copyright 2024 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#include +#include +#include + +/** + * This file contains process-handling tests that need to fork() a child process + * which totally confuses the unit test framework used by the other tests. + */ + +static bool failure = false; + +#define assert_int_equal(expr1, expr2) \ + if ((expr1) != (expr2)) \ + { \ + fprintf(stderr, "FAIL: "#expr1" != "#expr2" [%jd != %jd] (%s:%d)\n", ((intmax_t) expr1), ((intmax_t) expr2), __FILE__, __LINE__); \ + failure = true; \ + } + +#define assert_int_not_equal(expr1, expr2) \ + if ((expr1) == (expr2)) \ + { \ + fprintf(stderr, "FAIL: "#expr1" == "#expr2" [%jd == %jd] (%s:%d)\n", ((intmax_t) expr1), ((intmax_t) expr2), __FILE__, __LINE__); \ + failure = true; \ + } + +#define assert_true(expr) \ + if (!(expr)) \ + { \ + fprintf(stderr, "FAIL: "#expr" is FALSE (%s:%d)\n", __FILE__, __LINE__); \ + failure = true; \ + } + +#define assert_false(expr) \ + if ((expr)) \ + { \ + fprintf(stderr, "FAIL: "#expr" is TRUE (%s:%d)\n", __FILE__, __LINE__); \ + failure = true; \ + } + +pid_t SPAWNED_PID; +pid_t THIS_PID; +time_t THIS_STARTTIME; + + +static void test_process_start_time(void) +{ + /* Wait a couple of seconds so that process start time differs. */ + printf("Sleeping 2 seconds...\n"); + sleep(2); + + pid_t new_pid = fork(); + assert_true(new_pid >= 0); + + if (new_pid == 0) /* child */ + { + execl("/bin/sleep", "/bin/sleep", "30", NULL); + assert_true(false); /* unreachable */ + } + + SPAWNED_PID = new_pid; + time_t newproc_starttime = GetProcessStartTime(new_pid); + + printf("Spawned a \"sleep\" child with PID %jd and start_time %jd\n", + (intmax_t) new_pid, (intmax_t) newproc_starttime); + + // We might have slipped by a few seconds, but shouldn't be much. + assert_int_not_equal(newproc_starttime, PROCESS_START_TIME_UNKNOWN); + assert_true(newproc_starttime >= THIS_STARTTIME + 1); + assert_true(newproc_starttime <= THIS_STARTTIME + 15); + + kill(new_pid, SIGKILL); + wait(NULL); + SPAWNED_PID = 0; +} + +static void test_process_state(void) +{ + int ret; + + pid_t new_pid = fork(); + assert_true(new_pid >= 0); + + if (new_pid == 0) /* child */ + { + execl("/bin/sleep", "/bin/sleep", "30", NULL); + assert_true(false); /* unreachable */ + } + + SPAWNED_PID = new_pid; + printf("Spawned a \"sleep\" child with PID %d\n", new_pid); + + int state = -1000; + + for (int c = 0; c < 10; c++) + { + state = GetProcessState(new_pid); + if (state == PROCESS_STATE_RUNNING) + { + break; + } + else + { + usleep(200000); + } + } + printf("Started, state: %d\n", state); + assert_int_equal(state, PROCESS_STATE_RUNNING); + + ret = kill(new_pid, SIGSTOP); + assert_int_equal(ret, 0); + + for (int c = 0; c < 10; c++) + { + state = GetProcessState(new_pid); + if (state == PROCESS_STATE_STOPPED) + { + break; + } + else + { + usleep(200000); + } + } + printf("Stopped, state: %d\n", state); + assert_int_equal(state, PROCESS_STATE_STOPPED); + + ret = kill(new_pid, SIGCONT); + assert_int_equal(ret, 0); + + for (int c = 0; c < 10; c++) + { + state = GetProcessState(new_pid); + if (state == PROCESS_STATE_RUNNING) + { + break; + } + else + { + usleep(200000); + } + } + printf("Resumed, state: %d\n", state); + assert_int_equal(state, PROCESS_STATE_RUNNING); + + /* Terminate the child process and reap the zombie. */ + kill(new_pid, SIGKILL); + wait(NULL); + + state = GetProcessState(new_pid); + printf("Killed, state: %d\n", state); + assert_int_equal(state, PROCESS_STATE_DOES_NOT_EXIST); + + SPAWNED_PID = 0; +} + +static void test_graceful_terminate(void) +{ + int ret, state; + + pid_t new_pid = fork(); + assert_true(new_pid >= 0); + + if (new_pid == 0) /* child */ + { + execl("/bin/sleep", "/bin/sleep", "30", NULL); + assert_true(false); /* unreachable */ + } + + time_t start_time = GetProcessStartTime(new_pid); + SPAWNED_PID = new_pid; + + printf("Spawned a \"sleep\" child with PID %jd and start_time %jd\n", + (intmax_t) new_pid, (intmax_t) start_time); + + state = GetProcessState(new_pid); + assert_int_equal(state, PROCESS_STATE_RUNNING); + + printf("Killing child with wrong start_time, child should not die...\n"); + + ret = GracefulTerminate(new_pid, 12345); /* fake start time */ + assert_false(ret); + + state = GetProcessState(new_pid); + assert_int_equal(state, PROCESS_STATE_RUNNING); + + printf("Killing child with correct start_time, child should die...\n"); + + ret = GracefulTerminate(new_pid, start_time); + assert_true(ret); + + state = GetProcessState(new_pid); + assert_int_equal(state, PROCESS_STATE_ZOMBIE); + + wait(NULL); /* reap child */ + + state = GetProcessState(new_pid); + assert_int_equal(state, PROCESS_STATE_DOES_NOT_EXIST); + + printf("Child Dead!\n"); + SPAWNED_PID = 0; + + printf("Killing ourself, should fail...\n"); + ret = GracefulTerminate(THIS_PID, THIS_STARTTIME); + assert_false(ret); + + printf("Killing ourself without specifying starttime, should fail...\n"); + ret = GracefulTerminate(THIS_PID, PROCESS_START_TIME_UNKNOWN); + assert_false(ret); +} + + +int main() +{ + puts("=================================================="); + puts("Starting test process_test.c"); + puts("=================================================="); + + /* Don't miss the messages about GetProcessStartTime not implemented. */ + LogSetGlobalLevel(LOG_LEVEL_DEBUG); + + THIS_PID = getpid(); + THIS_STARTTIME = GetProcessStartTime(THIS_PID); + + printf("This parent process has PID %jd and start_time %jd\n", + (intmax_t) THIS_PID, (intmax_t) THIS_STARTTIME); + + test_process_start_time(); + test_process_state(); + test_graceful_terminate(); + + /* Make sure no child is alive. */ + if (SPAWNED_PID > 0) + { + kill(SPAWNED_PID, SIGKILL); + } + + return failure ? 1 : 0; +} diff --git a/tests/unit/protocol_test.c b/tests/unit/protocol_test.c new file mode 100644 index 0000000000..29b30d31a5 --- /dev/null +++ b/tests/unit/protocol_test.c @@ -0,0 +1,463 @@ +#include + +#include +#include +#include +#include +#include + +#include /* GetCommandClassic */ + + +/* + * The protocol consists of the following commands: + * "EXEC", + * "AUTH", + * "GET", + * "OPENDIR", + * "SYNCH", + * "CLASSES", + * "MD5", + * "SMD5", + * "CAUTH", + * "SAUTH", + * "SSYNCH", + * "SGET", + * "VERSION", + * "SOPENDIR", + * "VAR", + * "SVAR", + * "CONTEXT", + * "SCONTEXT", + * "SQUERY", + * "SCALLBACK", + */ + +static void test_command_parser(void) +{ + ProtocolCommandClassic parsed = PROTOCOL_COMMAND_BAD; + ProtocolCommandClassic expected = PROTOCOL_COMMAND_BAD; + /* + * Test all the commands, one by one. + */ + // EXEC + expected = PROTOCOL_COMMAND_EXEC; + parsed = GetCommandClassic("EXEC"); + assert_int_equal(expected, parsed); + // AUTH + expected = PROTOCOL_COMMAND_AUTH; + parsed = GetCommandClassic("AUTH"); + assert_int_equal(expected, parsed); + // GET + expected = PROTOCOL_COMMAND_GET; + parsed = GetCommandClassic("GET"); + assert_int_equal(expected, parsed); + // OPENDIR + expected = PROTOCOL_COMMAND_OPENDIR; + parsed = GetCommandClassic("OPENDIR"); + assert_int_equal(expected, parsed); + // SYNCH + expected = PROTOCOL_COMMAND_SYNC; + parsed = GetCommandClassic("SYNCH"); + assert_int_equal(expected, parsed); + // CLASSES + expected = PROTOCOL_COMMAND_CONTEXTS; + parsed = GetCommandClassic("CLASSES"); + assert_int_equal(expected, parsed); + // MD5 + expected = PROTOCOL_COMMAND_MD5; + parsed = GetCommandClassic("MD5"); + assert_int_equal(expected, parsed); + // SMD5 + expected = PROTOCOL_COMMAND_MD5_SECURE; + parsed = GetCommandClassic("SMD5"); + assert_int_equal(expected, parsed); + // CAUTH + expected = PROTOCOL_COMMAND_AUTH_PLAIN; + parsed = GetCommandClassic("CAUTH"); + assert_int_equal(expected, parsed); + // SAUTH + expected = PROTOCOL_COMMAND_AUTH_SECURE; + parsed = GetCommandClassic("SAUTH"); + assert_int_equal(expected, parsed); + // SSYNCH + expected = PROTOCOL_COMMAND_SYNC_SECURE; + parsed = GetCommandClassic("SSYNCH"); + assert_int_equal(expected, parsed); + // SGET + expected = PROTOCOL_COMMAND_GET_SECURE; + parsed = GetCommandClassic("SGET"); + assert_int_equal(expected, parsed); + // VERSION + expected = PROTOCOL_COMMAND_VERSION; + parsed = GetCommandClassic("VERSION"); + assert_int_equal(expected, parsed); + // SOPENDIR + expected = PROTOCOL_COMMAND_OPENDIR_SECURE; + parsed = GetCommandClassic("SOPENDIR"); + assert_int_equal(expected, parsed); + // VAR + expected = PROTOCOL_COMMAND_VAR; + parsed = GetCommandClassic("VAR"); + assert_int_equal(expected, parsed); + // SVAR + expected = PROTOCOL_COMMAND_VAR_SECURE; + parsed = GetCommandClassic("SVAR"); + assert_int_equal(expected, parsed); + // CONTEXT + expected = PROTOCOL_COMMAND_CONTEXT; + parsed = GetCommandClassic("CONTEXT"); + assert_int_equal(expected, parsed); + // SCONTEXT + expected = PROTOCOL_COMMAND_CONTEXT_SECURE; + parsed = GetCommandClassic("SCONTEXT"); + assert_int_equal(expected, parsed); + // SQUERY + expected = PROTOCOL_COMMAND_QUERY_SECURE; + parsed = GetCommandClassic("SQUERY"); + assert_int_equal(expected, parsed); + // SCALLBACK + expected = PROTOCOL_COMMAND_CALL_ME_BACK; + parsed = GetCommandClassic("SCALLBACK"); + assert_int_equal(expected, parsed); + /* + * Try using lowercase + */ + // EXEC + expected = PROTOCOL_COMMAND_BAD; + parsed = GetCommandClassic("exec"); + assert_int_equal(expected, parsed); + // AUTH + parsed = GetCommandClassic("auth"); + assert_int_equal(expected, parsed); + // GET + parsed = GetCommandClassic("get"); + assert_int_equal(expected, parsed); + // OPENDIR + parsed = GetCommandClassic("opendir"); + assert_int_equal(expected, parsed); + // SYNCH + parsed = GetCommandClassic("synch"); + assert_int_equal(expected, parsed); + // CLASSES + parsed = GetCommandClassic("classes"); + assert_int_equal(expected, parsed); + // MD5 + parsed = GetCommandClassic("md5"); + assert_int_equal(expected, parsed); + // SMD5 + parsed = GetCommandClassic("smd5"); + assert_int_equal(expected, parsed); + // CAUTH + parsed = GetCommandClassic("cauth"); + assert_int_equal(expected, parsed); + // SAUTH + parsed = GetCommandClassic("sauth"); + assert_int_equal(expected, parsed); + // SSYNCH + parsed = GetCommandClassic("synch"); + assert_int_equal(expected, parsed); + // SGET + parsed = GetCommandClassic("sget"); + assert_int_equal(expected, parsed); + // VERSION + parsed = GetCommandClassic("version"); + assert_int_equal(expected, parsed); + // SOPENDIR + parsed = GetCommandClassic("sopendir"); + assert_int_equal(expected, parsed); + // VAR + parsed = GetCommandClassic("var"); + assert_int_equal(expected, parsed); + // SVAR + parsed = GetCommandClassic("svar"); + assert_int_equal(expected, parsed); + // CONTEXT + parsed = GetCommandClassic("context"); + assert_int_equal(expected, parsed); + // SCONTEXT + parsed = GetCommandClassic("scontext"); + assert_int_equal(expected, parsed); + // SQUERY + parsed = GetCommandClassic("squery"); + assert_int_equal(expected, parsed); + // SCALLBACK + parsed = GetCommandClassic("scallback"); + assert_int_equal(expected, parsed); + /* + * Try the commands with something in front + */ + // EXEC + expected = PROTOCOL_COMMAND_BAD; + parsed = GetCommandClassic("eEXEC"); + assert_int_equal(expected, parsed); + // AUTH + parsed = GetCommandClassic("aAUTH"); + assert_int_equal(expected, parsed); + // GET + parsed = GetCommandClassic("gGET"); + assert_int_equal(expected, parsed); + // OPENDIR + parsed = GetCommandClassic("oOPENDIR"); + assert_int_equal(expected, parsed); + // SYNCH + parsed = GetCommandClassic("sSYNCH"); + assert_int_equal(expected, parsed); + // CLASSES + parsed = GetCommandClassic("cCLASSES"); + assert_int_equal(expected, parsed); + // MD5 + parsed = GetCommandClassic("mMD5"); + assert_int_equal(expected, parsed); + // SMD5 + parsed = GetCommandClassic("sMD5"); + assert_int_equal(expected, parsed); + // CAUTH + parsed = GetCommandClassic("cCAUTH"); + assert_int_equal(expected, parsed); + // SAUTH + parsed = GetCommandClassic("sSAUTH"); + assert_int_equal(expected, parsed); + // SSYNCH + parsed = GetCommandClassic("sSSYNCH"); + assert_int_equal(expected, parsed); + // SGET + parsed = GetCommandClassic("sSGET"); + assert_int_equal(expected, parsed); + // VERSION + parsed = GetCommandClassic("vVERSION"); + assert_int_equal(expected, parsed); + // SOPENDIR + parsed = GetCommandClassic("sSOPENDIR"); + assert_int_equal(expected, parsed); + // VAR + parsed = GetCommandClassic("vVAR"); + assert_int_equal(expected, parsed); + // SVAR + parsed = GetCommandClassic("sSVAR"); + assert_int_equal(expected, parsed); + // CONTEXT + parsed = GetCommandClassic("cCONTEXT"); + assert_int_equal(expected, parsed); + // SCONTEXT + parsed = GetCommandClassic("sSCONTEXT"); + assert_int_equal(expected, parsed); + // SQUERY + parsed = GetCommandClassic("sSQUERY"); + assert_int_equal(expected, parsed); + // SCALLBACK + parsed = GetCommandClassic("sSCALLBACK"); + assert_int_equal(expected, parsed); + /* + * Try the commands with something after them + */ + // EXEC + expected = PROTOCOL_COMMAND_BAD; + parsed = GetCommandClassic("EXECx"); + assert_int_equal(expected, parsed); + // AUTH + parsed = GetCommandClassic("AUTHx"); + assert_int_equal(expected, parsed); + // GET + parsed = GetCommandClassic("GETx"); + assert_int_equal(expected, parsed); + // OPENDIR + parsed = GetCommandClassic("OPENDIRx"); + assert_int_equal(expected, parsed); + // SYNCH + parsed = GetCommandClassic("SYNCHx"); + assert_int_equal(expected, parsed); + // CLASSES + parsed = GetCommandClassic("CLASSESx"); + assert_int_equal(expected, parsed); + // MD5 + parsed = GetCommandClassic("MD5x"); + assert_int_equal(expected, parsed); + // SMD5 + parsed = GetCommandClassic("SMD5x"); + assert_int_equal(expected, parsed); + // CAUTH + parsed = GetCommandClassic("CAUTHx"); + assert_int_equal(expected, parsed); + // SAUTH + parsed = GetCommandClassic("SAUTHx"); + assert_int_equal(expected, parsed); + // SSYNCH + parsed = GetCommandClassic("SSYNCHx"); + assert_int_equal(expected, parsed); + // SGET + parsed = GetCommandClassic("SGETx"); + assert_int_equal(expected, parsed); + // VERSION + parsed = GetCommandClassic("VERSIONx"); + assert_int_equal(expected, parsed); + // SOPENDIR + parsed = GetCommandClassic("SOPENDIRx"); + assert_int_equal(expected, parsed); + // VAR + parsed = GetCommandClassic("VARx"); + assert_int_equal(expected, parsed); + // SVAR + parsed = GetCommandClassic("SVARx"); + assert_int_equal(expected, parsed); + // CONTEXT + parsed = GetCommandClassic("CONTEXTx"); + assert_int_equal(expected, parsed); + // SCONTEXT + parsed = GetCommandClassic("SCONTEXTx"); + assert_int_equal(expected, parsed); + // SQUERY + parsed = GetCommandClassic("SQUERYx"); + assert_int_equal(expected, parsed); + // SCALLBACK + parsed = GetCommandClassic("SCALLBACKx"); + assert_int_equal(expected, parsed); + /* + * Try some common mispellings. + */ + // EXEC + expected = PROTOCOL_COMMAND_BAD; + parsed = GetCommandClassic("EXE"); + assert_int_equal(expected, parsed); + parsed = GetCommandClassic("EXECUTE"); + assert_int_equal(expected, parsed); + // AUTH + parsed = GetCommandClassic("AUTHORIZATION"); + assert_int_equal(expected, parsed); + // SYNCH + parsed = GetCommandClassic("SYNC"); + assert_int_equal(expected, parsed); + parsed = GetCommandClassic("SYNCHRONIZE"); + assert_int_equal(expected, parsed); + // CLASSES + parsed = GetCommandClassic("CLASS"); + assert_int_equal(expected, parsed); + // CAUTH + parsed = GetCommandClassic("CAUTHORIZATION"); + assert_int_equal(expected, parsed); + // SAUTH + parsed = GetCommandClassic("SAUTHORIZATION"); + assert_int_equal(expected, parsed); + // SSYNCH + parsed = GetCommandClassic("SSYNCHRONIZE"); + assert_int_equal(expected, parsed); + parsed = GetCommandClassic("SSYNC"); + assert_int_equal(expected, parsed); + // VERSION + parsed = GetCommandClassic("V"); + assert_int_equal(expected, parsed); + // VAR + parsed = GetCommandClassic("VARIABLE"); + assert_int_equal(expected, parsed); + // SVAR + parsed = GetCommandClassic("SVARIABLE"); + assert_int_equal(expected, parsed); + /* + * Finally, try the commands with a space and something else, they should be recognized" + */ + // EXEC + expected = PROTOCOL_COMMAND_EXEC; + parsed = GetCommandClassic("EXEC 123"); + assert_int_equal(expected, parsed); + // AUTH + expected = PROTOCOL_COMMAND_AUTH; + parsed = GetCommandClassic("AUTH 123"); + assert_int_equal(expected, parsed); + // GET + expected = PROTOCOL_COMMAND_GET; + parsed = GetCommandClassic("GET 123"); + assert_int_equal(expected, parsed); + // OPENDIR + expected = PROTOCOL_COMMAND_OPENDIR; + parsed = GetCommandClassic("OPENDIR 123"); + assert_int_equal(expected, parsed); + // SYNCH + expected = PROTOCOL_COMMAND_SYNC; + parsed = GetCommandClassic("SYNCH 123"); + assert_int_equal(expected, parsed); + // CLASSES + expected = PROTOCOL_COMMAND_CONTEXTS; + parsed = GetCommandClassic("CLASSES 123"); + assert_int_equal(expected, parsed); + // MD5 + expected = PROTOCOL_COMMAND_MD5; + parsed = GetCommandClassic("MD5 123"); + assert_int_equal(expected, parsed); + // SMD5 + expected = PROTOCOL_COMMAND_MD5_SECURE; + parsed = GetCommandClassic("SMD5 123"); + assert_int_equal(expected, parsed); + // CAUTH + expected = PROTOCOL_COMMAND_AUTH_PLAIN; + parsed = GetCommandClassic("CAUTH 123"); + assert_int_equal(expected, parsed); + // SAUTH + expected = PROTOCOL_COMMAND_AUTH_SECURE; + parsed = GetCommandClassic("SAUTH 123"); + assert_int_equal(expected, parsed); + // SSYNCH + expected = PROTOCOL_COMMAND_SYNC_SECURE; + parsed = GetCommandClassic("SSYNCH 123"); + assert_int_equal(expected, parsed); + // SGET + expected = PROTOCOL_COMMAND_GET_SECURE; + parsed = GetCommandClassic("SGET 123"); + assert_int_equal(expected, parsed); + // VERSION + expected = PROTOCOL_COMMAND_VERSION; + parsed = GetCommandClassic("VERSION 123"); + assert_int_equal(expected, parsed); + // SOPENDIR + expected = PROTOCOL_COMMAND_OPENDIR_SECURE; + parsed = GetCommandClassic("SOPENDIR 123"); + assert_int_equal(expected, parsed); + // VAR + expected = PROTOCOL_COMMAND_VAR; + parsed = GetCommandClassic("VAR 123"); + assert_int_equal(expected, parsed); + // SVAR + expected = PROTOCOL_COMMAND_VAR_SECURE; + parsed = GetCommandClassic("SVAR 123"); + assert_int_equal(expected, parsed); + // CONTEXT + expected = PROTOCOL_COMMAND_CONTEXT; + parsed = GetCommandClassic("CONTEXT 123"); + assert_int_equal(expected, parsed); + // SCONTEXT + expected = PROTOCOL_COMMAND_CONTEXT_SECURE; + parsed = GetCommandClassic("SCONTEXT 123"); + assert_int_equal(expected, parsed); + // SQUERY + expected = PROTOCOL_COMMAND_QUERY_SECURE; + parsed = GetCommandClassic("SQUERY 123"); + assert_int_equal(expected, parsed); + // SCALLBACK + expected = PROTOCOL_COMMAND_CALL_ME_BACK; + parsed = GetCommandClassic("SCALLBACK 123"); + assert_int_equal(expected, parsed); +} + +static void test_user_name(void) +{ + char invalid_user_name[] = "user\\"; + char invalid_user_name2[] = "user//"; + char invalid_user_name3[] = "//\\"; + char valid_user_name[] = "valid_user"; + + assert_false(IsUserNameValid(invalid_user_name)); + assert_false(IsUserNameValid(invalid_user_name2)); + assert_false(IsUserNameValid(invalid_user_name3)); + assert_true(IsUserNameValid(valid_user_name)); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_command_parser), + unit_test(test_user_name) + }; + + return run_tests(tests); +} diff --git a/tests/unit/redirection_test.c b/tests/unit/redirection_test.c new file mode 100644 index 0000000000..fc55d94829 --- /dev/null +++ b/tests/unit/redirection_test.c @@ -0,0 +1,86 @@ +#include + +#include +#include +#include +#include +#include +#include +#include // dirname + +/* + * This test checks for different types of IPC to check that redirection of + * STDIN will work. + * This test is only significant in Linux, because this is only used by the + * hub process that only runs on Linux. + */ +static char redirection[] = "/tmp/redirectionXXXXXX"; +static char path[PATH_MAX]; +static char *message = "This is the message to be written by our helper"; +static char message_back[128]; +static void test_fd_redirection(void) +{ + assert_true(path != NULL); + /* + * Create a file for writing, then start a new process and wait for it + * to write to the file. + * Check the contents of the file. + */ + int fd = -1; + fd = mkstemp(redirection); + assert_int_not_equal(-1, fd); + /* Start the new process */ + int pid = 0; + pid = fork(); + assert_int_not_equal(-1, pid); + if (pid == 0) + { + /* Child */ + dup2(fd, STDIN_FILENO); + char *argv[] = { path, message, NULL }; + char *envp[] = { NULL }; + printf("path: %s\n", path); + execve(path, argv, envp); + printf("execve failed: %s\n", strerror(errno)); + exit(-1); + } + else + { + /* Parent */ + int status = 0; + int options = 0; + /* Wait for the child to be done */ + assert_int_equal(waitpid(pid, &status, options), pid); + /* Did it exit correctly? */ + assert_true(WIFEXITED(status)); + assert_int_equal(WEXITSTATUS(status), 0); + /* Rewind the file so we can check the message */ + lseek(fd, 0, SEEK_SET); + /* Read back the message */ + assert_int_equal(strlen(message), read(fd, message_back, strlen(message))); + /* Compare it */ + assert_string_equal(message, message_back); + } + close (fd); +} + +void tests_teardown() +{ + unlink (redirection); +} + +int main(int argc, char **argv) +{ + PRINT_TEST_BANNER(); + assert(argc >= 1); + /* Find the proper path for our helper */ + char *base = dirname(argv[0]); + char *helper = "/../redirection_test_stub"; + sprintf(path, "%s%s", base, helper); + const UnitTest tests[] = + { + unit_test(test_fd_redirection) + }; + tests_teardown(); + return run_tests(tests); +} diff --git a/tests/unit/redirection_test_stub.c b/tests/unit/redirection_test_stub.c new file mode 100644 index 0000000000..089025aee9 --- /dev/null +++ b/tests/unit/redirection_test_stub.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 2) + return -1; + char *text = argv[1]; + int output = STDIN_FILENO; + int result = 0; + result = write(output, text, strlen(text)); + if (result < 0) + return -1; + fsync(output); + return 0; +} diff --git a/tests/unit/regex_test.c b/tests/unit/regex_test.c new file mode 100644 index 0000000000..dcfb660956 --- /dev/null +++ b/tests/unit/regex_test.c @@ -0,0 +1,58 @@ +#include + +#include +#include +#include +#include + +static void test_full_text_match(void) +{ + EvalContext *ctx = EvalContextNew(); + assert_int_equal(FullTextMatch(ctx, "[a-z]*", "1234abcd6789"), 0); + EvalContextDestroy(ctx); +} + +static void test_full_text_match2(void) +{ + EvalContext *ctx = EvalContextNew(); + assert_int_not_equal(FullTextMatch(ctx, "[1-4]*[a-z]*.*", "1234abcd6789"), 0); + EvalContextDestroy(ctx); +} + +static void test_block_text_match(void) +{ + EvalContext *ctx = EvalContextNew(); + int start, end; + + assert_int_not_equal(BlockTextMatch(ctx, "#[^\n]*", "line 1:\nline2: # comment to end\nline 3: blablab", &start, &end), + 0); + + assert_int_equal(start, 15); + assert_int_equal(end, 31); + EvalContextDestroy(ctx); +} + +static void test_block_text_match2(void) +{ + EvalContext *ctx = EvalContextNew(); + int start, end; + + assert_int_not_equal(BlockTextMatch(ctx, "[a-z]+", "1234abcd6789", &start, &end), 0); + assert_int_equal(start, 4); + assert_int_equal(end, 8); + EvalContextDestroy(ctx); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_full_text_match), + unit_test(test_full_text_match2), + unit_test(test_block_text_match), + unit_test(test_block_text_match2), + }; + + return run_tests(tests); +} diff --git a/tests/unit/rlist_test.c b/tests/unit/rlist_test.c new file mode 100644 index 0000000000..5064fe08a7 --- /dev/null +++ b/tests/unit/rlist_test.c @@ -0,0 +1,939 @@ +#include + +#include + +#include +#include + +#include +#include + +/* Stubs */ + +void FatalError(ARG_UNUSED char *s, ...) +{ + mock_assert(0, "0", __FILE__, __LINE__); + abort(); +} + +static void test_length(void) +{ + Rlist *list = NULL; + + assert_int_equal(RlistLen(list), 0); + + RlistPrepend(&list, "stuff", RVAL_TYPE_SCALAR); + assert_int_equal(RlistLen(list), 1); + + RlistPrepend(&list, "more-stuff", RVAL_TYPE_SCALAR); + assert_int_equal(RlistLen(list), 2); + + RlistDestroy(list); +} + +static void test_equality(void) +{ + Rlist *list1 = RlistFromSplitString("a,b,c", ','); + Rlist *list2 = RlistFromSplitString("a,b,c", ','); + Rlist *list3 = RlistFromSplitString("z,b,c", ','); + Rlist *list4 = RlistFromSplitString("a,b,c,d", ','); + + assert_true(RlistEqual(list1, list2)); + assert_false(RlistEqual(list1, list3)); + assert_false(RlistEqual(list1, list4)); + + RlistDestroy(list1); + RlistDestroy(list2); + RlistDestroy(list3); + RlistDestroy(list4); +} + +static void test_prepend_scalar_idempotent(void) +{ + Rlist *list = NULL; + + RlistPrependScalarIdemp(&list, "stuff"); + RlistPrependScalarIdemp(&list, "stuff"); + + assert_string_equal(RlistScalarValue(list), "stuff"); + assert_int_equal(RlistLen(list), 1); + + RlistDestroy(list); +} + +static void test_copy(void) +{ + Rlist *list = NULL, *copy = NULL; + + RlistPrepend(&list, "stuff", RVAL_TYPE_SCALAR); + RlistPrepend(&list, "more-stuff", RVAL_TYPE_SCALAR); + + copy = RlistCopy(list); + + assert_string_equal(RlistScalarValue(list), RlistScalarValue(copy)); + assert_string_equal(RlistScalarValue(list->next), RlistScalarValue(copy->next)); + + RlistDestroy(list); + RlistDestroy(copy); +} + +static void test_rval_to_scalar(void) +{ + Rval rval = { "abc", RVAL_TYPE_SCALAR }; + assert_string_equal("abc", RvalScalarValue(rval)); +} + +static void test_rval_to_scalar2(void) +{ + Rval rval = { NULL, RVAL_TYPE_FNCALL }; + expect_assert_failure(RvalScalarValue(rval)); +} + +static void test_rval_to_list(void) +{ + Rval rval = { NULL, RVAL_TYPE_SCALAR }; + expect_assert_failure(RvalRlistValue(rval)); +} + +static void test_rval_to_list2(void) +{ + Rval rval = { NULL, RVAL_TYPE_LIST }; + assert_false(RvalRlistValue(rval)); +} + +static void test_rval_to_fncall(void) +{ + Rval rval = { NULL, RVAL_TYPE_SCALAR }; + expect_assert_failure(RvalFnCallValue(rval)); +} + +static void test_rval_to_fncall2(void) +{ + Rval rval = { NULL, RVAL_TYPE_FNCALL }; + assert_false(RvalFnCallValue(rval)); +} + +static void test_last(void) +{ + Rlist *l = NULL; + assert_true(RlistLast(l) == NULL); + RlistAppendScalar(&l, "a"); + assert_string_equal("a", RlistScalarValue(RlistLast(l))); + RlistAppendScalar(&l, "b"); + assert_string_equal("b", RlistScalarValue(RlistLast(l))); + RlistDestroy(l); +} + +static bool is_even(void *item, void *data) +{ + // What does this function do? + long d = StringToLongDefaultOnError(data, 0); + long i = StringToLongExitOnError(item); + return i % 2 == d; +} + +static void test_filter(void) +{ + Rlist *list = NULL; + for (int i = 0; i < 10; i++) + { + char *item = StringFromLong(i); + RlistAppend(&list, item, RVAL_TYPE_SCALAR); + free(item); + } + + assert_int_equal(10, RlistLen(list)); + int mod_by = 0; + RlistFilter(&list, is_even, &mod_by, free); + assert_int_equal(5, RlistLen(list)); + + int i = 0; + for (Rlist *rp = list; rp; rp = rp->next) + { + int k = StringToLongExitOnError(rp->val.item); + assert_int_equal(i, k); + + i += 2; + } + + RlistDestroy(list); +} + +static void test_filter_everything(void) +{ + Rlist *list = NULL; + for (int i = 1; i < 10; i += 2) + { + char *item = StringFromLong(i); + RlistAppend(&list, item, RVAL_TYPE_SCALAR); + free(item); + } + + assert_int_equal(5, RlistLen(list)); + int mod_by = 0; + RlistFilter(&list, is_even, &mod_by, free); + assert_int_equal(0, RlistLen(list)); + + assert_true(list == NULL); +} + +static void test_reverse(void) +{ + Rlist *list = RlistFromSplitString("a,b,c", ','); + + RlistReverse(&list); + assert_int_equal(3, RlistLen(list)); + assert_string_equal("c", RlistScalarValue(list)); + assert_string_equal("b", RlistScalarValue(list->next)); + assert_string_equal("a", RlistScalarValue(list->next->next)); + + RlistDestroy(list); +} + +static void test_split_escaped(void) +{ + Rlist *list = RlistFromSplitString("a\\,b\\c\\,d,w\\,x\\,y\\,z", ','); + assert_int_equal(2, RlistLen(list)); + assert_string_equal("a,b\\c,d", RlistScalarValue(list)); + assert_string_equal("w,x,y,z", RlistScalarValue(list->next)); + + RlistDestroy(list); +} + +static void test_split_lines(void) +{ + Rlist *list = RlistFromStringSplitLines(NULL, true); + assert_true(list == NULL); + list = RlistFromStringSplitLines(NULL, false); + assert_true(list == NULL); + + list = RlistFromStringSplitLines("hello\nworld!\nCheers!\n", true); + assert_int_equal(3, RlistLen(list)); + assert_string_equal("hello", RlistScalarValue(list)); + assert_string_equal("world!", RlistScalarValue(list->next)); + assert_string_equal("Cheers!", RlistScalarValue(list->next->next)); + /* no empty string here as the last item */ + RlistDestroy(list); + + /* one extra blank line */ + list = RlistFromStringSplitLines("hello\nworld!\nCheers!\n\n", true); + assert_int_equal(4, RlistLen(list)); + assert_string_equal("hello", RlistScalarValue(list)); + assert_string_equal("world!", RlistScalarValue(list->next)); + assert_string_equal("Cheers!", RlistScalarValue(list->next->next)); + assert_string_equal("", RlistScalarValue(list->next->next->next)); + RlistDestroy(list); + + list = RlistFromStringSplitLines("hello\r\nworld!\r\nCheers!\r\n", true); + assert_int_equal(3, RlistLen(list)); + assert_string_equal("hello", RlistScalarValue(list)); + assert_string_equal("world!", RlistScalarValue(list->next)); + assert_string_equal("Cheers!", RlistScalarValue(list->next->next)); + /* no empty string here as the last item */ + RlistDestroy(list); + + /* one extra blank line */ + list = RlistFromStringSplitLines("hello\r\nworld!\r\nCheers!\r\n\r\n", true); + assert_int_equal(4, RlistLen(list)); + assert_string_equal("hello", RlistScalarValue(list)); + assert_string_equal("world!", RlistScalarValue(list->next)); + assert_string_equal("Cheers!", RlistScalarValue(list->next->next)); + assert_string_equal("", RlistScalarValue(list->next->next->next)); + RlistDestroy(list); + + /* UNIX-like newlines only */ + list = RlistFromStringSplitLines("hello\r\nworld!\r\nCheers!\r\n\r\n", false); + assert_int_equal(4, RlistLen(list)); + assert_string_equal("hello\r", RlistScalarValue(list)); + assert_string_equal("world!\r", RlistScalarValue(list->next)); + assert_string_equal("Cheers!\r", RlistScalarValue(list->next->next)); + assert_string_equal("\r", RlistScalarValue(list->next->next->next)); + RlistDestroy(list); +} + +static void test_split_long(void) +{ + char buf[CF_MAXVARSIZE * 2], *tail = buf + CF_MAXVARSIZE; + memset(buf, '$', sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + buf[CF_MAXVARSIZE - 1] = ','; + + Rlist *list = RlistFromSplitString(buf, ','); + assert_int_equal(2, RlistLen(list)); + + assert_string_equal(tail, RlistScalarValue(list)); + assert_string_equal(tail, RlistScalarValue(list->next)); + + RlistDestroy(list); +} + +static void test_split_long_escaped(void) +{ + char buf[CF_MAXVARSIZE * 2 + 2], *tail = buf + CF_MAXVARSIZE + 1; + memset(buf, '$', sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + buf[CF_MAXVARSIZE] = ','; + memcpy(buf + CF_MAXVARSIZE / 2, "\\,", 2); + memcpy(tail + CF_MAXVARSIZE / 2, "\\,", 2); + + Rlist *list = RlistFromSplitString(buf, ','); + assert_int_equal(2, RlistLen(list)); + + tail[CF_MAXVARSIZE / 2] = '$'; /* blot out the back-slash */ + assert_string_equal(tail + 1, RlistScalarValue(list)); + assert_string_equal(tail + 1, RlistScalarValue(list->next)); + + RlistDestroy(list); +} +/***************************************************************************/ +static struct ParseRoulette +{ + int nfields; + char *str; +} PR[] = +{ + /*Simple */ + { + 1, "{\"a\"}"}, + { + 2, "{\"a\",\"b\"}"}, + { + 3, "{\"a\",\"b\",\"c\"}"}, + /*Simple empty */ + { + 1, "{\"\"}"}, + { + 2, "{\"\",\"\"}"}, + { + 3, "{\"\",\"\",\"\"}"}, + /*Simple mixed kind of quotations */ + { + 1, "{\"'\"}"}, + { + 1, "{'\"'}"}, + { + 1, "{\",\"}"}, + { + 1, "{','}"}, + { + 1, "{\"\\\\\"}"}, + { + 1, "{'\\\\'}"}, + { + 1, "{\"}\"}"}, + { + 1, "{'}'}"}, + { + 1, "{\"{\"}"}, + { + 1, "{'{'}"}, + { + 1, "{\"'\"}"}, + { + 1, "{'\"'}"}, + { + 1, "{'\\\",'}"}, /* [",] */ + { + 1, "{\"\\',\"}"}, /* [",] */ + + { + 1, "{',\\\"'}"}, /* [,"] */ + { + 1, "{\",\\'\"}"}, /* [,"] */ + + { + 1, "{\",,\"}"}, /* [\\] */ + { + 1, "{',,'}"}, /* [\\] */ + + { + 1, "{\"\\\\\\\\\"}"}, /* [\\] */ + { + 1, "{'\\\\\\\\'}"}, /* [\\] */ + + { + 1, "{'\\\\\\\"'}"}, /* [\"] */ + { + 1, "{\"\\\\\\'\"}"}, /* [\"] */ + + { + 1, "{'\\\"\\\\'}"}, /* ["\] */ + { + 1, "{\"\\'\\\\\"}"}, /* ["\] */ + + /*Very long */ + { + 1, "{\"AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaA\"}"}, + { + 1, "{'AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaA'}"}, + + { + 2, "{\"Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa''aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaA\" , \"Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\\bbbbbbbbbbbbbbbbbbbbbbbb\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb''bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbB\" }"}, + { + 2, "{'Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaA' , 'Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\\bbbbbbbbbbbbbbbbbbbbbbbb\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbB' }"}, + { + 2, "{\"Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa''aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaA\" , 'Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\\bbbbbbbbbbbbbbbbbbbbbbbb\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbB' }"}, + + /*Inner space (inside elements) */ + { + 1, "{\" \"}"}, + { + 1, "{\" \"}"}, + { + 1, "{\" \"}"}, + { + 1, "{\"\t\"}"}, + /*Outer space (outside elements) */ + { + 1, " {\"\"} "}, + { + 1, " {\"a\"} "}, + { + 2, " {\"a\",\"b\"} "}, + { + 1, "{ \"a\" }"}, + { + 2, "{ \"a\",\"b\" }"}, + { + 2, "{ \"a\" ,\"b\" }"}, + { + 2, "{ \"a\", \"b\" }"}, + { + 2, "{ \"a\", \"b\"} "}, + /*Normal */ + { + 4, " { \" ab,c,d\\\\ \" , ' e,f\\\"g ' ,\"hi\\\\jk\", \"l''m \" } "}, + { + 21, " { 'A\\\"\\\\ ', \" \\\\\", \"}B\", \"\\\\\\\\\" , \" \\\\C\\'\" , \"\\',\" , ',\\\"D' , \" ,, \", \"E\\\\\\\\F\", \"\", \"{\", \" G '\" , \"\\\\\\'\", ' \\\" H \\\\ ', \", ,\" , \"I\", \" \", \"\\' J \", '\\\",', \",\\'\", \",\" } "}, + { + 3, "{ \" aaa \", \" bbbb \" , \" cc \" } "}, + { + 3, " { \" a'a \", \" b''b \" , \" c'c \" } "}, + { + 3, " { ' a\"a ', ' b\"\"b ' , ' c\"c ' } "}, + { + 3, " { ' a\"a ', \" b''b \" , ' c\"c ' } "}, + { + 3, " { ' a,\"a } { ', \" } b','b \" , ' {, c\"c } ' } "}, + { + -1, (char *)NULL} +}; + +static char *PFR[] = { + /* trim left failure */ + "", + " ", + "a", + "\"", + "'", + "\"\"", + "''", + "'\"", + "\"'", + /* trim right failure */ + "{", + "{ ", + "{a", + "{\"", + "{'", + "{\"\"", + "{''", + "{\"'", + /* parse failure */ + /* un-even number of quotation marks */ + "{\"\"\"}", + "{\"\",\"}", + "{\"\"\"\"}", + "{\"\"\"\"\"}", + "{\"\",\"\"\"}", + "{\"\"\"\",\"}", + "{\"\",\"\",\"}", + "{'''}", + "{'','}", + "{''''}", + "{'''''}", + "{'','''}", + "{'''','}", + "{\"}", + "{'}", + "{'','','}", + "{\"\"'}", + "{\"\",'}", + "{\"'\"'}", + "{\"\"'\"\"}", + "{\"\",'\"\"}", + "{'\"\"\",\"}", + "{'',\"\",'}", + + /* Misplaced commas*/ + "{\"a\",}", + "{,\"a\"}", + "{,,\"a\"}", + "{\"a\",,\"b\"}", + "{'a',}", + "{,'a'}", + "{,,'a'}", + "{'a',,'b'}", + "{\"a\",,'b'}", + "{'a',,\"b\"}", + " {,}", + " {,,}", + " {,,,}", + " {,\"\"}", + " {\"\",}", + " {,\"\",}", + " {\"\",,}", + " {\"\",,,}", + " {,,\"\",,}", + " {\"\",\"\",}", + " {\"\",\"\",,}", + " { \"\" , \"\" , , }", + " {,''}", + " {'',}", + " {,'',}", + " {'',,}", + " {'',,,}", + " {,,'',,}", + " {'','',}", + " {'','',,}", + " { '' , '' , , }", + " {'',\"\",}", + " {\"\",'',,}", + " { '' , \"\" , , }", + " { \"\" , '' , , }", + + /*Ignore space's oddities */ + "\" {\"\"}", + "{ {\"\"}", + "{\"\"}\"", + "{\"\"}\\", + "{\"\"} } ", + "a{\"\"}", + " a {\"\"}", + "{a\"\"}", + "{ a \"\"}", + "{\"\"}a", + "{\"\"} a ", + "{\"\"a}", + "{\"\" a }", + "a{\"\"}b", + "{a\"\"b}", + "a{\"\"b}", + "{a\"\"}b", + "{\"\"a\"\"}", + "{\"\",\"\"a\"\"}", + "' {''}", + "{ {''}", + "{''}'", + "{''}\\", + "{''} } ", + "a{''}", + " a {''}", + "{a''}", + "{ a ''}", + "{''}a", + "{''} a ", + "{''a}", + "{'' a }", + "a{''}b", + "{a''b}", + "a{''b}", + "{a''}b", + "{''a''}", + "{'',''a''}", + "{''a\"\"}", + "{\"\"a''}", + "{\"\",''a''}", + "{'',\"\"a''}", + "{'',''a\"\"}", + "{\"\",''a\"\"}", + /* Bad type of quotation inside an element */ + "{'aa'aa'}", + "{\"aa\"aa\"}", + "{'aa\"''}", + "{'aa\"\"''}", + "{\"aa'\"\"}", + "{\"aa''\"\"}", + "{'aa\"'', 'aa\"\"'',\"aa'\"\"}", + "{\"aa\"aa\", 'aa\"'', 'aa\"\"''}", + "{ \"aa\"aa\" ,'aa\"\"'',\"aa''\"\" }", + NULL +}; + + +static void test_new_parser_success() +{ + Rlist *list = NULL; + int i = 0; + while (PR[i].nfields != -1) + { + list = RlistParseString(PR[i].str); + assert_int_equal(PR[i].nfields, RlistLen(list)); + if (list != NULL) + { + RlistDestroy(list); + } + i++; + } +} + +static void test_new_parser_failure() +{ + int i = 0; + Rlist *list = NULL; + while (PFR[i] != NULL) + { + list = RlistParseString(PFR[i]); + assert_true(RlistLast(list) == NULL); + if(list) RlistDestroy(list); + i++; + } +} + +static void test_regex_split() +{ + Rlist *list = RlistFromRegexSplitNoOverflow("one-->two-->three", "-+>", 3); + + assert_int_equal(3, RlistLen(list)); + + assert_string_equal(RlistScalarValue(list), "one"); + assert_string_equal(RlistScalarValue(list->next), "two"); + assert_string_equal(RlistScalarValue(list->next->next), "three"); + + RlistDestroy(list); +} + +static void test_regex_split_too_few_chunks() +{ + Rlist *list = RlistFromRegexSplitNoOverflow("one:two:three", ":", 2); + + assert_int_equal(2, RlistLen(list)); + + assert_string_equal(RlistScalarValue(list), "one"); + assert_string_equal(RlistScalarValue(list->next), "two:three"); + + RlistDestroy(list); +} + +static void test_regex_split_too_many_chunks() +{ + Rlist *list = RlistFromRegexSplitNoOverflow("one:two:three:", ":", 10); + + assert_int_equal(4, RlistLen(list)); + + assert_string_equal(RlistScalarValue(list), "one"); + assert_string_equal(RlistScalarValue(list->next), "two"); + assert_string_equal(RlistScalarValue(list->next->next), "three"); + assert_string_equal(RlistScalarValue(list->next->next->next), ""); + + RlistDestroy(list); +} + +static void test_regex_split_empty_chunks() +{ + Rlist *list = RlistFromRegexSplitNoOverflow(":one:two:three:", ":", 5); + + assert_int_equal(5, RlistLen(list)); + + assert_string_equal(RlistScalarValue(list), ""); + assert_string_equal(RlistScalarValue(list->next), "one"); + assert_string_equal(RlistScalarValue(list->next->next), "two"); + assert_string_equal(RlistScalarValue(list->next->next->next), "three"); + assert_string_equal(RlistScalarValue(list->next->next->next->next), ""); + + RlistDestroy(list); +} + +static void test_regex_split_no_match() +{ + Rlist *list = RlistFromRegexSplitNoOverflow(":one:two:three:", "/", 2); + + assert_int_equal(1, RlistLen(list)); + assert_string_equal(RlistScalarValue(list), ":one:two:three:"); + + RlistDestroy(list); +} + +static void test_regex_split_adjacent_separators() +{ + Rlist *list = RlistFromRegexSplitNoOverflow(":one::two::three:", ":", 3); + + assert_int_equal(3, RlistLen(list)); + + assert_string_equal(RlistScalarValue(list), ""); + assert_string_equal(RlistScalarValue(list->next), "one"); + assert_string_equal(RlistScalarValue(list->next->next), ":two::three:"); + + RlistDestroy(list); + + + list = RlistFromRegexSplitNoOverflow(":one::two:::three:", ":", 4); + + assert_int_equal(4, RlistLen(list)); + + assert_string_equal(RlistScalarValue(list), ""); + assert_string_equal(RlistScalarValue(list->next), "one"); + assert_string_equal(RlistScalarValue(list->next->next), ""); + assert_string_equal(RlistScalarValue(list->next->next->next), "two:::three:"); + + RlistDestroy(list); + + + list = RlistFromRegexSplitNoOverflow(":one::two:::three:", ":", 7); + + assert_int_equal(7, RlistLen(list)); + + assert_string_equal(RlistScalarValue(list), ""); + assert_string_equal(RlistScalarValue(list->next), "one"); + assert_string_equal(RlistScalarValue(list->next->next), ""); + assert_string_equal(RlistScalarValue(list->next->next->next), "two"); + assert_string_equal(RlistScalarValue(list->next->next->next->next), ""); + assert_string_equal(RlistScalarValue(list->next->next->next->next->next), ""); + assert_string_equal(RlistScalarValue(list->next->next->next->next->next->next), "three:"); + + RlistDestroy(list); +} + +static void test_regex_split_real_regex() +{ + //whole string is matched by regex in below example + Rlist *list = RlistFromRegexSplitNoOverflow("one-two-three", ".+", 3); + + assert_int_equal(2, RlistLen(list)); + assert_string_equal(RlistScalarValue(list), ""); + assert_string_equal(RlistScalarValue(list->next), ""); + + RlistDestroy(list); + + + list = RlistFromRegexSplitNoOverflow("one>>>two<<<>four", "[<>]+", 4); + + assert_int_equal(4, RlistLen(list)); + assert_string_equal(RlistScalarValue(list), "one"); + assert_string_equal(RlistScalarValue(list->next), "two"); + assert_string_equal(RlistScalarValue(list->next->next), "three"); + assert_string_equal(RlistScalarValue(list->next->next->next), "four"); + + RlistDestroy(list); +} + +static void test_regex_split_overlapping_delimiters() +{ + Rlist *list = RlistFromRegexSplitNoOverflow("-one---two---three", "--", 3); + + assert_int_equal(3, RlistLen(list)); + + assert_string_equal(RlistScalarValue(list), "-one"); + assert_string_equal(RlistScalarValue(list->next), "-two"); + assert_string_equal(RlistScalarValue(list->next->next), "-three"); + + RlistDestroy(list); +} + +static void test_rval_write() +{ + Rval rval = { "\"Hello World!\"", RVAL_TYPE_SCALAR }; + Writer *writer = StringWriter(); + RvalWrite(writer, rval); + const char *actual = StringWriterData(writer); + assert_string_equal(actual, "\\\"Hello World!\\\""); + WriterClose(writer); +} + +static void test_rval_write_quoted() +{ + Rval rval = { "\"Hello World!\"", RVAL_TYPE_SCALAR }; + Writer *writer = StringWriter(); + RvalWriteQuoted(writer, rval); + const char *actual = StringWriterData(writer); + assert_string_equal(actual, "\"\\\"Hello World!\\\"\""); + WriterClose(writer); +} + +static void test_rval_write_raw() +{ + Rval rval = { "\"Hello World!\"", RVAL_TYPE_SCALAR }; + Writer *writer = StringWriter(); + RvalWriteRaw(writer, rval); + const char *actual = StringWriterData(writer); + assert_string_equal(actual, "\"Hello World!\""); + WriterClose(writer); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_prepend_scalar_idempotent), + unit_test(test_length), + unit_test(test_equality), + unit_test(test_copy), + unit_test(test_rval_to_scalar), + unit_test(test_rval_to_scalar2), + unit_test(test_rval_to_list), + unit_test(test_rval_to_list2), + unit_test(test_rval_to_fncall), + unit_test(test_rval_to_fncall2), + unit_test(test_last), + unit_test(test_filter), + unit_test(test_filter_everything), + unit_test(test_reverse), + unit_test(test_split_escaped), + unit_test(test_split_lines), + unit_test(test_split_long), + unit_test(test_split_long_escaped), + unit_test(test_new_parser_success), + unit_test(test_new_parser_failure), + unit_test(test_regex_split), + unit_test(test_regex_split_too_few_chunks), + unit_test(test_regex_split_too_many_chunks), + unit_test(test_regex_split_empty_chunks), + unit_test(test_regex_split_no_match), + unit_test(test_regex_split_adjacent_separators), + unit_test(test_regex_split_real_regex), + unit_test(test_regex_split_overlapping_delimiters), + unit_test(test_rval_write), + unit_test(test_rval_write_quoted), + unit_test(test_rval_write_raw), + }; + + return run_tests(tests); +} + +/* ===== Stub out functionality we don't really use. ===== */ + +/* Silence all "unused parameter" warnings. */ +#pragma GCC diagnostic ignored "-Wunused-parameter" + + + +char CONTEXTID[32]; + +void __ProgrammingError(const char *file, int lineno, const char *format, ...) +{ + mock_assert(0, "0", __FILE__, __LINE__); +} + +bool FullTextMatch(const char *regptr, const char *cmpptr) +{ + fail(); +} + +const void *EvalContextVariableGet(const EvalContext *ctx, const VarRef *lval, DataType *type_out) +{ + fail(); +} + +pthread_mutex_t *cft_lock; +int __ThreadLock(pthread_mutex_t *name) +{ + return true; +} + +int __ThreadUnlock(pthread_mutex_t *name) +{ + return true; +} + +int IsNakedVar(const char *str, char vtype) +{ + fail(); +} + +void FnCallPrint(Writer *writer, const FnCall *fp) +{ + fail(); +} + +void GetNaked(char *s1, const char *s2) +{ + fail(); +} + +/* +void Log(LogLevel level, const char *fmt, ...) +{ + fail(); +} +*/ + +DataType ScopeGetVariable(const char *scope, const char *lval, Rval *returnv) +{ + fail(); +} + +void DeleteAssoc(CfAssoc *ap) +{ + fail(); +} + +CfAssoc *CopyAssoc(CfAssoc *old) +{ + fail(); +} + +FnCall *FnCallCopy(const FnCall *f) +{ + fail(); +} + +void FnCallDestroy(FnCall *fp) +{ + fail(); +} + +int SubStrnCopyChr(char *to, const char *from, int len, char sep) +{ + fail(); +} + +int BlockTextMatch(const char *regexp, const char *teststring, int *s, int *e) +{ + fail(); +} + +JsonElement *FnCallToJson(const FnCall *fp) +{ + fail(); +} + +JsonElement *JsonObjectCreate(size_t initialCapacity) +{ + fail(); +} + +void JsonObjectAppendArray(JsonElement *object, const char *key, JsonElement *array) +{ + fail(); +} + +void JsonObjectAppendString(JsonElement *obj, const char *key, const char *value) +{ + fail(); +} + +JsonElement *JsonArrayCreate(size_t initialCapacity) +{ + fail(); +} + +void JsonArrayAppendString(JsonElement *array, const char *value) +{ + fail(); +} + +void JsonArrayAppendArray(JsonElement *array, JsonElement *childArray) +{ + fail(); +} + +void JsonArrayAppendObject(JsonElement *array, JsonElement *object) +{ + fail(); +} + +JsonElement *JsonStringCreate(const char *value) +{ + fail(); +} diff --git a/tests/unit/schema.h b/tests/unit/schema.h new file mode 100644 index 0000000000..17330a774a --- /dev/null +++ b/tests/unit/schema.h @@ -0,0 +1,155 @@ +#ifndef SCHEMA_H_ +# define SCHEMA_H_ + +// Printf formatting for xml CUNIT Schema +#define CUNIT_INIT \ + "<\?xml version=\"1.0\" \?>\n" \ + "<\?xml-stylesheet type=\"text/xsl\" href=\"CUnit-Run.xsl\" \?>\n" \ + "\n" \ + "\n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " %s suite \n" +#define CUNIT_RUN_TEST_SUCCESS \ + " \n" \ + " \n" \ + " %s \n" \ + " \n" \ + " \n" +#define CUNIT_RUN_TEST_FAILURE_START \ + " \n" \ + " \n" \ + " %s \n" +#define CUNIT_RUN_TEST_FAILURE_ASSERT \ + " \n" \ + " \n" \ + " %s \n" \ + " %s \n" \ + " %d \n" \ + " %s(%lld) \n" \ + " \n" \ + " \n" +#define CUNIT_RUN_TEST_FAILURE_ASSERT_EQUALITY_LLD \ + " \n" \ + " \n" \ + " %s \n" \ + " %s \n" \ + " %d \n" \ + " %s(%lld, %lld) \n" \ + " \n" \ + " \n" +#define CUNIT_RUN_TEST_FAILURE_ASSERT_EQUALITY_STRING \ + " \n" \ + " \n" \ + " %s \n" \ + " %s \n" \ + " %d \n" \ + " %s(%s %s) \n" \ + " \n" \ + " \n" +#define CUNIT_RUN_TEST_FAILURE_ASSERT_RANGE_LLD \ + " \n" \ + " \n" \ + " %s \n" \ + " %s \n" \ + " %d \n" \ + " %s(value=%lld, min=%lld, max=%lld) \n" \ + " \n" \ + " \n" +#define CUNIT_RUN_TEST_FAILURE_ASSERT_SET_LLD \ + " \n" \ + " \n" \ + " %s \n" \ + " %s \n" \ + " %d \n" \ + " %s(value=%lld, number_of_values=%lld) \n" \ + " \n" \ + " \n" +#define CUNIT_RUN_TEST_ERROR \ + " \n" \ + " \n" \ + " %s \n" \ + " %d \n" +#define CUNIT_RUN_SUMMARY \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " %s \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " \n" \ + " \n" \ + " %s \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " \n" \ + " \n" \ + " %s \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " %d \n" \ + " \n" \ + " \n" \ + " File Generated By CUnit v2.1-2 - %s\n" \ + " \n" \ + "\n" + +// Printf formatting for xml XS Schema +#define XS_INIT_TESTSUITE \ + "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n" \ + "\n" +#define XS_TESTCASE \ + " \n" +#define XS_RUN_TEST_FAILURE_ASSERT \ + " \n" \ + " \n" +#define XS_RUN_TEST_FAILURE_ASSERT_EQUALITY_LLD \ + " \n" \ + " \n" +#define XS_RUN_TEST_FAILURE_ASSERT_EQUALITY_STRING \ + " \n" \ + " \n" +#define XS_RUN_TEST_FAILURE_ASSERT_RANGE_LLD \ + " \n" \ + " \n" +#define XS_RUN_TEST_FAILURE_ASSERT_SET_LLD \ + " \n" \ + " \n" + +#define XS_RUN_TEST_ERROR \ + " \n" + +#define XS_TESTCASE_END \ + " \n" +#define XS_TESTSUITE_END \ + "\n" + +#endif diff --git a/tests/unit/scope_test.c b/tests/unit/scope_test.c new file mode 100644 index 0000000000..f1b959967b --- /dev/null +++ b/tests/unit/scope_test.c @@ -0,0 +1,31 @@ +#include + +#include +#include +#include + +static void test_name_join(void) +{ + { + char buf[CF_MAXVARSIZE] = { 0 }; + JoinScopeName(NULL, "sys", buf); + assert_string_equal("sys", buf); + } + + { + char buf[CF_MAXVARSIZE] = { 0 }; + JoinScopeName("ns", "b", buf); + assert_string_equal("ns:b", buf); + } +} + +int main() +{ + const UnitTest tests[] = +{ + unit_test(test_name_join), + }; + + PRINT_TEST_BANNER(); + return run_tests(tests); +} diff --git a/tests/unit/set_domainname_test.c b/tests/unit/set_domainname_test.c new file mode 100644 index 0000000000..233f065b05 --- /dev/null +++ b/tests/unit/set_domainname_test.c @@ -0,0 +1,89 @@ +#include + +#include +#include +#include +#include +#include +#include + +/* Global variables we care about */ + +char VFQNAME[CF_MAXVARSIZE]; +char VUQNAME[CF_MAXVARSIZE / 2]; +char VDOMAIN[CF_MAXVARSIZE / 2]; + +static struct hostent h = { + .h_name = "laptop.intra.cfengine.com" +}; + +#ifdef SOLARIS +int gethostname(char *name, ARG_UNUSED int len) +#else +int gethostname(char *name, ARG_UNUSED size_t len) +#endif +{ + strcpy(name, "laptop.intra"); + return 0; +} + +struct hostent *gethostbyname(const char *name) +{ + assert_string_equal(name, "laptop.intra"); + return &h; +} + +typedef struct +{ + const char *name; + const char *value; + bool found; +} ExpectedVars; + +ExpectedVars expected_vars[] = +{ + {"host", "laptop.intra"}, + {"fqhost", "laptop.intra.cfengine.com"}, + {"uqhost", "laptop.intra"}, + {"domain", "cfengine.com"}, +}; + +static void TestSysVar(EvalContext *ctx, const char *lval, const char *expected) +{ + VarRef *ref = VarRefParseFromScope(lval, "sys"); + assert_string_equal(expected, EvalContextVariableGet(ctx, ref, NULL)); + VarRefDestroy(ref); + + assert_string_equal(expected, EvalContextVariableGetSpecial(ctx, SPECIAL_SCOPE_SYS, lval, NULL)); + assert_string_equal(expected, EvalContextVariableGetSpecialString(ctx, SPECIAL_SCOPE_SYS, lval)); +} + +static void test_set_names(void) +{ + EvalContext *ctx = EvalContextNew(); + DetectDomainName(ctx, "laptop.intra"); + + assert_true(!EvalContextClassGet(ctx, NULL, "laptop_intra_cfengine_com")->is_soft); + assert_true(!EvalContextClassGet(ctx, NULL, "intra_cfengine_com")->is_soft); + assert_true(!EvalContextClassGet(ctx, NULL, "cfengine_com")->is_soft); + assert_true(!EvalContextClassGet(ctx, NULL, "com")->is_soft); + assert_true(!EvalContextClassGet(ctx, NULL, "laptop_intra")->is_soft); + + TestSysVar(ctx, "host", "laptop.intra"); + TestSysVar(ctx, "fqhost", "laptop.intra.cfengine.com"); + TestSysVar(ctx, "uqhost", "laptop.intra"); + TestSysVar(ctx, "domain", "cfengine.com"); + + EvalContextDestroy(ctx); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_set_names), + }; + + return run_tests(tests); +} diff --git a/tests/unit/solaris_process_test.c b/tests/unit/solaris_process_test.c new file mode 100644 index 0000000000..4ae186f389 --- /dev/null +++ b/tests/unit/solaris_process_test.c @@ -0,0 +1,178 @@ +#include + +#include +#include +#include + +/* + * procfs.h is not 64-bit off_t clean, but the only affected structure is + * priovec, which we don't use. Hence we may work around #error in sys/procfs.h + * by lying that we are not compiling with large file support (while we do). + */ +#define _FILE_OFFSET_BITS 32 + +#include + +int open(const char *filename, int flags, ...) +{ + if (!strcmp(filename, "/proc/1/psinfo")) + { + return 1; + } + + if (!strcmp(filename, "/proc/1/status")) + { + return 101; + } + + if (!strcmp(filename, "/proc/2/status")) + { + return 102; + } + + if (!strcmp(filename, "/proc/3/status")) + { + return 103; + } + + errno = ENOENT; + return -1; +} + +int fdpos[4]; + +ssize_t read(int fd, void *buf, size_t bufsize) +{ + if (fd == 1) + { + if (fdpos[0] == 0) + { + psinfo_t psinfo; + psinfo.pr_start.tv_sec = 100; + memcpy(buf, &psinfo, sizeof(psinfo)); + fdpos[0] = sizeof(psinfo); + return sizeof(psinfo); + } + else + { + return 0; + } + } + + if (fd == 101) + { + if (fdpos[1] == 0) + { + pstatus_t pstatus; + pstatus.pr_nlwp = 1; + pstatus.pr_lwp.pr_flags = PR_STOPPED; + pstatus.pr_lwp.pr_why = PR_SIGNALLED; + memcpy(buf, &pstatus, sizeof(pstatus)); + fdpos[1] = sizeof(pstatus); + return sizeof(pstatus); + } + else + { + return 0; + } + } + + if (fd == 102) + { + if (fdpos[2] == 0) + { + pstatus_t pstatus; + pstatus.pr_nlwp = 1; + pstatus.pr_lwp.pr_flags = 0; + pstatus.pr_lwp.pr_why = 0; + memcpy(buf, &pstatus, sizeof(pstatus)); + fdpos[2] = sizeof(pstatus); + return sizeof(pstatus); + } + else + { + return 0; + } + } + + if (fd == 103) + { + if (fdpos[3] == 0) + { + /* Currently this is unused, the test is switched off. */ + + pstatus_t pstatus; + pstatus.pr_nlwp = 0; /* zombie */ + memcpy(buf, &pstatus, sizeof(pstatus)); + fdpos[3] = sizeof(pstatus); + return sizeof(pstatus); + } + else + { + return 0; + } + } + + errno = EIO; + return -1; +} + +int close(int fd) +{ + return 0; +} + +static void test_get_start_time_process1(void) +{ + time_t t = GetProcessStartTime(1); + assert_int_equal(t, 100); +} + +static void test_get_start_time_process2(void) +{ + time_t t = GetProcessStartTime(2); + assert_int_equal(t, PROCESS_START_TIME_UNKNOWN); +} + +static void test_get_state_process1(void) +{ + ProcessState s = GetProcessState(1); + assert_int_equal(s, PROCESS_STATE_STOPPED); +} + +static void test_get_state_process2(void) +{ + ProcessState s = GetProcessState(2); + assert_int_equal(s, PROCESS_STATE_RUNNING); +} + +static void test_get_state_process3(void) +{ + ProcessState s = GetProcessState(3); + assert_int_equal(s, PROCESS_STATE_ZOMBIE); +} + +static void test_get_state_process4(void) +{ + ProcessState s = GetProcessState(4); + assert_int_equal(s, PROCESS_STATE_DOES_NOT_EXIST); +} + +int main() +{ + PRINT_TEST_BANNER(); + + const UnitTest tests[] = + { + unit_test(test_get_start_time_process1), + unit_test(test_get_start_time_process2), + unit_test(test_get_state_process1), + unit_test(test_get_state_process2), + /* TODO zombie process unit test for solaris is not currently working + * because of the huge amount of hacks needed to actually figure out + * if a process is zombie, see libpromises/process_solaris.c. */ +// unit_test(test_get_state_process3), + }; + + return run_tests(tests); +} diff --git a/tests/unit/sort_test.c b/tests/unit/sort_test.c new file mode 100644 index 0000000000..5ac4f8a0fb --- /dev/null +++ b/tests/unit/sort_test.c @@ -0,0 +1,158 @@ +#include + +#include +#include +#include +#include + +/* + * Those testcases only perform smoke testing of sorting functionality. + */ + +void test_sort_item_list_names(void) +{ + Item *head = xcalloc(1, sizeof(Item)); + head->name = xstrdup("c"); + head->next = xcalloc(1, sizeof(Item)); + head->next->name = xstrdup("b"); + head->next->next = xcalloc(1, sizeof(Item)); + head->next->next->name = xstrdup("a"); + + Item *sorted = SortItemListNames(head); + + assert_string_equal(sorted->name, "a"); + assert_string_equal(sorted->next->name, "b"); + assert_string_equal(sorted->next->next->name, "c"); + assert_int_equal(sorted->next->next->next, NULL); + + DeleteItemList(sorted); +} + +void test_sort_item_list_classes(void) +{ + Item *head = xcalloc(1, sizeof(Item)); + head->classes = xstrdup("b"); + head->next = xcalloc(1, sizeof(Item)); + head->next->classes = xstrdup("c"); + head->next->next = xcalloc(1, sizeof(Item)); + head->next->next->classes = xstrdup("a"); + + Item *sorted = SortItemListClasses(head); + + assert_string_equal(sorted->classes, "a"); + assert_string_equal(sorted->next->classes, "b"); + assert_string_equal(sorted->next->next->classes, "c"); + assert_int_equal(sorted->next->next->next, NULL); + + DeleteItemList(sorted); +} + +void test_sort_item_list_counters(void) +{ + Item *head = xcalloc(1, sizeof(Item)); + head->counter = -1; + head->next = xcalloc(1, sizeof(Item)); + head->next->counter = 42; + head->next->next = xcalloc(1, sizeof(Item)); + head->next->next->counter = 146; + + Item *sorted = SortItemListCounters(head); + + /* Weird. Counters are sorted backwards */ + assert_int_equal(sorted->counter, 146); + assert_int_equal(sorted->next->counter, 42); + assert_int_equal(sorted->next->next->counter, -1); + assert_int_equal(sorted->next->next->next, NULL); + + DeleteItemList(sorted); +} + +void test_sort_item_list_times(void) +{ + Item *head = xcalloc(1, sizeof(Item)); + head->time = 1; + head->next = xcalloc(1, sizeof(Item)); + head->next->time = 1998; + head->next->next = xcalloc(1, sizeof(Item)); + head->next->next->time = 4000; + + Item *sorted = SortItemListCounters(head); + + assert_int_equal(sorted->time, 4000); + assert_int_equal(sorted->next->time, 1998); + assert_int_equal(sorted->next->next->time, 1); + assert_int_equal(sorted->next->next->next, NULL); + + DeleteItemList(sorted); +} + +bool FirstItemShorter(const char *lhs, const char *rhs) +{ + return (strlen(lhs) < strlen(rhs)); +} + +void test_sort_rlist(void) +{ + Rlist *list = NULL; + RlistAppendScalar(&list, "bbb"); + RlistAppendScalar(&list, "cc"); + RlistAppendScalar(&list, "a"); + + Rlist *sorted = SortRlist(list, &FirstItemShorter); + + assert_string_equal(RlistScalarValue(sorted), "a"); + assert_string_equal(RlistScalarValue(sorted->next), "cc"); + assert_string_equal(RlistScalarValue(sorted->next->next), "bbb"); + assert_int_equal(sorted->next->next->next, NULL); + + RlistDestroy(sorted); +} + +void test_alpha_sort_rlist_names(void) +{ + Rlist *list = NULL; + RlistAppendScalar(&list, "c"); + RlistAppendScalar(&list, "a"); + RlistAppendScalar(&list, "b"); + + Rlist *sorted = AlphaSortRListNames(list); + + assert_string_equal(RlistScalarValue(sorted), "a"); + assert_string_equal(RlistScalarValue(sorted->next), "b"); + assert_string_equal(RlistScalarValue(sorted->next->next), "c"); + assert_int_equal(sorted->next->next->next, NULL); + + RlistDestroy(sorted); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_sort_item_list_names), + unit_test(test_sort_item_list_classes), + unit_test(test_sort_item_list_counters), + unit_test(test_sort_item_list_times), + unit_test(test_sort_rlist), + unit_test(test_alpha_sort_rlist_names), + }; + + return run_tests(tests); +} + +/* STUBS */ + +/* +void __ProgrammingError(const char *file, int lineno, const char *format, ...) +{ + fail(); + exit(42); +} + +void FatalError(char *s, ...) +{ + fail(); + exit(42); +} +*/ diff --git a/tests/unit/split_process_line_test.c b/tests/unit/split_process_line_test.c new file mode 100644 index 0000000000..6376136f3b --- /dev/null +++ b/tests/unit/split_process_line_test.c @@ -0,0 +1,920 @@ +#include + +#define TEST_UNIT_TEST + +#include + +static void free_and_null_strings(char **strings) +{ + for (int i = 0; i < CF_PROCCOLS; ++i) + { + free(strings[i]); + strings[i] = NULL; + } +} + +/* Actual ps output witnessed. */ +static void test_split_line_challenges(void) +{ + /* Collect all test data in one array to make alignments visible: */ + static const char *lines[] = { + "USER PID SZ VSZ RSS NLWP STIME ELAPSED TIME COMMAND", + "operatic 14338 1042534 4170136 2122012 9 Sep15 4-06:11:34 2-09:27:49 /usr/lib/opera/opera" + }; + char *name[CF_PROCCOLS] = { 0 }; /* Headers */ + char *field[CF_PROCCOLS] = { 0 }; /* Content */ + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + int user = 0, nlwp = 5; + + memset(name, 0, sizeof(name)); + memset(field, 0, sizeof(field)); + + /* Prepare data needed by tests and assert things tests can then assume: */ + GetProcessColumnNames(lines[0], name, start, end); + assert_string_equal(name[user], "USER"); + assert_string_equal(name[nlwp], "NLWP"); + + assert_true(SplitProcLine(lines[1], 1, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "operatic"); + assert_string_equal(field[nlwp], "9"); + + /* Finally, tidy away fields and headers: */ + free_and_null_strings(field); + free_and_null_strings(name); +} + +static void test_split_line_elapsed(void) +{ + /* Collect all test data in one array to make alignments visible: */ + static const char *lines[] = { + "USER PID STAT VSZ NI RSS NLWP STIME ELAPSED TIME COMMAND", + "space 14604 S 0 0 0 1 Sep 02 4-03:40:00 00:00:01 true", + "block 14604 S 0 0 0 1 Sep02 4-03:40:00 00:00:01 true" + }; + char *name[CF_PROCCOLS] = { 0 }; /* Headers */ + char *field[CF_PROCCOLS] = { 0 }; /* Content */ + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + int user = 0, stime = 7; + time_t pstime = 1410000000; /* 2014-09-06 12:40 */ + const char began[] = "1409641200"; /* 2014-09-02 9:0 */ + + /* Prepare data needed by tests and assert things tests can then assume: */ + GetProcessColumnNames(lines[0], name, start, end); + assert_string_equal(name[user], "USER"); + assert_string_equal(name[stime], "STIME"); + + assert_true(SplitProcLine(lines[2], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "block"); + /* Copes when STIME is a date with a space in it. */ + assert_string_equal(field[stime], began); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[1], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "space"); + /* Copes when STIME is a date with a space in it. */ + assert_string_equal(field[stime], began); + + /* Finally, tidy away headers: */ + free_and_null_strings(name); + free_and_null_strings(field); +} + +static void test_split_line_noelapsed(void) +{ + /* Collect all test data in one array to make alignments visible: */ + static const char *lines[] = { + "USER PID STAT VSZ NI RSS NLWP STIME TIME COMMAND", + "space 14604 S 0 0 0 1 Jul 02 00:00:01 true", + "block 14604 S 0 0 0 1 Jul02 00:00:01 true" + }; + char *name[CF_PROCCOLS] = { 0 }; /* Headers */ + char *field[CF_PROCCOLS] = { 0 }; /* Content */ + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + int user = 0, stime = 7; + time_t pstime = 1410000000; + + /* Prepare data needed by tests and assert things tests can then assume: */ + GetProcessColumnNames(lines[0], name, start, end); + assert_string_equal(name[user], "USER"); + assert_string_equal(name[stime], "STIME"); + + assert_true(SplitProcLine(lines[2], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "block"); + /* Copes when STIME is a date with a space in it. */ + assert_string_equal(field[stime], "Jul02"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[1], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "space"); + /* Copes when STIME is a date with a space in it. */ + assert_string_equal(field[stime], "Jul 02"); + + /* Finally, tidy away headers: */ + free_and_null_strings(name); + free_and_null_strings(field); +} + +static void test_split_line_longcmd(void) +{ + static const char *lines[] = { + "USER PID STAT VSZ NI RSS NLWP STIME ELAPSED TIME COMMAND", + "longcmd 923 S 32536 0 784 1 10:30 71-00:07:43 00:01:49 " + "java can end up with some insanely long command-lines, so we need to " + "be sure that we don't artificially limit the length of the command " + "field that we see when looking at the process table to match for the " + "details that the user might be interested in - so here we have an " + "absurdly long 'command' field just for the sake of testing that it " + "does not get truncated - see RedMine ticket 3974 for working round the " + "problems when ps itself does such truncation, but this test is not about " + "that so much as our own code not doing such truncation - and now for " + "some random drivel repeated a lot to bulk this command line length out " + "to more than 4k:" + /* 638 bytes thus far, +72 per repeat, * 50 for 4238 > 4096: */ + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens" + " the quick brown fox jumped over the lazy dogs after eating the chickens", + }; + char *name[CF_PROCCOLS] = { 0 }; /* Headers */ + char *field[CF_PROCCOLS] = { 0 }; /* Content */ + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + int user = 0, command = 10; + time_t pstime = 1410000000; + + /* Prepare data needed by tests and assert things assumed by test: */ + GetProcessColumnNames(lines[0], name, start, end); + assert_string_equal(name[user], "USER"); + assert_string_equal(name[command], "COMMAND"); + + assert_true(SplitProcLine(lines[1], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "longcmd"); + /* Does not truncate the command. */ + assert_string_equal(field[command], lines[1] + start[command]); + + /* Finally, tidy away headers: */ + free_and_null_strings(name); + free_and_null_strings(field); +} + +static void test_split_line(void) +{ + /* Collect all test data in one array to make alignments visible: */ + static const char *lines[] = { + /* Indent any continuation lines so that it's clear that's what they are. */ + /* Use the username field as a test name to confirm tests are in sync. */ + + "USER PID STAT VSZ NI RSS NLWP STIME ELAPSED TIME COMMAND", + "timeboth 923 S 32536 0 784 1 10:30 1271-00:07:43 00:01:49 true" + " to itself", + /* timeright: adapted from a Jenkins run that failed. */ + "timeright 941 S 39752 0 4552 1 May20 92-21:17:55 1-02:07:14 true", + "timesleft 923 S 32536 0 784 1 10:30 1271-00:07:43 00:01:49 true", + "timeleft 923 S 32536 0 784 1 10:30 271-00:07:43 00:01:49 true", + "numboth 923 S 32536 0 12784321 1 10:30 71-00:07:43 00:01:49 true", + "numleft 923 S 123432536 0 784 1 10:30 71-00:07:43 00:01:49 true", + "wordytoright 4 S 0 0 0 1 10:30 54:29 00:00:01 true", + "wordright 4 S 0 0 0 1 10:30 54:29 00:00:01 true", + + /* Long-standing: */ + "inspace 923 S 32536 0 784 1 10:30 71-00:07:43 00:01:49 echo" + " preserve\t embedded spaces in a text field", + "spacey 923 S 32536 0 784 1 10:30 71-00:07:43 00:01:49 echo " + "\t \t \r\n\f\n\v\n", + "basic 923 S 32536 0 784 1 10:30 71-00:07:43 00:01:49 true", + "", + NULL + }; + char *name[CF_PROCCOLS] = { 0 }; /* Headers */ + char *field[CF_PROCCOLS] = { 0 }; /* Content */ + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + int user = 0, vsz = 3, rss = 5, stime = 7, command = 10; + time_t pstime = 1410000000; + + /* Prepare data needed by tests and assert things tests can then assume: */ + GetProcessColumnNames(lines[0], name, start, end); + assert_string_equal(name[user], "USER"); + assert_string_equal(name[vsz], "VSZ"); + assert_string_equal(name[rss], "RSS"); + assert_string_equal(name[stime], "STIME"); + assert_string_equal(name[command], "COMMAND"); + assert_int_equal(start[command], 69); + + size_t line = sizeof(lines) / sizeof(const char *); + /* Higher indexed tests first; test lines[line] then decrement line. */ + assert_false(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); /* NULL */ + + free_and_null_strings(field); + assert_false(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); /* empty */ + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); /* basic */ + { + /* Each field is as expected: */ + const char *each[] = { + "basic", "923", "S", "32536", "0", "784", "1", + "10:30", "71-00:07:43", "00:01:49", "true" + }; + for (int i = 0; name[i] != NULL; i++) + { + assert_in_range(i, 0, sizeof(each) / sizeof(char *) - 1); + bool valid = field[i] != NULL && each[i] != NULL; + assert_true(valid); + assert_string_equal(field[i], each[i]); + } + /* That incidentally covers numeric (VSZ) and time (ELAPSED, + * TIME) fields overflowing to the left (but not so far as to + * go under the previous field's header). */ + } + /* See field[user] checks for names of remaining tests. */ + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "spacey"); + /* Discards leading and dangling space in command. */ + assert_string_equal(field[command], "echo"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "inspace"); + /* Preserves spaces within a text field. */ + assert_string_equal(field[command], lines[line] + start[command]); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + /* Handle a text field overflowing to the right. */ + assert_string_equal(field[user], "wordright"); + /* Shouldn't pollute PID: */ + assert_string_equal(field[user + 1], "4"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + /* Handle a text field overflowing under next header. */ + assert_string_equal(field[user], "wordytoright"); + /* Shouldn't pollute PID: */ + assert_string_equal(field[user + 1], "4"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "numleft"); + /* Handle numeric field overflowing under previous header. */ + assert_string_equal(field[vsz], "123432536"); + /* Shouldn't pollute STAT: */ + assert_string_equal(field[vsz - 1], "S"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "numboth"); + /* Handle numeric field overflowing under previous header. */ + assert_string_equal(field[rss], "12784321"); + /* Shouldn't pollute STAT or NI: */ + assert_string_equal(field[rss - 1], "0"); + assert_string_equal(field[rss + 1], "1"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "timeleft"); + /* Handle time fields overflowing almost under previous header. */ + assert_string_equal(field[stime + 1], "271-00:07:43"); + assert_string_equal(field[stime], "10:30"); + /* Shouldn't pollute NLWP: */ + assert_string_equal(field[stime - 1], "1"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "timesleft"); + /* Handle time fields overflowing under previous header. */ + assert_string_equal(field[stime + 1], "1271-00:07:43"); + assert_string_equal(field[stime], "10:30"); + /* Shouldn't pollute NLWP: */ + assert_string_equal(field[stime - 1], "1"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "timeright"); + /* Handle time field overflowing under next header. */ + assert_string_equal(field[command - 1], "1-02:07:14"); + /* Shouldn't pollute ELAPSED or COMMAND: */ + assert_string_equal(field[stime + 1], "92-21:17:55"); + assert_string_equal(field[command], "true"); + + free_and_null_strings(field); + assert_true(SplitProcLine(lines[--line], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "timeboth"); + assert_int_equal(command, stime + 3); /* with elapsed and time between */ + /* Handle a time field overflowing almost under previous header + * while also overflowing right and thus causing the next to + * overflow under the field beyond it. */ + assert_string_equal(field[command - 1], "00:01:49"); + assert_string_equal(field[stime + 1], "1271-00:07:43"); + /* Should shunt COMMAND two bytes to the right: */ + assert_string_equal(field[command], lines[line] + 2 + start[command]); + /* Shouldn't pollute COMMAND, NLWP or STIME: */ + assert_string_equal(field[stime], "10:30"); + assert_string_equal(field[stime - 1], "1"); + + assert_int_equal(line, 1); + /* Finally, tidy away headers: */ + free_and_null_strings(name); + free_and_null_strings(field); +} + +static void test_split_line_serious_overspill(void) +{ + /* + Test that columns spilling over into other columns do not confuse the + parser. + Collect all test data in one array to make alignments visible: + */ + static const char *lines[] = { + " USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND", + " johndoe 8263 0.0 0.2 19890 116241 ? S Jan_16 08:41:40 /usr/java/bin/java -server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4", + "noaccess 8264 0.0 0.2 19890 116242 ? S Jan_16 08:41:40 /usr/java/bin/java -server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4", + "noaccess 8265 0.0 0.2 19890 116243 ? S Jan_16 08:41:40 /usr/java/bin/java -server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4", + NULL + }; + + char *name[CF_PROCCOLS] = { 0 }; /* Headers */ + char *field[CF_PROCCOLS] = { 0 }; /* Content */ + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + int user = 0, pid = 1, sz = 4, rss = 5, command = 10; + time_t pstime = 1410000000; + + /* Prepare data needed by tests and assert things tests can then assume: */ + GetProcessColumnNames(lines[0], name, start, end); + assert_string_equal(name[user], "USER"); + assert_string_equal(name[pid], "PID"); + assert_string_equal(name[sz], "SZ"); + assert_string_equal(name[rss], "RSS"); + assert_string_equal(name[command], "COMMAND"); + assert_int_equal(start[command], 66); + + // Test content + { + assert_true(SplitProcLine(lines[1], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "johndoe"); + assert_string_equal(field[pid], "8263"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116241"); + assert_string_equal(field[command], lines[1] + 69); + free_and_null_strings(field); + } + + { + assert_true(SplitProcLine(lines[2], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "noaccess"); + assert_string_equal(field[pid], "8264"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116242"); + assert_string_equal(field[command], lines[2] + 69); + free_and_null_strings(field); + } + + { + assert_true(SplitProcLine(lines[3], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "noaccess"); + assert_string_equal(field[pid], "8265"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116243"); + assert_string_equal(field[command], lines[3] + 69); + free_and_null_strings(field); + } + free_and_null_strings(name); +} + +typedef struct +{ + FILE *fp; + const char **lines; +} LWData; + +static void *ListWriter(void *arg) +{ + LWData *data = (LWData *)arg; + for (int i = 0; data->lines[i]; i++) + { + fprintf(data->fp, "%s\n", data->lines[i]); + } + fclose(data->fp); + + return NULL; +} + +static void test_platform_extra_table(void) +{ + static const char *lines[] = { + " USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND", + " johndoe 8263 0.0 0.2 19890 116241 ? S Jan_16 08:41:40 /usr/java/bin/java -server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4", + "noaccess 8264 0.0 0.2 19890 116242 ? S Jan_16 08:41:40 /usr/java/bin/java -server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4", + "noaccess 8265 0.0 0.2 19890 116243 ? S Jan_16 08:41:40 /usr/java/bin/java -server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4", + " jenkins 22306 - - 0 0 ? Z - 00:00 ", + " jenkins 22307 - - 0 0 ? Z - 00:00 ", + " jenkins 22308 - - 0 0 ? Z - 00:00 ", + NULL + }; + static const char *ucb_lines[] = { + " PID TT S TIME COMMAND", + " 8263 ? S 521:40 /usr/java/bin/java blahblah", + // Takes from Solaris 10. Yep, the line really is that long. + " 8264 ? S 521:40 /usr/java/bin/java -server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4 -classpath /usr/share/webconsole/private/container/bin/bootstrap.jar:/usr/share/webconsole/private/container/bin/commons-logging.jar:/usr/share/webconsole/private/container/bin/log4j.jar:/usr/java/lib/tools.jar:/usr/java/jre/lib/jsse.jar -Djava.security.manager -Djava.security.policy==/var/webconsole/domains/console/conf/console.policy -Djavax.net.ssl.trustStore=/var/webconsole/domains/console/conf/keystore.jks -Djava.security.auth.login.config=/var/webconsole/domains/console/conf/consolelogin.conf -Dcatalina.home=/usr/share/webconsole/private/container -Dcatalina.base=/var/webconsole/domains/console -Dcom.sun.web.console.home=/usr/share/webconsole -Dcom.sun.web.console.conf=/etc/webconsole/console -Dcom.sun.web.console.base=/var/webconsole/domains/console -Dcom.sun.web.console.logdir=/var/log/webconsole/console -Dcom.sun.web.console.native=/usr/lib/webconsole -Dcom.sun.web.console.appbase=/var/webconsole/domains/console/webapps -Dcom.sun.web.console.secureport=6789 -Dcom.sun.web.console.unsecureport=6788 -Dcom.sun.web.console.unsecurehost=127.0.0.1 -Dwebconsole.default.file=/etc/webconsole/console/default.properties -Dwebconsole.config.file=/etc/webconsole/console/service.properties -Dcom.sun.web.console.startfile=/var/webconsole/tmp/console_start.tmp -Djava.awt.headless=true -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.NoOpLog org.apache.catalina.startup.Bootstrap start", + " 8265 ? S 521:40 /usr/java/bin/java blahblah", + // Taken from Solaris 10, notice the missing fields. + " 22306 Z 0:00 ", + " 22307 Z 0:00 ", + " 22308 Z 0:00", + NULL + }; + char *name[CF_PROCCOLS] = { 0 }; /* Headers */ + char *field[CF_PROCCOLS] = { 0 }; /* Content */ + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + int user = 0, pid = 1, sz = 4, rss = 5, command = 10; + time_t pstime = 1410000000; + + // Prepare to fill "/usr/ucb/ps" table with data. + ClearPlatformExtraTable(); + int pipefd[2]; + assert_int_equal(pipe(pipefd), 0); + LWData data; + data.fp = fdopen(pipefd[1], "w"); + data.lines = ucb_lines; + + // Feed the pipe from a separate thread. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_t tid; + pthread_create(&tid, &attr, &ListWriter, &data); + pthread_attr_destroy(&attr); + + FILE *cmd_output = fdopen(pipefd[0], "r"); + + UCB_PS_MAP = StringMapNew(); + ReadFromUcbPsPipe(cmd_output); + + /* Prepare data needed by tests and assert things tests can then assume: */ + GetProcessColumnNames(lines[0], name, start, end); + assert_string_equal(name[user], "USER"); + assert_string_equal(name[pid], "PID"); + assert_string_equal(name[sz], "SZ"); + assert_string_equal(name[rss], "RSS"); + assert_string_equal(name[command], "COMMAND"); + assert_int_equal(start[command], 66); + + // Test content + { + assert_true(SplitProcLine(lines[1], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "johndoe"); + assert_string_equal(field[pid], "8263"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116241"); + assert_string_equal(field[command], lines[1] + 69); + + ApplyPlatformExtraTable(name, field); + // Now check new and corrected values. + assert_string_equal(field[user], "johndoe"); + assert_string_equal(field[pid], "8263"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116241"); + assert_string_equal(field[command], ucb_lines[1] + 25); + free_and_null_strings(field); + } + + { + assert_true(SplitProcLine(lines[2], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "noaccess"); + assert_string_equal(field[pid], "8264"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116242"); + assert_string_equal(field[command], lines[2] + 69); + + ApplyPlatformExtraTable(name, field); + // Now check new and corrected values. + assert_string_equal(field[user], "noaccess"); + assert_string_equal(field[pid], "8264"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116242"); + assert_string_equal(field[command], ucb_lines[2] + 25); + free_and_null_strings(field); + } + + { + assert_true(SplitProcLine(lines[3], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "noaccess"); + assert_string_equal(field[pid], "8265"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116243"); + assert_string_equal(field[command], lines[3] + 69); + + ApplyPlatformExtraTable(name, field); + // Now check new and corrected values. + assert_string_equal(field[user], "noaccess"); + assert_string_equal(field[pid], "8265"); + assert_string_equal(field[sz], "19890"); + assert_string_equal(field[rss], "116243"); + assert_string_equal(field[command], ucb_lines[3] + 25); + free_and_null_strings(field); + } + + { + assert_true(SplitProcLine(lines[4], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "jenkins"); + assert_string_equal(field[pid], "22306"); + assert_string_equal(field[sz], "0"); + assert_string_equal(field[rss], "0"); + assert_string_equal(field[command], lines[4] + 66); + + ApplyPlatformExtraTable(name, field); + // Now check new and corrected values. + assert_string_equal(field[user], "jenkins"); + assert_string_equal(field[pid], "22306"); + assert_string_equal(field[sz], "0"); + assert_string_equal(field[rss], "0"); + assert_string_equal(field[command], ""); + free_and_null_strings(field); + } + + { + assert_true(SplitProcLine(lines[5], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "jenkins"); + assert_string_equal(field[pid], "22307"); + assert_string_equal(field[sz], "0"); + assert_string_equal(field[rss], "0"); + assert_string_equal(field[command], lines[5] + 66); + + ApplyPlatformExtraTable(name, field); + // Now check new and corrected values. + assert_string_equal(field[user], "jenkins"); + assert_string_equal(field[pid], "22307"); + assert_string_equal(field[sz], "0"); + assert_string_equal(field[rss], "0"); + assert_string_equal(field[command], ""); + free_and_null_strings(field); + } + + { + assert_true(SplitProcLine(lines[6], pstime, name, start, end, PCA_AllColumnsPresent, field)); + assert_string_equal(field[user], "jenkins"); + assert_string_equal(field[pid], "22308"); + assert_string_equal(field[sz], "0"); + assert_string_equal(field[rss], "0"); + assert_string_equal(field[command], lines[6] + 66); + + ApplyPlatformExtraTable(name, field); + // Now check new and corrected values. + assert_string_equal(field[user], "jenkins"); + assert_string_equal(field[pid], "22308"); + assert_string_equal(field[sz], "0"); + assert_string_equal(field[rss], "0"); + assert_string_equal(field[command], ""); + free_and_null_strings(field); + } + free_and_null_strings(name); + fclose(cmd_output); +} + +static void test_platform_specific_ps_examples(void) +{ + enum { + // Make sure the order matches the ps lines below. + TEST_LINUX = 0, + TEST_AIX, + TEST_HPUX, + TEST_SOLARIS9, + TEST_SOLARIS10, + TEST_SOLARIS11, + TEST_FREEBSD11, + TEST_ILLUMOS, + NUM_OF_PLATFORMS + }; + /* Simple, visual way of specifying the expected parse results, each row + alternates between the line we want to parse (with header first) and the + range we wish the parsing code to extract for us, denoted by '<' and '>'. + 'X' is used where they overlap, and '-' if the field is empty. '{' and + '}' a special case and means it should get the expected output from the + corresponding row in the exception table below. */ + const char *pslines[NUM_OF_PLATFORMS][20] = { + { + // Linux + "USER PID PPID PGID %CPU %MEM VSZ NI RSS NLWP STIME ELAPSED TIME COMMAND", + "< > < > < > < > < > < > < > <> < > < > < > < > < > < >", + "a10040 4831 1 4116 5.4 17.3 2763416 0 1400812 54 Apr04 4-02:54:24 05:25:51 /usr/lib/firefox/firefox", + "< > < > X < > < > < > < > X < > <> { } < > < > < >", + "a10040 5862 5851 4116 0.0 0.0 0 0 0 1 Apr04 4-02:38:28 00:00:00 [/usr/bin/termin] ", + "< > < > < > < > < > < > X X X X { } < > < > < >", + NULL + }, { + // AIX + " USER PID PPID PGID %CPU %MEM VSZ NI ST STIME TIME COMMAND", + " < > < > < > < > < > < > < > <> <> < > < > < >", + " jenkins 1036484 643150 1036484 0.0 0.0 584 20 A 09:29:20 00:00:00 bash", + " < > < > < > < > < > < > < > <> X < > < > < >", + " 254232 729146 729146 20 Z 00:00:00 ", + " - < > < > < > - - - <> X - < > < >", + " jenkins 205218 1 205218 0.0 7.0 125384 20 A Mar 16 00:36:46 /usr/java6_64/bin/java -jar slave.jar -jnlpUrl http://jenkins.usdc.cfengine.com/computer/buildslave-aix-5.3-ppc64/slave-agent.jnlp", + " < > < > X < > < > < > < > <> X < > < > < >", + // Extreme case, a lot of fields missing. This only works when the + // process is a zombie and the platform uses the + // PCA_ZombieSkipEmptyColumns algorithm (which AIX does). + " 205218 1 205218 20 Z", + " - < > X < > - - - <> X - - -", + NULL + }, { + // HPUX + " UID PID PPID C STIME TTY TIME COMMAND", + " < > < > < > X < > < > < > < >", + " jenkins 17345 17109 0 09:31:39 pts/0 0:00 perl -e my $v = fork(); if ($v != 0) { sleep(3600); }", + " < > < > < > X < > < > < > < >", + " jenkins 17374 17345 0 09:31:40 pts/0 0:00 ", + " < > < > < > X < > < > < > < >", + " root 832 1 0 May 4 ? 0:01 /usr/sbin/automountd", + " < > < > X X < > X < > < >", + NULL + }, { + // Solaris 9 + " USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND", + " < > < > < > < > <> < > <> X < > < > < >", + " jenkins 29769 0.0 0.0 810 2976 pts/1 S 07:22:43 0:00 /usr/bin/perl ../../ps.pl", + " < > < > < > < > < > < > < > X < > < > < >", + " jenkins 29835 - - 0 0 ? Z - 0:00 ", + " < > < > X X X X X X X < > < >", + " jenkins 10026 0.0 0.3 30927 143632 ? S Jan_21 01:18:58 /usr/jdk/jdk1.6.0_45/bin/java -jar slave.jar", + " < > < > < > < > < > < > X X < > < > < >", + NULL + }, { + // Solaris 10 + " USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND", + " < > < > < > < > <> < > <> X < > < > < >", + " root 19553 0.0 0.0 743 4680 ? S 04:03:10 00:00 /usr/lib/ssh/sshd", + " < > < > < > < > < > < > X X < > < > < >", + " jenkins 29770 - - 0 0 ? Z - 00:00 ", + " < > < > X X X X X X X < > < >", + "noaccess 8264 0.0 0.2 19890 116240 ? S Jan_16 08:49:25 /usr/java/bin/java -server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4 ", + "< > < > < > < > < > < > X X < > < > < >", + NULL + }, { + // Solaris 11 + " USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND", + " < > < > < > < > <> < > <> X < > < > < >", + " root 15449 0.0 0.0 835 4904 ? S 04:03:29 00:00 /usr/lib/ssh/sshd", + " < > < > < > < > < > < > X X < > < > < >", + " jenkins 5409 - - 0 0 ? Z - 00:00 ", + " < > < > X X X X X X X < > < >", + " jenkins 29997 0.0 0.5 51661 312120 ? S Jan_21 01:18:53 /usr/jdk/jdk1.6.0_45/bin/java -jar slave.jar", + " < > < > < > < > < > < > X X < > < > < >", + NULL + }, { + // FreeBSD 11 + "USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND", + "< > < > < > < > < > < > <> < > < > < > < >", + "skreuzer 6506 5.0 0.2 57192 25764 0- S+ 12:39 10:13.37 mosh-server new -c 256 -s -l LANG=en_US.UTF-8", + "< > < > < > < > < > < > <> <> < > < > < >", + "zookeeper 646 0.0 1.0 4538080 162140 - I 28Feb16 21:46.80 /usr/local/openjdk7/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dlog4j.configuration=file:/usr/local/etc/zookeeper/l", + "< > < > < > < > < > < > X X < > < > < >", + "skreuzer 40046 0.0 0.0 0 0 6 Z+ 21:34 0:00.00 ", + "< > < > < > < > X X X <> < > < > < >", + "skreuzer 50293 0.0 0.0 20612 5332 3 Is 2Mar16 0:00.62 -zsh (zsh)", + "< > < > < > < > < > < > X <> < > < > < >", + NULL + }, { + // Illumos + " USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND", + " < > < > < > < > <> < > <> X < > < > < >", + " bahamat 63679 0.0 0.1 1831 4340 pts/13 S 00:15:39 00:00 perl -e $p = fork(); if ($p != 0) { sleep(60); }", + " < > < > < > < > < > < > < > X < > < > < >", + " bahamat 63680 - - 0 0 ? Z - 00:00 ", + " < > < > X X X X X X X < > < >", + " root 72601 0.3 0.4 291631 1050796 ? S Feb_25 06:35:07 /opt/local/java/openjdk7/bin/java -Dhudson.DNSMultiCast.disabled=true -Xmx2048m", + " < > < > < > < > < > < > X X < > < > < >", + NULL + } + }; + + // Only half as many elements here, since there is only expected output. + const char *exceptions[NUM_OF_PLATFORMS][10] = { + { + // Linux + NULL, + "1459762277", + "1459763233", + NULL + }, { + // AIX + NULL + }, { + // HPUX + NULL + }, { + // Solaris 9 + NULL + }, { + // Solaris 10 + NULL + }, { + // Solaris 11 + NULL + }, { + // FreeBSD 11 + NULL + }, { + // Illumos + NULL + } + }; + + char *names[CF_PROCCOLS] = { 0 }; + char *fields[CF_PROCCOLS] = { 0 }; + int start[CF_PROCCOLS] = { 0 }; + int end[CF_PROCCOLS] = { 0 }; + + memset(names, 0, sizeof(names)); + memset(fields, 0, sizeof(fields)); + + for (int platform = 0; platform < NUM_OF_PLATFORMS; platform++) + { + PsColumnAlgorithm pca; + if (platform == TEST_AIX) + { + pca = PCA_ZombieSkipEmptyColumns; + } + else + { + pca = PCA_AllColumnsPresent; + } + + for (int linenum = 0; pslines[platform][linenum]; linenum += 2) + { + char **fields_to_check; + if (linenum == 0) + { + GetProcessColumnNames(pslines[platform][linenum], names, start, end); + fields_to_check = names; + } + else + { + assert_true(SplitProcLine(pslines[platform][linenum], 1460118341, names, start, end, pca, fields)); + fields_to_check = fields; + } + + bool in_field = false; + int field_start = -1; + int field_end = -1; + int field_num = 0; + bool exception = false; + for (int pos = 0;; pos++) + { + if (!pslines[platform][linenum + 1][pos]) + { + // There should be no excess space at the end of the + // expected replies. + assert_int_not_equal(pslines[platform][linenum + 1][pos - 1], ' '); + assert_false(in_field); + // We should have reached the end of both arrays. + assert_int_equal(names[field_num], NULL); + if (linenum != 0) + { + assert_int_equal(fields[field_num], NULL); + } + break; + } + + switch (pslines[platform][linenum + 1][pos]) + { + case ' ': + break; + case '{': + exception = true; + // fallthrough + case '<': + assert_false(in_field); + in_field = true; + field_start = pos; + break; + case '}': + case '>': + assert_true(in_field); + in_field = false; + // + 1 because we want it to be the next byte. + field_end = pos + 1; + break; + case 'X': + field_start = pos; + // + 1 because we want it to be the next byte. + field_end = pos + 1; + break; + case '-': + field_start = pos; + // This corresponds to an empty string. + field_end = pos; + break; + default: + assert_true(false); + break; + } + + if (field_start != -1 && field_end != -1) + { + if (exception) + { + assert_string_equal(fields_to_check[field_num], + exceptions[platform][linenum / 2]); + exception = false; + } + else + { + int field_len = field_end - field_start; + +#if 0 /* DEBUG OUTPUT */ + printf("Checking '%s' against '", fields_to_check[field_num]); + fwrite(pslines[platform][linenum] + field_start, field_len, 1, stdout); + printf("'\n"); +#endif /* DEBUG OUTPUT */ + + // Check if fields are identical. + assert_memory_equal(fields_to_check[field_num], + pslines[platform][linenum] + field_start, + field_len); + // And that it's not longer than what we expect. + assert_int_equal(fields_to_check[field_num][field_len], '\0'); + } + field_start = -1; + field_end = -1; + field_num++; + } + } + + free_and_null_strings(fields); + } + + free_and_null_strings(names); + } +} + +int main(void) +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_split_line_challenges), + unit_test(test_split_line_noelapsed), + unit_test(test_split_line_elapsed), + unit_test(test_split_line_longcmd), + unit_test(test_split_line), + unit_test(test_split_line_serious_overspill), + unit_test(test_platform_extra_table), + unit_test(test_platform_specific_ps_examples), + }; + + return run_tests(tests); +} diff --git a/tests/unit/string_expressions_test.c b/tests/unit/string_expressions_test.c new file mode 100644 index 0000000000..576511308a --- /dev/null +++ b/tests/unit/string_expressions_test.c @@ -0,0 +1,90 @@ +#include + +#include +#include +#include +#include + +static char *ForbiddenVarRefEval(ARG_UNUSED const char *varname, + ARG_UNUSED VarRefType type, + ARG_UNUSED void *param) +{ + fail(); +} + +static char *IdentityVarRefEval(const char *varname, + ARG_UNUSED VarRefType type, + ARG_UNUSED void *param) +{ + return xstrdup(varname); +} + +static char *AppendAVarRefEval(const char *varname, + ARG_UNUSED VarRefType type, + ARG_UNUSED void *param) +{ + return StringConcatenate(2, "a", varname); +} + +static char *DiscriminateVarTypesVarRefEval(const char *varname, VarRefType type, + ARG_UNUSED void *param) +{ + if (type == VAR_REF_TYPE_SCALAR) + { + return StringConcatenate(3, "cozy(", varname, ")"); + } + else + { + return StringConcatenate(3, "ugly{", varname, "}"); + } +} + +static void CheckParse(const char *string_expression, const char *expected_output, VarRefEvaluator evaluator, void *param) +{ + StringParseResult res = ParseStringExpression(string_expression, 0, strlen(string_expression)); + assert_true(res.result); + char *eval_result = EvalStringExpression(res.result, evaluator, param); + assert_string_equal(expected_output, eval_result); + free(eval_result); + FreeStringExpression(res.result); +} + +static void test_literal(void) +{ + CheckParse("hello", "hello", ForbiddenVarRefEval, NULL); +} + +static void test_var_naked(void) +{ + CheckParse("$(foo)", "foo", IdentityVarRefEval, NULL); +} + +static void test_var_naked_two_level(void) +{ + CheckParse("$($(foo))", "aafoo", AppendAVarRefEval, NULL); +} + +static void test_var_one_level(void) +{ + CheckParse("$(foo)x$(bar)y$(baz)", "fooxbarybaz", IdentityVarRefEval, NULL); +} + +static void test_different_var_types(void) +{ + CheckParse("@{a$(b@(c)${d})@(e)}", "ugly{acozy(bugly{c}cozy(d))ugly{e}}", DiscriminateVarTypesVarRefEval, NULL); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_literal), + unit_test(test_var_naked), + unit_test(test_var_naked_two_level), + unit_test(test_var_one_level), + unit_test(test_different_var_types), + }; + + return run_tests(tests); +} diff --git a/tests/unit/strlist_test.c b/tests/unit/strlist_test.c new file mode 100644 index 0000000000..7f0930a16d --- /dev/null +++ b/tests/unit/strlist_test.c @@ -0,0 +1,294 @@ +#include + +#include +#include + + +static StrList *PATH_SL, *HOSTNAME_SL; + + + +/* Strings that will be inserted to strlist, listed here in sorted order. */ +char *PATH_STRINGS[] = +{ + " ", + " ", + "/", + "/path", + "/path/to/file.name", + "blah", + "waza", +}; +#define PATH_STRINGS_LEN (sizeof(PATH_STRINGS) / sizeof(PATH_STRINGS[0])) + +/* Ideally we need the numbers [0..PATH_STRINGS_LEN) in random order in order to + * insert non-sorted. To make the test reproducible, here is one random + * order. Feel free to experiment with changing this. */ +size_t PATH_INSERT_ORDER[PATH_STRINGS_LEN] = +{ + 5, 3, 1, 0, 4, 6, 2 +}; + + + +/* Strings that will be inserted to strlist, listed here in special sorted + * order in the way they should end up after calling + * StrList_Sort(string_CompareFromEnd). */ +char *HOSTNAME_STRINGS[] = +{ + "*", /* Globs have no special meaning */ + ".*", /* Should not match anything */ + ".", /* Should not match as well */ + "com", /* No match, nobody has "com" fqdn */ + "cfengine.com", /* Match this hostname */ + ".allowed.cfengine.com", /* Allow everything under this domain */ + "www.cfengine.com", /* Match this hostname */ + ".no", /* Allow all norwegian hostnames */ +}; +#define HOSTNAME_STRINGS_LEN (sizeof(HOSTNAME_STRINGS) / sizeof(HOSTNAME_STRINGS[0])) + +/* Ideally we need the numbers [0..PATH_STRINGS_LEN) in random order in order to + * insert non-sorted. To make the test reproducible, here is one random + * order. Feel free to experiment with changing this. */ +size_t HOSTNAME_INSERT_ORDER[HOSTNAME_STRINGS_LEN] = +{ + 5, 3, 1, 0, 4, 6, 2, 7 +}; + + + +static StrList *init_strlist(char ** strings, size_t strings_len, + size_t *insert_order) +{ + StrList *sl = calloc(1, sizeof(*sl)); + /* Insert in random order. */ + for (size_t i = 0; i < strings_len; i++) + { + size_t ret = StrList_Append(&sl, strings[ insert_order[i] ]); + assert_int_equal(ret, i); + assert_string_equal(StrList_At(sl, i), strings[ insert_order[i] ]); + } + assert_int_equal(StrList_Len(sl), strings_len); + StrList_Finalise(&sl); + + return sl; +} +static void test_init_SL() +{ + PATH_SL = init_strlist(PATH_STRINGS, PATH_STRINGS_LEN, + PATH_INSERT_ORDER); + HOSTNAME_SL = init_strlist(HOSTNAME_STRINGS, HOSTNAME_STRINGS_LEN, + HOSTNAME_INSERT_ORDER); +} + +/* Sort PATH_STRLIST using the common way, and HOSTNAME_STRLIST in the order + * of reading the strings backwards. */ +static void test_StrList_Sort() +{ + StrList_Sort(PATH_SL, string_Compare); + assert_int_equal(StrList_Len(PATH_SL), PATH_STRINGS_LEN); + + for (size_t i = 0; i < PATH_STRINGS_LEN; i++) + { + assert_string_equal(StrList_At(PATH_SL, i), + PATH_STRINGS[i]); + } + + StrList_Sort(HOSTNAME_SL, string_CompareFromEnd); + assert_int_equal(StrList_Len(HOSTNAME_SL), HOSTNAME_STRINGS_LEN); + + for (size_t i = 0; i < HOSTNAME_STRINGS_LEN; i++) + { + assert_string_equal(StrList_At(HOSTNAME_SL, i), + HOSTNAME_STRINGS[i]); + } +} + +/* Only search in PATH_STRLIST which is sorted in the common way. */ +static void test_StrList_BinarySearch() +{ + size_t pos; + bool found; + + /* Search for existing strings. */ + for (size_t i = 0; i < PATH_STRINGS_LEN; i++) + { + found = StrList_BinarySearch(PATH_SL, PATH_STRINGS[i], &pos); + assert_int_equal(found, true); + assert_int_equal(pos, i); + } + + /* Search for inexistent entries, check that the returned position is the + * one they should be inserted into. */ + + found = StrList_BinarySearch(PATH_SL, "", &pos); + assert_int_equal(found, false); + assert_int_equal(pos, 0); /* empty string should always come first */ + + found = StrList_BinarySearch(PATH_SL, " ", &pos); + assert_int_equal(found, false); + assert_int_equal(pos, 2); + + found = StrList_BinarySearch(PATH_SL, "zzz", &pos); + assert_int_equal(found, false); + assert_int_equal(pos, PATH_STRINGS_LEN); + + found = StrList_BinarySearch(PATH_SL, "/path/", &pos); + assert_int_equal(found, false); + assert_int_equal(pos, 4); + + found = StrList_BinarySearch(PATH_SL, "/path/to", &pos); + assert_int_equal(found, false); + assert_int_equal(pos, 4); +} + +/* Only search in PATH_STRLIST because it makes sense to search longest prefix + * for paths. */ +static void test_StrList_SearchLongestPrefix() +{ + /* REMINDER: PATH_STRINGS[] = + { " ", " ", "/", "/path", "/path/to/file.name", "blah", "waza" }; */ + + size_t ret, ret2, ret3; + + /* These searches all search for "/path", since length is the same. */ + ret = StrList_SearchLongestPrefix(PATH_SL, "/path", 0, '/', true); + ret2 = StrList_SearchLongestPrefix(PATH_SL, "/path/", 5, '/', true); + ret3 = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name", 5, '/', true); + assert_string_equal(StrList_At(PATH_SL, ret), "/path"); + assert_string_equal(StrList_At(PATH_SL, ret2), "/path"); + assert_string_equal(StrList_At(PATH_SL, ret3), "/path"); + + /* Searching for "/path/" does not bring up "/path", but "/", since + * directories *must* have a trailing slash. */ + ret = StrList_SearchLongestPrefix(PATH_SL, "/path/", 0, '/', true); + assert_string_equal(StrList_At(PATH_SL, ret), "/"); + + ret = StrList_SearchLongestPrefix(PATH_SL, "/path.json", 0, '/', true); + assert_string_equal(StrList_At(PATH_SL, ret), "/"); + + + /* We insert a couple more directories and sort again. */ + StrList_Append(&PATH_SL, "/path/to/file.namewhatever/whatever"); + StrList_Append(&PATH_SL, "/path/to/file.name/whatever/"); + StrList_Append(&PATH_SL, "/path/to/"); + StrList_Sort(PATH_SL, string_Compare); + + ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name", + 0, '/', true); + assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/file.name"); + + ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file", + 0, '/', true); + assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/"); + + ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name/whatever/blah", + 0, '/', true); + assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/file.name/whatever/"); + + ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/", + 0, '/', true); + assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/"); +} + +/* Only search in HOSTNAME_STRLIST because it only makes sense to search for + * longest suffix with hostnames and subdomains. */ +static void test_StrList_SearchLongestSuffix() +{ + /* REMINDER: HOSTNAME_STRINGS[] = + { "*", ".*", ".", "com", "cfengine.com", ".allowed.cfengine.com", "www.cfengine.com", ".no" }; */ + + size_t ret, ret2, ret3, ret4, ret5, ret6, ret7, ret8, ret9, ret10, ret11; + + ret = StrList_SearchLongestPrefix(HOSTNAME_SL, "cfengine.com", 0, '.', false); + ret2 = StrList_SearchLongestPrefix(HOSTNAME_SL, "google.com", 0, '.', false); + ret3 = StrList_SearchLongestPrefix(HOSTNAME_SL, "yr.no", 0, '.', false); + ret4 = StrList_SearchLongestPrefix(HOSTNAME_SL, "ntua.gr", 0, '.', false); + ret5 = StrList_SearchLongestPrefix(HOSTNAME_SL, "disallowed.cfengine.com", 0, '.', false); + ret6 = StrList_SearchLongestPrefix(HOSTNAME_SL, "allowed.cfengine.com", 0, '.', false); + ret7 = StrList_SearchLongestPrefix(HOSTNAME_SL, "blah.allowed.cfengine.com", 0, '.', false); + ret8 = StrList_SearchLongestPrefix(HOSTNAME_SL, "www.cfengine.com", 0, '.', false); + ret9 = StrList_SearchLongestPrefix(HOSTNAME_SL, "www1.cfengine.com", 0, '.', false); + ret10 = StrList_SearchLongestPrefix(HOSTNAME_SL, "1www.cfengine.com", 0, '.', false); + ret11 = StrList_SearchLongestPrefix(HOSTNAME_SL, "no", 0, '.', false); + + assert_string_equal(StrList_At(HOSTNAME_SL, ret), "cfengine.com"); + assert_int_equal(ret2, (size_t) -1); + assert_string_equal(StrList_At(HOSTNAME_SL, ret3), ".no"); + assert_int_equal(ret4, (size_t) -1); + assert_int_equal(ret5, (size_t) -1); + assert_int_equal(ret6, (size_t) -1); + assert_string_equal(StrList_At(HOSTNAME_SL, ret7), ".allowed.cfengine.com"); + assert_string_equal(StrList_At(HOSTNAME_SL, ret8), "www.cfengine.com"); + assert_int_equal(ret9, (size_t) -1); + assert_int_equal(ret10, (size_t) -1); + assert_int_equal(ret11, (size_t) -1); +} + +static void test_StrList_SearchForSuffix() +{ + /* REMINDER: HOSTNAME_STRINGS[] = + { "*", ".*", ".", "com", "cfengine.com", ".allowed.cfengine.com", "www.cfengine.com", ".no" }; */ + + size_t ret, ret2, ret3, ret4, ret5, ret6, ret7, ret8, ret9, ret10, ret11; + + ret = StrList_SearchForPrefix(HOSTNAME_SL, "cfengine.com", 0, false); + ret2 = StrList_SearchForPrefix(HOSTNAME_SL, "google.com", 0, false); + ret3 = StrList_SearchForPrefix(HOSTNAME_SL, "yr.no", 0, false); + ret4 = StrList_SearchForPrefix(HOSTNAME_SL, "ntua.gr", 0, false); + ret5 = StrList_SearchForPrefix(HOSTNAME_SL, "disallowed.cfengine.com", 0, false); + ret6 = StrList_SearchForPrefix(HOSTNAME_SL, "allowed.cfengine.com", 0, false); + ret7 = StrList_SearchForPrefix(HOSTNAME_SL, "blah.allowed.cfengine.com", 0, false); + ret8 = StrList_SearchForPrefix(HOSTNAME_SL, "www.cfengine.com", 0, false); + ret9 = StrList_SearchForPrefix(HOSTNAME_SL, "www1.cfengine.com", 0, false); + ret10 = StrList_SearchForPrefix(HOSTNAME_SL, "1www.cfengine.com", 0, false); + ret11 = StrList_SearchForPrefix(HOSTNAME_SL, "no", 0, false); + + /* SearchForPrefix() does not guarantee which one of all the matches it + * will return if there are many matches. */ + + /* E.g. the first search might return "cfengine.com" or "com" entry. */ + LargestIntegralType set[] = {3, 4}; + assert_in_set(ret, set, 2); + assert_string_equal(StrList_At(HOSTNAME_SL, ret2), "com"); + assert_string_equal(StrList_At(HOSTNAME_SL, ret3), ".no"); + assert_int_equal(ret4, (size_t) -1); + assert_in_set(ret5, set, 2); + assert_in_set(ret6, set, 2); + LargestIntegralType set2[] = {3, 4, 5}; + assert_in_set(ret7, set2, 3); + assert_in_set(ret8, set, 2); + assert_in_set(ret9, set, 2); + LargestIntegralType set3[] = {3, 4, 6}; + assert_in_set(ret10, set3, 3); + assert_int_equal(ret11, (size_t) -1); +} + +static void test_StrList_Free() +{ + StrList_Free(&PATH_SL); + assert_int_equal(PATH_SL, NULL); + + StrList_Free(&HOSTNAME_SL); + assert_int_equal(HOSTNAME_SL, NULL); +} + + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_init_SL), + unit_test(test_StrList_Sort), + unit_test(test_StrList_BinarySearch), + unit_test(test_StrList_SearchLongestPrefix), + unit_test(test_StrList_SearchLongestSuffix), + unit_test(test_StrList_SearchForSuffix), + unit_test(test_StrList_Free) + }; + + int ret = run_tests(tests); + + return ret; +} diff --git a/tests/unit/syntax_test.c b/tests/unit/syntax_test.c new file mode 100644 index 0000000000..69b9062b59 --- /dev/null +++ b/tests/unit/syntax_test.c @@ -0,0 +1,185 @@ +#include + +#include +#include /* StringMatchFull */ + +static void test_lookup_promise_type_agent_vars(void) +{ + const PromiseTypeSyntax *s = PromiseTypeSyntaxGet("agent", "vars"); + assert_true(s); + assert_string_equal("vars", s->promise_type); +} + +static void test_lookup_promise_type_common_vars(void) +{ + const PromiseTypeSyntax *s = PromiseTypeSyntaxGet("common", "vars"); + assert_true(s); + assert_string_equal("vars", s->promise_type); +} + +static void test_lookup_promise_type_edit_xml_build_xpath(void) +{ + const PromiseTypeSyntax *s = PromiseTypeSyntaxGet("edit_xml", "build_xpath"); + assert_true(s); + assert_string_equal("build_xpath", s->promise_type); +} + +static void test_lookup_promise_type_edit_line_delete_lines(void) +{ + const PromiseTypeSyntax *s = PromiseTypeSyntaxGet("edit_line", "delete_lines"); + assert_true(s); + assert_string_equal("delete_lines", s->promise_type); +} + +static void test_lookup_promise_type_edit_xml_commons(void) +{ + const PromiseTypeSyntax *s = PromiseTypeSyntaxGet("edit_xml", "*"); + assert_true(s); + assert_string_equal("edit_xml", s->bundle_type); + assert_string_equal("*", s->promise_type); +} + +static void test_lookup_promise_type_global_commons(void) +{ + const PromiseTypeSyntax *s = PromiseTypeSyntaxGet("*", "*"); + assert_true(s); + assert_string_equal("*", s->bundle_type); + assert_string_equal("*", s->promise_type); +} + + +static void test_lookup_constraint_edit_xml_set_attribute_attribute_value(void) +{ + const PromiseTypeSyntax *s = PromiseTypeSyntaxGet("edit_xml", "set_attribute"); + assert_true(s); + assert_string_equal("set_attribute", s->promise_type); + + const ConstraintSyntax *x = PromiseTypeSyntaxGetConstraintSyntax(s, "attribute_value"); + assert_true(x); + assert_string_equal("attribute_value", x->lval); +} + +static void test_lookup_body_classes(void) +{ + const BodySyntax *x = BodySyntaxGet(PARSER_BLOCK_BODY, "classes"); + assert_true(x); + + const ConstraintSyntax *y = BodySyntaxGetConstraintSyntax(x->constraints, "promise_repaired"); + assert_true(y); + assert_string_equal("promise_repaired", y->lval); +} + +static void test_lookup_body_process_count(void) +{ + const BodySyntax *x = BodySyntaxGet(PARSER_BLOCK_BODY, "process_count"); + assert_true(x); + + const ConstraintSyntax *y = BodySyntaxGetConstraintSyntax(x->constraints, "match_range"); + assert_true(y); + assert_string_equal("match_range", y->lval); +} + +static void test_lookup_body_delete_select(void) +{ + const BodySyntax *x = BodySyntaxGet(PARSER_BLOCK_BODY, "delete_select"); + assert_true(x); + + const ConstraintSyntax *y = BodySyntaxGetConstraintSyntax(x->constraints, "delete_if_startwith_from_list"); + assert_true(y); + assert_string_equal("delete_if_startwith_from_list", y->lval); +} + +static void test_copy_from_servers(void) +{ + const BodySyntax *x = BodySyntaxGet(PARSER_BLOCK_BODY, "copy_from"); + assert_true(x); + + const ConstraintSyntax *y = BodySyntaxGetConstraintSyntax(x->constraints, "servers"); + assert_true(y); + + assert_true(StringMatchFull(y->range.validation_string, "127.0.0.1")); + assert_true(StringMatchFull(y->range.validation_string, "www-dashed.stuff.com")); + assert_true(StringMatchFull(y->range.validation_string, "2604:2000:8441:e300:224:d7ff:fec5:338")); +} + +static void test_typecheck_null_rval(void) +{ + SyntaxTypeMatch err = CheckConstraintTypeMatch("whatever", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, + CF_DATA_TYPE_STRING, "abc", 0); + assert_int_equal(SYNTAX_TYPE_MATCH_ERROR_GOT_NULL, err); +} + +static void test_check_parse_variable_name(void) +{ + // Test was added after function + // It shows what it actually does (not what was intended) + + // Allowed variable "names": + assert_true(CheckParseVariableName("a")); + assert_true(CheckParseVariableName("myvar")); + assert_true(CheckParseVariableName("SuperCaliFragilisticExpialidocius")); + assert_true(CheckParseVariableName("a.b")); + assert_true(CheckParseVariableName("a[b]")); + assert_true(CheckParseVariableName("a[b.c]")); + assert_true(CheckParseVariableName("a[b.c.d]")); + assert_true(CheckParseVariableName("a.b[c.d.e]")); + assert_true(CheckParseVariableName("a.b[c.d.e]")); + assert_true(CheckParseVariableName("Namespace.var[$(ns.expand)]")); + + // Not allowed: + assert_false(CheckParseVariableName("a.b.c")); + assert_false(CheckParseVariableName("abc.def.ghi")); + assert_false(CheckParseVariableName(".a")); + assert_false(CheckParseVariableName("a.")); + assert_false(CheckParseVariableName(".abc")); + assert_false(CheckParseVariableName("abc.")); + + // Reserved: + assert_false(CheckParseVariableName("promiser")); + assert_false(CheckParseVariableName("handle")); + assert_false(CheckParseVariableName("promise_filename")); + assert_false(CheckParseVariableName("promise_dirname")); + assert_false(CheckParseVariableName("promise_linenumber")); + assert_false(CheckParseVariableName("this")); + + // Edge cases, allowed: + assert_true(CheckParseVariableName("")); + assert_true(CheckParseVariableName(" ")); + assert_true(CheckParseVariableName(" ")); + assert_true(CheckParseVariableName("\t")); + assert_true(CheckParseVariableName("a[")); + assert_true(CheckParseVariableName("a.[")); + + // Edge cases, not allowed: + assert_false(CheckParseVariableName(".")); + assert_false(CheckParseVariableName("...")); + assert_false(CheckParseVariableName(".[]")); + assert_false(CheckParseVariableName(".[][]")); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_lookup_promise_type_agent_vars), + unit_test(test_lookup_promise_type_common_vars), + unit_test(test_lookup_promise_type_edit_xml_build_xpath), + unit_test(test_lookup_promise_type_edit_line_delete_lines), + + unit_test(test_lookup_promise_type_edit_xml_commons), + unit_test(test_lookup_promise_type_global_commons), + + unit_test(test_lookup_body_classes), + unit_test(test_lookup_body_process_count), + unit_test(test_lookup_body_delete_select), + + unit_test(test_lookup_constraint_edit_xml_set_attribute_attribute_value), + + unit_test(test_copy_from_servers), + unit_test(test_typecheck_null_rval), + unit_test(test_check_parse_variable_name), + }; + + return run_tests(tests); +} diff --git a/tests/unit/sysinfo_test.c b/tests/unit/sysinfo_test.c new file mode 100644 index 0000000000..63bdbbd847 --- /dev/null +++ b/tests/unit/sysinfo_test.c @@ -0,0 +1,119 @@ +#include + +#include +#include + +static void test_uptime(void) +{ + /* + * Assume we have been online at least one minute, and less than 5 years. + * Should be good enough for everyone... + */ + int uptime = GetUptimeMinutes(time(NULL)); + printf("Uptime: %.2f days\n", uptime / (60.0 * 24)); + assert_in_range(uptime, 1, 60*24*365*5); +} + +static void FindNextIntegerTestWrapper(char *str, char expected[][KIBIBYTE(1)], int num_expected) +{ + Seq *seq = SeqNew(num_expected, NULL); + char *integer; + + char *next = FindNextInteger(str, &integer); + if (integer != NULL) + { + SeqAppend(seq, integer); + } + while (next != NULL && integer != NULL) + { + next = FindNextInteger(next, &integer); + if (integer != NULL) + { + SeqAppend(seq, integer); + } + } + + assert_int_equal(num_expected, SeqLength(seq)); + for (int i = 0; i < num_expected; i++) + { + assert_string_equal((char *) SeqAt(seq, i), expected[i]); + } + + SeqDestroy(seq); +} + +static void test_find_next_integer(void) +{ + { + char str[] = "Ubuntu 20.04.1 LTS"; + char expected[3][KIBIBYTE(1)] = { "20", "04", "1" }; + FindNextIntegerTestWrapper(str, expected, 3); + } + { + char str[] = "canonified_5"; + char expected[1][KIBIBYTE(1)] = { "5" }; + FindNextIntegerTestWrapper(str, expected, 1); + } + { + char str[] = ""; + char expected[0][KIBIBYTE(1)] = { }; + FindNextIntegerTestWrapper(str, expected, 0); + } + { + char str[] = " "; + char expected[0][KIBIBYTE(1)] = { }; + FindNextIntegerTestWrapper(str, expected, 0); + } + { + char str[] = " no numbers in sight "; + char expected[0][KIBIBYTE(1)] = { }; + FindNextIntegerTestWrapper(str, expected, 0); + } + { + char str[] = "0"; + char expected[1][KIBIBYTE(1)] = { "0" }; + FindNextIntegerTestWrapper(str, expected, 1); + } + { + char str[] = "1k"; + char expected[1][KIBIBYTE(1)] = { "1" }; + FindNextIntegerTestWrapper(str, expected, 1); + } + { + char str[] = "1234"; + char expected[1][KIBIBYTE(1)] = { "1234" }; + FindNextIntegerTestWrapper(str, expected, 1); + } + { + char str[] = "1 2 3 4"; + char expected[4][KIBIBYTE(1)] = { "1", "2", "3", "4" }; + FindNextIntegerTestWrapper(str, expected, 4); + } + { + char str[] = "Debian 9"; + char expected[1][KIBIBYTE(1)] = { "9" }; + FindNextIntegerTestWrapper(str, expected, 1); + } + { + char str[] = "Cent OS 6.7"; + char expected[2][KIBIBYTE(1)] = { "6", "7" }; + FindNextIntegerTestWrapper(str, expected, 2); + } + { + char str[] = "CFEngine 3.18.0-2deadbeef"; + char expected[4][KIBIBYTE(1)] = { "3", "18", "0", "2" }; + FindNextIntegerTestWrapper(str, expected, 4); + } +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_uptime), + unit_test(test_find_next_integer) + }; + + return run_tests(tests); +} diff --git a/tests/unit/tar_portability_test.sh b/tests/unit/tar_portability_test.sh new file mode 100755 index 0000000000..15f9a07b78 --- /dev/null +++ b/tests/unit/tar_portability_test.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +if [ "$(uname)" != "Linux" ] +then + # Skip. + exit 77 +fi + +cd "$(dirname $0)/../.." + +tar --exclude="tests/acceptance/workdir" --format=ustar -cf /dev/null * + +exit $? diff --git a/tests/unit/test.c b/tests/unit/test.c new file mode 100644 index 0000000000..abea779971 --- /dev/null +++ b/tests/unit/test.c @@ -0,0 +1,65 @@ +#include + +#include +#include +#include +#include + +char *file_read_string(FILE *in) +{ + fpos_t pos; + long size; + char *buffer; + + assert_int_equal(fgetpos(in, &pos), 0); + + assert_int_equal(fseek(in, 0, SEEK_END), 0); + size = ftell(in); + assert_true(size >= 0); + + assert_int_equal(fseek(in, 0, SEEK_SET), 0); + + buffer = xcalloc(size + 1L, sizeof(char)); + assert_int_equal(fread(buffer, 1, size, in), size); + + assert_int_equal(fsetpos(in, &pos), 0); + + return buffer; +} + +void assert_file_equal(FILE *a, FILE *b) +{ + char *a_buffer = file_read_string(a); + char *b_buffer = file_read_string(b); + + if (strcmp(a_buffer, b_buffer) != 0) + { + printf("\n=====\n%s != \n=====\n%s\n", a_buffer, b_buffer); + fail(); + } + + free(a_buffer); + free(b_buffer); +} + +#define SMALL_DIFF 1e-14 + +void _assert_double_close(double left, double right, const char *const file, const int line) +{ + if (fabs(left - right) > SMALL_DIFF) + { + print_error("%f != %f (+- 1e-14)\n", left, right); + _fail(file, line); + } +} + +void test_progress() +{ + putchar('.'); + fflush(stdout); +} +void test_progress_end() +{ + putchar('\n'); + fflush(stdout); +} diff --git a/tests/unit/test.h b/tests/unit/test.h new file mode 100644 index 0000000000..7533cedb95 --- /dev/null +++ b/tests/unit/test.h @@ -0,0 +1,58 @@ +/* ALWAYS INCLUDE FIRST, so that platform.h is included first as well! */ + + +#ifndef CFENGINE_TEST_H +#define CFENGINE_TEST_H + + +#include + +#include +#include +#include +#include +#include + + +/* Use this define for specific overrides inside all our source tree. */ +#define CFENGINE_TEST + + +#define PRINT_TEST_BANNER() \ + printf("==================================================\n"); \ + printf("Starting test: %s\n", __FILE__); \ + printf("==================================================\n") + + +char *file_read_string(FILE *in); + +void assert_file_equal(FILE *a, FILE *b); + +#define assert_double_close(a, b) _assert_double_close(a, b, __FILE__, __LINE__) +void _assert_double_close(double left, double right, const char *const file, const int line); + +void test_progress(void); +void test_progress_end(void); + +// like assert_string_equal, but better with respect to pointers: +// if a and b are NULL, returns true +// if a or b is NULL print error using assert_int +// if a and b are not NULL, use assert_string +#define assert_string_int_equal(a, b)\ +{\ + const char* x = a;\ + const char* y = b;\ + if (x!=y)\ + {\ + if (x==NULL||y==NULL)\ + {\ + assert_int_equal(x, y);\ + }\ + else\ + {\ + assert_string_equal(x, y);\ + }\ + }\ +}\ + +#endif diff --git a/tests/unit/var_expressions_test.c b/tests/unit/var_expressions_test.c new file mode 100644 index 0000000000..9850be4ca1 --- /dev/null +++ b/tests/unit/var_expressions_test.c @@ -0,0 +1,202 @@ +#include + +#include + +static void test_plain_variable_with_no_stuff_in_it(void) +{ + VarRef *ref = VarRefParse("foo"); + assert_false(ref->ns); + assert_false(ref->scope); + assert_string_equal("foo", ref->lval); + assert_int_equal(0, ref->num_indices); + assert_false(ref->indices); + VarRefDestroy(ref); +} + +static void test_scoped(void) +{ + VarRef *ref = VarRefParse("scope.lval"); + assert_false(ref->ns); + assert_string_equal("scope", ref->scope); + assert_string_equal("lval", ref->lval); + assert_int_equal(0, ref->num_indices); + assert_false(ref->indices); + VarRefDestroy(ref); +} + +static void test_full(void) +{ + VarRef *ref = VarRefParse("ns:scope.lval"); + assert_string_equal("ns", ref->ns); + assert_string_equal("scope", ref->scope); + assert_string_equal("lval", ref->lval); + assert_int_equal(0, ref->num_indices); + assert_false(ref->indices); + VarRefDestroy(ref); +} + +static void test_dotted_array(void) +{ + VarRef *ref = VarRefParse("ns:scope.lval[la.la]"); + assert_string_equal("ns", ref->ns); + assert_string_equal("scope", ref->scope); + assert_string_equal("lval", ref->lval); + assert_int_equal(1, ref->num_indices); + assert_string_equal("la.la", ref->indices[0]); + VarRefDestroy(ref); +} + +static void test_levels(void) +{ + VarRef *ref = VarRefParse("ns:scope.lval[x][y][z]"); + assert_string_equal("ns", ref->ns); + assert_string_equal("scope", ref->scope); + assert_string_equal("lval", ref->lval); + assert_int_equal(3, ref->num_indices); + assert_string_equal("x", ref->indices[0]); + assert_string_equal("y", ref->indices[1]); + assert_string_equal("z", ref->indices[2]); + VarRefDestroy(ref); +} + +static void test_unqualified_array(void) +{ + VarRef *ref = VarRefParse("lval[x]"); + assert_false(ref->ns); + assert_false(ref->scope); + assert_string_equal("lval", ref->lval); + assert_int_equal(1, ref->num_indices); + assert_string_equal("x", ref->indices[0]); + VarRefDestroy(ref); +} + +static void test_qualified_array(void) +{ + VarRef *ref = VarRefParse("scope.lval[x]"); + assert_false(ref->ns); + assert_string_equal("scope", ref->scope); + assert_string_equal("lval", ref->lval); + assert_int_equal(1, ref->num_indices); + assert_string_equal("x", ref->indices[0]); + VarRefDestroy(ref); +} + +static void test_nested_array(void) +{ + VarRef *ref = VarRefParse("scope.lval[$(other[x])]"); + assert_false(ref->ns); + assert_string_equal("scope", ref->scope); + assert_string_equal("lval", ref->lval); + assert_int_equal(1, ref->num_indices); + assert_string_equal("$(other[x])", ref->indices[0]); + VarRefDestroy(ref); +} + +static void test_array_with_dot_colon_in_index(void) +{ + VarRef *ref = VarRefParse("lval[x-x.x:x]"); + assert_false(ref->ns); + assert_false(ref->scope); + assert_string_equal("lval", ref->lval); + assert_int_equal(1, ref->num_indices); + assert_string_equal("x-x.x:x", ref->indices[0]); + VarRefDestroy(ref); +} + +static void test_special_scope(void) +{ + Policy *p = PolicyNew(); + Bundle *bp = PolicyAppendBundle(p, "ns", "b", "agent", NULL, NULL); + + { + VarRef *ref = VarRefParseFromBundle("c.lval", bp); + assert_string_equal("ns", ref->ns); + assert_string_equal("c", ref->scope); + assert_string_equal("lval", ref->lval); + VarRefDestroy(ref); + } + + { + VarRef *ref = VarRefParseFromBundle("sys.lval", bp); + assert_false(ref->ns); + assert_string_equal("sys", ref->scope); + assert_string_equal("lval", ref->lval); + VarRefDestroy(ref); + } + PolicyDestroy(p); +} + +static void CheckToStringQualified(const char *str, const char *expect) +{ + VarRef *ref = VarRefParse(str); + char *out = VarRefToString(ref, true); + assert_string_equal(expect, out); + free(out); + VarRefDestroy(ref); +} + +static void test_to_string_qualified(void) +{ + CheckToStringQualified("ns:scope.lval[x][y]", "ns:scope.lval[x][y]"); + CheckToStringQualified("ns:scope.lval[x]", "ns:scope.lval[x]"); + CheckToStringQualified("ns:scope.lval", "ns:scope.lval"); + CheckToStringQualified("scope.lval", "default:scope.lval"); + CheckToStringQualified("lval", "lval"); +} + +static void test_to_string_unqualified(void) +{ + { + VarRef *ref = VarRefParse("ns:scope.lval[x][y]"); + char *out = VarRefToString(ref, false); + assert_string_equal("lval[x][y]", out); + free(out); + VarRefDestroy(ref); + } + + { + VarRef *ref = VarRefParse("ns:scope.lval[x]"); + char *out = VarRefToString(ref, false); + assert_string_equal("lval[x]", out); + free(out); + VarRefDestroy(ref); + } + + { + VarRef *ref = VarRefParse("scope.lval"); + char *out = VarRefToString(ref, false); + assert_string_equal("lval", out); + free(out); + VarRefDestroy(ref); + } + + { + VarRef *ref = VarRefParse("lval"); + char *out = VarRefToString(ref, false); + assert_string_equal("lval", out); + free(out); + VarRefDestroy(ref); + } +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_plain_variable_with_no_stuff_in_it), + unit_test(test_scoped), + unit_test(test_full), + unit_test(test_dotted_array), + unit_test(test_levels), + unit_test(test_unqualified_array), + unit_test(test_qualified_array), + unit_test(test_nested_array), + unit_test(test_array_with_dot_colon_in_index), + unit_test(test_special_scope), + unit_test(test_to_string_qualified), + unit_test(test_to_string_unqualified), + }; + + return run_tests(tests); +} diff --git a/tests/unit/variable_test.c b/tests/unit/variable_test.c new file mode 100644 index 0000000000..7bad4a7c68 --- /dev/null +++ b/tests/unit/variable_test.c @@ -0,0 +1,389 @@ +#include + +#include +#include + +struct Variable_ +{ + VarRef *ref; + Rval rval; + DataType type; + StringSet *tags; + char *comment; + const Promise *promise; // The promise that set the present value +}; + +static bool PutVar(VariableTable *table, char *var_str) +{ + VarRef *ref = VarRefParse(var_str); + Rval rval = (Rval) { var_str, RVAL_TYPE_SCALAR }; + bool ret = VariableTablePut(table, ref, &rval, CF_DATA_TYPE_STRING, NULL, NULL, NULL); + VarRefDestroy(ref); + return ret; +} + +static VariableTable *ReferenceTable(void) +{ + VariableTable *t = VariableTableNew(); + + assert_false(PutVar(t, "scope1.lval1")); + assert_false(PutVar(t, "scope1.lval2")); + assert_false(PutVar(t, "scope2.lval1")); + { + VarRef *ref = VarRefParse("scope1.array[one]"); + Rval rval = (Rval) { "scope1.array[one]", RVAL_TYPE_SCALAR }; + assert_false(VariableTablePut(t, ref, &rval, CF_DATA_TYPE_STRING, NULL, NULL, NULL)); + VarRefDestroy(ref); + } + { + VarRef *ref = VarRefParse("scope1.array[two]"); + Rval rval = (Rval) { "scope1.array[two]", RVAL_TYPE_SCALAR }; + assert_false(VariableTablePut(t, ref, &rval, CF_DATA_TYPE_STRING, NULL, NULL, NULL)); + VarRefDestroy(ref); + } + { + VarRef *ref = VarRefParse("scope1.array[two][three]"); + Rval rval = (Rval) { "scope1.array[two][three]", RVAL_TYPE_SCALAR }; + assert_false(VariableTablePut(t, ref, &rval, CF_DATA_TYPE_STRING, NULL, NULL, NULL)); + VarRefDestroy(ref); + } + { + VarRef *ref = VarRefParse("scope1.array[two][four]"); + Rval rval = (Rval) { "scope1.array[two][four]", RVAL_TYPE_SCALAR }; + assert_false(VariableTablePut(t, ref, &rval, CF_DATA_TYPE_STRING, NULL, NULL, NULL)); + VarRefDestroy(ref); + } + + { + VarRef *ref = VarRefParse("scope3.array[te][st]"); + Rval rval = (Rval) { "scope3.array[te][st]", RVAL_TYPE_SCALAR }; + assert_false(VariableTablePut(t, ref, &rval, CF_DATA_TYPE_STRING, NULL, NULL, NULL)); + VarRefDestroy(ref); + } + { + VarRef *ref = VarRefParse("scope3.array[t][e][s][t]"); + Rval rval = (Rval) { "scope3.array[t][e][s][t]", RVAL_TYPE_SCALAR }; + assert_false(VariableTablePut(t, ref, &rval, CF_DATA_TYPE_STRING, NULL, NULL, NULL)); + VarRefDestroy(ref); + } + + assert_false(PutVar(t, "ns1:scope1.lval1")); + assert_false(PutVar(t, "ns1:scope1.lval2")); + assert_false(PutVar(t, "ns1:scope2.lval1")); + + return t; +} + +static void TestGet(VariableTable *t, const char *ref_str) +{ + VarRef *ref = VarRefParse(ref_str); + Variable *v = VariableTableGet(t, ref); + assert_true(v != NULL); + assert_int_equal(0, VarRefCompare(ref, v->ref)); + assert_string_equal(ref_str, RvalScalarValue(v->rval)); + VarRefDestroy(ref); +} + +static void test_get_in_default_namespace(void) +{ + VariableTable *t = ReferenceTable(); + + TestGet(t, "scope1.lval1"); + TestGet(t, "scope1.lval2"); + TestGet(t, "scope2.lval1"); + + VariableTableDestroy(t); +} + +// Redmine 6674 +static void test_multi_index_array_conflation(void) +{ + VariableTable *t = ReferenceTable(); + + TestGet(t, "scope3.array[te][st]"); + TestGet(t, "scope3.array[t][e][s][t]"); + + VariableTableDestroy(t); +} + +static void test_get_different_namespaces(void) +{ + VariableTable *t = ReferenceTable(); + + TestGet(t, "scope1.lval1"); + TestGet(t, "ns1:scope1.lval1"); + + VariableTableDestroy(t); +} + +static void test_get_indices(void) +{ + VariableTable *t = ReferenceTable(); + + TestGet(t, "scope1.array[one]"); + TestGet(t, "scope1.array[two]"); + + VariableTableDestroy(t); +} + +static void test_replace(void) +{ + VariableTable *t = ReferenceTable(); + + VarRef *ref = VarRefParse("scope1.lval1"); + TestGet(t, "scope1.lval1"); + + Rval rval = (Rval) { "foo", RVAL_TYPE_SCALAR }; + assert_true(VariableTablePut(t, ref, &rval, CF_DATA_TYPE_STRING, NULL, NULL, NULL)); + + Variable *v = VariableTableGet(t, ref); + assert_true(v != NULL); + assert_string_equal("foo", RvalScalarValue(v->rval)); + + VarRefDestroy(ref); + + VariableTableDestroy(t); +} + +static void test_remove(void) +{ + VariableTable *t = ReferenceTable(); + + { + VarRef *ref = VarRefParse("scope1.array[one]"); + assert_true(VariableTableRemove(t, ref)); + assert_true(VariableTableGet(t, ref) == NULL); + assert_false(VariableTableRemove(t, ref)); + + assert_int_equal(11, VariableTableCount(t, NULL, NULL, NULL)); + + VarRefDestroy(ref); + } + + { + VarRef *ref = VarRefParse("ns1:scope1.lval1"); + assert_true(VariableTableRemove(t, ref)); + assert_true(VariableTableGet(t, ref) == NULL); + assert_false(VariableTableRemove(t, ref)); + + assert_int_equal(10, VariableTableCount(t, NULL, NULL, NULL)); + + VarRefDestroy(ref); + } + + VariableTableDestroy(t); +} + +static void test_clear(void) +{ + { + VariableTable *t = ReferenceTable(); + assert_false(VariableTableClear(t, "xxx", NULL, NULL)); + assert_false(VariableTableClear(t, NULL, "xxx", NULL)); + assert_false(VariableTableClear(t, NULL, NULL, "xxx")); + assert_int_equal(12, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } + + { + VariableTable *t = ReferenceTable(); + assert_true(VariableTableClear(t, NULL, NULL, NULL)); + assert_int_equal(0, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } + + { + VariableTable *t = ReferenceTable(); + assert_true(VariableTableClear(t, "default", NULL, NULL)); + assert_int_equal(3, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } + + { + VariableTable *t = ReferenceTable(); + assert_true(VariableTableClear(t, "default", "scope1", NULL)); + assert_int_equal(6, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } + + { + VariableTable *t = ReferenceTable(); + assert_true(VariableTableClear(t, "default", NULL, "array")); + assert_int_equal(6, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } + + { + VariableTable *t = ReferenceTable(); + assert_true(VariableTableClear(t, "ns1", NULL, NULL)); + assert_int_equal(9, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } + + { + VariableTable *t = ReferenceTable(); + assert_true(VariableTableClear(t, "ns1", "scope2", NULL)); + assert_int_equal(11, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } + + { + VariableTable *t = ReferenceTable(); + assert_true(VariableTableClear(t, "default", "scope1", "lval1")); + assert_int_equal(11, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } + + { + VariableTable *t = ReferenceTable(); + assert_true(VariableTableClear(t, "default", "scope1", "lval1")); + assert_int_equal(11, VariableTableCount(t, NULL, NULL, NULL)); + VariableTableDestroy(t); + } +} + +static void test_counting(void) +{ + VariableTable *t = ReferenceTable(); + + assert_int_equal(12, VariableTableCount(t, NULL, NULL, NULL)); + assert_int_equal(9, VariableTableCount(t, "default", NULL, NULL)); + assert_int_equal(8, VariableTableCount(t, NULL, "scope1", NULL)); + assert_int_equal(6, VariableTableCount(t, "default", "scope1", NULL)); + assert_int_equal(4, VariableTableCount(t, NULL, NULL, "lval1")); + assert_int_equal(3, VariableTableCount(t, "ns1", NULL, NULL)); + assert_int_equal(2, VariableTableCount(t, "ns1", "scope1", NULL)); + assert_int_equal(6, VariableTableCount(t, NULL, NULL, "array")); + assert_int_equal(1, VariableTableCount(t, "default", "scope1", "lval1")); + + VariableTableDestroy(t); +} + +static void test_iterate_indices(void) +{ + VariableTable *t = ReferenceTable(); + + { + VarRef *ref = VarRefParse("default:scope1.array"); + VariableTableIterator *iter = VariableTableIteratorNewFromVarRef(t, ref); + + unsigned short number_of_entries = 0; + while (VariableTableIteratorNext(iter)) + { + number_of_entries++; + } + assert_int_equal(4, number_of_entries); + + VariableTableIteratorDestroy(iter); + VarRefDestroy(ref); + } + + { + VarRef *ref = VarRefParse("default:scope1.array[two]"); + VariableTableIterator *iter = VariableTableIteratorNewFromVarRef(t, ref); + + unsigned short number_of_entries = 0; + while (VariableTableIteratorNext(iter)) + { + number_of_entries++; + } + + assert_int_equal(3, number_of_entries); + + VariableTableIteratorDestroy(iter); + VarRefDestroy(ref); + } + + VariableTableDestroy(t); +} + +// Below test relies on the ordering items in RB tree which is strongly +// related to the hash function used. +/* No more relevant, RBTree has been replaced with Map. */ +#if 0 +static void test_iterate_indices_ordering_related(void) +{ + VariableTable *t = ReferenceTable(); + + { + VarRef *ref = VarRefParse("default:scope1.array"); + VariableTableIterator *iter = VariableTableIteratorNewFromVarRef(t, ref); + + Variable *v = VariableTableIteratorNext(iter); + assert_true(v != NULL); + assert_int_equal(1, v->ref->num_indices); + assert_string_equal("two", v->ref->indices[0]); + + v = VariableTableIteratorNext(iter); + assert_true(v != NULL); + assert_int_equal(2, v->ref->num_indices); + assert_string_equal("two", v->ref->indices[0]); + assert_string_equal("three", v->ref->indices[1]); + + v = VariableTableIteratorNext(iter); + assert_true(v != NULL); + assert_int_equal(2, v->ref->num_indices); + assert_string_equal("two", v->ref->indices[0]); + assert_string_equal("four", v->ref->indices[1]); + + v = VariableTableIteratorNext(iter); + assert_true(v != NULL); + assert_int_equal(1, v->ref->num_indices); + assert_string_equal("one", v->ref->indices[0]); + + assert_false(VariableTableIteratorNext(iter) != NULL); + + VariableTableIteratorDestroy(iter); + VarRefDestroy(ref); + } + + { + VarRef *ref = VarRefParse("default:scope1.array[two]"); + VariableTableIterator *iter = VariableTableIteratorNewFromVarRef(t, ref); + + Variable *v = VariableTableIteratorNext(iter); + assert_true(v != NULL); + assert_int_equal(1, v->ref->num_indices); + assert_string_equal("two", v->ref->indices[0]); + + v = VariableTableIteratorNext(iter); + assert_true(v != NULL); + assert_int_equal(2, v->ref->num_indices); + assert_string_equal("two", v->ref->indices[0]); + assert_string_equal("three", v->ref->indices[1]); + + v = VariableTableIteratorNext(iter); + assert_true(v != NULL); + assert_int_equal(2, v->ref->num_indices); + assert_string_equal("two", v->ref->indices[0]); + assert_string_equal("four", v->ref->indices[1]); + + assert_false(VariableTableIteratorNext(iter) != NULL); + + VariableTableIteratorDestroy(iter); + VarRefDestroy(ref); + } + + VariableTableDestroy(t); +} +#endif + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_get_in_default_namespace), + unit_test(test_get_different_namespaces), + unit_test(test_get_indices), +// unit_test(test_iterate_indices_ordering_related), + unit_test(test_multi_index_array_conflation), + unit_test(test_replace), + unit_test(test_remove), + unit_test(test_clear), + unit_test(test_counting), + unit_test(test_iterate_indices), + }; + + return run_tests(tests); +} diff --git a/tests/unit/verify_databases_test.c b/tests/unit/verify_databases_test.c new file mode 100644 index 0000000000..eab671cea9 --- /dev/null +++ b/tests/unit/verify_databases_test.c @@ -0,0 +1,73 @@ +#include + +#include // Include .c file to test static functions + +void test_ValidateSQLTableName(void) +{ + char db[CF_MAXVARSIZE]; + char table[CF_MAXVARSIZE]; + + // Valid database table paths: + assert_true(ValidateSQLTableName("cfsettings.users", db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, "cfsettings"); + assert_string_equal(table, "users"); + + assert_true(ValidateSQLTableName("a.b", db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, "a"); + assert_string_equal(table, "b"); + + assert_true(ValidateSQLTableName(".b", db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, ""); + assert_string_equal(table, "b"); + + assert_true(ValidateSQLTableName("a.", db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, "a"); + assert_string_equal(table, ""); + + + // Invalid paths: + + char path[CF_MAXVARSIZE]; // ValidateSQLTableName will write to it :O + + strcpy(path, "nosep"); + db[0] = table[0] = '\0'; + assert_false(ValidateSQLTableName(path, db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, ""); + assert_string_equal(table, ""); + + strcpy(path, "cfsettings\\users/users"); + db[0] = table[0] = '\0'; + assert_false(ValidateSQLTableName(path, db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, ""); + assert_string_equal(table, ""); + + strcpy(path, "a.b/c"); + db[0] = table[0] = '\0'; + assert_false(ValidateSQLTableName(path, db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, ""); + assert_string_equal(table, ""); + + // Seems like they should work, but they are invalid because of bugs: + db[0] = table[0] = '\0'; + strcpy(path, "a/b"); + assert_false(ValidateSQLTableName(path, db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, ""); + assert_string_equal(table, ""); + + db[0] = table[0] = '\0'; + strcpy(path, "a\\b"); + assert_false(ValidateSQLTableName(path, db, sizeof(db), table, sizeof(table))); + assert_string_equal(db, ""); + assert_string_equal(table, ""); +} + +int main() +{ + PRINT_TEST_BANNER(); + const UnitTest tests[] = + { + unit_test(test_ValidateSQLTableName), + }; + + return run_tests(tests); +} diff --git a/tests/valgrind-check/Containerfile b/tests/valgrind-check/Containerfile new file mode 100644 index 0000000000..a0ef77c9f5 --- /dev/null +++ b/tests/valgrind-check/Containerfile @@ -0,0 +1,8 @@ +FROM ubuntu:22.04 AS build +RUN DEBIAN_FRONTEND=noninteractive apt-get update -y +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y libssl-dev libxml2-dev libpam0g-dev liblmdb-dev libacl1-dev libpcre2-dev +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python3 git flex bison byacc automake make autoconf libtool valgrind +COPY masterfiles masterfiles +COPY core core +WORKDIR core +CMD bash tests/valgrind-check/run_checks.sh diff --git a/tests/valgrind-check/Makefile.am b/tests/valgrind-check/Makefile.am new file mode 100644 index 0000000000..41d4ed5386 --- /dev/null +++ b/tests/valgrind-check/Makefile.am @@ -0,0 +1,25 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# + +DISTFILES = run_checks.sh run.sh valgrind.sh Makefile.in Makefile.am Containerfile diff --git a/tests/valgrind-check/run.sh b/tests/valgrind-check/run.sh new file mode 100644 index 0000000000..ea2ee561d5 --- /dev/null +++ b/tests/valgrind-check/run.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e +trap "echo FAILURE" ERR + +set -x + +cd ../../../ + +if which podman ; then + CLI="sudo podman --cgroup-manager=cgroupfs" +else + CLI="docker" +fi + +$CLI build --tag ubuntu:mycfecontainer -f ./core/tests/valgrind-check/Containerfile . +$CLI run --rm ubuntu:mycfecontainer diff --git a/tests/valgrind-check/run_checks.sh b/tests/valgrind-check/run_checks.sh new file mode 100755 index 0000000000..e8a186fa2b --- /dev/null +++ b/tests/valgrind-check/run_checks.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -x + +function check_with_valgrind() { + bash tests/valgrind-check/valgrind.sh +} + +cd "$(dirname $0)"/../../ + +failure=0 +check_with_valgrind || { echo "FAIL: valgrind check failed"; failure=1; } + +exit $failure diff --git a/tests/valgrind-check/valgrind.sh b/tests/valgrind-check/valgrind.sh new file mode 100644 index 0000000000..534309751f --- /dev/null +++ b/tests/valgrind-check/valgrind.sh @@ -0,0 +1,200 @@ +#!/bin/bash + +function print_ps { + set +e + echo "CFEngine processes:" + ps aux | grep [c]f- + + echo "Valgrind processes:" + ps aux | grep [v]algrind + set -e +} + +function stop_daemons { + echo "Stopping cfengine daemons" + pkill -f cf-serverd || echo "Did not kill cf-serverd" + pkill -f cf-execd || echo "Did not kill cf-execd" + pkill -f cf-monitord || echo "Did not kill cf-monitord" +} + +function no_errors { + set +e + grep -i "error" $1 + grep -q -i "error" $1 && exit 1 + set -e +} + +function check_daemon_output { + echo "Examining $1:" + no_errors $1 +} + +function check_valgrind_output { + set -e + if [ ! -f "$1" ]; then + echo "$1 does not exists!" + exit 1 + fi + echo "Looking for problems in $1:" + grep -i "ERROR SUMMARY: 0 errors" "$1" + cat $1 | sed -e "/ 0 errors/d" -e "/and suppressed error/d" -e "/a memory error detector/d" -e "/This database contains unknown binary data/d" > filtered.txt + no_errors filtered.txt + set +e + grep -i "at 0x" filtered.txt + grep -i -q "at 0x" filtered.txt && exit 1 + grep -i "by 0x" filtered.txt + grep -i -q "by 0x" filtered.txt && exit 1 + grep -i "Failed to connect" filtered.txt + grep -i -q "Failed to connect" filtered.txt && exit 1 + set -e +} + +function check_masterfiles_and_inputs { + set -e + echo "Comparing promises.cf from inputs and masterfiles:" + diff -u /var/cfengine/inputs/promises.cf /var/cfengine/masterfiles/promises.cf +} + +set -e +set -x + +# Assume we are in core directory +if [ -f ./configure ] ; then + ./configure -C --enable-debug +else + ./autogen.sh -C --enable-debug +fi +make +make install + +cd ../masterfiles +if [ -f ./configure ] ; then + ./configure -C --enable-debug +else + ./autogen.sh -C --enable-debug +fi +make +make install + +/var/cfengine/bin/cf-agent --version + +VG_OPTS="--leak-check=full --track-origins=yes --error-exitcode=1" +BOOTSTRAP_IP="127.0.0.1" + +valgrind $VG_OPTS /var/cfengine/bin/cf-key 2>&1 | tee cf-key.txt +check_valgrind_output cf-key.txt +valgrind $VG_OPTS /var/cfengine/bin/cf-agent -B $BOOTSTRAP_IP 2>&1 | tee bootstrap.txt +check_valgrind_output bootstrap.txt + +echo "Running cf-check diagnose --validate on all databases:" +valgrind $VG_OPTS /var/cfengine/bin/cf-check diagnose --validate 2>&1 | tee cf_check_validate_all_1.txt +check_valgrind_output cf_check_validate_all_1.txt + +check_masterfiles_and_inputs + +print_ps + +echo "Stopping before relaunch under valgrind" +stop_daemons +sleep 10 +print_ps + +echo "Starting cf-serverd with valgrind in background:" +valgrind $VG_OPTS --log-file=serverd.txt /var/cfengine/bin/cf-serverd --no-fork 2>&1 > serverd_output.txt & +server_pid="$!" +sleep 20 + +echo "Starting cf-execd with valgrind in background:" +valgrind $VG_OPTS --log-file=execd.txt /var/cfengine/bin/cf-execd --no-fork 2>&1 > execd_output.txt & +exec_pid="$!" +sleep 10 + +print_ps + +echo "Running cf-net:" +valgrind $VG_OPTS /var/cfengine/bin/cf-net GET /var/cfengine/masterfiles/promises.cf 2>&1 | tee get.txt +check_valgrind_output get.txt + +echo "Checking promises.cf diff (from cf-net GET):" +diff -u ./promises.cf /var/cfengine/masterfiles/promises.cf + +echo "Running update.cf:" +valgrind $VG_OPTS /var/cfengine/bin/cf-agent -K -f update.cf 2>&1 | tee update.txt +check_valgrind_output update.txt +check_masterfiles_and_inputs +echo "Running update.cf without local copy:" +valgrind $VG_OPTS /var/cfengine/bin/cf-agent -K -f update.cf -D mpf_skip_local_copy_optimization 2>&1 | tee update2.txt +check_valgrind_output update2.txt +check_masterfiles_and_inputs +echo "Running promises.cf:" +valgrind $VG_OPTS /var/cfengine/bin/cf-agent -K -f promises.cf 2>&1 | tee promises.txt +check_valgrind_output promises.txt + +# Dump all databases, use grep to filter the JSON lines +# (optional whitespace then double quote or curly brackets). +# Some of the databases have strings containing "error" +# which check_valgrind_output greps for. +echo "Running cf-check dump:" +valgrind $VG_OPTS /var/cfengine/bin/cf-check dump 2>&1 | grep -E '\s*[{}"]' --invert-match | tee cf_check_dump.txt +check_valgrind_output cf_check_dump.txt + +echo "Running cf-check diagnose on all databases" +valgrind $VG_OPTS /var/cfengine/bin/cf-check diagnose 2>&1 | tee cf_check_diagnose.txt +check_valgrind_output cf_check_diagnose.txt + +echo "Running cf-check diagnose --validate on all databases:" +valgrind $VG_OPTS /var/cfengine/bin/cf-check diagnose --validate 2>&1 | tee cf_check_validate_all_2.txt +check_valgrind_output cf_check_validate_all_2.txt + +echo "Checking that bootstrap ID doesn't change" +/var/cfengine/bin/cf-agent --show-evaluated-vars | grep bootstrap_id > id_a +/var/cfengine/bin/cf-agent -K --show-evaluated-vars | grep bootstrap_id > id_b +cat id_a +diff id_a id_b + +echo "Checking that bootstrap ID has expected length" +[ `cat id_a | awk '{print $2}' | wc -c` -eq 41 ] + +print_ps + +echo "cf-execd outputs:" +tail execd_output.txt +tail execd.txt + +echo "cf-serverd outputs:" +tail serverd_output.txt +tail serverd.txt + +echo "Checking that serverd and execd PIDs are still correct/alive:" +ps -p $exec_pid +ps -p $server_pid + +echo "Killing valgrind cf-execd" +kill $exec_pid +echo "Killing valgrind cf-serverd" +kill $server_pid + +wait $exec_pid +wait $server_pid + +echo "Output from cf-execd in valgrind:" +cat execd.txt +check_valgrind_output execd.txt +check_daemon_output execd_output.txt + +echo "Output from cf-serverd in valgrind:" +cat serverd.txt +check_valgrind_output serverd.txt +check_daemon_output serverd_output.txt + +stop_daemons + +echo "Done killing" +sleep 10 +print_ps + +echo "Check that bootstrap was successful" +grep "This host assumes the role of policy server" bootstrap.txt +grep "completed successfully!" bootstrap.txt + +echo "valgrind_health_check successful! (valgrind.sh)"